在對象建立的過程當中,常常會出現的一個問題就是經過顯示地指定一個類來建立對象,從而致使緊耦合。這是由於建立對象時指定類名將使你受特定實現的約束而不是特定接口的約束。這會使將來的變化更加複雜。要避免這種狀況,就應該間接地建立對象。java
這種緊耦合的問題很大程度是由new關鍵字帶來的,因爲new的緊耦合出現,使得緊耦合的類很難獨立地被複用,由於它們之間是相互依賴的。而且緊耦合產生單塊的系統,要改變或者刪掉一個類,就必需要理解和改變其餘許多類。這也是致使系統難以維護和移植的一個重要緣由。算法
因此能夠經過「對象建立」模式繞開new,從而避免在對象建立(new)過程當中所致使的緊耦合(依賴具體的類),以此支持對象建立的穩定。sql
那麼如何避免new呢?舉個例子!數據庫
public void fun1(){
//...
Product p = new Product(); //改成:Product p = productFactory.createProduct();
//...
}
複製代碼
這樣的方式就是經過一個工廠調用一個方法來建立相應的產品,可是可能你們又會產生一個問題,這樣操做雖然解決了Product
的new操做,可是對於ProductFactory
而言不是也須要經過new來產生嗎?編程
對於這個問題,我想是不少人在接觸到設計模式的時候都會去思考的問題,既然ProductFactory
仍是要用到new,那工廠類還有存在的必要嗎?這時,咱們能夠會想到兩種解決方式,一是將createProdyct()
方法寫成靜態方法,這樣調用的時候天然不須要new了。二是經過注入的方式,好比在應用類當中經過setter或是構造方法傳入一個工廠類的對象。設計模式
對於靜態方法而言,簡單地說,即便是使用靜態方法,那Product p = ProductFactory.createProduct()
這樣依然是一種緊耦合的方式,由於工廠類沒法替換,和直接new出產品區別不大。安全
對於注入方式,你們更多的是疑惑,既然能夠傳入一個工廠類對象,那爲何不直接傳入相應的產品,不是更簡單直接嗎?固然不是的,首先須要明白的是,工廠類的做用是做爲一個籠子,這個籠子須要幫助咱們束縛住 ‘將來的變化’ ,要知道一個產品的變化可能老是大於工廠的變化。在這種狀況下,舉出一個最簡單的例子,你在編碼的過程當中,可能會用到不僅一個產品,那你就可能須要不少setter或者修改構造方法;可是若是這些產品均可以經過這個工廠來獲取,是否是就至關於用籠子關住了變化,使其在一個範圍中跳動。bash
在學習設計模式時,永遠要記住的一句話就是「設計模式是用來教會咱們如何應對將來可能的變化」。若是你可以肯定本身的系統將來沒有變化,那天然用不到設計模式;或者你的系統將來全是變化,那也用不到設計模式,設計模式作的就是隔離穩定與變化,若是沒有穩定,那就用不到設計模式。app
‘new’是一種硬編碼,究竟 ’硬‘ 在那裏,一樣一個簡單的理由,若是將來構造方法發生變化或者說構造參數增長(減小),而在源碼中有不少地方都是經過new來獲取實例對象,找到並修改源碼將會是一項很大的工做。框架
在解決這樣的 「對象建立」 問題中就有工廠方法、抽象工廠、原型模式和建造者模式等相關設計模式。
意圖
定義一個用於建立對象的接口,讓子類決定實例化哪個類。FactoryMethod使得一個類的實例化延遲到其子類。
實例
Factory Method相對於簡單工廠而言,徹底遵循了「不改代碼」的原則,可是其使用情形相比抽象工廠使用條件沒有那麼高,所以能夠說是使用最多的建立型模式之一了。
考慮這樣一個應用,它能夠向用戶顯示多種文檔,好比word、pdf、txt等等。在這個框架中,首先,想到的可能就是應用簡單工廠模式。
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實現代碼,同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;
}
}
}
複製代碼
//簡單工廠模式在客戶類當中的調用
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();
}
}
複製代碼
這樣簡單工廠模式,在不考慮將來新文檔類型的狀況下,確實是一種不錯的實現方法。可是在後續的擴展過程中,若是須要增長新的文檔類,就須要去修改DocumentFactory
中的createDocument()
方法,增長新的類別,而且客戶還必須知道這些類別才能使用。
爲了應對這種狀況,就出現了工廠方法。工廠方法就直接將工廠抽象出來,每一個產品對應一個工廠,消除工廠模式中的條件分支結構(其實還有一種消除條件語句的模式,就是以前「組件協做」當中的策略模式)。
//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實現代碼
......
}
//而且後續能夠擴展新的文檔類
......
複製代碼
//修改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();
}
}
//若是後續有新的產品,直接再實現DocumentFactory,獲得新的工廠
......
複製代碼
//調用過程可作以下修改:
public class Client {
public static void main(String[] args) {
//利用多態性質,直接生成相應的factory子類
//消除了控制耦合
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();
}
}
複製代碼
有人可能會有疑問,這樣不是還沒徹底消除new嗎?首先這裏的客戶類已經到最高的調用層次了,這個過程中是必然會有new的出現,否則怎樣進行程序調用呢?
咱們所說的消除new的過程是指main與factory之間,產生的一箇中間層次(以下面的App)中去消除new。
//這樣的代碼中,就消除了new的存在
//具體的注入過程能夠由其餘的形式完成,好比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();
//一樣對於其餘的工廠類也是能夠採用一樣的方式調用。
......
}
}
複製代碼
這樣修改代碼的好處在那裏呢?第一,顯而易見的就是徹底實現了「開閉原則」的思想,擴展時再也不須要去修改源碼。第二,有些對象的建立過程可能比較複雜,所以若是直接在應用程序當中使用new或者其餘形式建立很麻煩,經過工廠建立以後,就再也不須要去關注那些複雜的建立過程。第三,經過new建立,始終是一種硬編碼的形式,若是在應用程序當中過多的使用這種方式,那麼一旦某對象的建立方式發生改變,修改源碼必然是很繁瑣的。
結構——類建立型模式
參與者
Product(Document)
定義工廠方法中工廠建立的對象的接口。
ConcreteProduct(PdfDocument、TxtDocument)
實現Product的接口。
Creator(DocumentFactory)
聲明工廠方法——createProduct(),能夠調用該方法返回一個Product類型的對象。
ConcreteCreator(PdfDocumentFactory、TxtDocumentFactory)
重定義工廠方法以返回具體的ConcreteProduct。
Client(客戶類)
使用工廠和產品,工廠方法模式中,客戶類也是一個重要的參與者,由於工廠方法主要的做用就是分離開客戶類與產品類之間的耦合關係,因此脫離客戶類去談工廠方法模式時,總會以爲差了些什麼東西,沒法徹底體會到工廠方法模式的優點。
適用性
在下列狀況下可使用Factory Method模式:
簡單地說,就是使用過程當中只須要聲明一個抽象工廠類的引用,具體調用那個工廠去生成那個對象,是由調用者去肯定的。
相關模式
Abstract Factory常常用工廠方法來實現,抽象工廠建立產品的過程就可使用工廠方法來完成。
工廠方法一般在Template Method中被調用,這一點在「組件協做」當中也提到過。
思考
意圖
提供一個建立一系列相關或相互依賴對象的接口,而無需指定他們具體的類。
實例
假定存在這樣一個服務層,該層當中須要作的就是訪問數據庫中的數據,而且執行一系列的相關操做。根據面向接口編程的思想,能夠先做這樣一個代碼編寫。
//對數據庫進行訪問的三個接口
//先創建鏈接,再執行相關操做,最後返回相應結果
public interface DBConnection{}
public interface DBCommand{}
public interface DBDataReader{}
//對於MySql,能夠創建如下實現
public class MySqlDBConnection implements DBConnection{}
public class MySqlDBCommand implements DBCommand{}
public class MySqlDBDataReader implements DBDataReader{}
//一樣對於Sql Server,Oricle也是這樣的實現
......
複製代碼
這樣的實現下,咱們能夠說是知足了面向接口編程的一個思想;而且在實現中,咱們能夠爲每一個接口,按照工廠方法模式,爲其建立一個工廠。
//工廠接口
public interface DBConnectionFactory{
public DBConnection createDBConnetion();
}
public interface DBCommandFactory{
public DBCommand createDBCommand();
}
public interface DBDataReaderFactory{
public DBDataReader createDBDataReader();
}
//而後對於每一個具體的數據庫,實現不一樣的具體工廠
//以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也是如此
......
複製代碼
工廠模式方法的調用就再也不演示,區別和工廠方法中的Document
例子中差異不大。
對於這樣的實現,雖然咱們很好的利用了工廠方法模式,可是也引入了工廠方法模式的一個弊端——大量的對象和類(本例當中,三個系列,每一個系列三個產品,光產品就是9個子類;每一個產品再對應一個工廠,一共就是18個子類)。在使用的過程當中,反而可以明顯的感受到系統複雜度不減反增。而且,DBConnection
、DBCommand
和DBDataReader
明顯是有着必定的關係的,換句話說,MySql創建的DBConnection
是和MySqlDBCommand、MySqlDBDataReader
一塊兒使用的,若是出現MySqlDBConnection、OricleDBCommand、SqlServerDBDataReader
這種組合確定是沒法正常執行的。這時抽象工廠的出現,就很好的解決了這樣的問題。
//首先,具體的產品類不會發生變化,簡化的主要是工廠層次
//先抽象出抽象工廠,將產品系列的建立方法合併到一個接口中
public interface DBFactory{
public DBConnection createDBConnetion();
public DBCommand createDBCommand();
public DBDataReader createDBDataReader();
}
//根據不一樣的具體工廠,建立具體的對象
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的工廠,一樣如此
......
複製代碼
抽象工廠主要是對工廠層次的簡化,這樣修改下來,對比工廠方法模式,減小了2/3的工廠子類建立,只須要3個工廠(有多少個產品系列就有多少個工廠子類)就能夠完成產品的建立。
這樣的一種建立工廠方式,不只減小了工廠的數量,並且使得產品的一致性得以保證,它能夠保證,一次只能使用同一個系列當中的對象。
public class Client {
public static void main(String[] args) {
DBFactory factory = new MySqlDBFactory();
App app = new App();
app.setFactory(factory);
app.operate();
//一樣對於其餘的工廠類也是能夠採用一樣的方式調用。
// ......
}
}
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();
//執行相關操做
.....
}
}
複製代碼
這樣的應用程序代碼,在必定程度上就減小了工廠子類的數量,而且在operate()
中保證了產品系列的一致性,使得MysqlDBFactory
生成的產品,只會是與MySql相關的。
結構——對象建立型模式
參與者
AbstractFactory(DBFactory)
聲明一個建立抽象產品對象的操做接口。
ConcreteFactory(MySqlDBFactory)
實現建立具體產品對象的操做。
AbstractProduct(DBConnection、DBCommand、DBDataReader)
爲一類產品對象聲明一個接口。
ConcreteProduct(MySqlDBConection、MySqlDBCommand、MySqlDBDataReader)
定義一個將被相應的具體工廠建立的產品對象,並實現抽象產品的相應接口。
Client
調用抽象工廠和抽象產品提供的接口。在建立者模式當中,客戶類也是重要的參與成員,由於對建立模式的理解容易混亂的點正是在客戶類中的調用過程 (new) 產生的,關於這個問題,已經在前面作過不少解釋了,再也不多說。
適用性
如下狀況可使用AbstractFactory模式:
相關模式
Singleton:一個具體的工廠一般會是一個單件。由於在一個應用中,通常每一個產品系列只須要一個具體工廠。
Factory Method:在Abstract Factory中,僅僅是聲明一個建立對象的接口,真正的建立過程是由具體工廠實現的。這時,能夠爲每個方法對應的具體對象之間再定義一個工廠方法。但其問題就在於,這樣的作法就像是在工廠方法上再套上一層抽象工廠,從而又增長了系統的複雜度。
思考
DBFactory
接口,而且涉及到DBFactory
及其子類的改變。意圖
用原型實例指定對象的建立種類,而且經過拷貝這些原型建立新的對象。
實例
Prototype模式,有點像是對工廠方法模式中產品與工廠的合併。怎麼說呢?看下面的代碼:
//工廠方法模式中的產品類與工廠方法類
public interface Document{
public void open();
public void close();
public void save();
......
}
public interface DocumentFactory{
public Document createDocument();
}
複製代碼
這是在Factory Method中使用的建立方式,而原型作的事就是,再也不用工廠來進行建立,而是轉而克隆的方式。變成下面這樣:
//合併Document和DocumentFactory
public abstract class Document
implements Cloneable{
public void open();
public void close();
public void save();
......
//至關於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!");
}
//......
}
//文檔類的實現與工廠方法中的同樣
......
複製代碼
那麼在具體的客戶類當中就是經過這樣一種方式來進行調用:
public class App{
//在工廠方法模式當中,這裏是documentFactory
private Document prototype;
public void setDoucument(Document document){
prototype = document;
}
public void useDocument(){
//documentFactory.createDocument();
Document doc = prototype.clone();
//而後使用prototype克隆出來的doc進行操做
doc.open();
......
}
}
//在客戶類中調用表現
public class Client {
public static void main(String[] args) {
Document doc = new PdfDocument();
App app = new App();
app.setFactory(doc);
app.useDocument();
//一樣對於其餘的工廠類也是能夠採用一樣的方式調用。
//......
}
}
複製代碼
問題來了,爲何不直接用原型(prototype),而是要多一步克隆?解決這個問題,首先要明白的是,咱們使用原型的目的不是將原型做爲客戶類的一個屬性去使用,而是一個建立者。既然是一個建立者,那麼在使用的過程當中,就不僅一個地方會用到一樣類型的對象;若是在不一樣的地方都直接使用原型,可能會在某個地方修改了原型的值,從而使得其餘直接使用原型的方法出現不可預知的錯誤。
結構——對象建立型模式
參與者
Prototype(Document)
聲明一個克隆自身的接口。
ConcretePrototype(PdfDocument...)
繼承Prototype並實現克隆自身的接口。
Client
讓一個原型克隆自身從而建立一個新的對象。
適用性
當一個系統應該獨立於它的產品建立、構成和表示時,可使用Prototype模式。
當要實例化的類是在運行時候指定時,好比動態裝載。
相關模式
Abstract Factory和Prototype在某種方面是相互競爭的,可是在某種狀況下也是能夠一塊兒使用,好比,在抽象工廠中存儲一個被克隆的產品集合,在使用時,直接根據集合中的對象返回相應的產品。
大量使用Composite(組合模式)和Decorator(裝飾器模式)的設計上也能夠採用Protorype來減小Composite或Decorator對象的建立。
思考
意圖
將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠有不一樣的表示。
實例
在遊戲場景當中,尤爲是3d場景中,必不可少就是建築物,好比說房子。對房子的構建確定不是一下所有構建完成的,而是會分紅幾個部分,好比牆、窗戶、地板、房頂、門,一部分、一部分地去構建。
public abstract class House{
//房子屬性,紋理、材質...
private Texture texture;
private Material material;
......
//牆、窗戶、地板、房頂、門
public Wall buildWall();
public Window buildWindow();
public Floor buildFloor();
public Door buildDoor();
public Roof buildRoof();
//房子構建過程
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();
}
}
複製代碼
這種構建方式,採用的明顯就是模板方法(Template Method),這種實現方式還能夠根據不一樣的房子類型,實現具體的細節。
//石頭屋
public class StoneHouse extends House{
//具體實現細節
.......
}
//茅草屋
public class ThatchedHouse extends House{
//具體實現細節
.......
}
複製代碼
//按照模板主方法在客戶類中的調用形式表現:
public class Client{
public static void main(String[] args){
//只須要生成一個相應對象便可在遊戲場景中完成相應類型房子的建立
House house = new StoneHouse();
house.buildHouse(); //生成石頭屋
house = new ThatchedHouse();
house.buildHouse(); //生成茅草屋
}
}
複製代碼
這種實現有什麼問題呢?類太臃腫了,對吧~這樣的實現過程,能夠體現複用的思想,可是問題之一就在於全部的內容所有都放在了一個類中,體現不出單一職責和類的信息與行爲集中;這時就能夠將建立過程分離出來,造成一個Builder,由這樣一個Builder來專門負責建立。
//Director
public class House{
//房子屬性,紋理、材質...
private Texture texture;
private Material material;
......
//增長builder的引入
private HouseBuilder houseBuilder;
public void setHouseBuilder(HouseBuilder hBuilder){
houseBuilder = hbuilder;
}
//房子構建過程
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();
}
}
//分離出來的builder
public interface HouseBuilder{
//牆、窗戶、地板、房頂、門
public Wall buildWall();
public Window buildWindow();
public Floor buildFloor();
public Door buildDoor();
public Roof buildRoof();
}
public class StoneHouseBuilder
implements HouseBuilder{
//具體實現細節
.......
}
public class ThatchedHouseBuilder
implements HouseBuilder{
//具體實現細節
.......
}
複製代碼
//修改事後,在客戶類中的調用形式表現:
public class Client{
public static void main(String[] args){
//只須要生成一個相應對象便可在遊戲場景中完成相應類型房子的建立
House house = new House();
HouseBuilder builder
= new StoneHouseBuilder();
house.setHouseBuilder(builder);
house.buildHouse();
builder = new ThatchedHouseBuilder();
//這個set過程能夠運行時完成
house.setHouseBuilder(builder);
house.buildHouse();
}
}
複製代碼
經過這樣一種方式,實現複雜對象構建與表示的分離,而且,對於不一樣的房子對象,若是房子其餘參數沒有任何差異,就只須要傳入相應的builder便可,而不須要再生成各類各樣的子類(如StoneHouse、ThatchedHouse
)。
一旦生成對象,只須要修改其builder就能夠立刻改變其對象表示,而不須要新生成對象。而且這種修改過程,是能夠動態完成的,就若是Spring當中的依賴注入過程同樣,能夠在運行時刻完成,而不必定是一開始就肯定的
結構——對象建立型模式
參與者
Builder(HouseBuilder)
爲建立一個Product對象的各個部件指定抽象接口。
ConcreteBuilder(StoneHouseBuilder、ThatchedHouseBuilder)
Director(House)
構造一個使用builder的對象。
Product(Wall、Window...)
包含了定義組成部件的類以及被構造的複雜對象等。
適用性
Builder的適用狀況:
Builder模式更多的是體現的一種思想,而不是具體的過程,這種思想就是,當一個類的信息與行爲過於臃腫時,也許能夠採用Builder這種方式對類的信息與行爲進行從新劃分,從而使得類看起來更加的「輕」 。
相關模式
Abstract Factory和Builder都是對複雜對象的建立封裝,但兩者的區別在於,Builder着重於一步一步構建一個複雜對象,而Abstract Factory着重於多個系列產品對象的建立,而且系列對象之間有着某種聯繫。
Composite模式中對象一般狀況下就是用Builder生成的。
思考