Encapsulate Collection (封装集合)

Summary:有个函数返回一个集合。让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数                                               171723_9dE1_134516.png

Motivation: 我们常常会在一个类中使用集合(collection,可能是arraylistsetvector)来保存一组实例。这样的类通常也会提供针对该集合的取值/设值函数。

但是,集合的处理方式应该和其他种类的数据略有不同。取值函数不该返回集合自身,因为这会让用户得以修改集合内容而集合拥有者却一无所悉。这也会对用户暴露过多对象内部数据结构的信息。如果一个取值函数确实需要返回多个值,它应该避免用户直接操作对象内所保存的集合,并隐藏对象内与用户无关的数据结构。至于如何做到这一点,视你使用的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();
    }


转载于:https://my.oschina.net/u/134516/blog/206910

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值