Summary:有个函数返回一个集合。让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数 java
Motivation: 咱们经常会在一个类中使用集合(collection,多是array、list、set或vector)来保存一组实例。这样的类一般也会提供针对该集合的取值/设值函数。数据结构
可是,集合的处理方式应该和其余种类的数据略有不一样。取值函数不应返回集合自身,由于这会让用户得以修改集合内容而集合拥有者却一无所悉。这也会对用户暴露过多对象内部数据结构的信息。若是一个取值函数确实须要返回多个值,它应该避免用户直接操做对象内所保存的集合,并隐藏对象内与用户无关的数据结构。至于如何作到这一点,视你使用的java版本不一样而有所不一样。另外,不该该为这整个结婚提供一个设置函数,但应该提供用觉得集合添加/移除元素的函数。这样,集合拥有者(对象)就能够控制集合元素的添加和移除。函数
若是你作到以上几点,集合就被很好的封装起来了,这即可以下降集合拥有者和用户之间的耦合度。测试
Mechanics:spa
1.加入为集合添加/移除元素的函数code
2.将保存集合的字段初始化为一个空集合。对象
3.编译。接口
4.找出集合设值函数的全部调用者。你能够修改那个设值函数,让它使用上述新创建的“添加/移除元素”函数;也能够直接修改调用端,改让它们调用上述新创建的“添加/删除元素”函数。ci
两种状况下须要用到集合设值函数:(1)集合为空时;(2)准备将原有集合替换为另外一个集合时rem
你或许会想运用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(); }