1. 程式人生 > >設計模式——建立型模式(工廠,簡單工廠,單例,建造者,原型)

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

目錄

  • 一、工廠模式
    • 簡單工廠模式
    • 工廠方法模式
  • 二、抽象工廠模式
  • 三、單例模式
  • 四、建造者模式
  • 五、原型模式

建立型模式對類的例項化過程進行了抽象,能夠將軟體模組中物件的建立和物件的使用分離

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

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

一、工廠模式

簡單工廠模式

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();
        }
        ...
    }
}

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

二、抽象工廠模式

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

圖中 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()));
    }
}