軟件開發設計模式之【6】個【創建型】設計模式

代碼碼雲倉庫地址:https://gitee.com/dzxmy/design_pattern

常用的創建型設計模式有:工廠方法模式,抽象工廠模式,建造者模式,單例模式。

不常用的創建型設計模式有:簡單工廠,原型模式

一、簡單工廠

定義:由一個工廠對象決定創建出哪一種產品類的實例

類型:創建型,但不屬於GOF23種設計模式

適用場景:

  • 工廠類負責創建的對象比較少
  • 客戶端應用層只知道傳入工廠類的參數對於如何創建對象不關心

優點:只需要傳入一個正確的參數,就可以獲取你所需要的對象,而無須知道其創建細節

缺點:工廠類的職責相對過重,增加新的產品,需要修改工廠類的判斷邏輯,違背開閉原則

com.dzx.design.creational.simplefactory 包下代碼: 簡單工廠

 

二、工廠方法

 定義:定義一個創建對象的接口,但讓實現這個接口的類來決定實例化哪個類,工廠方法讓類的實例化推遲到子類中進行

類型:創建型

適用場景:創建對象需要大量重複的代碼,客戶端應用層不依賴於產品類實例如何被創建、實現等細節,一個類通過其子類來指定創建哪個對象

優點:用戶只需要關心所需產品對應的工廠,無須關心創建細節,加入新產品符合開閉原則,提高可擴展性

缺點:類的個數容易過多,增加複雜度,增加了系統的抽象性和理解難度

com.dzx.design.creational.factorymethod 包下代碼:工廠方法

三、抽象工廠

定義:抽象工廠模式提供一個創建一系列相關或相互依賴對象的接口

無須指定它們具體的類

類型:創建型 

適用場景:

  • 客戶端應用層不依賴於產品類實例如何被創建、實現等細節
  • 強調一系列相關的產品對象(屬於同一產品族)一起使用創建對象需要大量重複的代碼
  • 提供一個產品類的庫,所有的產品以同樣的接口出現,從而使客戶端不依賴於具體實現

優點:具體產品在應用層代碼隔離,無需關心創建細節,將一個系列的產品族統一到一起創建

缺點:規定了所有可能被創建的產品集合,產品族中擴展新的產品困難,需要修改抽象工廠的接口

增加了系統的抽象性和理解難度

com.dzx.design.creational.abstractfactory  包下代碼:抽象工廠

 四、建造者模式

定義:將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示

用戶只需要指定建造的類型就可以得到它們,建造過程及細節不需要知道

類型:創建型

適用場景:

  • 如果一個對象有非常複雜的內部結構(很多屬性)
  • 想把複雜對象的創建和使用分離

優點:封裝性好,創建和使用分離,擴展性好、建造類之間一定程度上解耦

缺點:產生多餘的builder對象,產品內部發生變化,建造者都要修改,成本較大 

com.dzx.design.creational.builder  包下代碼: 建造者模式

 建造者模式改進版本(更加常用)

com.dzx.design.creational.builder.v2  包下代碼: 建造者模式改進版本

 

 五、單例模式

com.dzx.design.creational.singletonexample 包下代碼:單例模式

單例模式的 幾種方式比較
懶漢模式


/**
 * 懶漢模式
 * 單例的實例在第一次使用的時候進行創建
 * <p>
 * 在單線程的環境下,沒有問題,但是在多線程的環境下就會出問題。
 */
@NotThreadSafe
@Slf4j
public class SingletonExample1 {
 
    //私有構造函數
    private SingletonExample1() {
 
    }
 
    //單例對象
    private static SingletonExample1 instance = null;
 
 
    //靜態的工廠方法
    //懶漢模式本身時線程不安全的,加上了synchronized關鍵字之後就可以實現安全
    //但是降低了性能,因此並不推薦這種寫法
    public static  synchronized SingletonExample1 getInstance() {
        if (instance == null) {
            instance = new SingletonExample1();
        }
        return instance;
    }
}


餓漢模式 

/**
 * 餓漢模式
 */
public class SingletonExample2 {
 
 
    private SingletonExample2() {
 
    }
 
    private static SingletonExample2 singletonExample2 = new SingletonExample2();
 
    public static SingletonExample2 getInstance() {
        return singletonExample2;
    }
}



 懶漢模式+ 雙重檢測同步鎖

/**
 * 懶漢模式 -》》雙重同步鎖單例模式
 */
@NotThreadSafe
@Slf4j
public class SingletonExample3 {
    //私有構造函數
    private SingletonExample3() {
 
    }
    /**
     * 1.分配對象的內存空間
     * 2.初始化對象
     * 3.設置instance 指向剛分配的內存
     * 在單線程的情況下沒有問題,但是在多線程的情況下
     *
     * jvm 和cpu 優化,發生指令重排
     *
     * 1.分配對象的內存空間
     * 2.設置instance 指向剛分配的內存
     * 3.初始化對象
     *
     * 因此我們需要限制SingletonExample3類的創建的時候的指令重排,添加volatile關鍵字
     */
 
 
    //單例對象
    //volatile 加上 雙重檢測機制,禁止指令重排
    private volatile static SingletonExample3 instance = null;
 
 
    //靜態的工廠方法
    public static  SingletonExample3 getInstance() {
        if (instance == null) {     //雙重檢測機制
             synchronized (SingletonExample3.class) { //同步鎖
                 if (instance == null) {
                     instance = new SingletonExample3();
                 }
             }
        }
        return instance;
    }
}



 餓漢模式 (靜態塊的方式)

/**
 * static 靜態塊的餓漢模式
 */
public class SingletonExample6 {
    private  SingletonExample6(){}
 
    private static SingletonExample6 instance =null;
 
    static  {
        instance = new SingletonExample6();
    }
 
    private  static  SingletonExample6 getInstance(){
        return instance;
    }
    
    public static void  main(String[] args){
        System.out.println(getInstance());
        System.out.println(getInstance());
    }
}



枚舉模式(最推薦) 

/**
 * 枚舉模式,最安全
 * 利用枚舉的特性,一個枚舉的構造方法只會被調用一次實現單例模式,推薦使用這種方式
 * <p>
 * 優點:1、相比懶漢模式,更能保證線程安全性
 * 2、相比餓漢模式,也只會在第一次使用的時候進行創建實例,不會造成資源浪費
 */
@ThreadSafe
@Recommend
public class SingletonExample7 {
 
 
    private SingletonExample7() {
    }
 
    public static SingletonExample7 getInstance() {
        return Singleton.INSTANCE.getInstance();
    }
 
    private enum Singleton {
        INSTANCE; //只有一個枚舉變量,保證構造方法只調用一次
 
 
        private SingletonExample7 singletonExample7;
 
        //jvm保證這個方法絕對只調用一次
        Singleton() {
            System.out.println("我只會被調用一次");
            singletonExample7 = new SingletonExample7();
        }
 
        public SingletonExample7 getInstance() {
            return singletonExample7;
        }
    }
 
    public static void main(String[] args) {
        System.out.println(SingletonExample7.getInstance());
        System.out.println(SingletonExample7.getInstance());
        System.out.println(SingletonExample7.getInstance());
    }
}

 六、原型模式

定義:指原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象,不需要知道任何創建的細節,不調用構造函數

類型:創建型

適用場景:

  • 類初始化消耗較多資源
  • new產生的一個對象需要非常繁瑣的過程(數據準備,訪問權限等)
  • 構造函數比較複雜
  • 循環體中生產大量對象時

優點:

  • 原型模式性能比直接new一個對象性能高
  • 簡化創建過程

缺點:

  • 必須配備克隆方法
  • 對克隆複雜對象或者對克隆出的對象進行復雜改造時,容易引入風險
  • 深拷貝,淺拷貝要運用得當

深克隆:

一個類實現cloneable接口,並在重寫clone方法的時候對該類的引用數據類型對象屬性也需要調用clone方法進行拷貝

淺克隆:

一個類實現cloneable接口,默認就是淺克隆

com.dzx.design.creational.prototype 包下代碼 原型模式
package com.dzx.design.creational.prototype.clone;

import java.util.Date;

/**
 * @author dzx
 * @ClassName:
 * @Description: 原型模式
 * @date 2019年07月30日 10:58:53
 */
public class Pig implements Cloneable {

    private String name;
    private Date birthday;

    @Override
    public String toString() {
        return "Pig{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}' + super.toString();
    }

    public Pig(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Pig pig = (Pig) super.clone();
        pig.birthday = (Date) pig.getBirthday().clone();
        return pig;
    }
}
package com.dzx.design.creational.prototype.clone;

/**
 * @author dzx
 * @ClassName:
 * @Description: 原型模式
 * @date 2019年07月15日 10:02:06
 */

import java.io.Serializable;

/**
 * 餓漢式+靜態代碼塊
 */
public class SingletonExample4 implements Serializable,Cloneable {

    private SingletonExample4() {
    }

    /**
     * 防止通過反射和克隆方法破壞單例模式
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return getInstance();
    }

    private static SingletonExample4 singletonExample4 = null;

    static {
        singletonExample4 = new SingletonExample4();
    }

    public static SingletonExample4 getInstance() {
        return singletonExample4;
    }


}

克隆測試以及 通過克隆破壞單例模式

package com.dzx.design.creational.prototype.clone;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * @author dzx
 * @ClassName:
 * @Description: 原型模式
 * @date 2019年07月30日 10:59:57
 */
public class Test {


    public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Date date = new Date(0);
        Pig pig = new Pig("佩奇", date);
        Pig clone = (Pig) pig.clone();
        System.out.println(pig);
        System.out.println(clone);

        //默認爲淺克隆,當我們修改pig對象的生日時候,clone對象的生日也跟着被修改了
        //如果要做到深克隆,就需要重寫深克隆,並且在克隆的時候,需要克隆pig類裏面屬性爲引用數據類型的對象
        //否則很容易引起bug
        pig.getBirthday().setTime(66666666L);
        System.out.println(pig);
        System.out.println(clone);

        //利用反射和克隆破壞單例模式
        SingletonExample4 instance = SingletonExample4.getInstance();

        Method clone1 = instance.getClass().getDeclaredMethod("clone");
        clone1.setAccessible(true);//原本是protected,需要打開訪問權限
        //執行clone方法
        SingletonExample4 invoke = (SingletonExample4) clone1.invoke(instance);
        //發現instance 和 invoke 兩個對象的引用地址是不同的,說明單例已被破壞掉
        //解決防範:1 不實現cloneable接口  2在重寫的clone()方法中調用getInstance()方法
        System.out.println(instance);
        System.out.println(invoke);


    }
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章