創建型模式之原型模式與建造者模式(二)

一、原型模式

  原型模式是用於創建重複的對象,同時又能保證性能。這種類型的設計模式屬於創建型模式。它提供了一種創建對象的最佳方式。

  這種模式是實現一個原型接口,該接口用於創建當前對象的克隆。當直接創建對象的代價比較大時,則採用這種模式。例如:當一個對象需要在一個高代價的數據庫操作之後被創建。我們可以緩存該對象,在下一個請求返回它的克隆,在需要的時候更新數據庫,以此來減少數據庫的調用。

1,需求分析

  現在有一隻羊,姓名:tom,年齡:1,顏色:白。請編寫程序創建和tom羊屬性完全相同的10只羊。

2,傳統方式解決克隆羊

a)思路分析

  

b)代碼

  源碼:詳情

public class Sheep {

    private String name;
    private int age;
    private String color;

    public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    //...get set方法  
    
}

public class Client {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("tom", 1, "白色");
        Sheep sheep1 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
    }
}

c)優缺點

  • 優點:比較好理解,簡單易操作
  • 確定:在創建新的對象時,總是需要重新獲取原始對象的屬性,如果創建的對象比較複雜時,效率比較低;總是需要重新初始化對象,而不是動態地獲得對象運行時的狀態,不夠靈活

d)改進

  Java中Object類時所有類的根類,Object類提供了一個clone()方法,該方法可以將一個Java對象複製一份,但是需要實現clone的Java類必須要實現一個接口Cloneable,該接口表示該類能夠複製且具有複製的能力 => 原型模式

3,原型模式

a)基本介紹

  • 原型模式是指:用原型實例指定創建對象的種類,並且通過拷貝這些原型,創建新的對象(自我複製)
  • 原型模式是一種創建型設計模式,允許一個對象再創建另外一個可定製的對象,無需知道如果創建的細節
  • 工作原理:通過將一個原型對象傳給那個要發動創建的對象,這個要發動創建的對象通過請求原型對象拷貝它們自己來實施創建,即對象.clone()

b)原理結構圖

  

  原理結構圖說明:

  • Proptype:原型類,聲明一個克隆自己的接口
  • ConcreteProptype:具體的原型類,實現一個克隆自己的操作
  • Client:讓一個原型創建對象克隆自己,從而創建一個新的對象(成員屬性一樣)

c)採用原型模式解決克隆羊問題

   源碼:詳情

public class Sheep implements Cloneable {

    private String name;
    private int age;
    private String color;

    public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    @Override
    protected Sheep clone() {
        try {
            return (Sheep) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    //...get set
}

public class Client {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("tom", 1, "白色");
        Sheep sheep1 = sheep.clone();
        Sheep sheep2 = sheep.clone();
        Sheep sheep3 = sheep.clone();
        System.out.println("sheep = " + sheep);
        System.out.println("sheep1 = " + sheep1);
        System.out.println("sheep2 = " + sheep2);
        System.out.println("sheep3 = " + sheep3);
    }
}

4,原型模式在Spring框架中的源碼分析

  

public class ProtoType {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //獲取monster[通過id獲取monster]
        Object bean = applicationContext.getBean("id01");
        System.out.println("bean="+bean);
        
        Object bean2 = applicationContext.getBean("id01");
        System.out.println("bean="+bean);
        
        System.out.println(bean == bean2);//false:並不是同一個對象,只是兩個bean的屬性相同
    }
}

5,深拷貝與淺拷貝

a)淺拷貝

  • 對於數據類型是基本數據類型的成員變量,淺拷貝會直接進行值傳遞,也就是將該屬性值複製一份給新的對象。
  • 對於數據類型是引用數據類型的成員變量,比如說成員變量是某個數組、某個類的對象等,那麼淺拷貝會進行引用傳遞,也就是隻是將該成員變量的引用值(內存地址)複製一份給新的對象。因爲實際上兩個對象的成員變量都指向同一個實例。在這種情況下,在一個對象中修改該成員變量會影響到另一個對象的成員變量值
  • 前面克隆羊就是淺拷貝,使用默認的clone()方法來實現:sheep=(Sheep)super.clone();

b)深拷貝

  • 複製對象的所有基本數據類型的成員變量值
  • 爲所有引用數據類型的成員變量申請存儲空間,並複製每個引用數據類型成員變量所引用的對象,直到該對象可達的所有對象。也就是說,對象進行深拷貝要對整個對象(包括對象的引用類型)進行拷貝
  • 深拷貝的實現方式:1.重寫clone方法來實現深拷貝;2.通過對象序列化實現深拷貝(推薦)

c)深拷貝應用案例

  源碼:詳情

public class DeepPrototype implements Serializable,Cloneable {

    String name;
    DeepCloneTarget deepCloneTarget;

    //方案1:重寫clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object deep = null;
        //完成對基本類型和String的克隆
        deep = super.clone();
        //對引用類型的屬性進行單獨處理
        DeepPrototype deepPrototype = (DeepPrototype) deep;
        deepPrototype.deepCloneTarget = (DeepCloneTarget) deepCloneTarget.clone();
        return deepPrototype;
    }

    //方案2:通過對昂的序列化實現(推薦)
    public Object deepClone() {
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;

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

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

    @Override
    public String toString() {
        return "DeepPrototype{" +
                "hascode=" + this.hashCode() +
                " , name='" + name + '\'' +
                ", deepCloneTarget=" + deepCloneTarget +
                '}';
    }
}

class DeepCloneTarget implements Cloneable,Serializable {
    private static final long serialVersionUID = 1L;

    private String name1;
    private String name2;

    public DeepCloneTarget(String name1, String name2) {
        this.name1 = name1;
        this.name2 = name2;
    }

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

    @Override
    public String toString() {
        return "DeepCloneTarget{" +
                "hashcode= " + this.hashCode() +
                ", name1='" + name1 + '\'' +
                ", name2='" + name2 + '\'' +
                '}';
    }
}
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        DeepPrototype p = new DeepPrototype();
        p.name = "宋江";
        p.deepCloneTarget = new DeepCloneTarget("大牛", "小牛");

        //方式 1  完成深拷貝
        DeepPrototype p1 = (DeepPrototype) p.clone();
        System.out.println("p..." +p);
        System.out.println("p1..." +p1);

        //方式 2  完成深拷貝
        DeepPrototype p2 = (DeepPrototype) p.deepClone();
        System.out.println("p2..." +p2);
    }
}

6,原型模式注意事項

  • 創建新的對象比較複雜時,可以利用原型模式簡化對象的創建過程,同時也能夠提高效率
  • 並不用重複初始化對象,而是動態地或得對象運行時的狀態
  • 如果原始對象發生變化(增加或者減少屬性),其他克隆對象也會發生相應的變化,無需修改代碼
  • 在實現深克隆的時候推薦使用對象序列化
  • 缺點:需要爲每一個類配備一個克隆方法,這對全新的類來說不是很難,但對已有的類進行改造時,需要修改其源代碼,違背了ocp原則。

 二、建造者模式

1,基本介紹

  建造者模式(Builder Pattern)又叫生成器模式,時一種對象構建模式。它可以將複雜對象的建造過程抽象出來(抽象類別),使這個抽象過程的不同實現方法可以構造出不同表現(屬性)的對象。

  建造者模式是一步一步創建一個複雜的對象,它不允許用戶只通過指定複雜對象的類型和內容就可以構建它們,用戶不需要知道內部的具體構建細節。

a)uml類圖

   

b)角色分析

  • Product(產品角色):一個具體的產品對象
  • Builder(抽象建造者):創建一個Product對象的各個部件指定的接口/抽象類。(接口和抽象類都可以做抽象層)抽象構造者只需要指定建造一個產品的流程,並不管具體的建造細節。
  • ConcreteBuilder(具體建造者):實現接口/抽象類,構建和裝配各個部件。負責具體的建造細節。
  • Director(指揮者):構建一個使用Builder接口的對象。它主要是用於創建一個複雜的對象。它主要有兩個作用,一是:隔離了客戶與對象的生產過程,二是:負責控制產品對象的生產過程。

2,案例分析

a)需求

  • 需要建房子:這一過程爲打樁、砌牆、封頂
  • 房子有各種各樣的,比如普通房、高樓、別墅,各種房子的過程雖然一樣,但是要求使不同的

b)uml分析類圖

  

c)代碼實現

  源碼:詳情

//產品 
public class House {
    private String basic;
    private String wall;
    private String roof;
    
    ...get set

}

//抽象的構建者
public abstract class HouseBuilder {
    protected House house = new House();
    abstract void builderBasic();
    abstract void builderWall();
    abstract void roofed();
    public House build() {
        return house;
    }
}

//具體的構建者實現(省略HighBuilding..)
public class CommonHouse extends HouseBuilder {
    @Override
    public void builderBasic() {
        System.out.println("普通房子打地基");
    }
    @Override
    public void builderWall() {
        System.out.println("普通房子砌牆");
    }
    @Override
    public void roofed() {
        System.out.println("普通房子封頂");
    }
}

//指揮者,這裏會去指定製作流程,返回產品
public class HouseDirector {

    HouseBuilder houseBuilder = null;

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

    public House houseBuilder() {
        houseBuilder.builderBasic();
        houseBuilder.builderWall();
        houseBuilder.roofed();
        return houseBuilder.build();
    }
}

//客戶端調用者
public class Client {
    public static void main(String[] args) {
        HouseDirector houseDirector = new HouseDirector(new CommonHouse());
        House house = houseDirector.houseBuilder();
        System.out.println(house + "構建完畢");
    }
}

3,建造者模式在JDK應用(StringBuilder)

  

  • Appendable接口中定義了多個append方法(抽象方法),即Appendable爲抽象製造者,定義了抽象方法
  • AbstractStringBuilder雖然只是抽象類,但是它實現了Appendable接口的方法,因此這裏的AbstractStringBuilder已經是建造者,只是不能實例化(因爲它是抽象類)
  • StringBuilder既充當了指揮者角色,同時也充當了具體的製造者,建造方法的實現是由AbstractStringBuilder完成,而StringBuilder繼承了AbstractStringBuilder(它重寫了AbstractStringBuilder的append方法)

4,注意事項

  • 客戶端(使用程序)不必知道產品內部組成的細節(直接調用方法即可),將產品本身與產品的創建過程解耦,使得相同的創建過程可以創建不同的產品對象
  • 每一個具體建造者都相互獨立,而與其他的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者,用戶使用不同的具體建造者即可得到不同的產品對象
  • 可以更加精細地控制產品的創建過程。(在指揮者裏可以自行組織建造流程)將複雜產品的創建步驟分解在不同的方法中,使得創建過程更加清晰,也更方便使用程序來控制創建過程。
  • 增加新的具體建造者無須修改原有類庫的代碼,指揮者類針對抽象建造者類編程,系統擴展方便,符合“開閉原則”(例如:實例中只需增加新的構造者OtherHouse集成HouseBuilder,Client就可以直接使用)
  • 建造者模式所創建的產品一般具有較多的共同點,其組成部分相似,如果產品之間差異性很大,則不適合使用建造者模式,因此其使用範圍受到一定的限制
  • 如果產品的內部變化複雜,可能會導致需要定義很多具體建造者類來實現這種變化,導致系統變得很龐大,因此在這種情況下,要考慮是否選擇建造者模式。
  • 抽象工廠VS 建造者模式:抽象工廠模式實現對產品家族的創建:產品家族,具有不同分類維度的產品組合,採用抽象工廠模式不需要關心構建過程,只關係什麼產品由什麼工廠生產即可。建造者模式是要求按照指定的藍圖建造產品,它的主要目的是通過組裝零配件而生產一個新產品。

 

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