深入理解原型模式 ——通過複製生成例項

SnailClimb發表於2019-01-19

Java面試通關手冊(Java學習指南,歡迎Star,會一直完善下去,歡迎建議和指導):https://github.com/Snailclimb/Java_Guide

系列文章回顧:

設計模式專欄
深入理解單例模式
深入理解工廠模式

深入理解建造者模式 ——組裝複雜的例項

歷史文章推薦:

一隻準程式猿的嘮叨

可能是最漂亮的Spring事務管理詳解

Java多執行緒學習(八)執行緒池與Executor 框架

面試中關於Redis的問題看這篇就夠了

目錄:

[TOC]

一 原型模式介紹

在物件導向系統中,使用原型模式來複制一個物件自身,從而克隆出多個與原型物件一模一樣的物件。

另外在軟體系統中,有些物件的建立過程較為複雜,而且有時候需要頻繁建立,原型模式通過給出一個原型物件來指明所要建立的物件的型別,然後用複製這個原型物件的辦法建立出更多同型別的物件,這就是原型模式的意圖所在。

孫悟空分身

1.1 定義

GOF給出的原型模式定義如下:

Specify the kind of objects to create using a prototypical instance, and create new objects by copying this prototype. (使用原型例項指定將要建立的物件型別,通過複製這個例項建立新的物件。)

1.2 原型模式適用場景

我們現在一般會使用new關鍵字指定類名生成類的例項(PS:我們以前使用java.lang.Cloneable的一個很大原因是使用new建立物件的速度相對來說會慢一些,隨著JVM效能的提升,new的速度和Object的clone()方法的速度差不多了。)。

使用new關鍵字建立類的時候必須指定類名,但是在開發過程中也會有“在不指定類名的前提下生成例項”的需求。例如,在下面這些情況下,就需要根據現有的例項來生成新的例項。

1) 物件種類繁多,無法將他們整合到一個類的時候;

2) 難以根據類生成例項時;

3) 想解耦框架與生成的例項時。

如果想要讓生成例項的框架不再依賴於具體的類,這時,不能指定類名來生成例項,而要事先“註冊”一個“原型”例項,然後通過複製該例項來生成新的例項。

1.3 模式分析

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

能夠實現克隆的Java類必須實現一個標識介面Cloneable,表示這個Java類支援複製。如果一個類沒有實現這個介面但是呼叫了clone()方法,Java編譯器將丟擲一個CloneNotSupportedException異常。

注意: `java.lang.Cloneable 只是起到告訴程式可以呼叫clone方法的作用,它本身並沒有定義任何方法。

在使用原型模式克隆物件時,根據其成員物件是否也克隆,原型模式可以分為兩種形式:深克隆淺克隆

關於深克隆淺克隆 的詳細內容可以參考:詳解Java中的clone方法

1.4 模式優缺點分析

原型模式的優點:

  • 當建立新的物件例項較為複雜時,使用原型模式可以簡化物件的建立過程,通過一個已有例項可以提高新例項的建立效率。
  • 可以動態增加或減少產品類。
  • 原型模式提供了簡化的建立結構。
  • 可以使用深克隆的方式儲存物件的狀態。

原型模式的缺點:

  • 需要為每一個類配備一個克隆方法,而且這個克隆方法需要對類的功能進行通盤考慮,這對全新的類來說不是很難,但對已有的類進行改造時,不一定是件容易的事,必須修改其原始碼,違背了“開閉原則”。
  • 在實現深克隆時需要編寫較為複雜的程式碼。

二 示例程式

下面示例程式的作用是將字串放入方框中顯示出來或者是加了下劃線顯示出來。

類和介面一覽表:

類和介面一覽表

示例程式類圖:

2.1 Product介面 (Prototype)

Product介面是複製功能介面,該介面繼承了java.lang.Cloneable(只有實現了該介面的類的例項才可以呼叫clone()方法複製例項,否則會丟擲異常).
另外需要注意:`java.lang.Cloneable 只是起到告訴程式可以呼叫clone方法的作用,它本身並沒有定義任何方法。

package prototype_pattern;

public interface Product extends Cloneable{
   //use方法是用於“使用”的方法,具體怎麼“使用”,則被交給子類去實現。
    public abstract void use(String s);
    //creatClone方法是用於複製例項的方法
    public abstract Product creatClone();

}

2.2 Manager類(Client)

Manager類使用Product介面來複制例項。

Product介面以及Manager類的程式碼完全沒有出現在MessageBox類UnderlinePen類的名字,因此這意味著我們可以獨立地修改Product介面以及Manager類,不受MessageBox類UnderlinePen類的影響。這是非常重要的,因為 一旦在類中使用到了別的類名,就意味著該類與其他類緊密的地耦合在了一起 。在Manager類中,並沒有寫明具體的類名, 僅僅使用了Product這個介面名。也就是說,Product介面成為了連線Manager類與其他具體類之間的橋樑。

package prototype_pattern;

import java.util.HashMap;

public class Manager {
    //儲存例項的“名字”和“例項”之間的對應關係
    private HashMap<String, Product> showcase=new HashMap<String, Product>();
    //register方法將接收到的一組“名字”和“Product介面”註冊到showcase中。這裡Product是實現Product介面的例項,具體還未確定
    public void register(String name ,Product product){
        showcase.put(name, product);
    }
    public Product create(String productname){
        Product p=showcase.get(productname);
        return p.creatClone();
    }

}

2.3 MessageBox類(ConcreteProtorype)

裝飾方框樣式的具體原型,實現了 Product介面,實現複製現有例項並生成新例項的方法。

package prototype_pattern;

public class MessageBox implements Product {
    //儲存的是裝飾方框使用的字元樣式
    private char decochar;

    public MessageBox(char decochar) {
        this.decochar = decochar;
    }

    @Override
    public void use(String s) {
     int length=s.getBytes().length;
     for (int i = 0; i < length+4; i++) {
            System.out.print(decochar);    
    }
     System.out.println("");
     System.out.println(decochar+" "+s+" "+decochar);
     for (int i = 0; i < length+4; i++) {
        System.out.print(decochar);
    }
     System.out.println("");
    }
    
    //該方法用於複製自己
    @Override
    public Product creatClone() {
        Product p=null;
        try {
            p=(Product) clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }

}

只有類自己(或是它的子類)能夠呼叫Java語言中定義的clone方法。當其他類要求複製例項時,必須先呼叫createClone這樣的方法,然後在該方法內部在呼叫clone方法。

2.4 UnderlinePen類(ConcreteProtorype)

下劃線樣式的具體原型,實現了Product介面,用於實現複製現有例項並生成新例項的方法。UnderlinePen類的實現幾乎和 MessageBox類一樣,不同的可能只是use方法的實現。

package prototype_pattern;

public class UnderlinePen implements Product {

    private char ulchar;

    public UnderlinePen(char ulchar) {
        this.ulchar = ulchar;
    }

    @Override
    public void use(String s) {
        int length = s.getBytes().length;
        System.out.println("""+s+""");
        for (int i = 0; i <length+2; i++) {
            System.out.print(ulchar);
            
        }
        System.out.println("");
    }

    @Override
    public Product creatClone() {
        Product p=null;
        try {
            p=(Product) clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }

}

2.5 Main類

Main類首先生成Manager例項。接著,在Manager例項中通過`register方法註冊了UnderlinePen類的例項(帶名字)和MessageBox類的例項(帶名字)。

package prototype_pattern;

public class Main {

    public static void main(String[] args) {
        Manager manager = new Manager();
        UnderlinePen underlinePen=new UnderlinePen(`~`);
        MessageBox mbox=new MessageBox(`*`);
        MessageBox sbox=new MessageBox(`/`);
        manager.register("Strong message", underlinePen);
        manager.register("Waring Box", mbox);
        manager.register("Slash Box", sbox);
        Product p1=manager.create("Strong message");
        p1.use("hello world");
        Product p2=manager.create("Waring Box");
        p2.use("hello world");
        Product p3=manager.create("Slash Box");
        p3.use("hello world");
    }

}

執行結果:

執行結果

三 原型模式的角色分析

通過上面的例子,相信大家對於原型模式有了更進一步的認識,下面我們看看原型模式的幾個登場角色。

3.1 Prototype(抽象原型類)

Product角色負責定義用於複製現有例項來生成新例項的方法。在示例程式中的Product介面就是該角色。

3.2 ConcretePrototype(具體原型類)

ConcretePrototype角色負責實現複製現有例項並生成新例項的方法。在示例程式中,MessageBox和UnderlinePen都是該角色。

3.3 Client(客戶類/使用者)

Client角色負責使用複製例項的方法生成新的例項。在示例程式中,Manager類扮演的就是該角色。

Prototype模式的類圖:
Prototype模式的類圖

四 原型模式的實際應用案例

(1) 原型模式應用於很多軟體中,如果每次建立一個物件要花大量時間,原型模式是最好的解決方案。很多軟體提供的複製(Ctrl + C)貼上(Ctrl + V)操作就是原型模式的應用,複製得到的物件與原型物件是兩個型別相同但記憶體地址不同的物件,通過原型模式可以大大提高物件的建立效率。

(2) 在Struts2中為了保證執行緒的安全性,Action物件的建立使用了原型模式,訪問一個已經存在的`Action物件時將通過克隆的方式建立出一個新的物件,從而保證其中定義的變數無須進行加鎖實現同步,每一個Action中都有自己的成員變數,避免Struts1因使用單例模式而導致的併發和同步問題。

(3) 在Spring中,使用者也可以採用原型模式來建立新的bean例項,從而實現每次獲取的是通過克隆生成的新例項,對其進行修改時對原有例項物件不造成任何影響。

五 總結

本文主要介紹了:什麼是原型模式、原型模式的優缺點以及使用場景。另外,簡單介紹了深拷貝和淺拷貝以及原型模式的實際應用案例。

參考:

《圖解設計模式》

歡迎關注我的微信公眾號:”Java面試通關手冊“(一個有溫度的微信公眾號,無廣告,單純技術分享,期待與你共同進步~~~堅持原創,分享美文,分享各種Java學習資源。)

最後,就是使用阿里雲伺服器一段時間後,感覺阿里雲真的很不錯,就申請做了阿里雲大使,然後這是我的優惠券地址.

相關文章