处理多样性关系
舒适的家庭生活会导致一个或更多 “小人儿” 降临到这个家庭。但是,在增加小孩到家庭中之前,先确保 Person 真正有地方可住。给他们一个工作场所,或者还有一个很好的夏日度假屋。一个 Address 类型应该可以解决所有这三个地方。
清单 1. 添加一个 Address 类型到 Person 类中
package com.tedneward.model;
public class Address { public Address() { }
public Address(String street, String city, String state, String zip) { this.street = street; this.city = city; this.state = state; this.zip = zip; }
public String toString() { return "[Address: " + "street=" + street + " " + "city=" + city + " " + "state=" + state + " " + "zip=" + zip + "]"; }
public int hashCode() { return street.hashCode() & city.hashCode() & state.hashCode() & zip.hashCode(); }
public boolean equals(Object obj) { if (obj == this) return this;
if (obj instanceof Address) { Address rhs = (Address)obj;
return (this.street.equals(rhs.street) && this.city.equals(rhs.city) && this.state.equals(rhs.state) && this.zip.equals(rhs.zip)); } else return false; }
public String getStreet() { return this.street; } public void setStreet(String value) { this.street = value; }
public String getCity() { return this.city; } public void setCity(String value) { this.city = value; }
public String getState() { return this.state; } public void setState(String value) { this.state = value; }
public String getZip() { return this.zip; } public void setZip(String value) { this.zip = value; }
private String street; private String city; private String state; private String zip; }
可以看到,Address 只是一个简单的数据对象。将它添加到 Person 类中意味着 Person 将有一个名为 addresses 的 Address 数组作为字段。第一个地址是家庭住址,第二个是工作地址,第三个(如果不为 null 的话)是度假屋地址。当然,这些都被设置为 protected,以便将来通过方法来封装。
完成这些设置后,现在可以增强 Person 类,使之支持小孩,所以为 Person 定义一个新字段:一个 Person ArrayList,它同样也有一些相关的方法,以便进行适当的封装。
接下来,由于大多数小孩都有父母,还将添加两个字段来表示母亲和父亲,并增加适当的 accessor/mutator 方法。将为 Person 类增加一个新的方法,使之可以创建一个新的 Person,这个方法有一个贴切的名称,即 haveBaby。此外还增加一些业务规则,以支持生小孩的生物学需求,并将这个新的小 Person 添加到为母亲和父亲字段创建的 children ArrayList 中。做完这些之后,再将这个婴儿返回给调用者。
清单 2 显示,新定义的 Person 类可以处理这种多样性关系。
清单 2. 定义为多样性关系的家庭生活
package com.tedneward.model;
import java.util.List; import java.util.ArrayList; import java.util.Iterator;
public class Person { public Person() { } public Person(String firstName, String lastName, Gender gender, int age, Mood mood) { this.firstName = firstName; this.lastName = lastName; this.gender = gender; this.age = age; this.mood = mood; }
public String getFirstName() { return firstName; } public void setFirstName(String value) { firstName = value; }
public String getLastName() { return lastName; } public void setLastName(String value) { lastName = value; }
public Gender getGender() { return gender; }
public int getAge() { return age; } public void setAge(int value) { age = value; }
public Mood getMood() { return mood; } public void setMood(Mood value) { mood = value; }
public Person getSpouse() { return spouse; } public void setSpouse(Person value) { // A few business rules if (spouse != null) throw new IllegalArgumentException("Already married!");
if (value.getSpouse() != null && value.getSpouse() != this) throw new IllegalArgumentException("Already married!");
spouse = value;
// Highly sexist business rule if (gender == Gender.FEMALE) this.setLastName(value.getLastName());
// Make marriage reflexive, if its not already set that way if (value.getSpouse() != this) value.setSpouse(this); }
public Address getHomeAddress() { return addresses[0]; } public void setHomeAddress(Address value) { addresses[0] = value; }
public Address getWorkAddress() { return addresses[1]; } public void setWorkAddress(Address value) { addresses[1] = value; }
public Address getVacationAddress() { return addresses[2]; } public void setVacationAddress(Address value) { addresses[2] = value; }
public Iterator<Person> getChildren() { return children.iterator(); } public Person haveBaby(String name, Gender gender) { // Business rule if (this.gender.equals(Gender.MALE)) throw new UnsupportedOperationException("Biological impossibility!");
// Another highly objectionable business rule if (getSpouse() == null) throw new UnsupportedOperationException("Ethical impossibility!");
// Welcome to the world, little one! Person child = new Person(name, this.lastName, gender, 0, Mood.CRANKY); // Well, wouldnt YOU be cranky if youd just been pushed out of // a nice warm place?!?
// These are your parents... child.father = this.getSpouse(); child.mother = this;
// ... and youre their new baby. // (Everybody say "Awwww....") children.add(child); this.getSpouse().children.add(child);
return child; }
public String toString() { return "[Person: " + "firstName = " + firstName + " " + "lastName = " + lastName + " " + "gender = " + gender + " " + "age = " + age + " " + "mood = " + mood + " " + (spouse != null ? "spouse = " + spouse.getFirstName() + " " : "") + "]"; }
public boolean equals(Object rhs) { if (rhs == this) return true;
if (!(rhs instanceof Person)) return false;
Person other = (Person)rhs; return (this.firstName.equals(other.firstName) && this.lastName.equals(other.lastName) && this.gender.equals(other.gender) && this.age == other.age); }
private String firstName; private String lastName; private Gender gender; private int age; private Mood mood; private Person spouse; private Address[] addresses = new Address[3]; private List<Person> children = new ArrayList<Person>(); private Person mother; private Person father; }
即使包括所有这些代码,清单 2 提供的家庭关系模型还是过于简单。在这个层次结构中的某些地方,必须处理那些 null 值。但是,在 db4o 中,那个问题更应该在对象建模中解决,而不是在对象操作中解决。所以现在我可以放心地忽略它。
填充和测试对象模型
对于清单 2 中的 Person 类,需要重点注意的是,如果以关系的方式,使用父与子之间分层的、循环的引用来建模,那肯定会比较笨拙。通过一个实例化的对象模型可以更清楚地看到我所谈到的复杂性,所以我将编写一个探察测试来实例化 Person 类。注意,清单 3 中省略了 JUnit 支架(scaffolding)。 |