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(); }