Encapsulate Collection (封裝集合)

Summary:有個函數返回一個集合。讓這個函數返回該集合的一個只讀副本,並在這個類中提供添加/移除集合元素的函數                                               java

Motivation: 咱們經常會在一個類中使用集合(collection,多是arraylistsetvector)來保存一組實例。這樣的類一般也會提供針對該集合的取值/設值函數。數據結構

可是,集合的處理方式應該和其餘種類的數據略有不一樣。取值函數不應返回集合自身,由於這會讓用戶得以修改集合內容而集合擁有者卻一無所悉。這也會對用戶暴露過多對象內部數據結構的信息。若是一個取值函數確實須要返回多個值,它應該避免用戶直接操做對象內所保存的集合,並隱藏對象內與用戶無關的數據結構。至於如何作到這一點,視你使用的java版本不一樣而有所不一樣。另外,不該該爲這整個結婚提供一個設置函數,但應該提供用覺得集合添加/移除元素的函數。這樣,集合擁有者(對象)就能夠控制集合元素的添加和移除。函數

若是你作到以上幾點,集合就被很好的封裝起來了,這即可以下降集合擁有者和用戶之間的耦合度。測試

Mechanicsspa

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();
    }
相關文章
相關標籤/搜索