天天看點

架構師備戰(三)-軟體工程(十二) 設計模式之建立型模式

作者:IT動力

我們傳統的23種設定模式如下

  • 建立型模式:用于建立對象
    • 工廠方法(Factory Method) 模式
    • 抽象工廠(Abstract Factory) 模式
    • 原型(Protptype) 模式
    • 單例(Singleton) 模式
    • 建構器模式
  • 結構型模式:建立更大的結構
    • 擴充卡(Adapter)模式
    • 橋接(Bridge)模式
    • 組合(Composite)模式
    • 裝飾(Decorator)模式
    • 外觀(Facade)模式
    • 享元(Flyweight)模式
    • 代理(Proxy)模式
  • 行為型模式:互動及職責配置設定
    • 責任鍊(Chain of Responsibility)模式
    • 指令(Command)模式
    • 解釋器(Interpreter)模式
    • 疊代器(Iterator)模式
    • 中介者(Mediator)模式
    • 備忘錄(Memento)模式
    • 觀察者(Observer)模式
    • 狀态(State)模式
    • 政策(Strategy)模式
    • 模闆方法(Temmlate)模式
    • 通路者(Visotor)模式

本篇文章主要說明建立型模式,它的類圖,設計思想以及Java代碼的實作。

1、工廠方法模式

簡要說明

定義一個建立對象的接口,但是子類決定需要執行個體化哪一個類。工廠方法使得子類執行個體化的過程推遲。

速記關鍵字

動态生産對象

通俗的講,就是我們建立一個工廠類,我們要建立什麼對象,傳一個參數給工廠,工廠就負責建立一個對象傳回給我。

類圖如下

架構師備戰(三)-軟體工程(十二) 設計模式之建立型模式

Java代碼實作

/**
 * 産品接口
 */
public interface IProduct {

    /**
     * 生産産品的方法
     */
    void product();

}

public class Product1 implements IProduct{
    @Override
    public void product() {
        System.out.println("生産産品1:手機");
    }
}

public class Product2 implements IProduct{
    @Override
    public void product() {
        System.out.println("生産産品2:電腦");
    }
}

public class Product3 implements IProduct{
    @Override
    public void product() {
        System.out.println("生産産品3:平闆");
    }
}

/**
 * 工廠方法類
 */
public class Creator {

    /**
     * 根據産品類型生産産品 該類一般是靜态類
     * @param productType 産品類型
     * @return 産品
     */
    public static IProduct factory(Integer productType){
        if (productType == 1){
            return new Product1();
        }
        if (productType == 2){
            return new Product2();
        }
        if (productType == 3){
            return new Product3();
        }
        throw new RuntimeException("請傳入正确的産品類型");
    }
}

/**
 * 用戶端
 */
public class Client {
    public static void main(String[] args) {
        // 建立産品1
        IProduct product1 = Creator.factory(1);
        // 建立産品2
        IProduct product2 = Creator.factory(2);
        // 建立産品3
        IProduct product3 = Creator.factory(3);

        // 列印一下,看一下是不是對應的産品
        product1.product();
        product2.product();
        product3.product();
    }
}           

注意:這裡的工廠方法,也就是Creator的factory方法,一般是靜态的,因為它為了友善不用再去執行個體化工廠了,因為所有的事情都交給這一個工廠做了

輸出結果

架構師備戰(三)-軟體工程(十二) 設計模式之建立型模式

2、抽象工廠模式

由上面的工廠方法知道,我們的工廠方法講所有的事情都交給了工廠。但是我們如果要針對手機我們要生産一系列的産品,比如華為手機,蘋果手機。此時工廠方法就不能夠滿足了。我們需要一個華為手機工廠,蘋果手機工廠。這裡抽象工廠模式應運而生。它就是為了生成這樣的系列産品的工廠模式。

簡要說明

提供一個接口,可以建立一系列相關或者互相依賴的對象,而無需指定它們的具體的類。

速記關鍵字

生産成系列對象

類圖如下

架構師備戰(三)-軟體工程(十二) 設計模式之建立型模式

Java代碼實作

/**
 * 抽象産品A, 手機工廠,用于生産手機
 */
public interface AbstractProductA {
    void product();
}

public class ConcreteProductA1 implements AbstractProductA {
    @Override
    public void product() {
        System.out.println("生産産品A:華為手機");
    }
}

public class ConcreteProductA2 implements AbstractProductA {
    @Override
    public void product() {
        System.out.println("生産産品A:蘋果手機");
    }
}

/**
 * 抽象産品B, 筆記本電腦工廠,用于生産筆記本
 */
public interface AbstractProductB {
    void product();
}

public class ConcreteProductB1 implements AbstractProductB {
    @Override
    public void product() {
        System.out.println("生産産品B:華為筆記本");
    }
}

public class ConcreteProductB2 implements AbstractProductB {
    @Override
    public void product() {
        System.out.println("生産産品B:蘋果筆記本");
    }
}

/**
 * 抽象工廠 可以是接口 也可以是抽象類 總的定義需要生産哪些系列産品
 */
public interface AbstractFactory {
    // 建立産品A(傳回的對象是抽象産品A)
    AbstractProductA createProductA(Integer type);

    // 建立産品B(傳回的對象是抽象産品B)
    AbstractProductB createProductB(Integer type);
}

/**
 * 具體工廠A,實作了抽象工廠,但是隻實作抽象産品A的具體對象建立
 */
public class ConcreteFactoryA implements AbstractFactory{
    @Override
    public AbstractProductA createProductA(Integer type) {
        if (type == 1){
            return new ConcreteProductA1();
        }
        if (type == 2){
            return new ConcreteProductA2();
        }
        throw new RuntimeException("請傳入正确的産品A類型");
    }

    @Override
    public AbstractProductB createProductB(Integer type) {
        return null;
    }
}

/**
 * 具體工廠B,實作了抽象工廠,但是隻實作抽象産品B的具體對象建立
 */
public class ConcreteFactoryB implements AbstractFactory{
    @Override
    public AbstractProductA createProductA(Integer type) {
        return null;
    }

    @Override
    public AbstractProductB createProductB(Integer type) {
        if (type == 1){
            return new ConcreteProductB1();
        }
        if (type == 2){
            return new ConcreteProductB2();
        }
        throw new RuntimeException("請傳入正确的産品B類型");
    }
}

/**
 * 用戶端
 */
public class Client {
    public static void main(String[] args) {
        // 先執行個體化某個具體的工廠
        AbstractFactory factoryA = new ConcreteFactoryA();
        AbstractFactory factoryB = new ConcreteFactoryB();

        // 比如我要通過兩個工廠分别生産一個華為手機和一個華為筆記本
        AbstractProductA productA = factoryA.createProductA(1);
        AbstractProductB productB = factoryB.createProductB(1);

        // 輸出
        productA.product();
        productB.product();
    }
}           

運作結果

架構師備戰(三)-軟體工程(十二) 設計模式之建立型模式

由類圖和代碼可以清晰的看出來,我們的抽象工廠定義了要建立哪些系列的産品,這些産品交給哪些具體的工廠去實作。而每個具體的工廠都會根據産品的類型參數,去建立不同的産品。這樣我們新增一個具體的産品非常的容易,我們增加一個具體的工廠也改動不大,這就是設計模式的魅力所在。

3、原型模式

簡要說明

用原型執行個體指定建立對象的類型,并且通過拷貝這個原型來建立新的對象

速記關鍵字

克隆對象

類圖如下

架構師備戰(三)-軟體工程(十二) 設計模式之建立型模式

淺克隆與深克隆

淺克隆表示隻克隆對象的直接屬性,如果對象裡面包含另一個對象,不會連這個對象下面的屬性也克隆,而是設定的這個對象的引用。 深克隆表示除了克隆對象的直接屬性,如果對象裡面包含另一個對象,也會連這個對象下面的屬性也克隆,而不是設定的這個對象的引用。

Java代碼實作

@Data
public class User implements Cloneable{
    private String name;
    private Integer age;
    // 位址對象,淺克隆之後的這個對象不會克隆,使用的是原來的引用,深克隆則會克隆
    private Address address;

    // 淺克隆,實作Cloneable接口即可
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    // 深克隆,在實作淺克隆的基礎上,将Address手動克隆一遍,Address本身也需要實作Cloneable接口
    protected Object deepClone() throws CloneNotSupportedException {
        User clone = (User)super.clone();
        // 把裡裡面是對象的屬性,執行一遍克隆即可
        Address address = (Address)clone.getAddress().clone();
        clone.setAddress(address);
        return clone;
    }
}

@Data
public class Address implements Cloneable{
    private String province;
    private String city;
    private String area;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

/**
 * 用戶端實作
 */
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 建立對象
        Address address = new Address();
        address.setProvince("重慶");
        address.setProvince("重慶市");
        address.setCity("渝中區");
        User user = new User();
        user.setName("IT動力");
        user.setAge(18);
        user.setAddress(address);

        // 對象淺克隆, 淺克隆之後的屬性裡面的對象屬性是不變的,因為存儲的是引用
        User clone = (User)user.clone();
        System.out.println("淺克隆不會克隆address屬性,結果應該為true: " + (clone.getAddress() == user.getAddress()));

        // 對象深克隆
        User clone2 = (User)user.deepClone();
        System.out.println("深克隆會克隆address屬性, 結果應該為false: " + (clone2.getAddress() == user.getAddress()));
    }
}           

輸出結果

架構師備戰(三)-軟體工程(十二) 設計模式之建立型模式

從上面看到,深克隆的實作我們手動去做了克隆Address對象的操作,但是在實際代碼中,我們顯然不能夠去這樣做。而實作的方式就是每個對象都實作序列化接口。深克隆時會序列化成流,然後再從流裡讀取出來進行反序列化。這樣就能實作深克隆。

JSON序列化代碼實作

@Data
public class Address implements Serializable {
    private String province;
    private String city;
    private String area;
}

@Data
public class User implements Serializable {
    private String name;
    private Integer age;
    private Address address;

    protected Object deepClone() throws CloneNotSupportedException {
        // 這裡直接引入fastjson做測試
        return JSON.parseObject(JSON.toJSONString(this), User.class);
    }
}

/**
 * 用戶端實作
 */
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 建立對象
        com.dp.prototype.Address address = new Address();
        address.setProvince("重慶");
        address.setProvince("重慶市");
        address.setCity("渝中區");
        User user = new com.dp.prototype.User();
        user.setName("IT動力");
        user.setAge(18);
        user.setAddress(address);

        // 對象深克隆
        User clone2 = (User) user.deepClone();
        System.out.println("深克隆會克隆address屬性, 結果應該為false: " + (clone2.getAddress() == user.getAddress()));
    }
}           

輸出結果

架構師備戰(三)-軟體工程(十二) 設計模式之建立型模式

4、單例模式

簡要說明

保證一個類隻有一個執行個體,并提供一個通路它的全局通路點

速記關鍵字

單執行個體

類圖如下

架構師備戰(三)-軟體工程(十二) 設計模式之建立型模式

單例模式,無外乎就是如何建立一個單執行個體,并且提供一個唯一通路入口。但是單例如何執行個體化又分為了餓漢式單例模式和懶漢式單例模式。

餓漢式單例模式

餓漢式單例模式,就是我們建立私有的一個靜态變量,直接對進行執行個體化,并且提供一個私有的構造函數,防止對象被建立。最後提供一個公開的擷取執行個體的方法,直接傳回聲明的對象。

它是從出生就做好了執行個體化,後面也不需要再去執行個體化了,是以它是天生線程安全的。

懶漢式單例模式

餓漢式是一開始就把對象建立好了,懶漢式一開始比較懶,它不建立,在方法被調用時才會建立,是以叫懶漢式。但是這樣建立在多線程可能會有問題,是以一般需要加鎖。

加鎖得方式有整個方法加鎖,還有一種叫做雙重檢測鎖得單例模式,能夠應對高并發。

Java代碼實作

/**
 * 餓漢式單例模式
 */
public class HungrySingleton {
    // 私有得靜态得執行個體變量,餓漢式直接初始化對象
    private static final HungrySingleton instants = new HungrySingleton();

    // 私有構造函數,防止其他類通過反射執行個體化該類
    private HungrySingleton(){
        System.out.println("我是餓漢式單例的私有構造方法,我被調用了");
    }

    // 靜态的public方法,供外界網文的唯一入口
    public static HungrySingleton getInstance(){
        // 直接傳回上面對應的對象
        return instants;
    }
}

/**
 * 懶漢式單例模式
 */
public class LazySingleton {
    // 私有得靜态得執行個體變量,餓漢式直接初始化對象
    private static LazySingleton instants;

    // 私有構造函數,防止其他類通過反射執行個體化該類
    private LazySingleton(){
        System.out.println("我是懶漢式單例的私有構造方法,我被調用了");
    }

    // 靜态的public方法,供外界網文的唯一入口, 注意懶漢式在這裡需要加鎖,不然會存線上程安全問題
    public static synchronized LazySingleton getInstance(){
        // 為空 則建立并且指派給全局變量
        if (instants == null){
            instants = new LazySingleton();
        }
        // 傳回上面對應的對象
        return instants;
    }
}

/**
 * 懶漢式單例模式2 雙重檢測鎖(推薦)
 */
public class LazySingleton2 {
    // 私有得靜态得執行個體變量,餓漢式直接初始化對象 注意,這裡需要将全局變量加上volatile關鍵字,進而禁止重排序
    private static volatile LazySingleton2 instants;

    // 私有構造函數,防止其他類通過反射執行個體化該類
    private LazySingleton2(){
        System.out.println("我是懶漢式單例2的私有構造方法,我被調用了");
    }

    // 靜态的public方法,供外界網文的唯一入口
    public static LazySingleton2 getInstance(){
        // 第一個if判斷是否為空,不為空直接傳回,避免synchronized同步代碼塊的執行,多線程場景下頻繁加鎖會影響性能
        if(instants == null){
            // 為空的情況才加鎖
            synchronized (LazySingleton2.class){
                // 第二個if判斷是否為空,當a線程優先獲得鎖,執行到此處,b線程沒競争到鎖會被阻塞在外面,a線程判斷執行個體是否為空,為空則new執行個體,
                // a線程釋放鎖之後,b線程拿到鎖進來後判斷instance是否為null,此時不為null,則釋放鎖往下
                if(instants == null){
                    instants = new LazySingleton2();
                }
            }
        }

        return instants;
    }
}           

輸出結果

架構師備戰(三)-軟體工程(十二) 設計模式之建立型模式

單例模式的代碼實作方式有這麼多種,那麼工作中會使用哪種呢。如果需要考慮并發問題,就需要使用雙重檢測鎖,這樣性能會高一些。

為什麼不使用餓漢式呢,因為它一開始就把對象初始化了,也就是記憶體從一開始就占用了。如果存在膽量的單例,但是卻沒有使用,那就是對記憶體的一種極大的浪費。而懶漢式則隻有在真正需要時才會去建立對象。

5、構造器模式

簡要說明

講一個複雜類的表示與其構造相分離,使得形同的建構過程能夠得出不同的表示。

速記關鍵字

複雜對現象構造

類圖如下

架構師備戰(三)-軟體工程(十二) 設計模式之建立型模式

java代碼實作

/**
 * 房子類 包含了房子的長寬高
 */
@Data
public class Room {

    private String length;

    private String width;

    private String height;


    @Override
    public String toString() {
        return "Room{length='" + length + ", width='" + width + ", height='" + height + '}';
    }
}

/**
 * 建構器接口
 */
public interface Builder {
    // 建構房子的長度
    void buildLength();
    // 建構房子的寬度
    void buildWidth();
    // 建構房子的高度
    void buildHeight();
    // 建構房子
    Room buildRoom();
}

/*
 * 大房子的具體建構者
 */
public class LargeRoomBuilder implements Builder {
    private final Room room = new Room();
    @Override
    public void buildLength() {
        room.setLength("100米");
    }
    @Override
    public void buildWidth() {
        room.setWidth("120米");
    }
    @Override
    public void buildHeight() {
        room.setHeight("5米");
    }
    @Override
    public Room buildRoom() {
        return room;
    }
}

/*
 * 小房子的具體建構者
 */
public class SmallRoomBuilder implements Builder {
    private final Room room = new Room();
    @Override
    public void buildLength() {
        room.setLength("30米");
    }
    @Override
    public void buildWidth() {
        room.setWidth("40米");
    }
    @Override
    public void buildHeight() {
        room.setHeight("3米");
    }
    @Override
    public Room buildRoom() {
        return room;
    }
}

/**
 * 指揮者,引入指揮者的目的:指定構造流程、方式
 */
public class Director {

    // 引入建構器
    private final Builder builder;

    // 構造注入
    public Director(Builder builder) {
        this.builder = builder;
    }

    // 建構房間方法
    public Room buildRoom() {
        // 建構房間,分别建構長寬高,然後就能能夠建構一個房間
        builder.buildLength();
        builder.buildWidth();
        builder.buildHeight();
        return builder.buildRoom();
    }
}

/**
 * 用戶端
 */
public class Client {
    public static void main(String[] args) {
        // 指定構造者,交由指揮者建構大房子
        Room largeRoom = new Director(new LargeRoomBuilder()).buildRoom();
        System.out.println(largeRoom);

        // 指定構造者,交由指揮者建構小房子
        Room smallRoom = new Director(new SmallRoomBuilder()).buildRoom();
        System.out.println(smallRoom);
    }
}           

輸出列印

架構師備戰(三)-軟體工程(十二) 設計模式之建立型模式

6、小結

這裡主要說明了設計模式中的建立型模式的概念,類圖,以及代碼的實作。而結構型模式和行為型模式,将會在後面去說明,因為建立型模式相對比較簡單,也容易了解,是以放在最前面。後買你雖然會難一些,但是我們要知難而上。學無止境,加油!

繼續閱讀