這樣便引出了面向對象程序設計時一條常規的准則,我最早是在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對象。在《Design Patterns》中,它被粗略地稱呼為“創建范式”。要在這裡應用的特殊范式是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();後者在內存堆中創建某種Trash對象,並返回添加到Vector bin內的句柄。當然,如果改變了參數的數量及類型,仍然需要修改這個語句。但假如Info對象的創建是自動進行的,也可以避免那個麻煩。例如,可將參數的一個Vector傳遞到Info對象的構建器中(或直接傳入一個factory()調用)。這要求在運行期間對參數(自變量)進行分析與檢查,但確實提供了非常高的靈活程度。
大家從這個代碼可看出Factory要負責解決的“領頭變化”問題:如果向系統添加了新類型(發生了變化),唯一需要修改的代碼在Factory內部,所以Factory將那種變化的影響隔離出來了。