Summary:有个函数返回一个集合。让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数
Motivation: 我们常常会在一个类中使用集合(collection,可能是array、list、set或vector)来保存一组实例。这样的类通常也会提供针对该集合的取值/设值函数。
但是,集合的处理方式应该和其他种类的数据略有不同。取值函数不该返回集合自身,因为这会让用户得以修改集合内容而集合拥有者却一无所悉。这也会对用户暴露过多对象内部数据结构的信息。如果一个取值函数确实需要返回多个值,它应该避免用户直接操作对象内所保存的集合,并隐藏对象内与用户无关的数据结构。至于如何做到这一点,视你使用的java版本不同而有所不同。另外,不应该为这整个结婚提供一个设置函数,但应该提供用以为集合添加/移除元素的函数。这样,集合拥有者(对象)就可以控制集合元素的添加和移除。
如果你做到以上几点,集合就被很好的封装起来了,这便可以降低集合拥有者和用户之间的耦合度。
Mechanics:
1.加入为集合添加/移除元素的函数
2.将保存集合的字段初始化为一个空集合。
3.编译。
4.找出集合设值函数的所有调用者。你可以修改那个设值函数,让它使用上述新建立的“添加/移除元素”函数;也可以直接修改调用端,改让它们调用上述新建立的“添加/删除元素”函数。
两种情况下需要用到集合设值函数:(1)集合为空时;(2)准备将原有集合替换为另一个集合时
你或许会想运用Rename Method 为集合设值函数改名:从setXxx()改为initializeXxx()或replaceXxx().
5.编译,测试
6.找出所有“通过取值函数获得集合并修改其内容”的函数。逐一修改这些函数,让它们改用添加/移除函数。每次修改后,编译并测试。
7.修改完上述所有“通过取值函数获得集合并修改其内容”的函数后,修改取值函数自身,使它返回该集合的一个只读副本。
8.编译,测试
9.找出取值函数的所有用户,从中找出应该存在于集合所属对象内的代码。运用Extract Method 和Move Method将这些代码移到宿主对象去
范例1.
假设有个人要去上课。我们用一个简单的Course来表示"课程":
class Course...
public Course (String name, boolean isAdvanced){...};
public boolean isAdvanced(){...};
我不关系课程其他细节。我感兴趣的是表示“人”的Person:
class Person ...
public Set getCourses(){
return _courses;
}
public void setCourses(Set arg){
_courses = arg;
}
private Set _courses
有了这个接口,我们就可以这样为某人添加课程:
Person kent = new Person();
Set s = new HashSet();
s.add(new Course("Smalltalk Programming",false));
s.add(new Course("Appreciationg Single Malts",true));
kent.setCourses(s);
Assert.equals(2,kent.getCourses().size());
Course refact = new Course("refactoring",true);
kent.getCourses().add(refact);
kent.getCourses().add(new Course("Brutal Sarcasm",false));
Assert.equals(4, kent.getCourses().size());
kent.getCourses().remove(refact);
Assert.equals(3, kent.getCourses().size());
如果想了解高级课程,可以这么做
Iterator iter = person.getCourses().iterator();
int count = 0;
while (iter.hasNext()){
Course each = (Course) iter.next();
if(each.isAdvanced()){
count ++;
}
}
我们要做的第一件事就是为Person中的集合建立合适的修改函数(即添加/移除函数),如下所示,然后编译:
class Person
public void addCourse(Courese arg){
_courses.add(arg);
}
public void removeCourse(Course arg){
_courses.remove(arg);
}
如果像下面这样初始化_courses字段,会轻松很多:
private Set _courses = new HashSet();
接下来,我们需要观察设值函数的调用者。如果有许多地点大量运用了设值函数,就需要修改设值函数,令它调用添加/移除函数。这个过程的复杂度取决于设值函数的被使用方式。设值函数的用法有两种,最简单的情况就是:它被用来初始化集合。换句话说,设值函数被调用之前,_courses是个空集合。这种情况下只需修改设值函数,令它调用添加函数就行了:
class Person...
public void setCourses(Set arg){
Assert.isTrue(_courses.isEmpty());
Iterator iter = arg.iterator();
while(iter.hasNext()){
addCourse((Course)iter.next());
}
}
修改完毕后,最好以Rename Method 更明确地展示这个函数的意图:
public void initializeCourses(Set arg){
Assert.isTrue(_courses.isEmpty());
Iterator iter = arg.iterator();
while(iter.hasNext()){
addCourse((Course)iter.next());
}
}
更普通的情况下,我们必须首先调用移除函数将集合中的所有元素全部移除,然后再调用添加函数将元素一一添加进去。不过这种情况很少出现。
如果知道初始化时,除了添加元素,不再有其他行为,那么我们可以不适用循环,直接调用addAll()函数:
public void initializeCourses(Set arg){
Assert.isTrue(_courses.isEmpty());
_courses.addAll(arg);
}
我们不能直接把传入的set赋值给_coureses字段,就算原本这个字段是空的也不行。因为万一用户在把set传递给Person对象之后又去修改set中的元素,就会破坏封装。我们必须像上面那样创建set的一个副本。
如果用户仅仅只是创建一个set,然后使用设值函数,我们可以让它们直接使用添加/移除函数,并将设值函数完全移除。于是,以下代码
Person kent = new Person();
Set s = new HashSet();
s.add(new Course("Smalltalk Programming",false));
s.add(new Course("Appreciationg Single Malts",true));
kent.setCourses(s);
就变成了:
Person kent = new Person();
kent.addCourse(new Course("Smalltalk Programming",false));
kent.addCourse(new Course("Appreciationg Single Malts",true));
接下来,开始观察取值函数的使用情况。首先处理“通过取值函数修改集合元素的情况”,例如:
kent.getCourses().add(new Course("Brutal Sarcasm", false));
这种情况必须加以改变,使它调用新的修改函数:
kent.addCourse(new Course("Brutal Sarcasm", false));
修改完所有此类情况之后,可以让取值函数返回一个只读副本,用以确保没有任何一个用户能够通过取值函数修改集合:
public Set getCourses(){
return Collections.unmodifiableSet(_courses);
}
这样就完成了对集合的封装。此后,不通过Person提供的添加/移除函数,谁也不能修改集合内的元素。
将行为移到这个类中
我们拥有了合理的接口。现在开始观察取值函数的用户,从中找出应该属于Person的代码。下面这样的代码就应该搬移到Person去:
Iterator iter = person.getCourses().iterator();
int count = 0;
while (iter.hasNext()){
Course each = (Course) iter.next();
if(each.isAdvanced()){
count ++;
}
}
因为以上只使用了属于Person的数据。首先,使用Extract Method将这段代码提炼为一个独立函数:
int numberOfAdvancedCourses(Person person){
Iterator iter = person.getCourses().iterator();
int count = 0;
while (iter.hasNext()){
Course each = (Course) iter.next();
if(each.isAdvanced()){
count ++;
}
}
return count;
}
然后使用Move Method将这个函数搬移到Person中:
class Person...
int numberOfAdvancedCourses(Person person){
Iterator iter = person.getCourses().iterator();
int count = 0;
while (iter.hasNext()){
Course each = (Course) iter.next();
if(each.isAdvanced()){
count ++;
}
}
return count;
}
下列代码是一个常见的例子:
kent.getCourse().size();
可以将其修改成更具可读性的样子,像这样:
kent.numberOfCourses();
class Person ...
public int numberOfCourses(){
return _courses.size();
}