Java 在OO模型中添加双向关系的最佳实践
我正在努力想出一种在OO模型中添加双向关系的好方法。假设有一个客户可以下很多订单,也就是说,客户和订单类之间存在一对多的关联,需要在两个方向上进行遍历:对于特定客户,应该可以告诉他们下的所有订单,对于订单,应该可以告诉客户 下面是一段Java代码,尽管问题主要与语言无关:Java 在OO模型中添加双向关系的最佳实践,java,language-agnostic,oop,model,db4o,Java,Language Agnostic,Oop,Model,Db4o,我正在努力想出一种在OO模型中添加双向关系的好方法。假设有一个客户可以下很多订单,也就是说,客户和订单类之间存在一对多的关联,需要在两个方向上进行遍历:对于特定客户,应该可以告诉他们下的所有订单,对于订单,应该可以告诉客户 下面是一段Java代码,尽管问题主要与语言无关: class Customer { private Set orders = new HashSet<Order> (); public void placeOrder (Order o) {
class Customer {
private Set orders = new HashSet<Order> ();
public void placeOrder (Order o) {
orders.add(o);
o.setCustomer(this);
}
}
class Order {
private Customer customer;
public void setCustomer (Customer c) {
customer = c;
}
}
而不是正确的
c.placeOrder(o);
形成单向链路而不是双向链路
在学习OOP的过程中,是否有人能帮助我们找到一种惯用的、实用的方法来解决这个问题,而不必求助于“反射”或奇特的框架(无论如何,它都依赖于反射)
还有一个类似的问题:,但是我觉得它没有回答我的请求
附言:非常感谢您提供在db4o之上实现业务模型的实际项目的源代码链接 首先,除非您计划在客户之间移动订单,否则我认为您不应该提供
setCustomer()
方法,客户应该是构造函数的参数,并且保持不变
然后,用户不应该访问构造函数,只使用工厂方法
Owner
没有单一答案。这实际上取决于所涉及的课程。在您的情况下,您显然不想让人们选择做一些无效的事情,因此我会放弃Order.SetCustomer
不过,情况并非总是如此。正如我所说,这取决于所涉及的类。如果您在
Customer.placeOrder(Order)
中维护双向关系,为什么不在Order.setCustomer(Customer)
中执行相同的操作呢
这看起来像是复制代码,但它解决了问题。不过,最简单的方法是尽可能避免双向关系。这是一个非常有趣的问题,对OOP的理论和实践有着深远的影响。首先,我将告诉你快速和肮脏的方式(几乎)完成你的要求。一般来说,我不推荐这个解决方案,但由于没有人提到它(如果内存没有让我失望的话),Martin Fowler(UML蒸馏)的一本书中提到了它,所以它可能值得一谈;您可以从以下位置更改setCustomer方法的定义:
public void setCustomer (Customer c) {
customer = c;
}
致:
并确保客户和订单在同一包装中。如果不指定访问修饰符,setCustomer默认为package可见性,这意味着只能从同一个包中的类访问它。显然,这并不能保护您免受来自同一软件包中Customer以外的类的非法访问。此外,如果您决定将客户和订单分为两个不同的包,则代码将中断
在Java的常见编程实践中,包可视性在很大程度上是可以容忍的;我感觉在C++社区内,<强>朋友>强>修改器不能像java中的包可见性那样容忍,尽管它有类似的用途。我真的不明白为什么,因为friend更具选择性:基本上,对于每个类,您可以指定其他能够访问第一个类的私有成员的friend类和函数
然而,毫无疑问,无论是Java的包可见性还是C++的friend都不能很好地代表OOP的含义,甚至不能代表基于对象的编程的含义(OOP基本上是OBP加上继承和多态性;从现在起我将使用OOP这个术语)。OOP的核心方面是存在称为对象的实体,它们通过相互发送消息进行通信。对象具有内部状态,但此状态只能由对象本身更改。状态通常是结构化的,即它基本上是名称、年龄和顺序等字段的集合。在大多数语言中,消息是同步的,不能像邮件或UDP数据包那样被错误地丢弃。当您编写c.placeOrder(o)时,这意味着发送方,即此,正在向c发送消息。此消息的内容为placeOrder和o
Department department = (Department)cbDepartment.getSelectedItem();
Person person = new Person(tfFirstName.getText(), tfLastName.getText());
Contract contract = new Contract(tfPositionName.getText(), Integer.parseInt(tfSalary.getText()));
department.hire(person, contract);
当对象收到消息时,它必须处理它。java,C++,c++和许多其他语言假定对象只能处理一个消息,如果它的类定义了一个方法,该方法具有适当的名称和形式参数列表。一个类的方法集称为其接口,Java和C#等语言也有一个适当的构造,即接口来建模一组方法的概念。消息c.placeOrder(o)的处理程序是以下方法:
public void placeOrder(Order o) {
orders.add(o);
o.setCustomer(this);
}
方法主体是编写指令的地方,这些指令将在必要时改变对象c的状态。在本例中,订单字段被修改
这本质上就是OOP的意思。OOP是在模拟环境中开发的,在模拟环境中,基本上有许多相互通信的黑匣子,每个黑匣子负责自己的内部状态
大多数现代语言都完全遵循此方案,但前提是您仅限于私有字段和公共/受保护方法。不过,也有一些陷阱。例如,在类Customer的方法中,您可以访问另一个Customer对象的私有字段,例如orders
你链接的页面上的两个答案其实都很好,
void setCustomer (Customer c) {
customer = c;
}
public void placeOrder(Order o) {
orders.add(o);
o.setCustomer(this);
}
Department department = (Department)cbDepartment.getSelectedItem();
Person person = new Person(tfFirstName.getText(), tfLastName.getText());
Contract contract = new Contract(tfPositionName.getText(), Integer.parseInt(tfSalary.getText()));
department.hire(person, contract);
package com.example.payroll.domain;
public class Contract {
private String mPositionName;
private int mSalary;
public Contract(String positionName, int salary) {
mPositionName = positionName;
mSalary = salary;
}
public String getPositionName() {
return mPositionName;
}
public int getSalary() {
return mSalary;
}
/*
Not much business logic here. You can think
about a contract as a very simple, immutable type,
whose state doesn't change and that can't really
answer to any message, like a piece of paper.
*/
}
package com.example.payroll.domain;
public class Person {
private String mFirstName;
private String mLastName;
private Department mDepartment;
private boolean mResigning;
public Person(String firstName, String lastName) {
mFirstName = firstName;
mLastName = lastName;
mDepartment = null;
mResigning = false;
}
public String getFirstName() {
return mFirstName;
}
public String getLastName() {
return mLastName;
}
public Department getDepartment() {
return mDepartment;
}
public boolean isResigning() {
return mResigning;
}
// ========== Business logic ==========
public void youAreHired(Department department) {
assert(department != null);
assert(mDepartment != department);
assert(department.isBeingHired(this));
if (mDepartment != null)
resign();
mDepartment = department;
}
public void youAreFired() {
assert(mDepartment != null);
assert(mDepartment.isBeingFired(this));
mDepartment = null;
}
public void resign() {
assert(mDepartment != null);
mResigning = true;
mDepartment.iResign(this);
mDepartment = null;
mResigning = false;
}
}
package com.example.payroll.domain;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class Department {
private String mName;
private Map<Person, Contract> mEmployees;
private Person mBeingHired;
private Person mBeingFired;
public Department(String name) {
mName = name;
mEmployees = new HashMap<Person, Contract>();
mBeingHired = null;
mBeingFired = null;
}
public String getName() {
return mName;
}
public Collection<Person> getEmployees() {
return mEmployees.keySet();
}
public Contract getContract(Person employee) {
return mEmployees.get(employee);
}
// ========== Business logic ==========
public boolean isBeingHired(Person person) {
return mBeingHired == person;
}
public boolean isBeingFired(Person person) {
return mBeingFired == person;
}
public void hire(Person person, Contract contract) {
assert(!mEmployees.containsKey(person));
assert(!mEmployees.containsValue(contract));
mBeingHired = person;
mBeingHired.youAreHired(this);
mEmployees.put(mBeingHired, contract);
mBeingHired = null;
}
public void fire(Person person) {
assert(mEmployees.containsKey(person));
mBeingFired = person;
mBeingFired.youAreFired();
mEmployees.remove(mBeingFired);
mBeingFired = null;
}
public void iResign(Person employee) {
assert(mEmployees.containsKey(employee));
assert(employee.isResigning());
mEmployees.remove(employee);
}
}
package com.example.payroll;
import com.example.payroll.domain.*;
public class App {
private static Department resAndDev;
private static Department production;
private static Department[] departments;
static {
resAndDev = new Department("Research & Development");
production = new Department("Production");
departments = new Department[] {resAndDev, production};
}
public static void main(String[] args) {
Person person = new Person("John", "Smith");
printEmployees();
resAndDev.hire(person, new Contract("Project Manager", 3270));
printEmployees();
production.hire(person, new Contract("Quality Control Analyst", 3680));
printEmployees();
production.fire(person);
printEmployees();
}
private static void printEmployees() {
for (Department department : departments) {
System.out.println(String.format("Department: %s", department.getName()));
for (Person employee : department.getEmployees()) {
Contract contract = department.getContract(employee);
System.out.println(String.format(" %s. %s, %s. Salary: EUR %d", contract.getPositionName(), employee.getFirstName(), employee.getLastName(), contract.getSalary()));
}
}
System.out.println();
}
}
public void youAreHired(Department department) {
assert(department != null);
assert(mDepartment != department);
assert(department.isBeingHired(this));
if (mDepartment != null)
resign();
mDepartment = department;
}
public void youAreHired(Department department) {
assert(department != null);
assert(mDepartment == null);
assert(department.isBeingHired(this));
mDepartment = department;
}
public boolean youAreHired(Department department) {
assert(department != null);
assert(mDepartment != department);
assert(department.isBeingHired(this));
if (mDepartment != null)
if (!resign())
return false;
mDepartment = department;
return true;
}
class OrderManager {
void placeOrder(Customer c, Order o){
c.addOrder(o);
o.setCustomer(c);
}
}
class Customer {
private Set<Order> orders = new LinkedHashSet<Order>();
void addOrder(Order o){ orders.add(o); }
}
class Order {
private Customer customer;
void setCustomer(Customer c){ this.customer=c; }
}