設計模式——創建型模式(工廠,簡單工廠,單例,建造者,原型)

  • 2019 年 10 月 16 日
  • 筆記

創建型模式對類的實例化過程進行了抽象,能夠將軟件模塊中對象的創建和對象的使用分離

為了使軟件的結構更加清晰,外界對於這些對象只需要知道它們共同的接口,而不清楚其具體的實現細節,使整個系統的設計更加符合單一職責原則

創建型模式隱藏了類的實例的創建細節,通過隱藏對象如何被創建、如何組合在一起來實現使整個系統獨立的目的

一、工廠模式

簡單工廠模式

1、楔子

某暴發戶有若干輛車,每輛車品牌都不同(如奔馳、寶馬、奧迪),這些車來自同一個父類,在繼承父類後不同的子類修改了部分屬性從而使得它們產自不同的品牌

如果司機希望暴發戶坐車時,不需要知道這些具體車輛類的名字,只需要知道表示該車輛類的一個參數;同時提供一個方便調用的方法,只要暴發戶把參數傳入方法就能得到一個相應的車輛對象,這時就能使用簡單工廠模式

2、解析

又稱靜態工廠方法模式

在簡單工廠模式中,可以根據不同參數返回不同的類的實例

該模式專門定義一個類來負責創建其他類的實例,被創建的實例通常都具有共同的父類

簡單工廠模式存在的目的是定義一個用於創建對象的接口

模式組成有:

  1. 工廠類角色:簡單工廠模式的核心,含有一定的商業邏輯和判斷邏輯。在 Java中由具體類實現
  2. 抽象產品角色:一般是具體產品繼承的父類或實現的接口。在 Java中由接口或抽象類實現
  3. 具體產品角色:工廠類創建的對象就是本類的實例

3、舉例

若這個暴發戶有三輛車,Benz、Bmw、Audi。每次坐車時說話都很奇怪:坐 Benz時說「開奔馳車!」,坐 Bmw時說「開寶馬車!」,坐 Audi時說「開奧迪車!」。你肯定會覺得這人有病,直接說開車不就行了?

現在用簡單工廠模式改造暴發戶的坐車方式——暴發戶只需跟司機說「奔馳 / 寶馬 / 奧迪」就行了

//抽象產品角色  interface Car {      public void drive();  }    //具體產品角色  class Benz implements Car {      public void drive() {          System.out.println("開奔馳!");      }  }  class Bmw implements Car {      public void drive() {          System.out.println("開寶馬!");      }  }  class Audi implements Car {      public void drive() {          System.out.println("開奧迪!");      }  }    //工廠類角色  class Driver {      //工廠方法,注意返回類型為抽象產品角色      public static Car driverCar(String s) throws Exception {          //判斷邏輯,返回具體的產品角色給 Client          if(s.equalsIgnoreCase("Benz"))              return new Benz();          else if(s.equalsIgnoreCase("Bmw"))              return new Bmw();          else if(s.equalsIgnoreCase("Audi"))              return new Audi();          else throw new Exception();          ...      }  }    //歡迎暴發戶登場  public class Magnate {      public static void main(String[] args) {          try {              //告訴司機今天坐奔馳              Car car = Driver.driverCar("Benz");              //下令開車              car.drive();              ...          }      }  }

工廠方法模式

1、楔子

從開閉原則分析簡單工廠模式,暴發戶增加一輛車時,只要符合抽象產品指定合同,再通知工廠類即可使用,對產品部分而言符合開閉原則

但是對工廠類貌似不太理想,每增加一輛車,工廠類就要增加相應的業務邏輯或判斷邏輯,顯然違背了開閉原則。新產品的加入使工廠類非常被動,這種工廠類稱為全能類或上帝類

實際應用中,產品可能是一個多層次的樹狀結構,簡單工廠模式中只有一個工廠類,難以應付多種情況,於是出現了工廠方法模式

2、解析

工廠方法模式去掉了簡單工廠模式中工廠類的靜態屬性,使得它可以被子類繼承。這樣簡單工廠模式中集中在工廠方法上的壓力可由工廠方法模式中的不同類分擔

模式組成:

  1. 抽象工廠角色:工廠方法模式的核心,是具體工廠角色必須實現的接口或必須繼承的父類。與應用程序無關。在 Java中由抽象類或者接口實現
  2. 具體工廠角色:含和具體業務邏輯有關的代碼。由應用程序調用以創建對應的具體產品的對象
  3. 抽象產品角色:具體產品繼承的父類或實現的接口。在 Java中由抽象類或接口實現
  4. 具體產品角色:具體工廠角色創建的對象就是此角色的實例

工廠方法模式使用繼承自抽象工廠角色的多個子類來代替簡單工廠模式中的上帝類,以此分擔對象承受的壓力

適用工廠方法模式的情景:

  1. 客戶程序不需要知道要使用對象的創建過程
  2. 客戶程序使用的對象存在變動的可能,或者根本不知道使用哪個具體的對象

3、舉例

話說暴發戶生意越做越大,愛車越來越多。司機感覺很卑微,所有車他都要記住、維護,都必須經過他來使用。某天暴發戶良心發現,告訴司機「今後你不用這麼辛苦了,我給你安排了幾個人手,你只用管理他們就行了」

//抽象產品角色、具體產品角色與簡單工廠模式類似,這裡略    //抽象工廠角色  interface Driver {      public Car driverCar();  }  class BenzDriver implements Driver {      public Car driverCar() {          return new Benz();      }  }  class BmwDriver implements Driver {      public Car driverCar() {          return new Bmw();      }  }  class AudiDriver implements Driver {      public Car driverCar() {          return new Audi();      }  }    //和具體產品形成對應關係    //暴發戶登場  public class Magnate {      public static void main(String[] args) {          try {              Driver driver = new BenzDriver();              Car car = driver.driverCar();              car.drive();          }          ...      }  }

工廠方法模式雖然解決了簡單工廠模式的一些不足,但使對象的數量成倍增長,當產品種類很多時會出現大量對應的工廠對象,所以使用者可根據自身情況結合使用兩種工廠模式

二、抽象工廠模式

產品族:位於不同產品等級結構中,但功能相關聯的產品組成的家族

K9khZj.png

圖中 BmwCar和 BenzCar是兩個產品樹(產品層次結構)。而 BenzSportsCar和 BmwSportsCar就是一個產品族,它們都屬於跑車家族

1、解析

抽象工廠模式和工廠方法模式的區別在於創建對象的複雜度上,前者是三種工廠模式中最抽象、最具一般性的

抽象工廠模式目的:為客戶端提供一個接口,可以創建多個產品族中的產品對象

使用抽象工廠模式要滿足以下條件:

  1. 系統中有多個產品族,但系統一次只能消費其中一族產品
  2. 屬於同一產品族的產品一起使用

模式組成:(和工廠方法模式如出一轍)

  1. 抽象工廠角色:工廠方法模式的核心,是具體工廠角色必須實現的接口或必須繼承的父類。與應用程序無關。在 Java中由抽象類或者接口實現
  2. 具體工廠角色:含和具體業務邏輯有關的代碼。由應用程序調用以創建對應的具體產品的對象
  3. 抽象產品角色:具體產品繼承的父類或實現的接口。在 Java中由抽象類或接口實現
  4. 具體產品角色:具體工廠角色創建的對象就是此角色的實例

2、舉例

//抽象產品角色  abstract class Video {      public abstract void produce();  }    //具體產品角色  class PythonVideo extends Video {      public void produce() {          System.out.println("Python課程視頻");      }  }  class JavaVideo extends Video {      public void produce() {          System.out.println("錄製Java課程視頻");      }  }    //抽象工廠角色  interface CourseFactory {      Video getVideo();      Article getArticle();  }    //具體工廠角色  class JavaCourseFactory implements CourseFactory {      public Video getVideo() {          return new JavaVideo();      }      public Article getArticle() {          return new JavaArticle();      }  }  class PythonCourseFactory implements CourseFactory {      public Video getVideo() {          return new PythonVideo();      }      public Article getArticle() {          return new PythonArticle();      }  }

三、單例模式

單例模式在各種開源框架、應用系統中多有應用

1、解析

又稱單態模式或單件模式

單例模式保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。其目的是控制特定的類只產生一個對象,當然特殊情況下允許靈活改變對象的個數

實現單例模式的方法:將構造函數私有化(至少是受保護的),使得外面的類不能通過引用產生對象。用戶通過調用類方法得到類的對象

單例模式分為有狀態和無狀態。有狀態的單例對象一般是可變單例對象,多個對象在一起可以作為一個狀態倉庫向外提供服務;沒有狀態的單例對象一般是不可變單例對象,僅用作提供工具函數

2、舉例

//餓漢式  public class Singleton {      //在自己內部定義自己的一個實例。且是 private類型,只供內部使用      private static Singleton instance = new Singleton();        //再將構造函數私有化      private Singleton() {}        //靜態工廠方法,提供一個供外部訪問得到對象的方法      public static Singleton getIntance() {          return instance;      }  }
//懶漢式  public class Singleton {      //注意不同點      private static Singleton instance = null;        //再將構造函數私有化      private Singleton() {}        //靜態工廠方法,提供一個供外部訪問得到對象的方法      public static synchronized Singleton getIntance() {          if(null == instance) {              instance = new Singleton();          }          return instance;      }  }

比較兩種實現方式:

  • 兩者的構造函數都是私有的,斷開了通過構造函數獲得實例的渠道,同時失去了類的多態性
  • 後者對靜態工廠方法進行了同步處理,防止多線程環境產生多個實例,而前者不存在這種情況
  • 前者在類的加載時實例化,導致多次加載造成多次實例化,後者將類對自己的實例化延遲到第一次被引用,但因為同步處理的原因降低了反應速度

上述兩種方式均失去了多態性,不允許被繼承。靈活點的實現是,將構造函數設置為受保護的,這樣允許被繼承產生子類。新方法在具體實現上有所不同,可以將父類中獲得對象的靜態方法放到子類中再實現,也可以在父類的靜態方法中進行條件判斷來決定獲得哪個對象

// GOF認為最好的方式是維護一張存有對象和對應名稱的註冊表(可用 HashMap實現  import java.util.HashMap;    class Singleton { //父類      //存放對應關係      private static HashMap registry = new HashMap();      static private Singleton s = new Singleton();        //受保護的構造函數      protected Singleton() {}      public static Singleton getInstance(String name) {          if(name == null) {              name == "Singleton";          }          if(registry.get(name) == null) {              try {                  registry.put(name, Class.forName(name).newInstance());              } catch(Exception e) {                  e.printStackTrace();              }          }          return (Singleton)registry.get(name);      }      public void test() {          System.out.println("Success!");      }  }    class SingletonChild extends Singleton { //子類      public SingletonChild() {}      public static SingletonChild getInstance() {          return (SingletonChild)Singleton.getInstance("SingletonChild");      }      public void test() {          System.out.println("Success Again!");      }  }

由於 Java中子類構造函數的範圍不能比父類的小,所以可能有不守規則的客戶程序使用其構造函數產生實例,導致單例模式失效

四、建造者模式

1、楔子

在電腦裝配工廠中,有很多工人在熟練的裝機。他們不管用戶使用的 CPU是 Intel還是 AMD,也不管顯卡是上千的還是白送的,都能三下五除二迅速組裝起來——一台 PC就這麼誕生了。對於客戶而言,並不清楚太多關於 PC組裝的細節,這和建造者模式十分相似

2、解析

建造者模式將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示

創建者模式隱藏了複雜對象的創建過程,它把複雜對象的創建過程加以抽象,通過子類繼承或者重載的方式,動態的創建具有複合屬性的對象。

建造者模式一步一步創建一個複雜的對象,它允許用戶只通過指定複雜對象的類型和內容就可以構建它們,而不需要知道內部的具體構建細節

模式組成:

  1. 抽象建造者角色:規範產品對象各個組成部分的建造。一般獨立於應用程序的業務邏輯
  2. 具體建造者角色:與應用程序緊密相關的類,在指導者的調用下創建產品實例。該角色在實現抽象建造者角色提供方法的前提下完成產品組裝
  3. 指導者角色:調用具體建造者角色創建產品對象
  4. 產品角色:建造的複雜對象。包括定義組件的類和將這些組件裝配成產品的接口

適用場景:

  • 隔離複雜對象的創建和使用,相同的方法,不同執行順序,產生不同事件結果
  • 多個部件都可以裝配到一個對象中,但產生的運行結果不相同
  • 產品類非常複雜或者產品類因為調用順序不同而產生不同作用
  • 初始化一個對象時,參數過多,或者很多參數具有默認值
  • Builder模式不適合創建差異性很大的產品類。產品內部變化複雜,會導致需要定義很多具體建造者類實現變化,增加項目中類的數量,增加系統的理解難度和運行成本
  • 需要生成的產品對象有複雜的內部結構,這些產品對象具備共性

3、舉例

下面我們使用建造者模式來構造共享單車

//產品類  class Bike {      private IFrame frame;      private ISeat seat;      private ITire tire;        public IFrame getFrame() {          return frame;      }      public void setFrame(IFrame frame) {          this.frame = frame;      }      public ISeat getSeat() {          return seat;      }      public void setSeat(ISeat seat) {          this.seat = seat;      }      public ITire getTire() {          return tire;      }      public void setTire(ITire tire) {          this.tire = tire;      }  }    //抽象建造者類  abstract class Builder {      abstract void buildFrame();      abstract void buildSeat();      abstract void buildTire();      abstract Bike createBike();  }  //具體建造者類  class MobikeBuilder extends Builder{ //摩拜單車      private Bike mBike = new Bike();        void buildFrame() {          mBike.setFrame(new AlloyFrame());      }      void buildSeat() {          mBike.setSeat(new DermisSeat());      }      void buildTire() {          mBike.setTire(new SolidTire());      }      Bike createBike() {          return mBike;      }  }  class OfoBuilder extends Builder{ //OFO小黃車      private Bike oBike = new Bike();        void buildFrame() {          oBike.setFrame(new CarbonFrame());      }      void buildSeat() {          oBike.setSeat(new RubberSeat());      }      void buildTire() {          oBike.setTire(new InflateTire());      }      Bike createBike() {          return oBike;      }  }    //指揮者類  class Director {      private Builder mBuilder = null;        public Director(Builder builder) {          mBuilder = builder;      }      public Bike construct() {          mBuilder.buildFrame();          mBuilder.buildSeat();          mBuilder.buildTire();          return mBuilder.createBike();      }  }    //客戶端使用  public class Click {      public static void main(String[] args) {          showBike(new OfoBuilder());          showBike(new MobikeBuilder());      }      private void showBike(Builder builder) {          Director director = new Director(builder);          Bike bike = director.construct();          bike.getFrame().frame();          bike.getSeat().seat();          bike.getTire().tire();      }  } 

五、原型模式

1、楔子

古人云:書非借不能讀也。本人深諳古人教誨,希望博覽群書。奈何沒錢只能辦一張借書卡。但是借書的一個缺點,如果我看到有用的地方想進行標記時卻不能動筆,無奈之下,我只能將這頁內容複印下來,這樣就能保存自己的圈圈劃劃。而原型模型和這個類似

2、解析

原型模式用原型實例指定創建對象的種類,並且通過複製這些原型創建新的對象

基本工作原理是通過將一個原型對象傳給那個要發動創建的對象,這個要發動創建的對象通過請求原型對象拷貝原型自己來實現創建過程

模式組成:

  1. 客戶角色:讓一個原型克隆自己以得到一個新對象
  2. 抽象原型角色:實現自己的 clone方法。通常是抽象類,具有許多具體的子類
  3. 具體原型角色:被複制的對象。是抽象原型角色的具體子類

在原型模式結構中定義了一個抽象原型類,所有的 Java類都繼承自java.lang.Object,而 Object類提供一個clone()方法,可以將一個 Java對象複製一份。因此在 Java中可以直接使用 Object提供的 clone()方法來實現對象的克隆,Java語言中的原型模式實現很簡單

能夠實現克隆的 Java類必須實現一個標識接口 Cloneable,表示這個 Java類支持複製。如果一個類沒有實現這個接口但是調用了clone()方法,Java編譯器將拋出一個CloneNotSupportedException異常

通常情況下,一個類包含一些成員對象,在使用原型模式克隆對象時,根據其成員對象是否也克隆,原型模式可以分為兩種形式:深克隆和淺克隆

  • 在淺克隆中,如果原型對象的成員變量是值類型(八大基本類型,byte,short,int,long,char,double,float,boolean)就直接複製,如果是複雜的類型(枚舉,String,對象)就只複製對應的內存地址
  • 深克隆則是全部複製,然後各自獨立。修改克隆對象對原型對象沒有絲毫影響

適用場景:

  1. 對象種類繁多,無法將他們整合到一個類
  2. 難以根據類生成實例時
  3. 想解耦框架與生成的實例

3、舉例

//附件類  class Attachment {      private String name; //附件名        public String getName() {          return name;      }      public void setName(String name) {          this.name = name;      }        public void download() {          System.out.println("下載附件"+name);      }  }    //周報類:裏面很多屬性可以忽略,但再真正的操作時是確實存在的  //關鍵點在於,實現 cloneable接口以及用 object的 clone方法  class WeeklyLog implements Cloneable{      private Attachment attachment;      private String date;      private String name;      private String content;        public Attachment getAttachment() {          return attachment;      }      public void setAttachment(Attachment attachment) {          this.attachment = attachment;      }      public String getDate() {          return date;      }      public void setDate(String date) {          this.date = date;      }      public String getName() {          return name;      }      public void setName(String name) {          this.name = name;      }      public String getContent() {          return content;      }      public void setContent(String content) {          this.content = content;      }        //通過 clone()方法實現淺克隆      public WeeklyLog clone() {          //需要實現 cloneable的接口,直接繼承 object就好,它裏面自帶一個clone方法          Object obj = null;          try {              obj = super.clone();              return (WeeklyLog)obj;          } catch (CloneNotSupportedException e) {              // TODO Auto-generated catch block              System.out.println("不支持克隆方法!");              return null;          }      }  }    //測試類,客戶端  public class Client {      public static void main(String[] args) {          WeeklyLog log_1,log_2;          log_1 = new WeeklyLog();    //創建原型對象          Attachment attachment = new Attachment(); //創建附件對象          log_1.setAttachment(attachment);    //將附件添加到周報種去          log_2=log_1.clone();    //克隆周報          System.out.println("周報是否相同"+(log_1==log_2));          System.out.println("附件是否相同"+(log_1.getAttachment()==log_2.getAttachment()));      }  }