封裝變化之對(duì)象創(chuàng)建
在對(duì)象創(chuàng)建的過(guò)程中,經(jīng)常會(huì)出現(xiàn)的一個(gè)問(wèn)題就是通過(guò)顯示地指定一個(gè)類(lèi)來(lái)創(chuàng)建對(duì)象,從而導(dǎo)致緊耦合。這是因?yàn)閯?chuàng)建對(duì)象時(shí)指定類(lèi)名將使你受特定實(shí)現(xiàn)的約束而不是特定接口的約束。這會(huì)使未來(lái)的變化更加復(fù)雜。要避免這種情況,就應(yīng)該間接地創(chuàng)建對(duì)象。
這種緊耦合的問(wèn)題很大程度是由new關(guān)鍵字帶來(lái)的,由于new的緊耦合出現(xiàn),使得緊耦合的類(lèi)很難獨(dú)立地被復(fù)用,因?yàn)樗鼈冎g是相互依賴(lài)的。并且緊耦合產(chǎn)生單塊的系統(tǒng),要改變或者刪掉一個(gè)類(lèi),就必須要理解和改變其他許多類(lèi)。這也是導(dǎo)致系統(tǒng)難以維護(hù)和移植的一個(gè)重要原因。
所以可以通過(guò)“對(duì)象創(chuàng)建”模式繞開(kāi)new,從而避免在對(duì)象創(chuàng)建(new)過(guò)程中所導(dǎo)致的緊耦合(依賴(lài)具體的類(lèi)),以此支持對(duì)象創(chuàng)建的穩(wěn)定。
那么如何避免new呢?舉個(gè)例子!
public void fun1(){
//...
Product p = new Product(); //改為:Product p = productFactory.createProduct();
//...
}
這樣的方式就是通過(guò)一個(gè)工廠(chǎng)調(diào)用一個(gè)方法來(lái)創(chuàng)建相應(yīng)的產(chǎn)品,但是可能大家又會(huì)產(chǎn)生一個(gè)問(wèn)題,這樣操作雖然解決了Product 的new操作,但是對(duì)于ProductFactory 而言不是也需要通過(guò)new來(lái)產(chǎn)生嗎?
對(duì)于這個(gè)問(wèn)題,我想是很多人在接觸到設(shè)計(jì)模式的時(shí)候都會(huì)去思考的問(wèn)題,既然ProductFactory 還是要用到new,那工廠(chǎng)類(lèi)還有存在的必要嗎?這時(shí),我們可以會(huì)想到兩種解決方式,一是將createProdyct() 方法寫(xiě)成靜態(tài)方法,這樣調(diào)用的時(shí)候自然不需要new了。二是通過(guò)注入的方式,比如在應(yīng)用類(lèi)當(dāng)中通過(guò)setter或是構(gòu)造方法傳入一個(gè)工廠(chǎng)類(lèi)的對(duì)象。
對(duì)于靜態(tài)方法而言,簡(jiǎn)單地說(shuō),即使是使用靜態(tài)方法,那Product p = ProductFactory.createProduct() 這樣依然是一種緊耦合的方式,因?yàn)楣S(chǎng)類(lèi)無(wú)法替換,和直接new出產(chǎn)品區(qū)別不大。
對(duì)于注入方式,大家更多的是疑惑,既然可以傳入一個(gè)工廠(chǎng)類(lèi)對(duì)象,那為什么不直接傳入相應(yīng)的產(chǎn)品,不是更簡(jiǎn)單直接嗎?當(dāng)然不是的,首先需要明白的是,工廠(chǎng)類(lèi)的作用是作為一個(gè)籠子,這個(gè)籠子需要幫助我們束縛住 '未來(lái)的變化’ ,要知道一個(gè)產(chǎn)品的變化可能總是大于工廠(chǎng)的變化。在這種情況下,舉出一個(gè)最簡(jiǎn)單的例子,你在編碼的過(guò)程中,可能會(huì)用到不只一個(gè)產(chǎn)品,那你就可能需要很多setter或者修改構(gòu)造方法;但是如果這些產(chǎn)品都可以通過(guò)這個(gè)工廠(chǎng)來(lái)獲取,是不是就相當(dāng)于用籠子關(guān)住了變化,使其在一個(gè)范圍中跳動(dòng)。
在學(xué)習(xí)設(shè)計(jì)模式時(shí),永遠(yuǎn)要記住的一句話(huà)就是“設(shè)計(jì)模式是用來(lái)教會(huì)我們?nèi)绾螒?yīng)對(duì)未來(lái)可能的變化”。如果你能夠確定自己的系統(tǒng)未來(lái)沒(méi)有變化,那自然用不到設(shè)計(jì)模式;或者你的系統(tǒng)未來(lái)全是變化,那也用不到設(shè)計(jì)模式,設(shè)計(jì)模式做的就是隔離穩(wěn)定與變化,如果沒(méi)有穩(wěn)定,那就用不到設(shè)計(jì)模式。
'new’是一種硬編碼,究竟 ’硬' 在那里,同樣一個(gè)簡(jiǎn)單的理由,如果未來(lái)構(gòu)造方法發(fā)生變化或者說(shuō)構(gòu)造參數(shù)增加(減少),而在源碼中有很多地方都是通過(guò)new來(lái)獲取實(shí)例對(duì)象,找到并修改源碼將會(huì)是一項(xiàng)很大的工作。
在解決這樣的 “對(duì)象創(chuàng)建” 問(wèn)題中就有工廠(chǎng)方法、抽象工廠(chǎng)、原型模式和建造者模式等相關(guān)設(shè)計(jì)模式。
工廠(chǎng)方法(Factory Method)
-
意圖
定義一個(gè)用于創(chuàng)建對(duì)象的接口,讓子類(lèi)決定實(shí)例化哪一個(gè)類(lèi)。FactoryMethod使得一個(gè)類(lèi)的實(shí)例化延遲到其子類(lèi)。
-
實(shí)例
Factory Method相對(duì)于簡(jiǎn)單工廠(chǎng)而言,完全遵循了“不改代碼”的原則,但是其使用情形相比抽象工廠(chǎng)使用條件沒(méi)有那么高,因此可以說(shuō)是使用最多的創(chuàng)建型模式之一了。
考慮這樣一個(gè)應(yīng)用,它可以向用戶(hù)顯示多種文檔,比如word、pdf、txt等等。在這個(gè)框架中,首先,想到的可能就是應(yīng)用簡(jiǎn)單工廠(chǎng)模式。
public interface Document{
public void open();
public void close();
public void save();
// ......
}
public class PdfDocument implements Document{
@Override
public void open(){
//open pdfDocument code
System.out.println("open pdf!");
}
@Override
public void close() {
System.out.println("close pdf!");
}
@Override
public void save() {
System.out.println("save pdf!");
}
// ......
}
public class TxtDocument implements Document{
//Txt實(shí)現(xiàn)代碼,同PdfDocument
......
}
public class DocumentFactory{
public Document createDocument(String type){
if(type=="pdf"){
return new PdfDocument();
}else if(type=="txt"){
return new TxtDocument();
}else {
return null;
}
}
}
//簡(jiǎn)單工廠(chǎng)模式在客戶(hù)類(lèi)當(dāng)中的調(diào)用
public class Client {
public static void main(String[] args) {
DocumentFactory factory
= new DocumentFactory();
Document pdfDocument
= factory.createDocument("pdf");
pdfDocument.open();
pdfDocument.save();
pdfDocument.close();
Document txtDocument
= factory.createDocument("txt");
txtDocument.open();
txtDocument.save();
txtDocument.close();
}
}
這樣簡(jiǎn)單工廠(chǎng)模式,在不考慮未來(lái)新文檔類(lèi)型的情況下,確實(shí)是一種不錯(cuò)的實(shí)現(xiàn)方法。但是在后續(xù)的擴(kuò)展過(guò)程當(dāng)中,如果需要增加新的文檔類(lèi),就需要去修改DocumentFactory 中的createDocument() 方法,增加新的類(lèi)別,并且客戶(hù)還必須知道這些類(lèi)別才能使用。
為了應(yīng)對(duì)這種情況,就出現(xiàn)了工廠(chǎng)方法。工廠(chǎng)方法就直接將工廠(chǎng)抽象出來(lái),每個(gè)產(chǎn)品對(duì)應(yīng)一個(gè)工廠(chǎng),消除工廠(chǎng)模式中的條件分支結(jié)構(gòu)(其實(shí)還有一種消除條件語(yǔ)句的模式,就是之前“組件協(xié)作”當(dāng)中的策略模式)。
//Document部分不變
public interface Document{
public void open();
public void close();
public void save();
......
}
public class PdfDocument implements Document{
public void open(){
//open pdfDocument code
}
// close 和 save
......
}
public class TxtDocument implements Document{
//Txt實(shí)現(xiàn)代碼
......
}
//并且后續(xù)可以擴(kuò)展新的文檔類(lèi)
......
//修改factory部分如下
public interface DocumentFactory{
public Document createDocument();
}
public class PdfDocumentFactory
implements DocumentFactory {
@Override
public Document createDocument() {
return new PdfDocument();
}
}
public class TxtDocumentFactory
implements DocumentFactory {
@Override
public Document createDocument() {
return new TxtDocument();
}
}
//如果后續(xù)有新的產(chǎn)品,直接再實(shí)現(xiàn)DocumentFactory,得到新的工廠(chǎng)
......
//調(diào)用過(guò)程可做如下修改:
public class Client {
public static void main(String[] args) {
//利用多態(tài)性質(zhì),直接生成相應(yīng)的factory子類(lèi)
//消除了控制耦合
DocumentFactory factory = new PdfDocumentFactory();
Document pdfDocument
= factory.createDocument();
pdfDocument.open();
pdfDocument.save();
pdfDocument.close();
factory = new TxtDocumentFactory();
Document txtDocument
= factory.createDocument();
txtDocument.open();
txtDocument.save();
txtDocument.close();
}
}
有人可能會(huì)有疑問(wèn),這樣不是還沒(méi)完全消除new嗎?首先這里的客戶(hù)類(lèi)已經(jīng)到最高的調(diào)用層次了,這個(gè)過(guò)程當(dāng)中是必然會(huì)有new的出現(xiàn),不然怎樣進(jìn)行程序調(diào)用呢?
我們所說(shuō)的消除new的過(guò)程是指main與factory之間,產(chǎn)生的一個(gè)中間層次(如下面的App)中去消除new。
//這樣的代碼中,就消除了new的存在
//具體的注入過(guò)程可以由其他的形式完成,比如Spring中的DI
public class App{
private DocumentFactory factory;
public void setFactory(DocumentFactory factory) {
this.factory = factory;
}
public void operateDoc(){
Document document = factory.createDocument();
document.open();
document.save();
document.close();
}
}
//main中的代碼是最高層次,也是變化最頻繁的層次,這里是不可能消除new的
public class Client {
public static void main(String[] args) {
DocumentFactory factory = new PdfDocumentFactory();
App app = new App();
app.setFactory(factory);
app.operateDoc();
//同樣對(duì)于其他的工廠(chǎng)類(lèi)也是可以采用同樣的方式調(diào)用。
......
}
}
這樣修改代碼的好處在那里呢?第一,顯而易見(jiàn)的就是完全實(shí)現(xiàn)了“開(kāi)閉原則”的思想,擴(kuò)展時(shí)不再需要去修改源碼。第二,有些對(duì)象的創(chuàng)建過(guò)程可能比較復(fù)雜,因此如果直接在應(yīng)用程序當(dāng)中使用new或者其他形式創(chuàng)建很麻煩,通過(guò)工廠(chǎng)創(chuàng)建之后,就不再需要去關(guān)注那些復(fù)雜的創(chuàng)建過(guò)程。第三,通過(guò)new創(chuàng)建,始終是一種硬編碼的形式,如果在應(yīng)用程序當(dāng)中過(guò)多的使用這種方式,那么一旦某對(duì)象的創(chuàng)建方式發(fā)生改變,修改源碼必然是很繁瑣的。
-
結(jié)構(gòu)——類(lèi)創(chuàng)建型模式

-
參與者
定義工廠(chǎng)方法中工廠(chǎng)創(chuàng)建的對(duì)象的接口。
-
ConcreteProduct(PdfDocument、TxtDocument)
實(shí)現(xiàn)Product的接口。
-
Creator(DocumentFactory)
聲明工廠(chǎng)方法——createProduct(),可以調(diào)用該方法返回一個(gè)Product類(lèi)型的對(duì)象。
-
ConcreteCreator(PdfDocumentFactory、TxtDocumentFactory)
重定義工廠(chǎng)方法以返回具體的ConcreteProduct。
-
Client(客戶(hù)類(lèi))
使用工廠(chǎng)和產(chǎn)品,工廠(chǎng)方法模式中,客戶(hù)類(lèi)也是一個(gè)重要的參與者,因?yàn)楣S(chǎng)方法主要的作用就是分離開(kāi)客戶(hù)類(lèi)與產(chǎn)品類(lèi)之間的耦合關(guān)系,所以脫離客戶(hù)類(lèi)去談工廠(chǎng)方法模式時(shí),總會(huì)覺(jué)得差了些什么東西,無(wú)法完全體會(huì)到工廠(chǎng)方法模式的優(yōu)勢(shì)。
-
適用性
在下列情況下可以使用Factory Method模式:
- 當(dāng)一個(gè)類(lèi)不知道它所必須創(chuàng)建的對(duì)象的類(lèi)的時(shí)候。
- 當(dāng)一個(gè)類(lèi)希望由它的子類(lèi)來(lái)指定它所創(chuàng)建的對(duì)象的時(shí)候。
- 當(dāng)類(lèi)將創(chuàng)建對(duì)象的職責(zé)委托給多個(gè)幫助子類(lèi)中的某一個(gè),并且你希望將哪一個(gè)幫助子類(lèi)是代理者這一信息局部化的時(shí)候。
簡(jiǎn)單地說(shuō),就是使用過(guò)程中只需要聲明一個(gè)抽象工廠(chǎng)類(lèi)的引用,具體調(diào)用那個(gè)工廠(chǎng)去生成那個(gè)對(duì)象,是由調(diào)用者去確定的。
-
相關(guān)模式
Abstract Factory經(jīng)常用工廠(chǎng)方法來(lái)實(shí)現(xiàn),抽象工廠(chǎng)創(chuàng)建產(chǎn)品的過(guò)程就可以使用工廠(chǎng)方法來(lái)完成。
工廠(chǎng)方法通常在Template Method中被調(diào)用,這一點(diǎn)在“組件協(xié)作”當(dāng)中也提到過(guò)。
-
思考
- Creator的兩種實(shí)現(xiàn)情況。第一種情況,Creator只作為抽象層,也就是只聲明接口,不做任何的實(shí)現(xiàn);這種情況就必須要子類(lèi)來(lái)實(shí)現(xiàn)。第二種情況,Creator作為一個(gè)具體的實(shí)現(xiàn)類(lèi),而不是抽象類(lèi),這種情況下,Creator可以提供一個(gè)缺省的實(shí)現(xiàn)接口,這樣即使沒(méi)有子類(lèi)重寫(xiě)它,客戶(hù)可以通過(guò)這樣一個(gè)缺省的實(shí)現(xiàn)完成任務(wù)。
- 靈活運(yùn)用工廠(chǎng)方法模式。作為一種創(chuàng)建類(lèi)模式,在任何需要生成復(fù)雜對(duì)象的地方,都可以使用工廠(chǎng)方法模式。有一點(diǎn)需要注意的地方就是復(fù)雜對(duì)象適合使用工廠(chǎng)方法模式,而簡(jiǎn)單對(duì)象,特別是只需要通過(guò) new 就可以完成創(chuàng)建的對(duì)象(這也是為什么工廠(chǎng)方法講解用到的例子總是無(wú)法說(shuō)服人的原因之一),無(wú)需使用工廠(chǎng)方法模式;因?yàn)槭褂霉S(chǎng)方法模式,就需要引入一個(gè)工廠(chǎng)類(lèi),會(huì)增加系統(tǒng)的復(fù)雜度。
抽象工廠(chǎng)(Abstract Factory)
-
意圖
提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴(lài)對(duì)象的接口,而無(wú)需指定他們具體的類(lèi)。
-
實(shí)例
假定存在這樣一個(gè)服務(wù)層,該層當(dāng)中需要做的就是訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)中的數(shù)據(jù),并且執(zhí)行一系列的相關(guān)操作。根據(jù)面向接口編程的思想,可以先作這樣一個(gè)代碼編寫(xiě)。
//對(duì)數(shù)據(jù)庫(kù)進(jìn)行訪(fǎng)問(wèn)的三個(gè)接口
//先建立連接,再執(zhí)行相關(guān)操作,最后返回相應(yīng)結(jié)果
public interface DBConnection{}
public interface DBCommand{}
public interface DBDataReader{}
//對(duì)于MySql,可以建立以下實(shí)現(xiàn)
public class MySqlDBConnection implements DBConnection{}
public class MySqlDBCommand implements DBCommand{}
public class MySqlDBDataReader implements DBDataReader{}
//同樣對(duì)于Sql Server,Oricle也是這樣的實(shí)現(xiàn)
......
這樣的實(shí)現(xiàn)下,我們可以說(shuō)是滿(mǎn)足了面向接口編程的一個(gè)思想;并且在實(shí)現(xiàn)中,我們可以為每個(gè)接口,按照工廠(chǎng)方法模式,為其創(chuàng)建一個(gè)工廠(chǎng)。
//工廠(chǎng)接口
public interface DBConnectionFactory{
public DBConnection createDBConnetion();
}
public interface DBCommandFactory{
public DBCommand createDBCommand();
}
public interface DBDataReaderFactory{
public DBDataReader createDBDataReader();
}
//然后對(duì)于每個(gè)具體的數(shù)據(jù)庫(kù),實(shí)現(xiàn)不同的具體工廠(chǎng)
//以MySql為例
public class MySqlDBConnetionFactory implements DBConnectionFactory {
@Override
public DBConnection createDBConnetion() {
return new MySqlDBConnection();
}
}
public class MySqlDBCommandFactory implements DBCommandFactory {
@Override
public DBDBCommand createDBCommand() {
return new MySqlDBCommand();
}
}
public class MySqlDataReaderFactory implements DataReaderFactory {
@Override
public DBDataReader createDataReader() {
return new MySqlDataReader();
}
}
//剩下的Orcle,Sql Server也是如此
......
工廠(chǎng)模式方法的調(diào)用就不再演示,區(qū)別和工廠(chǎng)方法中的Document 例子中差別不大。
對(duì)于這樣的實(shí)現(xiàn),雖然我們很好的利用了工廠(chǎng)方法模式,但是也引入了工廠(chǎng)方法模式的一個(gè)弊端——大量的對(duì)象和類(lèi)(本例當(dāng)中,三個(gè)系列,每個(gè)系列三個(gè)產(chǎn)品,光產(chǎn)品就是9個(gè)子類(lèi);每個(gè)產(chǎn)品再對(duì)應(yīng)一個(gè)工廠(chǎng),一共就是18個(gè)子類(lèi))。在使用的過(guò)程中,反而能夠明顯的感覺(jué)到系統(tǒng)復(fù)雜度不減反增。并且,DBConnection 、DBCommand 和DBDataReader 明顯是有著一定的關(guān)系的,換句話(huà)說(shuō),MySql建立的DBConnection 是和MySqlDBCommand、MySqlDBDataReader 一起使用的,如果出現(xiàn)MySqlDBConnection、OricleDBCommand、SqlServerDBDataReader 這種組合肯定是無(wú)法正常執(zhí)行的。這時(shí)抽象工廠(chǎng)的出現(xiàn),就很好的解決了這樣的問(wèn)題。
//首先,具體的產(chǎn)品類(lèi)不會(huì)發(fā)生變化,簡(jiǎn)化的主要是工廠(chǎng)層次
//先抽象出抽象工廠(chǎng),將產(chǎn)品系列的創(chuàng)建方法合并到一個(gè)接口中
public interface DBFactory{
public DBConnection createDBConnetion();
public DBCommand createDBCommand();
public DBDataReader createDBDataReader();
}
//根據(jù)不同的具體工廠(chǎng),創(chuàng)建具體的對(duì)象
public class MySqlDBFactory implements DBFactory {
@Override
public DBConnection createDBConnetion() {
return new MySqlDBConnection();
}
@Override
public DBCommand createDBCommand() {
return new MySqlDBCommand();
}
@Override
public DBDataReader createDBDataReader() {
return new MySqlDBDataReader();
}
}
//Oricle,sql server的工廠(chǎng),同樣如此
......
抽象工廠(chǎng)主要是對(duì)工廠(chǎng)層次的簡(jiǎn)化,這樣修改下來(lái),對(duì)比工廠(chǎng)方法模式,減少了2/3的工廠(chǎng)子類(lèi)創(chuàng)建,只需要3個(gè)工廠(chǎng)(有多少個(gè)產(chǎn)品系列就有多少個(gè)工廠(chǎng)子類(lèi))就可以完成產(chǎn)品的創(chuàng)建。
這樣的一種創(chuàng)建工廠(chǎng)方式,不僅減少了工廠(chǎng)的數(shù)量,而且使得產(chǎn)品的一致性得以保證,它可以保證,一次只能使用同一個(gè)系列當(dāng)中的對(duì)象。
public class Client {
public static void main(String[] args) {
DBFactory factory = new MySqlDBFactory();
App app = new App();
app.setFactory(factory);
app.operate();
//同樣對(duì)于其他的工廠(chǎng)類(lèi)也是可以采用同樣的方式調(diào)用。
// ......
}
}
class App{
private DBFactory factory;
public void setFactory(DBFactory factory) {
this.factory = factory;
}
public void operate(){
DBConnection connection
= factory.createDBConnetion();
DBCommand command
= factory.createDBCommand();
DBDataReader reader
= factory.createDBDataReader();
//執(zhí)行相關(guān)操作
.....
}
}
這樣的應(yīng)用程序代碼,在一定程度上就減少了工廠(chǎng)子類(lèi)的數(shù)量,并且在operate() 中保證了產(chǎn)品系列的一致性,使得MysqlDBFactory 生成的產(chǎn)品,只會(huì)是與MySql相關(guān)的。
-
結(jié)構(gòu)——對(duì)象創(chuàng)建型模式

-
參與者
-
AbstractFactory(DBFactory)
聲明一個(gè)創(chuàng)建抽象產(chǎn)品對(duì)象的操作接口。
-
ConcreteFactory(MySqlDBFactory)
實(shí)現(xiàn)創(chuàng)建具體產(chǎn)品對(duì)象的操作。
-
AbstractProduct(DBConnection、DBCommand、DBDataReader)
為一類(lèi)產(chǎn)品對(duì)象聲明一個(gè)接口。
-
ConcreteProduct(MySqlDBConection、MySqlDBCommand、MySqlDBDataReader)
定義一個(gè)將被相應(yīng)的具體工廠(chǎng)創(chuàng)建的產(chǎn)品對(duì)象,并實(shí)現(xiàn)抽象產(chǎn)品的相應(yīng)接口。
-
Client
調(diào)用抽象工廠(chǎng)和抽象產(chǎn)品提供的接口。在創(chuàng)建者模式當(dāng)中,客戶(hù)類(lèi)也是重要的參與成員,因?yàn)閷?duì)創(chuàng)建模式的理解容易混亂的點(diǎn)正是在客戶(hù)類(lèi)中的調(diào)用過(guò)程 (new) 產(chǎn)生的,關(guān)于這個(gè)問(wèn)題,已經(jīng)在前面做過(guò)很多解釋了,不再多說(shuō)。
-
適用性
以下情況可以使用AbstractFactory模式:
- 一個(gè)系統(tǒng)要獨(dú)立于它的產(chǎn)品的創(chuàng)建、組合和表示時(shí)。
- 一個(gè)系統(tǒng)要由多個(gè)產(chǎn)品系列中的一個(gè)來(lái)配置時(shí)。
- 當(dāng)你要強(qiáng)調(diào)一系列相關(guān)的產(chǎn)品對(duì)象的設(shè)計(jì)以便進(jìn)行聯(lián)合使用時(shí)。
- 當(dāng)你提供一個(gè)產(chǎn)品類(lèi)庫(kù),而只想顯示它們的接口而非實(shí)現(xiàn)時(shí)。
-
相關(guān)模式
Singleton:一個(gè)具體的工廠(chǎng)通常會(huì)是一個(gè)單件。因?yàn)樵谝粋€(gè)應(yīng)用中,一般每個(gè)產(chǎn)品系列只需要一個(gè)具體工廠(chǎng)。
Factory Method:在Abstract Factory中,僅僅是聲明一個(gè)創(chuàng)建對(duì)象的接口,真正的創(chuàng)建過(guò)程是由具體工廠(chǎng)實(shí)現(xiàn)的。這時(shí),可以為每一個(gè)方法對(duì)應(yīng)的具體對(duì)象之間再定義一個(gè)工廠(chǎng)方法。但其問(wèn)題就在于,這樣的做法就像是在工廠(chǎng)方法上再套上一層抽象工廠(chǎng),從而又增加了系統(tǒng)的復(fù)雜度。
-
思考
- 難以支持新種類(lèi)的產(chǎn)品 對(duì)于工廠(chǎng)模式而言,它的最大優(yōu)點(diǎn)就在于保證了產(chǎn)品的一致性,但也正是如此,這就使得它的產(chǎn)品系列需要保持穩(wěn)定,如果在后續(xù)的過(guò)程中出現(xiàn)新的產(chǎn)品,比如在實(shí)例當(dāng)中需要增加一個(gè)新的功能系列,就需要去擴(kuò)展
DBFactory 接口,并且涉及到DBFactory 及其子類(lèi)的改變。
- 定義可擴(kuò)展的工廠(chǎng) 這是對(duì)于新種類(lèi)產(chǎn)品創(chuàng)建靈活性的提高,但是不太安全;就是給創(chuàng)建 對(duì)象的操作增加一個(gè)參數(shù),該參數(shù)來(lái)指定將被創(chuàng)建的對(duì)象種類(lèi)。使用這種方式,抽象工廠(chǎng)就只需要一個(gè)“Create”方法和一個(gè)標(biāo)識(shí)符參數(shù)即可完成創(chuàng)建操作。但問(wèn)題就在于,客戶(hù)需要完全了解所有的參數(shù)才能更好使用工廠(chǎng)創(chuàng)建自己所需要的對(duì)象。
原型模式(Prototype)
-
意圖
用原型實(shí)例指定對(duì)象的創(chuàng)建種類(lèi),并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。
-
實(shí)例
Prototype模式,有點(diǎn)像是對(duì)工廠(chǎng)方法模式中產(chǎn)品與工廠(chǎng)的合并。怎么說(shuō)呢?看下面的代碼:
//工廠(chǎng)方法模式中的產(chǎn)品類(lèi)與工廠(chǎng)方法類(lèi)
public interface Document{
public void open();
public void close();
public void save();
......
}
public interface DocumentFactory{
public Document createDocument();
}
這是在Factory Method中使用的創(chuàng)建方式,而原型做的事就是,不再用工廠(chǎng)來(lái)進(jìn)行創(chuàng)建,而是轉(zhuǎn)而克隆的方式。變成下面這樣:
//合并Document和DocumentFactory
public abstract class Document
implements Cloneable{
public void open();
public void close();
public void save();
......
//相當(dāng)于Factory中的createDocument();
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
public class PdfDocument implements Document{
@Override
public void open(){
//open pdfDocument code
System.out.println("open pdf!");
}
@Override
public void close() {
System.out.println("close pdf!");
}
@Override
public void save() {
System.out.println("save pdf!");
}
//......
}
//文檔類(lèi)的實(shí)現(xiàn)與工廠(chǎng)方法中的一樣
......
那么在具體的客戶(hù)類(lèi)當(dāng)中就是通過(guò)這樣一種方式來(lái)進(jìn)行調(diào)用:
public class App{
//在工廠(chǎng)方法模式當(dāng)中,這里是documentFactory
private Document prototype;
public void setDoucument(Document document){
prototype = document;
}
public void useDocument(){
//documentFactory.createDocument();
Document doc = prototype.clone();
//然后使用prototype克隆出來(lái)的doc進(jìn)行操作
doc.open();
......
}
}
//在客戶(hù)類(lèi)中調(diào)用表現(xiàn)
public class Client {
public static void main(String[] args) {
Document doc = new PdfDocument();
App app = new App();
app.setFactory(doc);
app.useDocument();
//同樣對(duì)于其他的工廠(chǎng)類(lèi)也是可以采用同樣的方式調(diào)用。
//......
}
}
問(wèn)題來(lái)了,為什么不直接用原型(prototype),而是要多一步克???解決這個(gè)問(wèn)題,首先要明白的是,我們使用原型的目的不是將原型作為客戶(hù)類(lèi)的一個(gè)屬性去使用,而是一個(gè)創(chuàng)建者。既然是一個(gè)創(chuàng)建者,那么在使用的過(guò)程中,就不只一個(gè)地方會(huì)用到同樣類(lèi)型的對(duì)象;如果在不同的地方都直接使用原型,可能會(huì)在某個(gè)地方修改了原型的值,從而使得其他直接使用原型的方法出現(xiàn)不可預(yù)知的錯(cuò)誤。
-
結(jié)構(gòu)——對(duì)象創(chuàng)建型模式

-
參與者
-
Prototype(Document)
聲明一個(gè)克隆自身的接口。
-
ConcretePrototype(PdfDocument...)
繼承Prototype并實(shí)現(xiàn)克隆自身的接口。
-
Client
讓一個(gè)原型克隆自身從而創(chuàng)建一個(gè)新的對(duì)象。
-
適用性
-
當(dāng)一個(gè)系統(tǒng)應(yīng)該獨(dú)立于它的產(chǎn)品創(chuàng)建、構(gòu)成和表示時(shí),可以使用Prototype模式。
-
當(dāng)要實(shí)例化的類(lèi)是在運(yùn)行時(shí)候指定時(shí),比如動(dòng)態(tài)裝載。
-
為了避免創(chuàng)建一個(gè)產(chǎn)品類(lèi)平行的工廠(chǎng)類(lèi)層次時(shí)。
-
當(dāng)一個(gè)類(lèi)的實(shí)例只能有幾種不同狀態(tài)組合中的一種時(shí),建立相應(yīng)數(shù)目的原型并克隆他們可以比每次用合適的狀態(tài)手工實(shí)例化該類(lèi)更方便一些。
-
相關(guān)模式
Abstract Factory和Prototype在某種方面是相互競(jìng)爭(zhēng)的,但是在某種情況下也是可以一起使用,比如,在抽象工廠(chǎng)中存儲(chǔ)一個(gè)被克隆的產(chǎn)品集合,在使用時(shí),直接根據(jù)集合中的對(duì)象返回相應(yīng)的產(chǎn)品。
大量使用Composite(組合模式)和Decorator(裝飾器模式)的設(shè)計(jì)上也可以采用Protorype來(lái)減少Composite或Decorator對(duì)象的創(chuàng)建。
-
思考
- 減少了子類(lèi)的構(gòu)造。實(shí)例當(dāng)中就可以看出,原型模式簡(jiǎn)化了工廠(chǎng)方法模式中的工廠(chǎng)類(lèi),因此,減少了許多子類(lèi)的創(chuàng)建。
- 資源優(yōu)化。類(lèi)初始化可能需要消化非常多的資源,這個(gè)資源包括數(shù)據(jù)、硬件資源等等,使用原型模式就可以減少這樣一個(gè)初始化過(guò)程。
- 深、淺拷貝的問(wèn)題。對(duì)于java而言,淺拷貝實(shí)現(xiàn) Cloneable,深拷貝是通過(guò)實(shí)現(xiàn) Serializable 讀取二進(jìn)制流。
建造者模式( Builder)
-
意圖
將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過(guò)程可以有不同的表示。
-
實(shí)例
在游戲場(chǎng)景當(dāng)中,尤其是3d場(chǎng)景中,必不可少就是建筑物,比如說(shuō)房子。對(duì)房子的構(gòu)建肯定不是一下全部構(gòu)建完成的,而是會(huì)分成幾個(gè)部分,比如墻、窗戶(hù)、地板、房頂、門(mén),一部分、一部分地去構(gòu)建。
public abstract class House{
//房子屬性,紋理、材質(zhì)...
private Texture texture;
private Material material;
......
//墻、窗戶(hù)、地板、房頂、門(mén)
public Wall buildWall();
public Window buildWindow();
public Floor buildFloor();
public Door buildDoor();
public Roof buildRoof();
//房子構(gòu)建過(guò)程
public void buildHouse(){
buildFloor();
Wall[] walls = new Wall[4];
for(int i=0;i<walls.length;i++)
walls[i] = buildWall();
Window window = buildWindow();
wall[1].setWindow(window);
Door door = builDoor();
wall[2].setDoor(door);
buildRoof();
}
}
這種構(gòu)建方式,采用的明顯就是模板方法(Template Method),這種實(shí)現(xiàn)方式還可以根據(jù)不同的房子類(lèi)型,實(shí)現(xiàn)具體的細(xì)節(jié)。
//石頭屋
public class StoneHouse extends House{
//具體實(shí)現(xiàn)細(xì)節(jié)
.......
}
//茅草屋
public class ThatchedHouse extends House{
//具體實(shí)現(xiàn)細(xì)節(jié)
.......
}
//按照模板主方法在客戶(hù)類(lèi)中的調(diào)用形式表現(xiàn):
public class Client{
public static void main(String[] args){
//只需要生成一個(gè)相應(yīng)對(duì)象即可在游戲場(chǎng)景中完成相應(yīng)類(lèi)型房子的創(chuàng)建
House house = new StoneHouse();
house.buildHouse(); //生成石頭屋
house = new ThatchedHouse();
house.buildHouse(); //生成茅草屋
}
}
這種實(shí)現(xiàn)有什么問(wèn)題呢?類(lèi)太臃腫了,對(duì)吧~這樣的實(shí)現(xiàn)過(guò)程,可以體現(xiàn)復(fù)用的思想,但是問(wèn)題之一就在于所有的內(nèi)容全部都放在了一個(gè)類(lèi)中,體現(xiàn)不出單一職責(zé)和類(lèi)的信息與行為集中;這時(shí)就可以將創(chuàng)建過(guò)程分離出來(lái),形成一個(gè)Builder,由這樣一個(gè)Builder來(lái)專(zhuān)門(mén)負(fù)責(zé)創(chuàng)建。
//Director
public class House{
//房子屬性,紋理、材質(zhì)...
private Texture texture;
private Material material;
......
//增加builder的引入
private HouseBuilder houseBuilder;
public void setHouseBuilder(HouseBuilder hBuilder){
houseBuilder = hbuilder;
}
//房子構(gòu)建過(guò)程
public void buildHouse(){
houseBuilder.buildFloor();
Wall[] walls = new Wall[4];
for(int i=0;i<walls.length;i++)
walls[i] = houseBuilder.buildWall();
Window window
= houseBuilder.buildWindow();
wall[1].setWindow(window);
Door door = houseBuilder.builDoor();
wall[2].setDoor(door);
houseBuilder.buildRoof();
}
}
//分離出來(lái)的builder
public interface HouseBuilder{
//墻、窗戶(hù)、地板、房頂、門(mén)
public Wall buildWall();
public Window buildWindow();
public Floor buildFloor();
public Door buildDoor();
public Roof buildRoof();
}
public class StoneHouseBuilder
implements HouseBuilder{
//具體實(shí)現(xiàn)細(xì)節(jié)
.......
}
public class ThatchedHouseBuilder
implements HouseBuilder{
//具體實(shí)現(xiàn)細(xì)節(jié)
.......
}
//修改過(guò)后,在客戶(hù)類(lèi)中的調(diào)用形式表現(xiàn):
public class Client{
public static void main(String[] args){
//只需要生成一個(gè)相應(yīng)對(duì)象即可在游戲場(chǎng)景中完成相應(yīng)類(lèi)型房子的創(chuàng)建
House house = new House();
HouseBuilder builder
= new StoneHouseBuilder();
house.setHouseBuilder(builder);
house.buildHouse();
builder = new ThatchedHouseBuilder();
//這個(gè)set過(guò)程可以運(yùn)行時(shí)完成
house.setHouseBuilder(builder);
house.buildHouse();
}
}
通過(guò)這樣一種方式,實(shí)現(xiàn)復(fù)雜對(duì)象構(gòu)建與表示的分離,并且,對(duì)于不同的房子對(duì)象,如果房子其他參數(shù)沒(méi)有任何差別,就只需要傳入相應(yīng)的builder即可,而不需要再生成各種各樣的子類(lèi)(如StoneHouse、ThatchedHouse )。
一旦生成對(duì)象,只需要修改其builder就可以馬上改變其對(duì)象表示,而不需要新生成對(duì)象。并且這種修改過(guò)程,是可以動(dòng)態(tài)完成的,就如果Spring當(dāng)中的依賴(lài)注入過(guò)程一樣,可以在運(yùn)行時(shí)刻完成,而不一定是一開(kāi)始就確定的
-
結(jié)構(gòu)——對(duì)象創(chuàng)建型模式

-
參與者
-
Builder(HouseBuilder)
為創(chuàng)建一個(gè)Product對(duì)象的各個(gè)部件指定抽象接口。
-
ConcreteBuilder(StoneHouseBuilder、ThatchedHouseBuilder)
- 實(shí)現(xiàn)Builder的接口以構(gòu)造各裝配該產(chǎn)品的各個(gè)部件。
- 定義并明確它所創(chuàng)建的表示。
-
Director(House)
構(gòu)造一個(gè)使用builder的對(duì)象。
-
Product(Wall、Window...)
包含了定義組成部件的類(lèi)以及被構(gòu)造的復(fù)雜對(duì)象等。
-
適用性
Builder的適用情況:
- 當(dāng)創(chuàng)建復(fù)雜對(duì)象的算法應(yīng)該獨(dú)立于該對(duì)象的組成部分,以及它們的裝配方式時(shí)。
- 當(dāng)構(gòu)造過(guò)程必須允許被構(gòu)造的對(duì)象的不同的表示時(shí)。
Builder模式更多的是體現(xiàn)的一種思想,而不是具體的過(guò)程,這種思想就是,當(dāng)一個(gè)類(lèi)的信息與行為過(guò)于臃腫時(shí),也許可以采用Builder這種方式對(duì)類(lèi)的信息與行為進(jìn)行重新劃分,從而使得類(lèi)看起來(lái)更加的“輕” 。
-
相關(guān)模式
Abstract Factory和Builder都是對(duì)復(fù)雜對(duì)象的創(chuàng)建封裝,但二者的區(qū)別在于,Builder著重于一步一步構(gòu)建一個(gè)復(fù)雜對(duì)象,而Abstract Factory著重于多個(gè)系列產(chǎn)品對(duì)象的創(chuàng)建,并且系列對(duì)象之間有著某種聯(lián)系。
Composite模式中對(duì)象通常情況下就是用Builder生成的。
-
思考
- 可以改變產(chǎn)品的內(nèi)部表示 Builder對(duì)象提供給Director一個(gè)構(gòu)造產(chǎn)品對(duì)象的內(nèi)部接口,從而使得產(chǎn)品的表示和內(nèi)部結(jié)構(gòu)得以隱藏。因此,在構(gòu)建過(guò)程中,只需要替換不同的Builder就可以得到不同的表示。
- Builder中的方法可以缺省為空 這樣在具體的實(shí)現(xiàn)過(guò)程中,客戶(hù)可以只實(shí)現(xiàn)他們感興趣的操作,這種方式其實(shí)在很多模式中都可以應(yīng)用,其最主要的思想就是能用默認(rèn)配置當(dāng)然是最好的。
|