金昌網(wǎng)站建設(shè)seo服務(wù)公司招聘
目錄
創(chuàng)建型模式概述
Factory Method: 唯一的類創(chuàng)建型模式
Abstract Factory
Builder模式
Prototype模式
Singleton模式
最近在參與一個(gè)量化交易系統(tǒng)的項(xiàng)目,里面涉及到用java來(lái)重構(gòu)部分vnpy的開(kāi)源框架,因?yàn)槭强蚣艿拇罱?#xff0c;所以會(huì)涉及到像事件驅(qū)動(dòng)等設(shè)計(jì)模式的應(yīng)用,因此不了解基礎(chǔ)的設(shè)計(jì)模式就無(wú)法理解框架設(shè)計(jì)者對(duì)于各個(gè)模塊以及類的設(shè)計(jì),而這也正是我目前所欠缺的能力,之前看到大多數(shù)是偏編碼規(guī)則。這也是我選取《設(shè)計(jì)模式-可復(fù)用面向?qū)?duì)象軟件的基礎(chǔ)》這本書(shū)進(jìn)行學(xué)習(xí)的原因。
這本書(shū)分為三個(gè)部分,第一部分是導(dǎo)論,雖然說(shuō)是導(dǎo)論但我覺(jué)得更多的是跳出設(shè)計(jì)模式的細(xì)節(jié)來(lái)看它具體在實(shí)際項(xiàng)目中的工作流以及一些設(shè)計(jì)模式的抽象概念,這部分我自認(rèn)為對(duì)于一個(gè)對(duì)于各個(gè)設(shè)計(jì)模式?jīng)]有實(shí)際實(shí)現(xiàn)經(jīng)驗(yàn)的初學(xué)者來(lái)說(shuō)過(guò)于抽象,所以我準(zhǔn)備放到最后來(lái)看,可能會(huì)有更深的體會(huì)。第二部分是一個(gè)案例學(xué)習(xí),帶領(lǐng)你從零開(kāi)始構(gòu)建一個(gè)文本編輯器的設(shè)計(jì)模式并實(shí)現(xiàn)它,這部分我準(zhǔn)備放在第二部分來(lái)看。第三部分就是介紹了三類23個(gè)典型的設(shè)計(jì)模式,這部分我準(zhǔn)備首先了解,以期對(duì)于設(shè)計(jì)模式先有一個(gè)更加深刻的認(rèn)識(shí)。
這篇文章先總結(jié)一下三類設(shè)計(jì)模式中的第一類-創(chuàng)建型模式。
創(chuàng)建型模式概述
首先,要明確這里所說(shuō)的設(shè)計(jì)模式重點(diǎn)聚焦系統(tǒng)級(jí)的設(shè)計(jì),系統(tǒng)本質(zhì)上是一組類實(shí)例的組合,它區(qū)別于單一的類或者接口的設(shè)計(jì),需要一定的抽象才能夠使得后續(xù)的編碼工作更有效率。創(chuàng)建型模式的最主要作用就是抽象了系統(tǒng)的實(shí)例化過(guò)程。在沒(méi)有這種抽象之前,系統(tǒng)的實(shí)例化只是單純的被看作是N個(gè)類的實(shí)例化,不利于后續(xù)的編碼執(zhí)行。
這種抽象在大體上有兩個(gè)實(shí)現(xiàn)的原則。其一是將系統(tǒng)所需要用到的各種類信息給封裝起來(lái);其二是把這些類的創(chuàng)建和組合方式也給隱藏起來(lái)??傊?#xff0c;整個(gè)系統(tǒng)有一套抽象的接口負(fù)責(zé)統(tǒng)一和外部對(duì)接。至于這個(gè)系統(tǒng)內(nèi)各個(gè)類的配置可以是靜態(tài)的(在編譯時(shí)固定,也就是類創(chuàng)建型模式),或者是動(dòng)態(tài)的(在運(yùn)行時(shí)指定,也就是對(duì)象創(chuàng)建型模式)。換句話說(shuō),類創(chuàng)建模式就是為每一個(gè)系統(tǒng)預(yù)先定義好類的框架,每一個(gè)框架通過(guò)特定的方法完成創(chuàng)建;而對(duì)象創(chuàng)建型模式就是沒(méi)有預(yù)先定義系統(tǒng)類框架,系統(tǒng)是在程序執(zhí)行中動(dòng)態(tài)完成創(chuàng)建的。
Factory Method: 唯一的類創(chuàng)建型模式
Factory Method(工廠方法)首先針對(duì)目標(biāo)系統(tǒng)定義一個(gè)抽象接口,然后讓具體的系統(tǒng)實(shí)現(xiàn)類來(lái)決定如何實(shí)例化,它要求使用者必須先定義系統(tǒng)類的框架,而把類的實(shí)例化延遲到了子類。
文中作者舉了一個(gè)案例,對(duì)于要設(shè)計(jì)一套面向不同文件類型的應(yīng)用處理系統(tǒng),比如對(duì)于圖像文件要有圖像文件處理系統(tǒng),對(duì)于文本文件要有文本文件處理系統(tǒng)等等。
首先我預(yù)先定義好文件類型的接口以及具體的實(shí)現(xiàn)類:
//抽象接口
public interface Document {void open();void close();void save();
}//實(shí)現(xiàn)類FigureDocument
public class FigureDocument implements Document {@Overridepublic void open() {System.out.println("Opening Figure Document");}@Overridepublic void close() {System.out.println("Closing Figure Document");}@Overridepublic void save() {System.out.println("Saving Figure Document");}}//實(shí)現(xiàn)類TextDocument
public class TextDocument implements Document {@Overridepublic void open(){System.out.println("Text Document Opened");}@Overridepublic void close() {System.out.println("Text Document Closed");}@Overridepublic void save() {System.out.println("Text Document Saved");}}
很顯然在Application抽象實(shí)例中,它是不知道要在什么情況下創(chuàng)建什么文件的,因此我們針對(duì)每一個(gè)文檔都設(shè)計(jì)對(duì)應(yīng)的application實(shí)現(xiàn)類,并且重構(gòu)對(duì)應(yīng)的document工廠方法:
//抽象接口
public interface Application {Document createDocument();
}//實(shí)現(xiàn)類FigureApplication
public class FigureApplication implements Application {@Overridepublic Document createDocument() {return new FigureDocument();}}//實(shí)現(xiàn)類TextApplication
public class TextApplication implements Application {public Document createDocument(){return new TextDocument();}
}
然后客戶端就可以輕松針對(duì)不同的application實(shí)現(xiàn)來(lái)針對(duì)性的創(chuàng)建對(duì)應(yīng)的文件:
public class Client {public static void main(String[] args) {Application figureApplication = new FigureApplication();Document figuredoc = figureApplication.createDocument();figuredoc.open();}
}
優(yōu)點(diǎn)
首先我認(rèn)為設(shè)計(jì)模式的優(yōu)點(diǎn)最主要還是要面向系統(tǒng)框架的應(yīng)用開(kāi)發(fā)人員,他們是不是能在系統(tǒng)穩(wěn)定的前提下簡(jiǎn)化并且高效率的使用這個(gè)框架是核心。從這個(gè)維度上說(shuō)工廠模式有以下的優(yōu)點(diǎn):它將具體的系統(tǒng)類構(gòu)建和它的抽象框架分離。使得應(yīng)用開(kāi)發(fā)人員除了在實(shí)例化的時(shí)候要關(guān)注創(chuàng)建的系統(tǒng)實(shí)例類型,其余時(shí)候都只需要對(duì)著抽象框架的接口來(lái)編程。
除此以外,工廠方法有兩個(gè)注意事項(xiàng):
工廠方法可以為子類提供一個(gè)擴(kuò)展功能的鉤子:通過(guò)在AbstractCreator的抽象工廠方法內(nèi)提供一個(gè)缺省的實(shí)現(xiàn),可以擴(kuò)展工廠方法的功能,并且這個(gè)擴(kuò)展的功能是可以根據(jù)不同的子類進(jìn)行變化的(當(dāng)然這個(gè)缺省的實(shí)現(xiàn)要通過(guò)super繼承到子類的工廠方法中)。
連接系統(tǒng)外的類:只要是引入了對(duì)應(yīng)工廠方法的類,相當(dāng)于都整體納入了你所設(shè)計(jì)系統(tǒng)的抽象體系當(dāng)中,要注意在設(shè)計(jì)層面抽象的顆粒度。
Abstract Factory
Abstract Factory(抽象工廠設(shè)計(jì)模式)是對(duì)象創(chuàng)建型的,所以它并不是預(yù)先定義好系統(tǒng)的類框架,而是通過(guò)設(shè)計(jì)一個(gè)工廠體系來(lái)承擔(dān)起系統(tǒng)的創(chuàng)建,每一個(gè)工廠里都包含了組成系統(tǒng)所必需組件的不同實(shí)現(xiàn)方式:
相比于工廠方法設(shè)計(jì)模式,抽象工廠設(shè)計(jì)模式面向需要更加靈活的系統(tǒng)設(shè)計(jì),同時(shí)系統(tǒng)的各個(gè)組件未來(lái)預(yù)計(jì)會(huì)有大量?jī)?yōu)化迭代需求。比如文中舉例的多視感用戶界面應(yīng)用,每一個(gè)應(yīng)用實(shí)例內(nèi)的組件都會(huì)有個(gè)性化的視覺(jué)需求,而且這種視覺(jué)需求需要不停的進(jìn)行優(yōu)化迭代,以期保持產(chǎn)品的競(jìng)爭(zhēng)力。?這種場(chǎng)景里,再使用工廠方法在每一個(gè)子類中對(duì)每一個(gè)組件進(jìn)行硬編碼就顯得過(guò)于耦合,也不利于后續(xù)各組件的優(yōu)化迭代。
在具體實(shí)現(xiàn)時(shí),首先對(duì)于系統(tǒng)的各個(gè)核心組件定義抽象接口(而不再是系統(tǒng)的抽象接口):
//核心組件Button的抽象接口
public interface Button {void paintButton();void clickButton();...
}//核心組件TextBox的抽象接口
public interface TextBox {void paintTextBox();...}
隨后分別去實(shí)現(xiàn)不同風(fēng)格的組件實(shí)例:
public class MacOSButton implements Button {private String ButtonName;public MacOSButton(String ButtonName){this.ButtonName = ButtonName;}public void paintButton(){System.out.println("MacOS Button " + ButtonName + " painted");}public void clickButton(){System.out.println("MacOS Button " + ButtonName + " clicked");}
}public class WindowsButton implements Button{private String ButtonName;public WindowsButton(String ButtonName){this.ButtonName = ButtonName;}public void paintButton(){System.out.println("Windows Button: " + ButtonName);}public void clickButton(){System.out.println("Windows Button: " + ButtonName + " is clicked");}
}public class MacOSTextBox implements TextBox{private String TextBoxName;public MacOSTextBox(String TextBoxName){this.TextBoxName = TextBoxName;}public void paintTextBox(){System.out.println("Paint MacOS TextBox: " + TextBoxName);}}public class WindowsTextBox implements TextBox {private String textBoxName;public WindowsTextBox(String textBoxName){this.textBoxName = textBoxName;}public void paintTextBox(){System.out.println("Paint Windows TextBox: " + textBoxName);}
}
隨后定義相應(yīng)的工廠體系,明確對(duì)于每一種風(fēng)格的系統(tǒng)組件實(shí)現(xiàn)的組合:
//抽象工廠接口
public interface GUIFactory {Button createButton(String ButtonName);TextBox createTextBox(String TextBoxName);}//工廠實(shí)例1
public class MacOSFactory implements GUIFactory{public Button createButton(String ButtonName){return new MacOSButton(ButtonName);}public TextBox createTextBox(String TextBoxName){return new MacOSTextBox(TextBoxName);}}//工廠實(shí)例2
public class WindoxsFactory implements GUIFactory{public Button createButton(String ButtonName){return new WindowsButton(ButtonName);}public TextBox createTextBox(String TextBoxName){return new WindowsTextBox(TextBoxName);}
}
在客戶端具體實(shí)現(xiàn)時(shí)可以直接通過(guò)統(tǒng)一的抽象方法來(lái)創(chuàng)建相應(yīng)的組件,實(shí)現(xiàn)應(yīng)用開(kāi)發(fā)者接口調(diào)用的無(wú)感化:
public class Application {public static void main(String[] args) {GUIFactory macOSFactory = new MacOSFactory();Button buttonA = macOSFactory.createButton("ButtonA");//工廠方法無(wú)需具像化到特定組件類型TextBox textBoxA = macOSFactory.createTextBox("TextBoxA");//工廠方法無(wú)需具像化到特定組件類型buttonA.paintButton();textBoxA.paintTextBox();}}
優(yōu)點(diǎn)
其實(shí)前文也說(shuō)明了,抽象工廠設(shè)計(jì)模式拋棄了系統(tǒng)類的設(shè)計(jì),將系統(tǒng)打散成了各個(gè)核心的組件來(lái)單獨(dú)設(shè)計(jì),最后通過(guò)工廠方法將對(duì)應(yīng)的組件實(shí)例串起來(lái)??梢钥闯?#xff0c;這個(gè)設(shè)計(jì)模式更加的靈活,體現(xiàn)在一下幾個(gè)方面:
- 使得產(chǎn)品系列的切換變得非常容易:基于上面的案例,可以更進(jìn)一步在應(yīng)用類中設(shè)置一個(gè)應(yīng)用的創(chuàng)建方法,將工廠類作為輸入,就可以實(shí)現(xiàn)只要一個(gè)工廠方法就可以完成一套系統(tǒng)所有組件的部署,只要改變一個(gè)工廠方法,整個(gè)應(yīng)用的所有組件就會(huì)立刻完成變化。
- 有利于產(chǎn)品的一致性:同一個(gè)工廠方法定義了所有組件的一套版本,不會(huì)出現(xiàn)不同版本沖突的問(wèn)題。
缺點(diǎn):
- 難以支持新的組件:新的組件首先要在抽象工廠方法中定義創(chuàng)建接口,然后在每一個(gè)具體的工廠方法中分別實(shí)現(xiàn),所需要的工作量比較大。
Builder模式
Builder模式側(cè)重于實(shí)現(xiàn)系統(tǒng)的創(chuàng)建流程和具體的系統(tǒng)實(shí)例分離,也就是可以實(shí)現(xiàn)用一套創(chuàng)建流程來(lái)創(chuàng)建多種系統(tǒng)實(shí)例。細(xì)心的朋友們可以發(fā)現(xiàn),這個(gè)功能Abstract Factory也可以實(shí)現(xiàn),其實(shí)整體來(lái)看這兩種方法非常的相似,都是嘗試用一層抽象來(lái)統(tǒng)一不同系統(tǒng)實(shí)例的創(chuàng)建,但是兩者存在一些差異,這個(gè)放到最后再說(shuō)。
?從這張架構(gòu)圖就可以看出來(lái)builder模式和Abstract Factory的相似性之高(Director這個(gè)角色在Abstract Factory中也完全可以設(shè)置,就是其優(yōu)點(diǎn)第一點(diǎn)中所提的應(yīng)用類中一個(gè)應(yīng)用的創(chuàng)建方法)。而且Builder的設(shè)計(jì)與Factory也非常的類似,我把文中兩個(gè)關(guān)于Maze的C++抽象類貼出來(lái),大家可以對(duì)比一下:
首先對(duì)于各個(gè)組件的構(gòu)建沒(méi)有太大的區(qū)別,只不過(guò)Factory提供了缺省的實(shí)現(xiàn),并且提供了公有的構(gòu)造器(其實(shí)我覺(jué)得用protected未嘗不可,這是一個(gè)抽象類,也不應(yīng)該被子類之外來(lái)調(diào)用,大家批評(píng)指正)。
唯一MazeBuilder多的是GetMaze()這個(gè)方法,他會(huì)返回一個(gè)完整的Maze對(duì)象,這是Factory所沒(méi)有的,這就引出了這兩者最主要的區(qū)別,Builder實(shí)例中是帶著系統(tǒng)實(shí)例的,但是Factory實(shí)例中沒(méi)有,它只是單純的工廠方法集合。?這就決定了他們創(chuàng)建系統(tǒng)實(shí)例的邏輯是不一樣的,如下對(duì)比所示
factory方法是先創(chuàng)建maze實(shí)例,然后通過(guò)其他的工廠方法去為這個(gè)系統(tǒng)實(shí)例添加各個(gè)組件,而builder模式是一步一步的構(gòu)建各個(gè)組件,最終通過(guò)get方法獲取系統(tǒng)實(shí)例(其實(shí)再往下看一層,builder也是先創(chuàng)建系統(tǒng)實(shí)例,但是在面向應(yīng)用開(kāi)發(fā)接口的具體實(shí)現(xiàn)這一層(CreateMaze的方法實(shí)現(xiàn)),他是最后才獲得系統(tǒng)實(shí)例,所以如果要說(shuō)本質(zhì)的區(qū)別,那就是builder相對(duì)于abstract factory再抽象了一層吧,至于這一層的抽象有沒(méi)有意義,我覺(jué)得有吧,至少看著更簡(jiǎn)潔了一點(diǎn),就像老馬的猛禽1和猛禽3)。
知道了這個(gè)區(qū)別以后就不難理解builder模式的幾個(gè)優(yōu)勢(shì):
- 面向應(yīng)用開(kāi)發(fā)人員將系統(tǒng)的創(chuàng)建流程和具體的系統(tǒng)實(shí)例分離(這一點(diǎn)Abstract Factory也可以做到)
- 可以使得應(yīng)用開(kāi)發(fā)人員對(duì)于系統(tǒng)實(shí)例的創(chuàng)建過(guò)程進(jìn)行更加精確的控制,并且這個(gè)控制相對(duì)于其他的創(chuàng)建模式更加簡(jiǎn)潔
Prototype模式
Prototype(原型)模式的核心就是用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并且通過(guò)拷貝的方式創(chuàng)建這些原型新的對(duì)象。
原型模式主要應(yīng)用于系統(tǒng)的各組件需要保持一致的場(chǎng)景,通過(guò)克隆的方式高效的保證所有組件都是一致的。
書(shū)中舉了一個(gè)樂(lè)譜編輯器的例子,這個(gè)編輯器的主要處理對(duì)象當(dāng)然是樂(lè)譜,以及在樂(lè)譜上的各種音符(以全音符和二分音符為例),這三個(gè)東西都是有標(biāo)準(zhǔn)的不會(huì)因?yàn)椴煌臉?lè)譜對(duì)象而改變。所以適合使用原型模式。
首先定義這三個(gè)實(shí)現(xiàn)類及其接口,可以看到他們都實(shí)現(xiàn)了clone方法(建議還是要自定義一個(gè)接口,而不要直接使用Clonable接口,應(yīng)用研發(fā)人員不易看懂):
//組件接口
public interface Graphic {void draw(Position position);Graphic clone();
}//樂(lè)譜實(shí)現(xiàn)類
public class Staff implements Graphic {public void draw(Position position){System.out.println("Drawing staff at line "+position.getLineCount()+", column "+position.getColumnCount());}public Graphic clone(){try {return (Graphic) super.clone();} catch (Exception e) {throw new AssertionError("Clone not supported");}}
}//半分音符實(shí)現(xiàn)類
public class HalfNote implements Graphic{public void draw(Position position){System.out.println("Drawing HalfNote at line "+position.getLineCount()+", column "+position.getColumnCount());}public Graphic clone(){try {return (Graphic) super.clone();} catch (Exception e) {throw new AssertionError("Clone not supported");}}
}//全音符實(shí)現(xiàn)類
public class WholeNote implements Graphic {public void draw(Position position){System.out.println("Drawing WholeNote at line "+position.getLineCount()+", column "+position.getColumnCount());}public Graphic clone(){try {return (Graphic) super.clone();} catch (Exception e) {throw new AssertionError("Clone not supported");}}}
然后可以直接定義一個(gè)工廠方法,通過(guò)已經(jīng)實(shí)現(xiàn)的組件實(shí)例作為輸入來(lái)調(diào)用他們的clone方法,達(dá)到創(chuàng)建的目的:
public class GraphicCreateFactory{private Staff staff;private WholeNote wholeNote;private HalfNote halfNote;public GraphicCreateFactory(Staff staff, WholeNote wholeNote, HalfNote halfNote){this.staff = staff;this.wholeNote = wholeNote;this.halfNote = halfNote;};public Staff createStaff(){return staff.clone();}public WholeNote createWholeNote(){return wholeNote.clone();}public HalfNote createHalfNote(){return halfNote.clone();}}
所以客戶端可以預(yù)先創(chuàng)建原型后,作為參數(shù)調(diào)用相應(yīng)的創(chuàng)建方法。
Singleton模式
Singleton(單例)模式在之前Effective Java中也介紹過(guò),就是保證一個(gè)類僅有一個(gè)實(shí)例,并且提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)。它要實(shí)現(xiàn)的目的其實(shí)和Prototype有些類似,就是要保證組件的唯一性。
下面的單例模式設(shè)計(jì)可以根據(jù)環(huán)境變量type的值來(lái)定向?qū)嵗m當(dāng)?shù)腗azeFactory子類。
public class MazeFactory {private static MazeFactory instance;protected MazeFactory(){};public static MazeFactory getInstance(String type){if(instance == null){synchronized(MazeFactory.class){if (instance==null) {if (type.equals("Standard")) {instance = new MazeFactory();}if (type.equals("Bombed")) {instance = new BombMazeFactory();}}}}return instance;}public Maze createMaze(){return new Maze();}public Room createRoom(int roomNo){return new Room(roomNo);}public Wall createWall(){return new Wall();}public Door createDoor(Room room1, Room room2){return new Door(room1, room2);}
}
還有就是單例的實(shí)現(xiàn)方式要注意并發(fā)的訪問(wèn)設(shè)計(jì),這部分在Effective Java學(xué)習(xí)筆記--單例(Singleton)及其屬性的增強(qiáng)有涉及,這里就不展開(kāi)了。