<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>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 16.4 改進設計 《設計模式》書內所有方案的組織都圍繞“程序進化時會發生什么變化”這個問題展開。對于任何設計來說,這都可能是最重要的一個問題。若根據對這個問題的回答來構造自己的系統,就可以得到兩個方面的結果:系統不僅更易維護(而且更廉價),而且能產生一些能夠重復使用的對象,進而使其他相關系統的構造也變得更廉價。這正是面向對象程序設計的優勢所在,但這一優勢并不是自動體現出來的。它要求對我們對需要解決的問題有全面而且深入的理解。在這一節中,我們準備在系統的逐步改進過程中向大家展示如何做到這一點。 就目前這個回收系統來說,對“什么會變化”這個問題的回答是非常普通的:更多的類型會加入系統。因此,設計的目標就是盡可能簡化這種類型的添加。在回收程序中,我們準備把涉及特定類型信息的所有地方都封裝起來。這樣一來(如果沒有別的原因),所有變化對那些封裝來說都是在本地進行的。這種處理方式也使代碼剩余的部分顯得特別清爽。 ## 16.4.1 “制作更多的對象” 這樣便引出了面向對象程序設計時一條常規的準則,我最早是在Grady Booch那里聽說的:“若設計過于復雜,就制作更多的對象”。盡管聽起來有些曖昧,且簡單得可笑,但這確實是我知道的最有用一條準則(大家以后會注意到“制作更多的對象”經常等同于“添加另一個層次的迂回”)。一般情況下,如果發現一個地方充斥著大量繁復的代碼,就需要考慮什么類能使它顯得清爽一些。用這種方式整理系統,往往會得到一個更好的結構,也使程序更加靈活。 首先考慮Trash對象首次創建的地方,這是`main()`里的一個`switch`語句: ``` for(int i = 0; i < 30; i++) switch((int)(Math.random() * 3)) { case 0 : bin.addElement(new Aluminum(Math.random() * 100)); break; case 1 : bin.addElement(new Paper(Math.random() * 100)); break; case 2 : bin.addElement(new Glass(Math.random() * 100)); } ``` 這些代碼顯然“過于復雜”,也是新類型加入時必須改動代碼的場所之一。如果經常都要加入新類型,那么更好的方案就是建立一個獨立的方法,用它獲取所有必需的信息,并創建一個引用,指向正確類型的一個對象——已經向上轉換到一個`Trash`對象。在《設計模式》中,它被粗略地稱呼為“創建模式”。要在這里應用的特殊模式是`Factory`方法的一種變體。在這里,`Factory`方法屬于`Trash`的一名`static`(靜態)成員。但更常見的一種情況是:它屬于派生類中一個被重載的方法。 `Factory`方法的基本原理是我們將創建對象所需的基本信息傳遞給它,然后返回并等候引用(已經向上轉換至基類型)作為返回值出現。從這時開始,就可以按多態性的方式對待對象了。因此,我們根本沒必要知道所創建對象的準確類型是什么。事實上,`Factory`方法會把自己隱藏起來,我們是看不見它的。這樣做可防止不慎的誤用。如果想在沒有多態性的前提下使用對象,必須明確地使用RTTI和指定轉換。 但仍然存在一個小問題,特別是在基類中使用更復雜的方法(不是在這里展示的那種),且在派生類里重載(覆蓋)了它的前提下。如果在派生類里請求的信息要求更多或者不同的參數,那么該怎么辦呢?“創建更多的對象”解決了這個問題。為實現`Factory`方法,`Trash`類使用了一個新的方法,名為`factory`。為了將創建數據隱藏起來,我們用一個名為`Info`的新類包含`factory`方法創建適當的`Trash`對象時需要的全部信息。下面是`Info`一種簡單的實現方式: ``` class Info { int type; // Must change this to add another type: static final int MAX_NUM = 4; double data; Info(int typeNum, double dat) { type = typeNum % MAX_NUM; data = dat; } } ``` `Info`對象唯一的任務就是容納用于`factory()`方法的信息。現在,假如出現了一種特殊情況,`factory()`需要更多或者不同的信息來新建一種類型的`Trash`對象,那么再也不需要改動`factory()`了。通過添加新的數據和構造器,我們可以修改`Info`類,或者采用子類處理更典型的面向對象形式。 用于這個簡單示例的`factory()`方法如下: ``` static Trash factory(Info i) { switch(i.type) { default: // To quiet the compiler case 0: return new Aluminum(i.data); case 1: return new Paper(i.data); case 2: return new Glass(i.data); // Two lines here: case 3: return new Cardboard(i.data); } } ``` 在這里,對象的準確類型很容易即可判斷出來。但我們可以設想一些更復雜的情況,`factory()`將采用一種復雜的算法。無論如何,現在的關鍵是它已隱藏到某個地方,而且我們在添加新類型時知道去那個地方。 新對象在`main()`中的創建現在變得非常簡單和清爽: ``` for(int i = 0; i < 30; i++) bin.addElement( Trash.factory( new Info( (int)(Math.random() * Info.MAX_NUM), Math.random() * 100))); ``` 我們在這里創建了一個`Info`對象,用于將數據傳入`factory()`;后者在內存堆中創建某種T`rash`對象,并返回添加到`Vector bin`內的引用。當然,如果改變了參數的數量及類型,仍然需要修改這個語句。但假如`Info`對象的創建是自動進行的,也可以避免那個麻煩。例如,可將參數的一個`Vector`傳遞到`Info`對象的構造器中(或直接傳入一個`factory()`調用)。這要求在運行期間對參數進行分析與檢查,但確實提供了非常高的靈活程度。 大家從這個代碼可看出`Factory`要負責解決的“領頭變化”問題:如果向系統添加了新類型(發生了變化),唯一需要修改的代碼在`Factory`內部,所以`Factory`將那種變化的影響隔離出來了。 ## 16.4.2 用于原型創建的一個模式 上述設計模式的一個問題是仍然需要一個中心場所,必須在那里知道所有類型的對象:在`factory()`方法內部。如果經常都要向系統添加新類型,`factory()`方法為每種新類型都要修改一遍。若確實對這個問題感到苦惱,可試試再深入一步,將與類型有關的所有信息——包括它的創建過程——都移入代表那種類型的類內部。這樣一來,每次新添一種類型的時候,需要做的唯一事情就是從一個類繼承。 為將涉及類型創建的信息移入特定類型的Trash里,必須使用“原型”(`prototype`)模式(來自《設計模式》那本書)。這里最基本的想法是我們有一個主控對象序列,為自己感興趣的每種類型都制作一個。這個序列中的對象只能用于新對象的創建,采用的操作類似內建到Java根類`Object`內部的`clone()`機制。在這種情況下,我們將克隆方法命名為`tClone()`。準備創建一個新對象時,要事先收集好某種形式的信息,用它建立我們希望的對象類型。然后在主控序列中遍歷,將手上的信息與主控序列中原型對象內任何適當的信息作對比。若找到一個符合自己需要的,就克隆它。 采用這種方案,我們不必用硬編碼的方式植入任何創建信息。每個對象都知道如何揭示出適當的信息,以及如何對自身進行克隆。所以一種新類型加入系統的時候,`factory()`方法不需要任何改變。 為解決原型的創建問題,一個方法是添加大量方法,用它們支持新對象的創建。但在Java 1.1中,如果擁有指向`Class`對象的一個引用,那么它已經提供了對創建新對象的支持。利用Java 1.1的“反射”(已在第11章介紹)技術,即便我們只有指向`Class`對象的一個引用,亦可正常地調用一個構造器。這對原型問題的解決無疑是個完美的方案。 原型列表將由指向所有想創建的`Class`對象的一個引用列表間接地表示。除此之外,假如原型處理失敗,則`factory()`方法會認為由于一個特定的`Class`對象不在列表中,所以會嘗試裝載它。通過以這種方式動態裝載原型,`Trash`類根本不需要知道自己要操縱的是什么類型。因此,在我們添加新類型時不需要作出任何形式的修改。于是,我們可在本章剩余的部分方便地重復利用它。 ``` //: Trash.java // Base class for Trash recycling examples package c16.trash; import java.util.*; import java.lang.reflect.*; public abstract class Trash { private double weight; Trash(double wt) { weight = wt; } Trash() {} public abstract double value(); public double weight() { return weight; } // Sums the value of Trash in a bin: public static void sumValue(Vector bin) { Enumeration e = bin.elements(); double val = 0.0f; while(e.hasMoreElements()) { // One kind of RTTI: // A dynamically-checked cast Trash t = (Trash)e.nextElement(); val += t.weight() * t.value(); System.out.println( "weight of " + // Using RTTI to get type // information about the class: t.getClass().getName() + " = " + t.weight()); } System.out.println("Total value = " + val); } // Remainder of class provides support for // prototyping: public static class PrototypeNotFoundException extends Exception {} public static class CannotCreateTrashException extends Exception {} private static Vector trashTypes = new Vector(); public static Trash factory(Info info) throws PrototypeNotFoundException, CannotCreateTrashException { for(int i = 0; i < trashTypes.size(); i++) { // Somehow determine the new type // to create, and create one: Class tc = (Class)trashTypes.elementAt(i); if (tc.getName().indexOf(info.id) != -1) { try { // Get the dynamic constructor method // that takes a double argument: Constructor ctor = tc.getConstructor( new Class[] {double.class}); // Call the constructor to create a // new object: return (Trash)ctor.newInstance( new Object[]{new Double(info.data)}); } catch(Exception ex) { ex.printStackTrace(); throw new CannotCreateTrashException(); } } } // Class was not in the list. Try to load it, // but it must be in your class path! try { System.out.println("Loading " + info.id); trashTypes.addElement( Class.forName(info.id)); } catch(Exception e) { e.printStackTrace(); throw new PrototypeNotFoundException(); } // Loaded successfully. Recursive call // should work this time: return factory(info); } public static class Info { public String id; public double data; public Info(String name, double data) { id = name; this.data = data; } } } ///:~ ``` 基本`Trash`類和`sumValue()`還是象往常一樣。這個類剩下的部分支持原型模式。大家首先會看到兩個內部類(被設為`static`屬性,使其成為只為代碼組織目的而存在的內部類),它們描述了可能出現的異常。在它后面跟隨的是一個`Vector trashTypes`,用于容納`Class`引用。 在`Trash.factory()`中,`Info`對象`id`(`Info`類的另一個版本,與前面討論的不同)內部的`String`包含了要創建的那種`Trash`的類型名稱。這個`String`會與列表中的`Class`名比較。若存在相符的,那便是要創建的對象。當然,還有很多方法可以決定我們想創建的對象。之所以要采用這種方法,是因為從一個文件讀入的信息可以轉換成對象。 發現自己要創建的`Trash`(垃圾)種類后,接下來就輪到“反射”方法大顯身手了。`getConstructor()`方法需要取得自己的參數——由`Class`引用構成的一個數組。這個數組代表著不同的參數,并按它們正確的順序排列,以便我們查找的構造器使用。在這兒,該數組是用Java 1.1的數組創建語法動態創建的: ``` new Class[] {double.class} ``` 這個代碼假定所有`Trash`類型都有一個需要`double`數值的構造器(注意`double.class`與`Double.class`是不同的)。若考慮一種更靈活的方案,亦可調用`getConstructors()`,令其返回可用構造器的一個數組。 從`getConstructors()`返回的是指向一個`Constructor`對象的引用(該對象是`java.lang.reflect`的一部分)。我們用方法`newInstance()`動態地調用構造器。該方法需要獲取包含了實際參數的一個`Object`數組。這個數組同樣是按Java 1.1的語法創建的: ``` new Object[] {new Double(info.data)} ``` 在這種情況下,`double`必須置入一個封裝(容器)類的內部,使其真正成為這個對象數組的一部分。通過調用`newInstance()`,會提取出`double`,但大家可能會覺得稍微有些迷惑——參數既可能是`double`,也可能是`Double`,但在調用的時候必須用`Double`傳遞。幸運的是,這個問題只存在于基本數據類型中間。 理解了具體的過程后,再來創建一個新對象,并且只為它提供一個`Class`引用,事情就變得非常簡單了。就目前的情況來說,內部循環中的`return`永遠不會執行,我們在終點就會退出。在這兒,程序動態裝載`Class`對象,并把它加入`trashTypes`(垃圾類型)列表,從而試圖糾正這個問題。若仍然找不到真正有問題的地方,同時裝載又是成功的,那么就重復調用`factory`方法,重新試一遍。 正如大家會看到的那樣,這種設計模式最大的優點就是不需要改動代碼。無論在什么情況下,它都能正常地使用(假定所有`Trash`子類都包含了一個構造器,用以獲取單個`double`參數)。 (1) Trash子類 為了與原型機制相適應,對`Trash`每個新子類唯一的要求就是在其中包含了一個構造器,指示它獲取一個`double`參數。Java 1.1的“反射”機制可負責剩下的所有工作。 下面是不同類型的`Trash`,每種類型都有它們自己的文件里,但都屬于`Trash`包的一部分(同樣地,為了方便在本章內重復使用): ``` //: Aluminum.java // The Aluminum class with prototyping package c16.trash; public class Aluminum extends Trash { private static double val = 1.67f; public Aluminum(double wt) { super(wt); } public double value() { return val; } public static void value(double newVal) { val = newVal; } } ///:~ ``` 下面是一種新的`Trash`類型: ``` //: Cardboard.java // The Cardboard class with prototyping package c16.trash; public class Cardboard extends Trash { private static double val = 0.23f; public Cardboard(double wt) { super(wt); } public double value() { return val; } public static void value(double newVal) { val = newVal; } } ///:~ ``` 可以看出,除構造器以外,這些類根本沒有什么特別的地方。 (2) 從外部文件中解析出`Trash` 與`Trash`對象有關的信息將從一個外部文件中讀取。針對`Trash`的每個方面,文件內列出了所有必要的信息——每行都代表一個方面,采用`垃圾(廢品)名稱:值`的固定格式。例如: ``` c16.Trash.Glass:54 c16.Trash.Paper:22 c16.Trash.Paper:11 c16.Trash.Glass:17 c16.Trash.Aluminum:89 c16.Trash.Paper:88 c16.Trash.Aluminum:76 c16.Trash.Cardboard:96 c16.Trash.Aluminum:25 c16.Trash.Aluminum:34 c16.Trash.Glass:11 c16.Trash.Glass:68 c16.Trash.Glass:43 c16.Trash.Aluminum:27 c16.Trash.Cardboard:44 c16.Trash.Aluminum:18 c16.Trash.Paper:91 c16.Trash.Glass:63 c16.Trash.Glass:50 c16.Trash.Glass:80 c16.Trash.Aluminum:81 c16.Trash.Cardboard:12 c16.Trash.Glass:12 c16.Trash.Glass:54 c16.Trash.Aluminum:36 c16.Trash.Aluminum:93 c16.Trash.Glass:93 c16.Trash.Paper:80 c16.Trash.Glass:36 c16.Trash.Glass:12 c16.Trash.Glass:60 c16.Trash.Paper:66 c16.Trash.Aluminum:36 c16.Trash.Cardboard:22 ``` 注意在給定類名的時候,類路徑必須包含在內,否則就找不到類。 為解析它,每一行內容都會讀入,并用字符串方法`indexOf()`來建立`:`的一個索引。首先用字符串方法`substring()`取出垃圾的類型名稱,接著用一個靜態方法`Double.valueOf()`取得相應的值,并轉換成一個`double`值。`trim()`方法則用于刪除字符串兩頭的多余空格。 `Trash`解析器置入單獨的文件中,因為本章將不斷地用到它。如下所示: ``` //: ParseTrash.java // Open a file and parse its contents into // Trash objects, placing each into a Vector package c16.trash; import java.util.*; import java.io.*; public class ParseTrash { public static void fillBin(String filename, Fillable bin) { try { BufferedReader data = new BufferedReader( new FileReader(filename)); String buf; while((buf = data.readLine())!= null) { String type = buf.substring(0, buf.indexOf(':')).trim(); double weight = Double.valueOf( buf.substring(buf.indexOf(':') + 1) .trim()).doubleValue(); bin.addTrash( Trash.factory( new Trash.Info(type, weight))); } data.close(); } catch(IOException e) { e.printStackTrace(); } catch(Exception e) { e.printStackTrace(); } } // Special case to handle Vector: public static void fillBin(String filename, Vector bin) { fillBin(filename, new FillableVector(bin)); } } ///:~ ``` 在`RecycleA.java`中,我們用一個`Vector`容納`Trash`對象。然而,亦可考慮采用其他集合類型。為做到這一點,`fillBin()`的第一個版本將獲取指向一個`Fillable`的引用。后者是一個接口,用于支持一個名為`addTrash()`的方法: ``` //: Fillable.java // Any object that can be filled with Trash package c16.trash; public interface Fillable { void addTrash(Trash t); } ///:~ ``` 支持該接口的所有東西都能伴隨`fillBin`使用。當然,`Vector`并未實現`Fillable`,所以它不能工作。由于`Vector`將在大多數例子中應用,所以最好的做法是添加另一個重載的`fillBin()`方法,令其以一個`Vector`作為參數。利用一個適配器(`Adapter`)類,這個`Vector`可作為一個`Fillable`對象使用: ``` //: FillableVector.java // Adapter that makes a Vector Fillable package c16.trash; import java.util.*; public class FillableVector implements Fillable { private Vector v; public FillableVector(Vector vv) { v = vv; } public void addTrash(Trash t) { v.addElement(t); } } ///:~ ``` 可以看到,這個類唯一的任務就是負責將`Fillable`的`addTrash()`同`Vector`的`addElement()`方法連接起來。利用這個類,已重載的`fillBin()`方法可在`ParseTrash.java`中伴隨一個`Vector`使用: ``` public static void fillBin(String filename, Vector bin) { fillBin(filename, new FillableVector(bin)); } ``` 這種方案適用于任何頻繁用到的集合類。除此以外,集合類還可提供它自己的適配器類,并實現`Fillable`(稍后即可看到,在`DynaTrash.java`中)。 (3) 原型機制的重復應用 現在,大家可以看到采用原型技術的、修訂過的`RecycleA.java`版本了: ``` //: RecycleAP.java // Recycling with RTTI and Prototypes package c16.recycleap; import c16.trash.*; import java.util.*; public class RecycleAP { public static void main(String[] args) { Vector bin = new Vector(); // Fill up the Trash bin: ParseTrash.fillBin("Trash.dat", bin); Vector glassBin = new Vector(), paperBin = new Vector(), alBin = new Vector(); Enumeration sorter = bin.elements(); // Sort the Trash: while(sorter.hasMoreElements()) { Object t = sorter.nextElement(); // RTTI to show class membership: if(t instanceof Aluminum) alBin.addElement(t); if(t instanceof Paper) paperBin.addElement(t); if(t instanceof Glass) glassBin.addElement(t); } Trash.sumValue(alBin); Trash.sumValue(paperBin); Trash.sumValue(glassBin); Trash.sumValue(bin); } } ///:~ ``` 所有`Trash`對象——以及`ParseTrash`及支撐類——現在都成為名為`c16.trash`的一個包的一部分,所以它們可以簡單地導入。 無論打開包含了`Trash`描述信息的數據文件,還是對那個文件進行解析,所有涉及到的操作均已封裝到`static`(靜態)方法`ParseTrash.fillBin()`里。所以它現在已經不是我們設計過程中要注意的一個重點。在本章剩余的部分,大家經常都會看到無論添加的是什么類型的新類,`ParseTrash.fillBin()`都會持續工作,不會發生改變,這無疑是一種優良的設計模式。 提到對象的創建,這一方案確實已將新類型加入系統所需的變動嚴格地“本地化”了。但在使用RTTI的過程中,卻存在著一個嚴重的問題,這里已明確地顯露出來。程序表面上工作得很好,但卻永遠偵測到不能“硬紙板”(`Cardboard`)這種新的廢品類型——即使列表里確實有一個硬紙板類型!之所以會出現這種情況,完全是由于使用了RTTI的緣故。RTTI只會查找那些我們告訴它查找的東西。RTTI在這里錯誤的用法是“系統中的每種類型”都進行了測試,而不是僅測試一種類型或者一個類型子集。正如大家以后會看到的那樣,在測試每一種類型時可換用其他方式來運用多態性特征。但假如以這種形式過多地使用RTTI,而且又在自己的系統里添加了一種新類型,很容易就會忘記在程序里作出適當的改動,從而埋下以后難以發現的Bug。因此,在這種情況下避免使用RTTI是很有必要的,這并不僅僅是為了表面好看——也是為了產生更易維護的代碼。
                  <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>

                              哎呀哎呀视频在线观看