【創(chuàng)建型模式三】抽象工廠(Abstract Factory)

1 場(chǎng)景問題#

1.1 選擇組裝電腦的配件##

舉個(gè)生活中常見的例子——組裝電腦,我們?cè)诮M裝電腦的時(shí)候,通常需要選擇一系列的配件,比如:CPU、硬盤、內(nèi)存、主板、電源、機(jī)箱等等。為了使討論簡單點(diǎn),只考慮選擇CPU和主板的問題。

事實(shí)上,我們?cè)谶x擇CPU的時(shí)候,面臨一系列的問題,比如:品牌、型號(hào)、針腳數(shù)目、主頻等問題,只有把這些都確定下來,才能確定具體的CPU。同樣,在選擇主板的時(shí)候,也有一系列的問題,比如:品牌、芯片組、集成芯片、總線頻率等問題,也只有這些都確定了,才能確定具體的主板。

選擇不同的CPU和主板,是每個(gè)客戶去組裝電腦的時(shí)候,向裝機(jī)公司提出的要求,也就是我們每個(gè)人自己擬定的裝機(jī)方案。

在最終確定這個(gè)裝機(jī)方案之前,還需要整體考慮各個(gè)配件之間的兼容性,比如:CPU和主板,如果CPU針腳數(shù)和主板提供的CPU插口不兼容,是無法組裝的。也就是說,裝機(jī)方案是有整體性的,里面選擇的各個(gè)配件之間是有關(guān)聯(lián)的。

對(duì)于裝機(jī)工程師而言,他只知道組裝一臺(tái)電腦,需要相應(yīng)的配件,但是具體使用什么樣的配件,還得由客戶說了算。也就是說裝機(jī)工程師只是負(fù)責(zé)組裝,而客戶負(fù)責(zé)選擇裝配所需要的具體的配件。因此,當(dāng)裝機(jī)工程師為不同的客戶組裝電腦時(shí),只需要按照客戶的裝機(jī)方案,去獲取相應(yīng)的配件,然后組裝即可。

現(xiàn)在需要使用程序來把這個(gè)裝機(jī)的過程,尤其是選擇組裝電腦配件的過程實(shí)現(xiàn)出來,該如何實(shí)現(xiàn)呢?

1.2 不用模式的解決方案##

考慮客戶的功能,需要選擇自己需要的CPU和主板,然后告訴裝機(jī)工程師自己的選擇,接下來就等著裝機(jī)工程師組裝機(jī)器了。

對(duì)裝機(jī)工程師而言,只是知道CPU和主板的接口,而不知道具體實(shí)現(xiàn),很明顯可以用上簡單工廠或工廠方法模式,為了簡單,這里選用簡單工廠吧??蛻舾嬖V裝機(jī)工程師自己的選擇,然后裝機(jī)工程師會(huì)通過相應(yīng)的工廠去獲取相應(yīng)的實(shí)例對(duì)象。

  1. 先來看看CPU和主板的接口,先看CPU的接口定義,示例代碼如下:
/** 
   * CPU的接口 
   */  
public interface CPUApi {  
      /** 
       * 示意方法,CPU具有運(yùn)算的功能 
       */  
      public void calculate();  
}  

再看看主板的接口定義,示例代碼如下:

/**
   * Mainboard的接口
   */
public interface MainboardApi {
     public void installCPU();
}
  1. 接下來看看具體的CPU實(shí)現(xiàn),先看Intel的CPU實(shí)現(xiàn),示例代碼如下:
/** 
   * Intel的CPU實(shí)現(xiàn) 
   */  
public class IntelCPU implements CPUApi{  
      /** 
       * CPU的針腳數(shù)目 
       */  
      private int pins = 0;  
      /** 
       * 構(gòu)造方法,傳入CPU的針腳數(shù)目 
       * @param pins CPU的針腳數(shù)目 
       */  
      public IntelCPU(int pins){  
          this.pins = pins;  
      }  
      public void calculate() {  
          System.out.println("now in Intel CPU,pins="+pins);  
      }  
}  

再看看AMD的CPU實(shí)現(xiàn),示例代碼如下:

/** 
   * AMD的CPU實(shí)現(xiàn) 
   */  
public class AMDCPU implements CPUApi{  
      /** 
       * CPU的針腳數(shù)目 
       */  
      private int pins = 0;  
      /** 
       * 構(gòu)造方法,傳入CPU的針腳數(shù)目 
       * @param pins CPU的針腳數(shù)目 
       */  
      public AMDCPU(int pins){  
          this.pins = pins;  
      }  
      public void calculate() {  
          System.out.println("now in AMD CPU,pins="+pins);  
      }  
}  
  1. 接下來看看具體的主板實(shí)現(xiàn),先看技嘉的主板實(shí)現(xiàn),示例代碼如下:
/** 
   * 技嘉的主板 
   */  
public class GAMainboard implements MainboardApi {  
      /** 
       * CPU插槽的孔數(shù) 
       */  
      private int cpuHoles = 0;  
      /** 
       * 構(gòu)造方法,傳入CPU插槽的孔數(shù) 
       * @param cpuHoles CPU插槽的孔數(shù) 
       */  
      public GAMainboard(int cpuHoles){  
          this.cpuHoles = cpuHoles;  
      }  
      public void installCPU() {  
          System.out.println("now in GAMainboard,cpuHoles=" + cpuHoles);  
      }  
}  

再看看微星的主板實(shí)現(xiàn),示例代碼如下:

/** 
   * 微星的主板 
   */  
public class MSIMainboard implements MainboardApi{  
      /** 
       * CPU插槽的孔數(shù) 
       */  
      private int cpuHoles = 0;  
      /** 
       * 構(gòu)造方法,傳入CPU插槽的孔數(shù) 
       * @param cpuHoles CPU插槽的孔數(shù) 
       */  
      public MSIMainboard(int cpuHoles){  
          this.cpuHoles = cpuHoles;  
      }  
      public void installCPU() {  
          System.out.println("now in MSIMainboard,cpuHoles=" + cpuHoles);  
      }
}  
  1. 接下來看看創(chuàng)建CPU和主板的工廠,先看創(chuàng)建CPU的工廠實(shí)現(xiàn),示例代碼如下:
/** 
   * 創(chuàng)建CPU的簡單工廠 
   */  
public class CPUFactory {  
      /** 
       * 創(chuàng)建CPU接口對(duì)象的方法 
       * @param type 選擇CPU類型的參數(shù) 
       * @return CPU接口對(duì)象的方法 
       */  
      public static CPUApi createCPUApi(int type){  
          CPUApi cpu = null;  
          //根據(jù)參數(shù)來選擇并創(chuàng)建相應(yīng)的CPU對(duì)象  
          if(type==1){  
              cpu = new IntelCPU(1156);  
          }else if(type==2){  
              cpu = new AMDCPU(939);  
          }  
          return cpu;  
      }    
}  

再看看創(chuàng)建主板的工廠實(shí)現(xiàn),示例代碼如下:

/** 
   * 創(chuàng)建主板的簡單工廠 
   */  
public class MainboardFactory {  
      /** 
       * 創(chuàng)建主板接口對(duì)象的方法 
       * @param type 選擇主板類型的參數(shù) 
       * @return 主板接口對(duì)象的方法 
       */  
      public static MainboardApi createMainboardApi(int type){  
          MainboardApi mainboard = null;  
          //根據(jù)參數(shù)來選擇并創(chuàng)建相應(yīng)的主板對(duì)象  
          if(type==1){  
              mainboard = new GAMainboard(1156);  
          }else if(type==2){  
              mainboard = new MSIMainboard(939);  
          }  
          return mainboard;  
      }  
}  
  1. 接下來看看裝機(jī)工程師的實(shí)現(xiàn),示例代碼如下:
/** 
   * 裝機(jī)工程師的類 
   */  
public class ComputerEngineer {  
      /** 
       * 定義組裝機(jī)器需要的CPU 
       */  
      private CPUApi cpu= null;  
      /** 
       * 定義組裝機(jī)器需要的主板 
       */  
      private MainboardApi mainboard = null;  
      /** 
       * 裝機(jī)過程 
       * @param cpuType 客戶選擇所需CPU的類型 
       * @param mainboardType 客戶選擇所需主板的類型 
       */  
      public void makeComputer(int cpuType,int mainboardType){  
          //1:首先準(zhǔn)備好裝機(jī)所需要的配件  
          prepareHardwares(cpuType,mainboardType);  
          //2:組裝機(jī)器       
          //3:測(cè)試機(jī)器       
          //4:交付客戶  
      }  
      /** 
       * 準(zhǔn)備裝機(jī)所需要的配件 
       * @param cpuType 客戶選擇所需CPU的類型 
       * @param mainboardType 客戶選擇所需主板的類型 
       */  
      private void prepareHardwares(int cpuType,int mainboardType){  
          //這里要去準(zhǔn)備CPU和主板的具體實(shí)現(xiàn),為了示例簡單,這里只準(zhǔn)備這兩個(gè)  
          //可是,裝機(jī)工程師并不知道如何去創(chuàng)建,怎么辦呢?  

          //直接找相應(yīng)的工廠獲取  
          this.cpu = CPUFactory.createCPUApi(cpuType);  
          this.mainboard = MainboardFactory.createMainboardApi(mainboardType);  
          //測(cè)試一下配件是否好用  
          this.cpu.calculate();  
          this.mainboard.installCPU();  
      }  
} 
  1. 看看此時(shí)的客戶端,應(yīng)該通過裝機(jī)工程師來組裝電腦,客戶需要告訴裝機(jī)工程師他選擇的配件,示例代碼如下:
public class Client {  
      public static void main(String[] args) {  
          //創(chuàng)建裝機(jī)工程師對(duì)象  
          ComputerEngineer engineer = new ComputerEngineer();  
          //告訴裝機(jī)工程師自己選擇的配件,讓裝機(jī)工程師組裝電腦  
          engineer.makeComputer(1,1);  
      }  
}  

1.3 有何問題##

看了上面的實(shí)現(xiàn),會(huì)感覺到很簡單嘛,通過使用簡單工廠來獲取需要的CPU和主板對(duì)象,然后就可以組裝電腦了。有何問題呢?

雖然上面的實(shí)現(xiàn),通過簡單工廠解決解決了:對(duì)于裝機(jī)工程師,只知CPU和主板的接口,而不知道具體實(shí)現(xiàn)的問題。但還有一個(gè)問題沒有解決,什么問題呢?那就是這些CPU對(duì)象和主板對(duì)象其實(shí)是有關(guān)系的,是需要相互匹配的。而在上面的實(shí)現(xiàn)中,并沒有維護(hù)這種關(guān)聯(lián)關(guān)系,CPU和主板是由客戶隨意選擇的。這是有問題的。

這就是沒有維護(hù)配件之間的關(guān)系造成的。該怎么解決這個(gè)問題呢?

2 解決方案#

2.1 抽象工廠模式來解決##

用來解決上述問題的一個(gè)合理的解決方案就是抽象工廠模式。那么什么是抽象工廠模式呢?

  1. 抽象工廠模式定義
抽象工廠模式定義
  1. 應(yīng)用抽象工廠模式來解決的思路

仔細(xì)分析上面的問題,其實(shí)有兩個(gè)問題點(diǎn),一個(gè)是只知道所需要的一系列對(duì)象的接口,而不知具體實(shí)現(xiàn),或者是不知道具體使用哪一個(gè)實(shí)現(xiàn);另外一個(gè)是這一系列對(duì)象是相關(guān)或者相互依賴的。也就是說既要?jiǎng)?chuàng)建接口的對(duì)象,還要約束它們之間的關(guān)系。

有朋友可能會(huì)想,工廠方法模式或者是簡單工廠,不就可以解決只知接口而不知實(shí)現(xiàn)的問題嗎?怎么這些問題又冒出來了呢?

請(qǐng)注意,這里要解決的問題和工廠方法模式或簡單工廠解決的問題是有很大不同的,工廠方法模式或簡單工廠關(guān)注的是單個(gè)產(chǎn)品對(duì)象的創(chuàng)建,比如創(chuàng)建CPU的工廠方法,它就只關(guān)心如何創(chuàng)建CPU的對(duì)象,而創(chuàng)建主板的工廠方法,就只關(guān)心如何創(chuàng)建主板對(duì)象。

這里要解決的問題是,要?jiǎng)?chuàng)建一系列的產(chǎn)品對(duì)象,而且這一系列對(duì)象是構(gòu)建新的對(duì)象所需要的組成部分,也就是這一系列被創(chuàng)建的對(duì)象相互之間是有約束的。

解決這個(gè)問題的一個(gè)解決方案就是抽象工廠模式。在這個(gè)模式里面,會(huì)定義一個(gè)抽象工廠,在里面虛擬的創(chuàng)建客戶端需要的這一系列對(duì)象,所謂虛擬的就是定義創(chuàng)建這些對(duì)象的抽象方法,并不去真的實(shí)現(xiàn),然后由具體的抽象工廠的子類來提供這一系列對(duì)象的創(chuàng)建。這樣一來可以為同一個(gè)抽象工廠提供很多不同的實(shí)現(xiàn),那么創(chuàng)建的這一系列對(duì)象也就不一樣了,也就是說,抽象工廠在這里起到一個(gè)約束的作用,并提供所有子類的一個(gè)統(tǒng)一外觀,來讓客戶端使用。

2.2 模式結(jié)構(gòu)和說明##

抽象工廠模式結(jié)構(gòu)如圖所示:

抽象工廠模式結(jié)構(gòu)

AbstractFactory:抽象工廠,定義創(chuàng)建一系列產(chǎn)品對(duì)象的操作接口。

ConcreteFactory:具體的工廠,實(shí)現(xiàn)抽象工廠定義的方法,具體實(shí)現(xiàn)一系列產(chǎn)品對(duì)象的創(chuàng)建。

AbstractProduct:定義一類產(chǎn)品對(duì)象的接口。

ConcreteProduct:具體的產(chǎn)品實(shí)現(xiàn)對(duì)象,通常在具體工廠里面,會(huì)選擇具體的產(chǎn)品實(shí)現(xiàn)對(duì)象,來創(chuàng)建符合抽象工廠定義的方法返回的產(chǎn)品類型的對(duì)象。

Client:客戶端,主要使用抽象工廠來獲取一系列所需要的產(chǎn)品對(duì)象,然后面向這些產(chǎn)品對(duì)象的接口編程,以實(shí)現(xiàn)需要的功能。

2.3 抽象工廠模式示例代碼##

  1. 先看看抽象工廠的定義,示例代碼如下:
/** 
   * 抽象工廠的接口,聲明創(chuàng)建抽象產(chǎn)品對(duì)象的操作 
   */  
public interface AbstractFactory {  
      /** 
       * 示例方法,創(chuàng)建抽象產(chǎn)品A的對(duì)象 
       * @return 抽象產(chǎn)品A的對(duì)象 
       */  
      public AbstractProductA createProductA();  
      /** 
       * 示例方法,創(chuàng)建抽象產(chǎn)品B的對(duì)象 
       * @return 抽象產(chǎn)品B的對(duì)象 
       */  
      public AbstractProductB createProductB();  
}  
  1. 接下來看看產(chǎn)品的定義,由于只是示意,并沒有去定義具體的方法,示例代碼如下:
/** 
   * 抽象產(chǎn)品A的接口 
   */  
public interface AbstractProductA {  
      //定義抽象產(chǎn)品A相關(guān)的操作  
} 

/** 
   * 抽象產(chǎn)品B的接口 
   */  
public interface AbstractProductB {  
      //定義抽象產(chǎn)品B相關(guān)的操作  
}  
  1. 同樣的,產(chǎn)品的各個(gè)實(shí)現(xiàn)對(duì)象也是空的,示例代碼如下:
/** 
   * 產(chǎn)品A的具體實(shí)現(xiàn) 
   */  
public class ProductA1 implements AbstractProductA {  
      //實(shí)現(xiàn)產(chǎn)品A的接口中定義的操作  
}  

/** 
   * 產(chǎn)品A的具體實(shí)現(xiàn) 
   */  
public class ProductA2 implements AbstractProductA {  
      //實(shí)現(xiàn)產(chǎn)品A的接口中定義的操作  
} 

/** 
   * 產(chǎn)品B的具體實(shí)現(xiàn) 
   */  
public class ProductB1 implements AbstractProductB {  
      //實(shí)現(xiàn)產(chǎn)品B的接口中定義的操作  
}  

/** 
   * 產(chǎn)品B的具體實(shí)現(xiàn) 
   */  
public class ProductB2 implements AbstractProductB {  
      //實(shí)現(xiàn)產(chǎn)品B的接口中定義的操作  
}
  1. 接下來看看具體的工廠的實(shí)現(xiàn)示意,示例代碼如下:
/** 
   * 具體的工廠實(shí)現(xiàn)對(duì)象,實(shí)現(xiàn)創(chuàng)建具體的產(chǎn)品對(duì)象的操作 
   */  
public class ConcreteFactory1 implements AbstractFactory {  
      public AbstractProductA createProductA() {  
          return new ProductA1();  
      }  
      public AbstractProductB createProductB() {  
          return new ProductB1();  
      }  
} 

/** 
   * 具體的工廠實(shí)現(xiàn)對(duì)象,實(shí)現(xiàn)創(chuàng)建具體的產(chǎn)品對(duì)象的操作 
   */  
public class ConcreteFactory2 implements AbstractFactory {  
      public AbstractProductA createProductA() {  
          return new ProductA2();  
      }  
      public AbstractProductB createProductB() {  
          return new ProductB2();  
      }  
}   
  1. 最后來看看客戶端的實(shí)現(xiàn)示意,示例代碼如下:
public class Client {  
      public static void main(String[] args) {  
          //創(chuàng)建抽象工廠對(duì)象  
          AbstractFactory af = new ConcreteFactory1();  
          //通過抽象工廠來獲取一系列的對(duì)象,如產(chǎn)品A和產(chǎn)品B  
          af.createProductA();  
          af.createProductB();  
      }  
}  

2.4 使用抽象工廠模式重寫示例##

要使用抽象工廠模式來重寫示例,先來看看如何使用抽象工廠模式來解決前面提出的問題。

裝機(jī)工程師要組裝電腦對(duì)象,需要一系列的產(chǎn)品對(duì)象,比如CPU、主板等,于是創(chuàng)建一個(gè)抽象工廠給裝機(jī)工程師使用,在這個(gè)抽象工廠里面定義抽象的創(chuàng)建CPU和主板的方法,這個(gè)抽象工廠就相當(dāng)于一個(gè)抽象的裝機(jī)方案,在這個(gè)裝機(jī)方案里面,各個(gè)配件是能夠相互匹配的

每個(gè)裝機(jī)的客戶,會(huì)提出他們自己的具體裝機(jī)方案,或者是選擇已有的裝機(jī)方案,相當(dāng)于為抽象工廠提供了具體的子類,在這些具體的裝機(jī)方案類里面,會(huì)創(chuàng)建具體的CPU和主板實(shí)現(xiàn)對(duì)象。

此時(shí)系統(tǒng)的結(jié)構(gòu)如圖所示:

系統(tǒng)的結(jié)構(gòu)

雖然說是重寫示例,但并不是前面寫的都不要了,而是修改前面的示例,使它能更好的實(shí)現(xiàn)需要的功能。

  1. 前面示例實(shí)現(xiàn)的CPU接口和CPU實(shí)現(xiàn)對(duì)象,還有主板的接口和實(shí)現(xiàn)對(duì)象,都不需要變化,這里就不去贅述了。

  2. 前面示例中的創(chuàng)建CPU的簡單工廠和創(chuàng)建主板的簡單工廠,都不再需要了,直接刪除即可,這里也就不去管了。

  3. 看看新加入的抽象工廠的定義,示例代碼如下:

/** 
   * 抽象工廠的接口,聲明創(chuàng)建抽象產(chǎn)品對(duì)象的操作 
   */  
public interface AbstractFactory {  
      /** 
       * 創(chuàng)建CPU的對(duì)象 
       * @return CPU的對(duì)象 
       */  
      public CPUApi createCPUApi();  
      /** 
       * 創(chuàng)建主板的對(duì)象 
       * @return 主板的對(duì)象 
       */  
      public MainboardApi createMainboardApi();  
}  
  1. 再看看抽象工廠的實(shí)現(xiàn)對(duì)象,也就是具體的裝機(jī)方案對(duì)象,先看看裝機(jī)方案一的實(shí)現(xiàn),示例代碼如下:
/** 
   * 裝機(jī)方案一:Intel 的CPU + 技嘉的主板 
   * 這里創(chuàng)建CPU和主板對(duì)象的時(shí)候,是對(duì)應(yīng)的,能匹配上的 
   */  
public class Schema1 implements AbstractFactory{  
      public CPUApi createCPUApi() {  
          return new IntelCPU(1156);  
      }  
      public MainboardApi createMainboardApi() {  
          return new GAMainboard(1156);  
      }    
}  

/** 
   * 裝機(jī)方案二:AMD的CPU + 微星的主板 
   * 這里創(chuàng)建CPU和主板對(duì)象的時(shí)候,是對(duì)應(yīng)的,能匹配上的 
   */  
public class Schema2 implements AbstractFactory{  
      public CPUApi createCPUApi() {  
          return new AMDCPU(939);  
      }  
      public MainboardApi createMainboardApi() {  
          return new MSIMainboard(939);  
      }    
}
  1. 再來看看裝機(jī)工程師類的實(shí)現(xiàn),在現(xiàn)在的實(shí)現(xiàn)里面,裝機(jī)工程師相當(dāng)于使用抽象工廠的客戶端,雖然是由真正的客戶來選擇和創(chuàng)建具體的工廠對(duì)象,但是使用抽象工廠的是裝機(jī)工程師對(duì)象。

裝機(jī)工程師類跟前面的實(shí)現(xiàn)相比,主要的變化是:從客戶端,不再傳入選擇CPU和主板的參數(shù),而是直接傳入客戶選擇并創(chuàng)建好的裝機(jī)方案對(duì)象。這樣就避免了單獨(dú)去選擇CPU和主板,客戶要選就是一套,就是一個(gè)系列。示例代碼如下:

/** 
   * 裝機(jī)工程師的類 
   */  
public  class ComputerEngineer {  
      /** 
       * 定義組裝機(jī)器需要的CPU 
       */  
      private CPUApi cpu= null;  
      /** 
       * 定義組裝機(jī)器需要的主板 
       */  
      private MainboardApi mainboard = null;  

      /** 
       * 裝機(jī)過程 
       * @param schema 客戶選擇的裝機(jī)方案 
       */  
      public void makeComputer(AbstractFactory schema){  
          //1:首先準(zhǔn)備好裝機(jī)所需要的配件  
          prepareHardwares(schema);  
          //2:組裝機(jī)器       
          //3:測(cè)試機(jī)器       
          //4:交付客戶  
      }  
      /** 
       * 準(zhǔn)備裝機(jī)所需要的配件 
       * @param schema 客戶選擇的裝機(jī)方案 
       */  
      private void prepareHardwares(AbstractFactory schema){  
          //這里要去準(zhǔn)備CPU和主板的具體實(shí)現(xiàn),為了示例簡單,這里只準(zhǔn)備這兩個(gè)  
          //可是,裝機(jī)工程師并不知道如何去創(chuàng)建,怎么辦呢?  

          //使用抽象工廠來獲取相應(yīng)的接口對(duì)象  
          this.cpu = schema.createCPUApi();  
          this.mainboard = schema.createMainboardApi();  

          //測(cè)試一下配件是否好用  
          this.cpu.calculate();  
          this.mainboard.installCPU();  
      }  
}  
  1. 都定義好了,看看客戶端如何使用抽象工廠,示例代碼如下:
public class Client {  
      public static void main(String[] args) {  
          //創(chuàng)建裝機(jī)工程師對(duì)象  
          ComputerEngineer engineer = new ComputerEngineer();  
          //客戶選擇并創(chuàng)建需要使用的裝機(jī)方案對(duì)象  
          AbstractFactory schema = new Schema1();  
          //告訴裝機(jī)工程師自己選擇的裝機(jī)方案,讓裝機(jī)工程師組裝電腦  
          engineer.makeComputer(schema);  
      }
}

如同前面的示例,定義了一個(gè)抽象工廠AbstractFactory,在里面定義了創(chuàng)建CPU和主板對(duì)象的接口的方法,但是在抽象工廠里面,并沒有指定具體的CPU和主板的實(shí)現(xiàn),也就是無須指定它們具體的實(shí)現(xiàn)類。

CPU和主板是相關(guān)的對(duì)象,是構(gòu)建電腦的一系列相關(guān)配件,這個(gè)抽象工廠就相當(dāng)于一個(gè)裝機(jī)方案,客戶選擇裝機(jī)方案的時(shí)候,一選就是一套,CPU和主板是確定好的,不讓客戶分開選擇,這就避免了出現(xiàn)不匹配的錯(cuò)誤。

3 模式講解#

3.1 認(rèn)識(shí)抽象工廠模式##

  1. 模式的功能

抽象工廠的功能是為一系列相關(guān)對(duì)象或相互依賴的對(duì)象創(chuàng)建一個(gè)接口,一定要注意,這個(gè)接口內(nèi)的方法不是任意堆砌的,而是一系列相關(guān)或相互依賴的方法,比如上面例子中的CPU和主板,都是為了組裝一臺(tái)電腦的相關(guān)對(duì)象。

從某種意義上看,抽象工廠其實(shí)是一個(gè)產(chǎn)品系列,或者是產(chǎn)品簇。上面例子中的抽象工廠就可以看成是電腦簇,每個(gè)不同的裝機(jī)方案,代表一種具體的電腦系列。

  1. 實(shí)現(xiàn)成接口

AbstractFactory在Java中通常實(shí)現(xiàn)成為接口,大家不要被名稱誤導(dǎo)了,以為是實(shí)現(xiàn)成為抽象類,當(dāng)然,如果需要為這個(gè)產(chǎn)品簇提供公共的功能,也不是不可以把AbstractFactory實(shí)現(xiàn)成為抽象類,但一般不這么做。

  1. 使用工廠方法

AbstractFactory定義了創(chuàng)建產(chǎn)品所需要的接口,具體的實(shí)現(xiàn)是在實(shí)現(xiàn)類里面,通常在實(shí)現(xiàn)類里面就需要選擇多種更具體的實(shí)現(xiàn),所以AbstractFactory定義的創(chuàng)建產(chǎn)品的方法可以看成是工廠方法,而這些工廠方法的具體實(shí)現(xiàn)就延遲到了具體的工廠里面。也就是說使用工廠方法來實(shí)現(xiàn)抽象工廠。

  1. 切換產(chǎn)品簇

由于抽象工廠定義的一系列對(duì)象,通常是相關(guān)或者相依賴的,這些產(chǎn)品對(duì)象就構(gòu)成了一個(gè)產(chǎn)品簇,也就是抽象工廠定義了一個(gè)產(chǎn)品簇。這就帶來非常大的靈活性,切換一個(gè)產(chǎn)品簇的時(shí)候,只要提供不同的抽象工廠實(shí)現(xiàn)就好了,也就是說現(xiàn)在是以產(chǎn)品簇做為一個(gè)整體被切換。

  1. 抽象工廠模式的調(diào)用順序示意圖
抽象工廠模式的調(diào)用順序示意圖

3.2 定義可擴(kuò)展的工廠##

在前面的示例中,抽象工廠為每一種它能創(chuàng)建的產(chǎn)品對(duì)象都定義了相應(yīng)的方法,比如創(chuàng)建CPU的方法和創(chuàng)建主板的方法等。

這種實(shí)現(xiàn)有一個(gè)麻煩,就是如果在產(chǎn)品簇中要新增加一種產(chǎn)品,比如現(xiàn)在要求抽象工廠除了能夠創(chuàng)建CPU和主板外,還要能夠創(chuàng)建內(nèi)存對(duì)象,那么就需要在抽象工廠里面添加創(chuàng)建內(nèi)存的這么一個(gè)方法。當(dāng)抽象工廠一發(fā)生變化,所有的具體工廠實(shí)現(xiàn)都要發(fā)生變化,這非常的不靈活。

現(xiàn)在有一種相對(duì)靈活,但是不太安全的改進(jìn)方式來解決這個(gè)問題,思路如下:抽象工廠里面不需要定義那么多方法,定義一個(gè)方法就可以了,給這個(gè)方法設(shè)置一個(gè)參數(shù),通過這個(gè)參數(shù)來判斷具體創(chuàng)建什么產(chǎn)品對(duì)象;由于只有一個(gè)方法,在返回類型上就不能是具體的某個(gè)產(chǎn)品類型了,只能是所有的產(chǎn)品對(duì)象都繼承或者實(shí)現(xiàn)的這么一個(gè)類型,比如讓所有的產(chǎn)品都實(shí)現(xiàn)某個(gè)接口,或者干脆使用Object類型。

還是看看代碼來體會(huì)一下,把前面那個(gè)示例改造成可擴(kuò)展的工廠實(shí)現(xiàn)。

  1. 先來改造抽象工廠,示例代碼如下:
/** 
   * 可擴(kuò)展的抽象工廠的接口 
   */  
public interface AbstractFactory {  
      /** 
       * 一個(gè)通用的創(chuàng)建產(chǎn)品對(duì)象的方法,為了簡單,直接返回Object 
       * 也可以為所有被創(chuàng)建的產(chǎn)品定義一個(gè)公共的接口 
       * @param type 具體創(chuàng)建的產(chǎn)品類型標(biāo)識(shí) 
       * @return 創(chuàng)建出的產(chǎn)品對(duì)象 
       */  
      public Object createProduct(int type);  
}

這里要特別注意傳入createProduct的參數(shù)所代表的含義,這個(gè)參數(shù)只是用來標(biāo)識(shí)現(xiàn)在是在創(chuàng)建什么類型的產(chǎn)品,比如標(biāo)識(shí)現(xiàn)在是創(chuàng)建CPU還是創(chuàng)建主板,一般這個(gè)type的含義到此就結(jié)束了,不再進(jìn)一步表示具體是什么樣的CPU或具體什么樣的主板,也就是說type不再表示具體是創(chuàng)建Intel的CPU還是創(chuàng)建AMD的CPU,這就是一個(gè)參數(shù)所代表的含義的深度的問題,要注意。雖然也可以延伸參數(shù)的含義到具體的實(shí)現(xiàn)上,但這不是可擴(kuò)展工廠這種設(shè)計(jì)方式的本意,一般也不這么做。

  1. CPU的接口和實(shí)現(xiàn),主板的接口和實(shí)現(xiàn)跟前面的示例是一樣的,就不再示范了。CPU還是分成Intel的CPU和AMD的CPU,主板還是分成技嘉的主板和微星的主板。

  2. 下面來提供具體的工廠實(shí)現(xiàn),也就是相當(dāng)于以前的裝機(jī)方案,先改造原來的方案一吧,現(xiàn)在的實(shí)現(xiàn)會(huì)有較大的變化,示例代碼如下:

/** 
   * 裝機(jī)方案一:Intel 的CPU + 技嘉的主板 
   * 這里創(chuàng)建CPU和主板對(duì)象的時(shí)候,是對(duì)應(yīng)的,能匹配上的 
   */  
public class Schema1 implements AbstractFactory{  
      public Object createProduct(int type) {  
          Object retObj = null;  
          //type為1表示創(chuàng)建CPU,type為2表示創(chuàng)建主板  
          if(type==1){  
              retObj = new IntelCPU(1156);  
          }else if(type==2){  
              retObj = new GAMainboard(1156);  
          }  
          return retObj;  
      }    
} 

/** 
   * 裝機(jī)方案二:AMD的CPU + 微星的主板 
   * 這里創(chuàng)建CPU和主板對(duì)象的時(shí)候,是對(duì)應(yīng)的,能匹配上的 
   */  
public class Schema2 implements AbstractFactory{  
      public Object createProduct(int type) {  
          Object retObj = null;  
          //type為1表示創(chuàng)建CPU,type為2表示創(chuàng)建主板  
          if(type==1){  
              retObj = new AMDCPU(939);  
          }else if(type==2){  
              retObj = new MSIMainboard(939);  
          }  
          return retObj;  
      }    
}
  1. 看看這個(gè)時(shí)候使用抽象工廠的客戶端實(shí)現(xiàn),也就是在裝機(jī)工程師類里面,通過抽象工廠來獲取相應(yīng)的配件產(chǎn)品對(duì)象,示例代碼如下:
public  class ComputerEngineer {  
      private CPUApi cpu= null;  
      private MainboardApi mainboard = null;  
      public void makeComputer(AbstractFactory schema){  
          prepareHardwares(schema);  
      }  
      private void prepareHardwares(AbstractFactory schema){  
          //這里要去準(zhǔn)備CPU和主板的具體實(shí)現(xiàn),為了示例簡單,這里只準(zhǔn)備這兩個(gè)  
          //可是,裝機(jī)工程師并不知道如何去創(chuàng)建,怎么辦呢?  

          //使用抽象工廠來獲取相應(yīng)的接口對(duì)象  
          this.cpu = (CPUApi)schema.createProduct(1);  
          this.mainboard = (MainboardApi)schema.createProduct(2);  

          //測(cè)試一下配件是否好用  
          this.cpu.calculate();  
          this.mainboard.installCPU();  
      }  
}  

通過上面的示例,能看到可擴(kuò)展工廠的基本實(shí)現(xiàn)。從客戶端的代碼會(huì)發(fā)現(xiàn),為什么說這種方式是不太安全的呢?

你會(huì)發(fā)現(xiàn)創(chuàng)建產(chǎn)品對(duì)象返回來過后,需要造型成為具體的對(duì)象,因?yàn)榉祷氐氖荗bject,如果這個(gè)時(shí)候沒有匹配上,比如返回的不是CPU對(duì)象,但是要強(qiáng)制造型成為CPU,那么就會(huì)發(fā)生錯(cuò)誤,因此這種實(shí)現(xiàn)方式的一個(gè)潛在缺點(diǎn)就是不太安全。

  1. 接下來,體會(huì)一下這種方式的靈活性:

假如現(xiàn)在要加入一個(gè)新的產(chǎn)品——內(nèi)存,當(dāng)然可以提供一個(gè)新的裝機(jī)方案來使用它,這樣已有的代碼就不需要變化了。

先看看內(nèi)存的接口吧,示例代碼如下:

/** 
   * 內(nèi)存的接口 
   */  
public interface MemoryApi {  
      /** 
       * 示意方法,內(nèi)存具有緩存數(shù)據(jù)的能力 
       */  
      public void cacheData();  
} 

提供一個(gè)現(xiàn)代內(nèi)存的基本實(shí)現(xiàn),示例代碼如下:

/** 
   * 現(xiàn)代內(nèi)存的類 
   */  
public class HyMemory implements MemoryApi{  
      public void cacheData() {  
          System.out.println("現(xiàn)在正在使用現(xiàn)代內(nèi)存");  
      }  
}  

現(xiàn)在想要使用這個(gè)新加入的產(chǎn)品,以前實(shí)現(xiàn)的代碼都不用變化,只需新添加一個(gè)方案,在這個(gè)方案里面使用新的產(chǎn)品,然后客戶端使用這個(gè)新的方案即可,示例代碼如下:

/** 
   * 裝機(jī)方案三:Intel 的CPU + 技嘉的主板 + 現(xiàn)代的內(nèi)存 
   */  
public class Scheme3 implements AbstractFactory{  
      public Object createProduct(int type) {  
          Object retObj = null;  
          //type為1表示創(chuàng)建CPU,type為2表示創(chuàng)建主板,type為3表示創(chuàng)建內(nèi)存  
          if(type==1){  
              retObj = new IntelCPU(1156);  
          }else if(type==2){  
              retObj = new GAMainboard(1156);  
          }  
          //創(chuàng)建新添加的產(chǎn)品  
          else if(type==3){  
              retObj = new HyMemory();  
          }  
          return retObj;  
      }  
}  

這個(gè)時(shí)候的裝機(jī)工程師類,如果要?jiǎng)?chuàng)建帶內(nèi)存的機(jī)器,需要在裝機(jī)工程師類里面添加對(duì)內(nèi)存的使用,示例代碼如下:

public  class ComputerEngineer {  
      private CPUApi cpu= null;  
      private MainboardApi mainboard = null;  
      /** 
       * 定義組裝機(jī)器需要的內(nèi)存 
       */  
      private MemoryApi memory = null;  
      public void makeComputer(AbstractFactory schema){  
          prepareHardwares(schema);  
      }  
      private void prepareHardwares(AbstractFactory schema){  
          //使用抽象工廠來獲取相應(yīng)的接口對(duì)象  
          this.cpu = (CPUApi)schema.createProduct(1);  
          this.mainboard = (MainboardApi)schema.createProduct(2);  
          this.memory = (MemoryApi)schema.createProduct(3);  

          //測(cè)試一下配件是否好用  
          this.cpu.calculate();  
          this.mainboard.installCPU();  
          if(memory!=null){  
              this.memory.cacheData();  
          }  
      }  
}  

3.3 抽象工廠模式和DAO##

  1. 首先來看看什么是DAO

DAO:數(shù)據(jù)訪問對(duì)象,是Data Access Object首字母的簡寫。

DAO是JEE(也稱JavaEE,原J2EE)中的一個(gè)標(biāo)準(zhǔn)模式,通過它來解決訪問數(shù)據(jù)對(duì)象所面臨的一系列問題,比如:數(shù)據(jù)源不同、存儲(chǔ)類型不同、訪問方式不同、供應(yīng)商不同、版本不同等等,這些不同會(huì)造成訪問數(shù)據(jù)的實(shí)現(xiàn)上差別很大。

數(shù)據(jù)源的不同,比如存放于數(shù)據(jù)庫的數(shù)據(jù)源,存放于LDAP(輕型目錄訪問協(xié)議)的數(shù)據(jù)源;又比如存放于本地的數(shù)據(jù)源和遠(yuǎn)程服務(wù)器上的數(shù)據(jù)源等等

存儲(chǔ)類型的不同,比如關(guān)系型數(shù)據(jù)庫(RDBMS)、面向?qū)ο髷?shù)據(jù)庫(ODBMS)、純文件、XML等等

訪問方式的不同,比如訪問關(guān)系型數(shù)據(jù)庫,可以用JDBC、EntityBean、JPA等來實(shí)現(xiàn),當(dāng)然也可以采用一些流行的框架,如Hibernate、IBatis等等

供應(yīng)商的不同,比如關(guān)系型數(shù)據(jù)庫,流行如Oracel、DB2、SqlServer、MySql等等,它們的供應(yīng)商是不同的

版本不同,比如關(guān)系型數(shù)據(jù)庫,不同的版本,實(shí)現(xiàn)的功能是有差異的,就算是對(duì)標(biāo)準(zhǔn)的SQL的支持,也是有差異的

但是對(duì)于需要進(jìn)行數(shù)據(jù)訪問的邏輯層而言,它可不想面對(duì)這么多不同,也不想處理這么多差異,它希望能以一個(gè)統(tǒng)一的方式來訪問數(shù)據(jù)。此時(shí)系統(tǒng)結(jié)構(gòu)如圖所示:

系統(tǒng)結(jié)構(gòu)如圖

也就是說,DAO需要抽象和封裝所有對(duì)數(shù)據(jù)的訪問,DAO承擔(dān)和數(shù)據(jù)倉庫交互的職責(zé),這也意味著,訪問數(shù)據(jù)所面臨的所有問題,都需要DAO在內(nèi)部來自行解決。

  1. DAO和抽象工廠的關(guān)系

事實(shí)上,在實(shí)現(xiàn)DAO模式的時(shí)候,最常見的實(shí)現(xiàn)策略就是使用工廠的策略,而且多是通過抽象工廠模式來實(shí)現(xiàn),當(dāng)然在使用抽象工廠模式來實(shí)現(xiàn)的時(shí)候,可以結(jié)合工廠方法模式。因此DAO模式和抽象工廠模式有很大的聯(lián)系。

  1. DAO模式的工廠實(shí)現(xiàn)策略

(1)采用工廠方法模式

假如現(xiàn)在在一個(gè)訂單處理的模塊里面,大家都知道,訂單通常又分成兩個(gè)部分,一個(gè)部分是訂單主記錄或者是訂單主表,另一個(gè)部分是訂單明細(xì)記錄或者是訂單子表,那么現(xiàn)在業(yè)務(wù)對(duì)象需要操作訂單的主記錄,也需要操作訂單的子記錄。

如果這個(gè)時(shí)候的業(yè)務(wù)比較簡單,而且對(duì)數(shù)據(jù)的操作是固定的,比如就是操作數(shù)據(jù)庫,不管訂單的業(yè)務(wù)如何變化,底層數(shù)據(jù)存儲(chǔ)都是一樣的,那么這種情況下,可以采用工廠方法模式,此時(shí)系統(tǒng)結(jié)構(gòu)如圖所示:

系統(tǒng)結(jié)構(gòu)如圖

從上面的結(jié)構(gòu)示意圖可以看出,如果底層存儲(chǔ)固定的時(shí)候,DAOFactory就相當(dāng)于工廠方法模式中的Creator,在里面定義兩個(gè)工廠方法,分別創(chuàng)建訂單主記錄的DAO對(duì)象和創(chuàng)建訂單子記錄的DAO對(duì)象,因?yàn)楣潭ㄊ菙?shù)據(jù)庫實(shí)現(xiàn),因此提供一個(gè)具體的工廠RdbDAOFactory(Rdb:關(guān)系型數(shù)據(jù)庫),來實(shí)現(xiàn)對(duì)象的創(chuàng)建。也就是說DAO可以采用工廠方法模式來實(shí)現(xiàn)。

采用工廠方法模式的情況,要求DAO底層存儲(chǔ)實(shí)現(xiàn)方式是固定的,這種多用在一些簡單的小項(xiàng)目開發(fā)上。

(2)采用抽象工廠模式

實(shí)際上更多的時(shí)候,DAO底層存儲(chǔ)實(shí)現(xiàn)方式是不固定的,DAO通常會(huì)支持多種存儲(chǔ)實(shí)現(xiàn)方式,具體使用哪一種存儲(chǔ)方式可能是由應(yīng)用動(dòng)態(tài)決定,或者是通過配置來指定。這種情況多見于產(chǎn)品開發(fā)、或者是稍復(fù)雜的應(yīng)用、或者是較大的項(xiàng)目中。

對(duì)于底層存儲(chǔ)方式不固定的時(shí)候,一般是采用抽象工廠模式來實(shí)現(xiàn)DAO。比如現(xiàn)在的實(shí)現(xiàn)除了RDB的實(shí)現(xiàn),還會(huì)有Xml的實(shí)現(xiàn),它們會(huì)被應(yīng)用動(dòng)態(tài)的選擇,此時(shí)系統(tǒng)結(jié)構(gòu)如圖所示:

系統(tǒng)結(jié)構(gòu)如圖

從上面的結(jié)構(gòu)示意圖可以看出,采用抽象工廠模式來實(shí)現(xiàn)DAO的時(shí)候,DAOFactory就相當(dāng)于抽象工廠,里面定義一系列創(chuàng)建相關(guān)對(duì)象的方法,分別是創(chuàng)建訂單主記錄的DAO對(duì)象和創(chuàng)建訂單子記錄的DAO對(duì)象,此時(shí)OrderMainDAO和OrderDetailDAO就相當(dāng)于被創(chuàng)建的產(chǎn)品,RdbDAOFactory和XmlDAOFactory就相當(dāng)于抽象工廠的具體實(shí)現(xiàn),在它們里面會(huì)選擇相應(yīng)的具體的產(chǎn)品實(shí)現(xiàn)來創(chuàng)建對(duì)象。

  1. 代碼示例使用抽象工廠實(shí)現(xiàn)DAO模式

(1)先看看抽象工廠的代碼實(shí)現(xiàn),示例代碼如下:

/** 
   * 抽象工廠,創(chuàng)建訂單主、子記錄對(duì)應(yīng)的DAO對(duì)象 
   */  
public abstract class DAOFactory {  
      /** 
       * 創(chuàng)建訂單主記錄對(duì)應(yīng)的DAO對(duì)象 
       * @return 訂單主記錄對(duì)應(yīng)的DAO對(duì)象 
       */  
      public abstract OrderMainDAO createOrderMainDAO();  
      /** 
       * 創(chuàng)建訂單子記錄對(duì)應(yīng)的DAO對(duì)象 
       * @return 訂單子記錄對(duì)應(yīng)的DAO對(duì)象 
       */  
      public abstract OrderDetailDAO createOrderDetailDAO();  
}  

(2)看看產(chǎn)品對(duì)象的接口,就是訂單主、子記錄的DAO定義,先看訂單主記錄的DAO定義,示例代碼如下:

/** 
   * 訂單主記錄對(duì)應(yīng)的DAO操作接口 
   */  
public interface OrderMainDAO {  
      /** 
       * 示意方法,保存訂單主記錄 
       */  
      public void saveOrderMain();  
}

再看看訂單子記錄的DAO定義,示例代碼如下:

/** 
   * 訂單子記錄對(duì)應(yīng)的DAO操作接口 
   */  
public interface OrderDetailDAO {  
      /** 
       * 示意方法,保存訂單子記錄 
       */  
      public void saveOrderDetail();  
}

(3)接下來實(shí)現(xiàn)訂單主、子記錄的DAO,先看關(guān)系型數(shù)據(jù)庫的實(shí)現(xiàn)方式,示例代碼如下:

public class RdbMainDAOImpl implements OrderMainDAO{  
      public void saveOrderMain() {  
          System.out.println("now in RdbMainDAOImpl saveOrderMain");  
      }  
}  

public class RdbDetailDAOImpl implements OrderDetailDAO{  
      public void saveOrderDetail() {  
          System.out.println("now in RdbDetailDAOImpl saveOrderDetail");  
      }  
}  

Xml實(shí)現(xiàn)的方式一樣,為了演示簡單,都是輸出了一句話,示例代碼如下:

public class XmlMainDAOImpl implements OrderMainDAO{  
      public void saveOrderMain() {  
          System.out.println("now in XmlMainDAOImpl saveOrderMain");  
      }  
}  

(4)再看看具體的工廠實(shí)現(xiàn),先看關(guān)系型數(shù)據(jù)庫實(shí)現(xiàn)方式的工廠,示例代碼如下:

public class RdbDAOFactory extends DAOFactory{  
      public OrderDetailDAO createOrderDetailDAO() {  
          return new RdbDetailDAOImpl();  
      }  
      public OrderMainDAO createOrderMainDAO() {  
          return new RdbMainDAOImpl();  
      }  
}  

Xml實(shí)現(xiàn)方式的工廠,示例代碼如下:

public class XmlDAOFactory extends DAOFactory {  
      public OrderDetailDAO createOrderDetailDAO() {  
          return new XmlDetailDAOImpl();  
      }  
      public OrderMainDAO createOrderMainDAO() {  
          return new XmlMainDAOImpl();  
      }  
}  

(5)好了,使用抽象工廠來簡單的實(shí)現(xiàn)了DAO模式,那么在客戶端,通常是由業(yè)務(wù)對(duì)象來調(diào)用DAO,那么該怎么使用這個(gè)DAO呢?示例代碼如下:

public class BusinessObject {  
      public static void main(String[] args) {  
          //創(chuàng)建DAO的抽象工廠  
          DAOFactory df = new RdbDAOFactory();  
          //通過抽象工廠來獲取需要的DAO接口  
          OrderMainDAO mainDAO = df.createOrderMainDAO();  
          OrderDetailDAO detailDAO = df.createOrderDetailDAO();  
          //調(diào)用DAO來完成數(shù)據(jù)存儲(chǔ)的功能  
          mainDAO.saveOrderMain();  
          detailDAO.saveOrderDetail();  
      }  
}

通過上面的示例,可以看出DAO可以采用抽象工廠模式來實(shí)現(xiàn),這也是大部分DAO實(shí)現(xiàn)采用的方式。

3.4 模式的優(yōu)缺點(diǎn)##

  1. 分離接口和實(shí)現(xiàn)

客戶端使用抽象工廠來創(chuàng)建需要的對(duì)象,而客戶端根本就不知道具體的實(shí)現(xiàn)是誰,客戶端只是面向產(chǎn)品的接口編程而已,也就是說,客戶端從具體的產(chǎn)品實(shí)現(xiàn)中解耦。

  1. 使得切換產(chǎn)品簇變得容易

因?yàn)橐粋€(gè)具體的工廠實(shí)現(xiàn)代表的是一個(gè)產(chǎn)品簇,比如上面例子的Scheme1代表裝機(jī)方案一:Intel 的CPU + 技嘉的主板,如果要切換成為Scheme2,那就變成了裝機(jī)方案二:AMD的CPU + 微星的主板。

客戶端選用不同的工廠實(shí)現(xiàn),就相當(dāng)于是在切換不同的產(chǎn)品簇。

  1. 不太容易擴(kuò)展新的產(chǎn)品

前面也提到這個(gè)問題了,如果需要給整個(gè)產(chǎn)品簇添加一個(gè)新的產(chǎn)品,那么就需要修改抽象工廠,這樣就會(huì)導(dǎo)致修改所有的工廠實(shí)現(xiàn)類。在前面提供了一個(gè)可以擴(kuò)展工廠的方式來解決這個(gè)問題,但是又不夠安全。如何選擇,根據(jù)實(shí)際應(yīng)用來權(quán)衡吧。

  1. 容易造成類層次復(fù)雜

在使用抽象工廠模式的時(shí)候,如果需要選擇的層次過多,那么會(huì)造成整個(gè)類層次變得復(fù)雜。

舉個(gè)例子來說,就比如前面講到的那個(gè)DAO的示例,現(xiàn)在這個(gè)DAO只有一個(gè)選擇的層次,也就是選擇一下是使用關(guān)系型數(shù)據(jù)庫來實(shí)現(xiàn),還是用Xml來實(shí)現(xiàn)。現(xiàn)在考慮這樣一種情況,如果關(guān)系型數(shù)據(jù)庫實(shí)現(xiàn)里面又分成幾種,比如:基于Oracle的實(shí)現(xiàn),基于SqlServer的實(shí)現(xiàn),基于MySql的實(shí)現(xiàn)等等。

那么客戶端怎么選呢?不會(huì)把所有可能的實(shí)現(xiàn)情況全部都做到一個(gè)層次上吧,這個(gè)時(shí)候客戶端就需要一層一層選擇,也就是整個(gè)抽象工廠的實(shí)現(xiàn)也需要分出層次來,每一層負(fù)責(zé)一種選擇,也就是一層屏蔽一種變化,這樣很容易造成復(fù)雜的類層次結(jié)構(gòu)。

3.5 思考抽象工廠模式##

  1. 抽象工廠模式的本質(zhì)

抽象工廠模式的本質(zhì):選擇產(chǎn)品簇的實(shí)現(xiàn)。

工廠方法是選擇單個(gè)產(chǎn)品的實(shí)現(xiàn),雖然一個(gè)類里面可以有多個(gè)工廠方法,但是這些方法之間一般是沒有聯(lián)系的,即使看起來像有聯(lián)系

但是抽象工廠著重的就是為一個(gè)產(chǎn)品簇選擇實(shí)現(xiàn),定義在抽象工廠里面的方法通常是有聯(lián)系的,它們都是產(chǎn)品的某一部分或者是相互依賴的。如果抽象工廠里面只定義一個(gè)方法,直接創(chuàng)建產(chǎn)品,那么就退化成為工廠方法了。

  1. 何時(shí)選用抽象工廠模式

建議在如下情況中,選用抽象工廠模式:

如果希望一個(gè)系統(tǒng)獨(dú)立于它的產(chǎn)品的創(chuàng)建,組合和表示的時(shí)候,換句話說,希望一個(gè)系統(tǒng)只是知道產(chǎn)品的接口,而不關(guān)心實(shí)現(xiàn)的時(shí)候。

如果一個(gè)系統(tǒng)要由多個(gè)產(chǎn)品系列中的一個(gè)來配置的時(shí)候,換句話說,就是可以動(dòng)態(tài)的切換產(chǎn)品簇的時(shí)候。

如果要強(qiáng)調(diào)一系列相關(guān)產(chǎn)品的接口,以便聯(lián)合使用它們的時(shí)候。

3.6 相關(guān)模式##

  1. 抽象工廠模式和工廠方法模式

這兩個(gè)模式既有區(qū)別,又有聯(lián)系,可以組合使用。

工廠方法模式一般是針對(duì)單獨(dú)的產(chǎn)品對(duì)象的創(chuàng)建,而抽象工廠模式注重產(chǎn)品簇對(duì)象的創(chuàng)建,這是它們的區(qū)別。

如果把抽象工廠創(chuàng)建的產(chǎn)品簇簡化,這個(gè)產(chǎn)品簇就只有一個(gè)產(chǎn)品,那么這個(gè)時(shí)候的抽象工廠跟工廠方法是差不多的,也就是抽象工廠可以退化成工廠方法,而工廠方法又可以退化成簡單工廠,這是它們的聯(lián)系

在抽象工廠的實(shí)現(xiàn)中,還可以使用工廠方法來提供抽象工廠的具體實(shí)現(xiàn),也就是說它們可以組合使用。

  1. 抽象工廠模式和單例模式

這兩個(gè)模式可以組合使用。

在抽象工廠模式里面,具體的工廠實(shí)現(xiàn),在整個(gè)應(yīng)用中,通常一個(gè)產(chǎn)品系列只需要一個(gè)實(shí)例就可以了,因此可以把具體的工廠實(shí)現(xiàn)成為單例。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 1.初識(shí)抽象工廠模式 提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴對(duì)象的接口,而無需指定它們具體的類。 AbstractFac...
    王偵閱讀 421評(píng)論 0 0
  • 【學(xué)習(xí)難度:★★★★☆,使用頻率:★★★★★】直接出處:抽象工廠模式梳理和學(xué)習(xí):https://github.co...
    BruceOuyang閱讀 599評(píng)論 0 1
  • 一、場(chǎng)景 舉個(gè)生活中常見的例子——組裝電腦,我們?cè)诮M裝電腦的時(shí)候,通常需要選擇一系列的配件,比如CPU、硬盤、內(nèi)存...
    KwongRay閱讀 362評(píng)論 0 0
  • 場(chǎng)景問題 舉個(gè)生活中常見的例子——組裝電腦,我們?cè)诮M裝電腦的時(shí)候,通常需要選擇一系列的配件,比如CPU、硬盤、內(nèi)存...
    碼出高效閱讀 414評(píng)論 0 0
  • 新年愿望 5歲那年你希望過年那天有吃不完的糖,拿到心儀的玩具,還有放不完的炮仗。 14歲那年你禱告能走歡喜的姑娘心...
    豪生閱讀 237評(píng)論 0 3