設計模式 - 創建型模式總結


在軟件工程中,創建型模式是處理對象創建的設計模式,試圖根據實際情況使用合適的方式創建對象。基本的對象創建方式可能會導致設計上的問題,或增加設計的復雜度。創建型模式通過以某種方式控制對象的創建來解決問題。

常用創建型模式有:單例模式、工廠模式、抽象工廠模式、原型模式、建造者模式

一、單例模式

單例模式有以下8種寫法:

  1. 餓漢式:
    1. 靜態常量
    2. 靜態代碼塊
  2. 懶漢式:
    1. 線程不安全
    2. 線程安全,同步方法
    3. 線程安全,同步代碼塊
  3. 雙重檢查
  4. 靜態內部類
  5. 枚舉

單例模式的使用場景:

需要頻繁創建和銷毀的對象;創建時耗時過多或消耗資源過多,但又經常用到的對象(比如session工廠、數據源等)

1. 餓漢式 - 靜態常量寫法

代碼實現:

/**
 * 設計模式之單例模式
 * 餓漢式(靜態常量)
 */
public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println("兩次獲取的實例一樣嗎:" + (instance1 == instance2)); //true
    }
}

class Singleton {

    //私有構造方法,使其不可在外部通過構造器實例化
    private Singleton() {
    }

    //定義為常量,保證實例對象不變
    private final static Singleton instance = new Singleton();

    //通過此方法獲取實例
    public static Singleton getInstance() {
        return instance;
    }

}

分析:

優點:

  • 使用方式簡單,在類加載的時候創建實例對象,避免了線程同步問題

缺點:

  • 在類加載的時候創建實例對象,但不確定何時使用、是否使用,可能造成內存浪費

2. 餓漢式 - 靜態代碼塊寫法

代碼實現:

/**
 * 設計模式之單例模式
 * 餓漢式(靜態代碼塊寫法)
 */
class Singleton{
    
    //私有構造方法,使其不可在外部通過構造器實例化
    private Singleton(){
    }
    
    //定義為常量,保證實例對象不變
    private final static Singleton instance;

    static {
        instance = new Singleton();
    }

    //通過此方法獲取實例
    public static Singleton getInstance(){
        return instance;
    }

}

分析:

和靜態常量一致,只不過初始化的位置不同,一個在靜態代碼塊,一個直接在常量聲明處初始化

3. 懶漢式 - 線程不安全

代碼實現:

/**
 * 設計模式之單例模式
 * 懶漢式(線程不安全)
 */
class Singleton {

    //私有構造方法,使其不可在外部通過構造器實例化
    private Singleton() {
    }

    //聲明實例對象
    private static Singleton instance;

    //通過此方法獲取實例
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

}

分析:

優點:

  • 滿足隨用隨拿的特點,解決了內存浪費的問題

缺點:

  • 線程不安全,當多個線程訪問時,可能創建多個實例,因此實際開發中不可使用

4. 懶漢式 - 線程安全 - 同步方法寫法

代碼實現:

/**
 * 設計模式之單例模式
 * 懶漢式(同步方法)
 */
class Singleton {

    //私有構造方法,使其不可在外部通過構造器實例化
    private Singleton() {
    }

    //聲明實例對象
    private static Singleton instance;

    //通過此方法獲取實例
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

}

分析:

雖然解決了線程不安全問題,但鎖的范圍太大,效率低,開發中盡量不要使用

5. 懶漢式 - 線程安全 - 同步代碼塊寫法

代碼實現:

/**
 * 設計模式之單例模式
 * 懶漢式(同步代碼塊寫法)
 */
class Singleton {

    //私有構造方法,使其不可在外部通過構造器實例化
    private Singleton() {
    }

    //聲明實例對象
    private static Singleton instance;

    //通過此方法獲取實例
    public static Singleton getInstance() {
        if (instance == null) {
            //同步代碼
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }

}

分析:

這種方式將同步鎖縮小了范圍,本意是解決效率問題,但又造成了線程不安全,因此開發中不可使用

6. 懶漢式 - 雙重檢查(推薦使用)

代碼實現:

/**
 * 設計模式之單例模式
 * 雙重檢查
 */
class Singleton {

    //私有構造方法,使其不可在外部通過構造器實例化
    private Singleton() {
    }

    //聲明實例對象
    private static volatile Singleton instance;

    //雙重判斷 + 同步鎖
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

分析:

既提高了效率也解決了線程安全問題,推薦使用這種方法

7. 懶漢式 - 靜態內部類(推薦使用)

代碼實現:

/**
 * 設計模式之單例模式
 * 靜態內部類
 */
class Singleton {

    //私有構造方法,使其不可在外部通過構造器實例化
    private Singleton() {
    }

    //靜態內部類
    private static class SingletonInstance{
        private final static Singleton INSTANCE = new Singleton();
    }

    //獲取實例
    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }

}

分析:

利用了類加載機制,保證初始化實例時只有一個線程。Singleton類被裝載時並不會被實例化,當調用getInstance方法時才會裝載SingletonInstance

8. 懶漢式 - 枚舉法(推薦使用)

代碼實現:

/**
 * 設計模式之單例模式
 * 枚舉
 */
enum Singleton{
    INSTANCE;
}

分析:

不僅能規避線程不安全,還能防止反序列化重新創建新的對象

二、工廠模式

1. 簡單工廠模式

1.1 介紹

​ 嚴格來說,簡單工廠模式並不是23種常見的設計模式之一,它只算工廠模式的一個特殊實現。簡單工廠模式在實際中的應用相對於其他2個工廠模式用的還是相對少得多,因為它只適應很多簡單的情況。

​ 簡單工廠模式違背了 開閉原則 (但可以通過反射的機制來避免) 。因為每次你要新添加一個功能,都需要在生switch-case 語句(或者if-else 語句)中去修改代碼,添加分支條件。

1.2 適用場景

  • 需要創建的對象較少
  • 客戶端不關心對象的創建過程

1.3 簡單工廠模式角色分配

  • 工廠(Factory)角色 :簡單工廠模式的核心,它負責實現創建所有實例的內部邏輯。工廠類可以被外界直接調用,創建所需的產品對象。
  • 抽象產品(Product)角色 :簡單工廠模式所創建的所有對象的父類,它負責描述所有實例所共有的公共接口。
  • 具體產品(Concrete Product)角色:簡單工廠模式的創建目標,所有創建的對象都是充當這個角色的某個具體類的實例。

1.4 簡單工廠模式代碼實現

新建一個抽象產品 Shape

public interface Shape {
    void draw();
}

具體產品 Circle、Square,實現 Shape 接口

public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("圓形");
    }
}

public class Square implements Shape {
    @Override
    public void draw() {
        System.out.println("正方形");
    }
}

工廠 ShapeFactory

public class ShapeFactory {
    public static Shape getShape(String name){
        if(name == null){
            return null;
        }
        if ("SQUARE".equalsIgnoreCase(name)){
            return new Square();
        }else if("CIRCLE".equalsIgnoreCase(name)){
            return new Circle();
        }else{
            return null;
        }
    }
}

客戶端 Client

public class Client {
    public static void main(String[] args) {
        Shape circle = ShapeFactory.getShape("circle");
        circle.draw();
        Shape square = ShapeFactory.getShape("square");
        square.draw();
    }
}

運行結果

圓形
正方形

雖然實現了簡單工廠模式,但是當我們新增一個需求的時候,需要修改ShapeFactory類的代碼,違反了開閉原則,我們可以用反射的方式重寫工廠方法

public class ShapeFactory2 {
    public static Object getClass(Class<? extends Shape> clazz){
        if (clazz == null){
            return null;
        }
        try {
            return clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
}

測試

public class Client2 {
    public static void main(String[] args) {
        Circle circle = (Circle) ShapeFactory2.getClass(Circle.class);
        circle.draw();
        Square square = (Square) ShapeFactory2.getClass(Square.class);
        square.draw();
    }
}

運行結果

圓形
正方形

2. 工廠方法模式

2.1 介紹

​ 工廠方法模式應該是在工廠模式家族中是用的最多模式,一般項目中存在最多的就是這個模式。

工廠方法模式是簡單工廠的僅一步深化, 在工廠方法模式中,我們不再提供一個統一的工廠類來創建所有的對象,而是針對不同的對象提供不同的工廠。也就是說 每個對象都有一個與之對應的工廠

2.2 適用場景

  • 一個類不知道它所需要的對象的類:在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品對象由具體工廠類創建;客戶端需要知道創建具體產品的工廠類。

  • 一個類通過其子類來指定創建哪個對象:在工廠方法模式中,對於抽象工廠類只需要提供一個創建產品的接口,而由其子類來確定具體要創建的對象,利用面向對象的多態性和里氏

  • 將創建對象的任務委托給多個工廠子類中的某一個,客戶端在使用時可以無需關心是哪一個工廠子類創建產品子類,需要時再動態指定,可將具體工廠類的類名存儲在配置文件或數據庫中。

2.3 工廠方法模式角色分配

  • 抽象工廠(Abstract Factory)角色:是工廠方法模式的核心,與應用程序無關。任何在模式中創建的對象的工廠類必須實現這個接口。
  • 具體工廠(Concrete Factory)角色 :這是實現抽象工廠接口的具體工廠類,包含與應用程序密切相關的邏輯,並且受到應用程序調用以創建某一種產品對象。
  • 抽象產品(Abstract Product)角色 :工廠方法模式所創建的對象的超類型,也就是產品對象的共同父類或共同擁有的接口。
  • 具體產品(Concrete Product)角色 :這個角色實現了抽象產品角色所定義的接口。某具體產品有專門的具體工廠創建,它們之間往往一一對應

2.4 工廠方法模式代碼實現

抽象工廠 Factory

public interface Factory {
    Shape getShape();
}

具體工廠 CircleFactory、SquareFactory

public class CircleFactory implements Factory {
    @Override
    public Shape getShape() {
        return new Circle();
    }
}

public class SquareFactory implements Factory {
    @Override
    public Shape getShape() {
        return new Square();
    }
}

抽象產品和具體產品繼續使用簡單工廠模式中的類

客戶端

public class Client {
    public static void main(String[] args) {
        Shape circle = new CircleFactory().getShape();
        circle.draw();
        Shape square = new SquareFactory().getShape();
        square.draw();
    }
}

運行結果

圓形
正方形

3. 抽象工廠模式

3.1 介紹

​ 在工廠方法模式中,其實我們有一個潛在意識的意識。那就是我們生產的都是同一類產品。抽象工廠模式是工廠方法的僅一步深化,在這個模式中的工廠類不單單可以創建一種產品,而是可以創建一組產品。

​ 將工廠抽象成兩層,Abstract Factory(抽象工廠)和具體實現的工廠子類。程序員可以根據創建對象類型使用對應的工廠子類。這樣將單個的簡單工廠類變成了工廠簇,更利於代碼的維護和擴展。

3.2 適用場景

  • 和工廠方法一樣客戶端不需要知道它所創建的對象的類。

  • 需要一組對象共同完成某種功能時,並且可能存在多組對象完成不同功能的情況。(同屬於同一個產品族的產品)

  • 系統結構穩定,不會頻繁的增加對象。(因為一旦增加就需要修改原有代碼,不符合開閉原則)

3.3 抽象工廠模式角色分配

  • 抽象工廠(Abstract Factory)角色 :是工廠方法模式的核心,與應用程序無關。任何在模式中創建的對象的工廠類必須實現這個接口。
  • 具體工廠類(Concrete Factory)角色 :這是實現抽象工廠接口的具體工廠類,包含與應用程序密切相關的邏輯,並且受到應用程序調用以創建某一種產品對象。
  • 抽象產品(Abstract Product)角色 :工廠方法模式所創建的對象的超類型,也就是產品對象的共同父類或共同擁有的接口。
  • 具體產品(Concrete Product)角色 :抽象工廠模式所創建的任何產品對象都是某一個具體產品類的實例。在抽象工廠中創建的產品屬於同一產品族,這不同於工廠模式中的工廠只創建單一產品。

3.4 抽象工廠的工廠和工廠方法中的工廠有什么區別?

​ 抽象工廠是生產一整套有產品的(至少要生產兩個產品),這些產品必須相互是有關系或有依賴的,而工廠方法中的工廠是生產單一產品的工廠。

3.5 抽象工廠模式代碼實現

抽象產品:Gun、Bullet

public interface Gun {
    void shooting();
}

public interface Bullet {
    void loading();
}

具體產品:AK47、AK47Bullet、M16、M16Bullet

public class AK47 implements Gun {
    @Override
    public void shooting() {
        System.out.println("AK47射擊");
    }
}

public class AK47Bullet implements Bullet {
    @Override
    public void loading() {
        System.out.println("AK47裝子彈");
    }
}

public class M16 implements Gun {
    @Override
    public void shooting() {
        System.out.println("M16射擊");
    }
}

public class M16Bullet implements Bullet {
    @Override
    public void loading() {
        System.out.println("M16裝子彈");
    }
}

抽象工廠:ArmsFactory

public interface ArmsFactory {
    Gun produceGun();
    Bullet produceBullet();
}

具體工廠:

public class AK47Factory implements ArmsFactory{

    @Override
    public Gun produceGun() {
        return new AK47();
    }

    @Override
    public Bullet produceBullet() {
        return new AK47Bullet();
    }
}

public class M16Factory implements ArmsFactory{

    @Override
    public Gun produceGun() {
        return new M16();
    }

    @Override
    public Bullet produceBullet() {
        return new M16Bullet();
    }
}

測試

public class Client {
    public static void main(String[] args) {
        ArmsFactory factory;

        factory = new AK47Factory();
        Gun ak47 = factory.produceGun();
        Bullet ak47Bullet = factory.produceBullet();
        ak47Bullet.loading();
        ak47.shooting();
        
        factory = new M16Factory();
        Gun m16 = factory.produceGun();
        Bullet m16Bullet = factory.produceBullet();
        m16Bullet.loading();
        m16.shooting();
    }
}

結果

AK47裝子彈
AK47射擊
M16裝子彈
M16射擊

參考:深入理解工廠模式

三、原型模式

原型模式是指:用原型實例指定創建對象的種類,並且通過拷貝這些原型,創建新的對象。

原型模式是一種創建型設計模式,允許一個對象再創建另外一個可定制的對象,無需知道如何創建的細節

可以通過重寫clone方法實現拷貝,拷貝又分為淺拷貝和深拷貝

1. 淺拷貝:

  • 對於基本數據類型的成員變量,淺拷貝會直接進行值傳遞,將該屬性值復制一份給新的對象

  • 對於引用類型的成員變量,淺拷貝知識將該成員變量的引用值(內存地址)復制一份給新的對象,因此會造成一個對象修改值影響另外一個對象

  • 淺拷貝時使用默認的clone()方法來實現,例如:

    Person = (Person)super.clone()

2. 深拷貝:

  • 復制對象的所有基本數據類型的成員變量值
  • 為所有引用類型的成員變量申請存儲空間,並復制每個引用數據類型成員變量所引用的對象
  • 可通過重寫clone方法和對象序列化方式(推薦)實現
//使用clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
    Company company = null;
    try {
        company = (Company) super.clone();
        //處理引用類型
        company.employee = (Employee) employee.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return company;
}
//使用序列化
protected Object deepClone(){
    ByteArrayInputStream bis = null;
    ByteArrayOutputStream bos = null;
    ObjectInputStream ois = null;
    ObjectOutputStream oos = null;

    try {
        //序列化
        bos = new ByteArrayOutputStream();
        oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

        //反序列化
        bis = new ByteArrayInputStream(bos.toByteArray());
        ois = new ObjectInputStream(bis);
        return ois.readObject();
    }catch (Exception e){
        e.printStackTrace();
    }
    return null;
}

3. 原型模式的分析:

  • 創建新的對象比較復雜時,可以利用原型模式簡化對象的創建過程,同時也能夠提高效率
  • 不用重新初始化對象,而是動態的獲取對象運行時的狀態
  • 如果原始對象發生變化(增加或減少屬性),其它克隆對象也會發生變化,無需修改代碼
  • 淺拷貝和深拷貝對引用類型的處理方式不一樣

四、建造者模式

1. 基本介紹

  1. 建造者模式(Builder Pattern)又叫生成器模式,是一種對象構建模式。它可以將復雜對象的建造過程抽象出來(抽象類別),使這個抽象過程的不同實現方法可以構造出不同表現(屬性)的對象。
  2. 建造者模式是一步步創建一個復雜的對象,它允許用戶只通過指定復雜對象的類型和內容就可以構造它們,用戶不需要知道內部的具體構建細節。

2. 建造者模式的四個角色

  • 產品角色(Product):一個具體的產品對象
  • 抽象建造者(Builder):創建一個Product對象的各個部件指定的接口抽象類
  • 具體建造者(Concrete Builder):實現接口,構建和裝配各個部件。
  • 指揮者(Director):構建一個使用Builder接口的對象。它主要是用於創建一個復雜的對象。它主要有兩個作用:① 隔離了客戶與對象的生產過程,②負責控制產品對象的生產過程。

3. 原理類圖:

4. 建造者模式在 JDK - StringBuilder 中的應用

在 StringBuilder 繼承關系中:

StringBuilder 繼承了 AbstractStringBuilder ,AbstractStringBuilder 實現了 Appendable 接口

角色分析:

  • 抽象建造者:Appendable
  • 具體建造者:AbstractStringBuilder
  • 指揮者:StringBuilder

5. 建造者模式注意事項與細節

  1. 客戶端(使用程序)不必知道產品內部組成的細節,將產品本身與產品的創建過程解耦,使得相同的創建過程可以創建不同的產品對象
  2. 每一個具體建造者都相對獨立,而與其他的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者,用戶使用不同的具體建造者即可得到不同的產品對象
  3. 可以更加精細地控制產品的創建過程。將復雜產品的創建步驟分解在不同的方法中,使得創建過程更加清晰,也更方便使用程序來控制創建過程
  4. 增加新的具體建造者無須修改原有類庫的代碼,指揮者類針對抽象建造者類編程,系統擴展方便,符合“開閉原則”

6. 建造者模式代碼實現

具體產品:House

public class House {
    private String basic;
    private String wall;
    private String roofed;
    //省略getter setter toString()方法
}

抽象建造者:HouseBuilder

public abstract class HouseBuilder {
    protected House house = new House();

    public abstract void buildBasic();

    public abstract void buildWall();

    public abstract void buildRoof();

    public House buildHouse() {
        return house;
    }
}

具體建造者:CommonHouse、HighHouse

public class CommonHouse extends HouseBuilder {
    @Override
    public void buildBasic() {
        System.out.println("打10m的地基");
    }

    @Override
    public void buildWall() {
        System.out.println("砌20cm的牆");
    }

    @Override
    public void buildRoof() {
        System.out.println("封瓦片頂");
    }
}

public class HighHouse extends HouseBuilder {
    @Override
    public void buildBasic() {
        System.out.println("打20m的地基");
    }

    @Override
    public void buildWall() {
        System.out.println("砌50cm的牆");
    }

    @Override
    public void buildRoof() {
        System.out.println("封玻璃頂");
    }
}

指揮者:HouseDirector

public class HouseDirector {

    private HouseBuilder builder;

    public HouseDirector(HouseBuilder builder) {
        this.builder = builder;
    }

    public House build(){
        builder.buildBasic();
        builder.buildWall();
        builder.buildRoof();
        return builder.buildHouse();
    }
}

測試:

public class Client {
    public static void main(String[] args) {
        HouseDirector builderDirector1 = new HouseDirector(new CommonHouse());
        builderDirector1.build();
        System.out.println("---------");
        HouseDirector builderDirector2 = new HouseDirector(new HighHouse());
        builderDirector2.build();
    }
}

結果:

打10m的地基
砌20cm的牆
封瓦片頂
---------
打20m的地基
砌50cm的牆
封玻璃頂

p.s. 所有代碼和筆記均可在 我的GitHub 中獲取,如果對您有幫助的話,可以點個 star 支持一下 🙈


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM