<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ### Encapsulate Collection(封裝群集) 有個函數(method)返回一個群集(collection)。 讓這個函數返回該群集的一個只讀映件(read-only view),并在這個class中提供「添加/移除」(add/remove)群集元素的函數。 ![](https://box.kancloud.cn/2016-08-15_57b1b5a97f53f.gif) **動機(Motivation)** class常常會使用群集(collection,可能是array、list、set或vector)來保存一組實體。這樣的class通常也會提供針對該群集的「取值/設值函數」(getter/setter)。 但是,群集的處理方式應該和其他種類的數據略有不同。取值函數(getter)不該返回群集自身,因為這將讓用戶得以修改群集內容而群集擁有者卻一無所悉。這也會對用戶暴露過多「對象內部數據結構」的信息。如果一個取值函數(getter)確實需要返回多個值,它應該避免用戶直接操作對象內所保存的群集,并隱藏對象內「與用戶無關」的數據結構。至于如何做到這一點,視你使用的版本不同而有所不同。 另外,不應該為這整個群集提供一個設值函數(setter),但應該提供用以為群集添加/移除(add/remove)元素的函數。這樣,群集擁有者(對象)就可以控制群集元素的添加和移除。 如果你做到以上數點,群集(collection)就被很好地封裝起來了,這便可以降低群集擁有者(class)和用戶之間的耦合度。 **作法(Mechanics)** - 加入「為群集添加(add)、移除(remove)元素」的函數。 - 將「用以保存群集」的值域初始化為一個空群集。 - 編譯。 - 找出「群集設值函數」的所有調用者。你可以修改那個設值函數,讓它使用 上述新建立的「添加/移除元素」函數;也可以直接修改調用端,改讓它們調用上述新建立的「添加/移除元素」函數。 - 兩種情況下需要用到「群集設值函數」:(1) 群集為空時;(2) 準備將原有群集替換為另一個群集時。 - 你或許會想運用Rename Method 為「群集設值函數」改名,從setXxx()改為initialzeXxx()或replaceXxx()。 - 編譯,測試。 - 找出所有「通過取值函數(getter)獲得群集并修改其內容」的函數。逐一修改這些函數,讓它們改用「添加/移除」(add/remove)函數。每次修改后,編譯并測試。 - 修改完上述所有「通過取值函數(getter)獲得群集并修改群集內容」的函數后,修改取值函數自身,使它返回該群集的一個只讀映件(read-only view)。 - 在Java 2中,你可以使用Collection.unmodifiableXxx()得到該群集的只讀映件。 - 在Java 1.1中,你應該返回群集的一份拷貝。 - 編譯,測試。 - 找出取值函數(getter)的所有用戶,從中找出應該存在于「群集之宿主對象(host object)內的代碼。運用 Extract Method 和 Move Method 將這些代碼移到宿主對象去。 如果你使用Java 2,那么本項重構到此為止。如果你使用Java 1.1,那么用戶也許會喜歡使用枚舉(enumeration)。為了提供這個枚舉,你應該這樣做: - 修改現有取值函數(getter)的名字,然后添加一個新取值函數,使其返回一個枚舉。找出舊取值函數的所有被使用點,將它們都改為使用新取值函數。 - 如果這一步跨度太大,你可以先使用Rename Method 修改原取值函數的名稱;再建立一個新取值函數用以返回枚舉;最后再修改 所有調用者,使其調用新取值函數。 - 編譯,測試。 **范例(Example)** Java 2擁有一組全新群集(collections)——并非僅僅加入一些新classes,而是完全改變了群集的風格。所以在Java 1.1和Java 2中,封裝群集的方式也完全不同。我首先討論Java 2的方式,因為我認為功能更強大的Java 2 collections會取代Java 1.1 collections 的地位。 **范例(Example):Java 2** 假設有個人要去上課。我們用一個簡單的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 ("Appreciating 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中的群集(collection)建立合適的修改函數(modifiers,亦即add/remove函數:),如下所示,然后編譯: ~~~ class Person public void addCourse (Course arg) { _courses.add(arg); } public void removeCourse (Course arg) { _courses.remove(arg); } ~~~ 如果我像下面這樣初始化_courses值域,我的人生會輕松得多: ~~~ private Set _courses = new HashSet(); ~~~ 接下來我需要觀察設值函數(setter)的調用者。如果有許多地點大量運用了設值函數,我就需要修改設值函數,令它調用添加/移除(add/remove)函數。這個過程的復雜度取決于設值函數的被使用方式。設值函數的用法有兩種,最簡單的情況就是: 它被用來「對群集進行初始化動作」。換句話說,設值函數被調用之前,_courses 是個空群集。這種情況下我只需修改設值函數,令它調用添加函數(add)就行了 : ~~~ 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()); } } ~~~ 更普通(譯注:而非上述所言對「空群集」設初值)的情況下,我必須首先以移除函數(remove)將群集中的所有元素全部移除,然后再調用添加函數(add)將 元素一一添加進去。不過我發現這種情況很少出現(晤,愈是普通的情況,愈少出現)。 如果我知道初始化時,除了添加元素,不會再有其他行為,那么我可以不使用循環, 直接調用addAll() 函數: ~~~ public void initializeCourses(Set arg) { Assert.isTrue(_courses.isEmpty()); _courses.addAll(arg); } ~~~ 我不能僅僅對這個set 賦值,就算原本這個set 是空的也不行。因為萬一用戶在「把set 傳遞給Person 對象」之后又去修改它,會破壞封裝。我必須像上面那樣創建set 的一個拷貝。 如果用戶僅僅只是創建一個set,然后使用設值函數(setter。譯注:目前已改名為initializeCourses()),我可以讓它們直接使用添加/移除(add/remove)函數, 并將設值函數完全移除。于是,以下代碼: ~~~ Person kent = new Person(); Set s = new HashSet(); s.add(new Course ("Smalltalk Programming", false)); s.add(new Course ("Appreciating Single Malts", true)); kent.initializeCourses(s); ~~~ 就變成了: ~~~ Person kent = new Person(); kent.addCourse(new Course ("Smalltalk Programming", false)); kent.addCourse(new Course ("Appreciating Single Malts", true)); ~~~ 接下來我幵始觀察取值函數(getter)的使用情況。首先處理「有人以取值函數修改底部群集(underlying collection)」的情況,例如: ~~~ kent.getCourses().add(new Course ("Brutal Sarcasm", false)); ~~~ 這種情況下我必須加以改變,使它調用新的修改函數(modifier): ~~~ kent.addCourse(new Course ("Brutal Sarcasm", false)); ~~~ 修改完所有此類情況之后,我可以讓取值函數(getter)返回一個只讀映件(read-only view),用以確保沒有任何一個用戶能夠通過取值函數(getter)修改群集: ~~~ public Set getCourses() { return Collections.unmodifiableSet(_courses); } ~~~ 這樣我就完成了對群集的封裝。此后,不通過Person 提供的add/remove 函數,誰也不能修改群集內的元素。 將行為移到這個class中 我擁有了合理的接口。現在開始觀察取值函數(getter)的用戶,從中找出應該屬于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() { Iterator iter = getCourses().iterator(); int count = 0; while (iter.hasNext()) { Course each = (Course) iter.next(); if (each.isAdvanced()) count ++; } return count; } ~~~ 舉個常見例子,下列代碼: ~~~ kent.getCourses().size() ~~~ 可以修改成更具可讀性的樣子,像這樣: ~~~ kent.numberOfCourses() class Person... public int numberOfCourses() { return _courses.size(); } ~~~ 數年以前,我曾經擔心將這樣的行為搬移到Person 中會導致Person 變得臃腫。但是在實際工作經驗中,我發現這通常并不成為問題。 **范例: Java 1.1** 在很多地方,Java 1.1的情況和Java 2非常相似。這里我使用同一個范例,不過群集改為vector (譯注:因為vector 屬于Java 1.1,不屬于Java 2): ~~~ class Person... public Vector getCourses() { return _courses; } public void setCourses(Vector arg) { _courses = arg; } private Vector _courses; ~~~ 同樣地,我首先建立修改函數(modifiers:add/remove 函數),并初始化_courses值域,如下所示: ~~~ class Person public void addCourse(Course arg) { _courses.addElement(arg); } public void removeCourse(Course arg) { _courses.removeElement(arg); } private Vector _courses = new Vector(); ~~~ 我可以修改setCourses()來初始化這個vector: ~~~ public void initializeCourses(Vector arg) { Assert.isTrue(_courses.isEmpty()); Enumeration e = arg.elements(); while (e.hasMoreElements()) { addCourse((Course) e.nextElement()); } } ~~~ 然后,我修改取值函數(getter)調用點,讓它們改用新建的修改函數(modifiers)。 于是下列代碼: ~~~ kent.getCourses().addElement(new Course ("Brutal Sarcasm", false)); ~~~ 就變成了: ~~~ kent.addCourse(new Course ("Brutal Sarcasm", false)); ~~~ 最后一步需要有點改變,因為Java 1.1的Vector class并沒有提供「不可修改版」(unmodifiable version): ~~~ class Person... Vector getCourses() { return (Vector) _courses.clone(); } ~~~ 這樣便完成了群集的封裝。此后,如果不通過Person 提供的函數,誰也不能改變群集的元素。 **范例:封裝數組(Encapsulating Arrays)** 數組(array)很常被使用,特別是對于那些不熟悉群集(collections)的程序員而言。我很少使用數組,因為我更喜歡功能更加豐富的群集類。進行封裝時,我常把數組換成其他群集。 這次我們的范例從一個字符串數組(string array)開始: ~~~ String[] getSkills() { return _skills; } void setSkills (String[] arg) { _skills = arg; } String[] _skills; ~~~ 同樣地,首先我要提供一個修改函數(modifier)。由于用戶有可能修改數組中某一特定位置上的值,所以我提供的setSkill()必須能對任何特定位置上的元素賦值: ~~~ void setSkill(int index, String newSkill) { _skills[index] = newSkill; } ~~~ 如果我需要對整個數組賦值,可以使用下列函數: ~~~ void setSkills (String[] arg) { _skills = new String[arg.length]; for (int i=0; i < arg.length; i++) setSkill(i,arg[i]); } ~~~ 如果需要處理「被移除元素」(removed elements),就會有些困難。如果作為引數(argument)的數組和原數組長度不同,情況也會比較復雜。這也是我優先選擇群集的原因之一。 現在,我需要觀察取值函數(getter)的調用者。我可以把下列代碼: ~~~ kent.getSkills()[1] = "Refactoring"; ~~~ 改成: ~~~ kent.setSkill(1,"Refactoring"); ~~~ 完成這一系列修改之后,我可以修改取值函數(getter),令它返回一份數組拷貝: ~~~ String[] getSkills() { String[] result = new String[_skills.length]; System.arraycopy(_skills, 0, result, 0, _skills.length); return result; } ~~~ 現在,是把數組換成list 的時候了: ~~~ class Person... String[] getSkills() { return (String[]) _skills.toArray(new String[0]); } void setSkill(int index, String newSkill) { _skills.set(index,newSkill); } List _skills = new ArrayList(); ~~~
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看