四、原型模式與建造者模式詳解

5.原型模式

5.1.課程目標

1、掌握原型模式和建造者模式的應(yīng)用場景

2、掌握原型模式的淺克隆和深克隆的寫法。

3、掌握建造者模式的基本寫法。

4、了解克隆是如何破壞單例的。

5、了解原型模式的優(yōu)、缺點

6、掌握建造者模式和工廠模式的區(qū)別。

5.2.內(nèi)容定位

1、已了解并掌握工廠模式的人群。

2、已了解并掌握單例模式。

3、聽說過原型模式,但不知道應(yīng)用場景的人群。

5.3.定義

原型模式(PrototypePattern)是指原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象,屬于創(chuàng)建型模式。

官方原文:Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.

原型模式的核心在于拷貝原型對象。以系統(tǒng)中已存在的一個對象為原型,直接基于內(nèi)存二進制流進行拷貝,無需再經(jīng)歷耗時的對象初始化過程(不調(diào)用構(gòu)造函數(shù)),性能提升許多。當對象的構(gòu)建過程比較耗時時,可以利用當前系統(tǒng)中已存在的對象作為原型,對其進行克?。ㄒ话闶腔诙M制流的復(fù)制),躲避初始化過程,使得新對象的創(chuàng)建時間大大減少。下面,我們來看看原型模式類結(jié)構(gòu)圖:

<img src="https://gitee.com/woshiamiaojiang/image-hosting/raw/master/image-20200301170301439.png" alt="image-20200301170301439" style="zoom: 50%;" />

從 UML 圖中,我們可以看到,原型模式 主要包含三個角色:

客戶(Client):客戶類提出創(chuàng)建對象的請求。

抽象原型(Prototype):規(guī)定拷貝接口。

具體原型(Concrete Prototype):被拷貝的對象。

注:對不通過 new 關(guān)鍵字,而是通過對象拷貝來實現(xiàn)創(chuàng)建對象的模式就稱作原型模式。

5.4.原型模式的應(yīng)用場景

你一定遇到過大篇幅getter、setter賦值的場景。

代碼非常工整,命名非常規(guī)范,注釋也寫的很全面,其實這就是原型模式的需求場景。但是,大家覺
得這樣的代碼優(yōu)雅嗎?我認為,這樣的代碼屬于純體力勞動。那原型模式,能幫助我們解決這樣的問題。

原型模式主要適用于以下場景:

1、類初始化消耗資源較多。

2、new產(chǎn)生的一個對象需要非常繁瑣的過程(數(shù)據(jù)準備、訪問權(quán)限等)

3、構(gòu)造函數(shù)比較復(fù)雜。

4、循環(huán)體中生產(chǎn)大量對象時。

在 Spring 中,原型模式應(yīng)用得非常廣泛。例如 scope=“prototype”,在我們經(jīng)常用的
JSON.parseObject()也是一種原型模式。

5.5.原型模式的通用寫法(淺拷貝)

一個標準的原型模式代碼,應(yīng)該是這樣設(shè)計的。先創(chuàng)建原型IPrototype接口:

public interface IPrototype<T> {
    T clone();
}

創(chuàng)建具體需要克隆的對象ConcretePrototype

public class ConcretePrototype implements IPrototype {

    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public ConcretePrototype clone() {
        ConcretePrototype concretePrototype = new ConcretePrototype();
        concretePrototype.setAge(this.age);
        concretePrototype.setName(this.name);
        return concretePrototype;
    }

    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

測試代碼:

public class Client {
    public static void main(String[] args) {
        //創(chuàng)建原型對象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(18);
        prototype.setName("Tom");
        System.out.println(prototype);

        //拷貝原型對象
        ConcretePrototype cloneType = prototype.clone();
        System.out.println(cloneType);
    }
}

運行結(jié)果:

ConcretePrototype{age=18, name='Tom'}
ConcretePrototype{age=18, name='Tom'}

這時候,有小伙伴就問了,原型模式就這么簡單嗎?對,就是這么簡單。在這個簡單的場景之下,看上
去操作好像變復(fù)雜了。但如果有幾百個屬性需要復(fù)制,那我們就可以一勞永逸。但是,上面的復(fù)制過程
是我們自己完成的,在實際編碼中,我們一般不會浪費這樣的體力勞動,JDK已經(jīng)幫我們實現(xiàn)了一個現(xiàn)
成的API,我們只需要實現(xiàn)Cloneable接口即可。來改造一下代碼,修改ConcretePrototype類:

@Data
public class ConcretePrototype implements Cloneable {

    private int age;
    private String name;

    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

重新運行,也會得到同樣的結(jié)果。

有了JDK的支持再多的屬性復(fù)制我們也能輕而易舉地搞定了。下面我
們再來做一個測試,給ConcretePrototype增加一個個人愛好的屬性hobbies:

@Data
public class ConcretePrototype implements Cloneable {

    private int age;
    private String name;
    private List<String> hobbies;

    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

修改客戶端測試代碼:

public class Client {

    public static void main(String[] args) {
        //創(chuàng)建原型對象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(18);
        prototype.setName("Tom");
        List<String> hobbies = new ArrayList<String>();
        hobbies.add("書法");
        hobbies.add("美術(shù)");
        prototype.setHobbies(hobbies);

        //拷貝原型對象
        ConcretePrototype cloneType = prototype.clone();
        cloneType.getHobbies().add("技術(shù)控");
        System.out.println("原型對象:" + prototype);
        System.out.println("克隆對象:" + cloneType);
        System.out.println(prototype == cloneType);
        
        System.out.println("原型對象的愛好:" + prototype.getHobbies());
        System.out.println("克隆對象的愛好:" + cloneType.getHobbies());
        System.out.println(prototype.getHobbies() == cloneType.getHobbies());
    }
}

運行結(jié)果:

原型對象:ConcretePrototype(age=18, name=Tom, hobbies=[書法, 美術(shù), 技術(shù)控])
克隆對象:ConcretePrototype(age=18, name=Tom, hobbies=[書法, 美術(shù), 技術(shù)控])
false
原型對象的愛好:[書法, 美術(shù), 技術(shù)控]
克隆對象的愛好:[書法, 美術(shù), 技術(shù)控]
true

我們給,復(fù)制后的克隆對象新增一項愛好,發(fā)現(xiàn)原型對象也發(fā)生了變化,這顯然不符合我們的預(yù)期。
因為我們希望克隆出來的對象應(yīng)該和原型對象是兩個獨立的對象,不應(yīng)該再有聯(lián)系了。從測試結(jié)果分析
來看,應(yīng)該是hobbies共用了一個內(nèi)存地址,意味著復(fù)制的不是值,而是引用的地址。這樣的話,如果我們修改任意一個對象中的屬性值,prototype 和cloneType的hobbies值都會改變。這就是我們常
說的淺克隆。只是完整復(fù)制了值類型數(shù)據(jù),沒有賦值引用對象。換言之,所有的引用對象仍然指向原來
的對象,顯然不是我們想要的結(jié)果。那如何解決這個問題呢?下面我們來看深度克隆繼續(xù)改造。

擴展知識:String對象在內(nèi)存中是不可變的(final類型),雖然克隆后,兩個對象String的引用指向的是同一個內(nèi)存地址,但是如果給克隆后的對象的String屬性改變值,那么相當于是在內(nèi)存中重新開辟了一塊內(nèi)存來存儲這個改變的值,而此時的String屬性對象就指向了該內(nèi)存值,所以這個時候克隆前和克隆后對象的String屬性是不一樣的)。

String 每次賦值,相當于new String()。

5.6.使用序列化實現(xiàn)深度克隆

在上面的基礎(chǔ)上我們繼續(xù)改造,來看代碼,增加一個deepClone()方法:

@Data
public class ConcretePrototype implements Cloneable,Serializable {

    private int age;
    private String name;
    private List<String> hobbies;

    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    public ConcretePrototype deepClone(){
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (ConcretePrototype)ois.readObject();
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
}

來看客戶端調(diào)用代碼:

public class Client {
    public static void main(String[] args) {
        //創(chuàng)建原型對象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(18);
        prototype.setName("Tom");
        List<String> hobbies = new ArrayList<String>();
        hobbies.add("書法");
        hobbies.add("美術(shù)");
        prototype.setHobbies(hobbies);

        //拷貝原型對象
        ConcretePrototype cloneType = prototype.deepClone();
        cloneType.getHobbies().add("技術(shù)控");

        System.out.println("原型對象:" + prototype);
        System.out.println("克隆對象:" + cloneType);
        System.out.println(prototype == cloneType);

        System.out.println("原型對象的愛好:" + prototype.getHobbies());
        System.out.println("克隆對象的愛好:" + cloneType.getHobbies());
        System.out.println(prototype.getHobbies() == cloneType.getHobbies());
    }
}

運行程序,我們發(fā)現(xiàn)得到了我們期望的結(jié)果:

原型對象:ConcretePrototype(age=18, name=Tom, hobbies=[書法, 美術(shù)])
克隆對象:ConcretePrototype(age=18, name=Tom, hobbies=[書法, 美術(shù), 技術(shù)控])
false
原型對象的愛好:[書法, 美術(shù)]
克隆對象的愛好:[書法, 美術(shù), 技術(shù)控]
false

5.7.克隆破壞單例模式

如果我們克隆的目標的對象是單例對象,那意味著,深克隆就會破壞單例。實際上防止克隆破壞單
例解決思路非常簡單,禁止深克隆便可。要么你我們的單例類不實現(xiàn) Cloneable 接口;要么我們重寫
clone()方法,在clone方法中返回單例對象即可,具體代碼如下:

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

5.8.原型模式在源碼中的應(yīng)用

先來JDK中Cloneable接口:

public interface Cloneable {
}

接口定義還是很簡單的,我們找源碼其實只需要找到看哪些接口實現(xiàn)了 Cloneable 即可。來看
ArrayList類的實現(xiàn)。

Object方法

protected native Object clone() throws CloneNotSupportedException;

ArrayList是實現(xiàn)的clone方法

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
}

我們發(fā)現(xiàn)方法中只是將List中的元素循環(huán)遍歷了一遍。這個時候我們再思考一下,是不是這種形式
就是深克隆呢?其實用代碼驗證一下就知道了,繼續(xù)修改 ConcretePrototype 類,增加一個
deepCloneHobbies()方法:

@Data
public class ConcretePrototype implements Cloneable,Serializable {
    ...
    public ConcretePrototype deepCloneHobbies(){
        try {
            ConcretePrototype result = (ConcretePrototype)super.clone();
            result.hobbies = (List)((ArrayList)result.hobbies).clone();
            return result;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
    ...
}

修改客戶端代碼:

public class Client {
    public static void main(String[] args) {
        ...
        //拷貝原型對象
        ConcretePrototype cloneType = prototype.deepCloneHobbies();
        ...
    }
}

運行也能得到期望的結(jié)果。但是這樣的代碼,其實是硬編碼,如果在對象中聲明了各種集合類型,
那每種情況都需要單獨處理。因此,深克隆的寫法,一般會直接用序列化來操作。

5.9.原型模式的優(yōu)缺點

優(yōu)點:

1、性能優(yōu)良,Java自帶的 原型模式 是基于內(nèi)存二進制流的拷貝,比直接new一個對象性能上提
升了許多。

2、可以使用深克隆方式保存對象的狀態(tài),使用原型模式將對象復(fù)制一份并將其狀態(tài)保存起來,簡化
了創(chuàng)建對象的過程,以便在需要的時候使用(例如恢復(fù)到歷史某一狀態(tài)),可輔助實現(xiàn)撤銷操作。

缺點:

1、需要為每一個類配置一個克隆方法。

2、克隆方法位于類的內(nèi)部,當對已有類進行改造的時候,需要修改代碼,違反了開閉原則。

3、在實現(xiàn)深克隆時需要編寫較為復(fù)雜的代碼,而且當對象之間存在多重嵌套引用時,為了實現(xiàn)深克
隆,每一層對象對應(yīng)的類都必須支持深克隆,實現(xiàn)起來會比較麻煩。因此,深拷貝、淺拷貝需要運用得
當。

6.0總結(jié)

克隆方式:1.序列化 反序列化 2.jsonobject 3淺克隆加賦值

淺克?。豪^承Cloneable接口的都是淺克隆。

深克隆兩種方式:序列化,轉(zhuǎn)JSON。

6.建造者模式

6.1.定義

建造者模式(Builder Pattern)是將一個復(fù)雜對象的構(gòu)建過程與它的表示分離,使得同樣的構(gòu)建過
程可以創(chuàng)建不同的表示,屬于創(chuàng)建型模式。使用建造者模式對于用戶而言只需指定需要建造的類型就可
以獲得對象,建造過程及細節(jié)不需要了解。

官方原文:Separate the construction of a complex object from its representation so that the same construction process can create different representations.

建造者模式適用于創(chuàng)建對象需要很多步驟,但是步驟的順序不一定固定。如果一個對象有非常復(fù)雜
的內(nèi)部結(jié)構(gòu)(很多屬性),可以將復(fù)雜對象的創(chuàng)建和使用進行分離。先來看一下建造者模式的類圖:

<img src="https://gitee.com/woshiamiaojiang/image-hosting/raw/master/image-20200301183423834.png" alt="image-20200301183423834" style="zoom: 50%;" />

建造者模式的設(shè)計中主要有四個角色:

1、產(chǎn)品(Product):要創(chuàng)建的產(chǎn)品類對象

2、建造者抽象(Builder):建造者的抽象類,規(guī)范產(chǎn)品對象的各個組成部分的建造,一般由子類
實現(xiàn)具體的建造過程。

3、建造者(ConcreteBuilder):具體的Builder類,根據(jù)不同的業(yè)務(wù)邏輯,具體化對象的各個組成
部分的創(chuàng)建。

4、調(diào)用者(Director):調(diào)用具體的建造者,來創(chuàng)建對象的各個部分,在指導(dǎo)者中不涉及具體產(chǎn)品
的信息,只負責保證對象各部分完整創(chuàng)建或按某種順序創(chuàng)建。

6.2.建造者模式的應(yīng)用場景

建造者模式適用于一個具有較多的零件的復(fù)雜產(chǎn)品的創(chuàng)建過程,由于需求的變化,組成這個復(fù)雜產(chǎn)
品的各個零件經(jīng)常猛烈變化,但是它們的組合方式卻相對穩(wěn)定。

  1. 相同的方法,不同的執(zhí)行順序,產(chǎn)生不同的結(jié)果時
  2. 多個部件或零件,都可以裝配到一個對象中,但是產(chǎn)生的結(jié)果又不相同。
  3. 產(chǎn)品類非常復(fù)雜,或者產(chǎn)品類中的調(diào)用順序不同產(chǎn)生不同的作用。
  4. 當初始化一個對象特別復(fù)雜,參數(shù)多,而且很多參數(shù)都具有默認值時。

建造者模式,只關(guān)注用戶需要什么,將最少的關(guān)鍵字傳過來,生成你想要的結(jié)果。

實際順序是在build方法里面。那是順序和條件都確定了。每個順序和條件都分別存儲下來了。判斷有沒有,有就添加到product后面。當然就是先判斷條件再判斷order順序了

6.3.建造者模式的基本寫法

我們還是以課程為例,一個完整的課程需要由PPT課件、回放視頻、課堂筆記、課后作業(yè)組成,但
是這些內(nèi)容的設(shè)置順序可以隨意調(diào)整,我們用建造者模式來代入理解一下。首先我們創(chuàng)建一個需要構(gòu)造
的產(chǎn)品類Course:

@Data
public class Course {
    private String name;
    private String ppt;
    private String video;
    private String note;
    private String homework;
}

然后創(chuàng)建建造者類CourseBuilder,將復(fù)雜的構(gòu)造過程封裝起來,構(gòu)造步驟由用戶決定:

public class CourseBuilder{

    private Course course = new Course();

    public void addName(String name) {
        course.setName(name);
    }
    
    public void addPPT(String ppt) {
        course.setPpt(ppt);
    }
    
    public void addVideo(String video) {
        course.setVideo(video);
    }
    
    public void addNote(String note) {
        course.setNote(note);
    }
    
    public void addHomework(String homework) {
        course.setHomework(homework);
    }
    
    public Course build() {
        return course;
    }
}

編寫測試類:

public class Test {
    public static void main(String[] args) {
        CourseBuilder builder = new CourseBuilder();
        builder.addName("設(shè)計模式");
        builder.addPPT("【PPT課件】");
        builder.addVideo("【回放視頻】");
        builder.addNote("【課堂筆記】");
        builder.addHomework("【課后作業(yè)】");
        System.out.println(builder.build());
    }
}

運行結(jié)果:

Course(name=設(shè)計模式, ppt=【PPT課件】, video=【回放視頻】, note=【課堂筆記】, homework=【課后作業(yè)】)

來看一下類結(jié)構(gòu)圖:

<img src="https://gitee.com/woshiamiaojiang/image-hosting/raw/master/image-20200301184320779.png" alt="image-20200301184320779" style="zoom: 50%;" />

6.4.建造者模式的鏈式寫法

在平時的應(yīng)用中,建造者模式通常是采用鏈式編程的方式構(gòu)造對象,下面我們來一下演示代碼,修
改CourseBuilder類,將Course變?yōu)镃ourseBuilder的內(nèi)部類。然后,將構(gòu)造步驟添加進去,每完成一個步驟,都返回this:

public class CourseBuilder {
    private Course course = new Course();

    public CourseBuilder addName(String name) {
        course.setName(name);
        return this;
    }

    public CourseBuilder addPPT(String ppt) {
        course.setPpt(ppt);
        return this;
    }

    public CourseBuilder addVideo(String video) {
        course.setVideo(video);
        return this;
    }

    public CourseBuilder addNote(String note) {
        course.setNote(note);
        return this;
    }

    public CourseBuilder addHomework(String homework) {
        course.setHomework(homework);
        return this;
    }

    public Course build() {
        return this.course;
    }

    @Data
    public class Course {
        private String name;
        private String ppt;
        private String video;
        private String note;
        private String homework;
    }
}

客戶端使用:

public class Test {
    public static void main(String[] args) {
        CourseBuilder builder = new CourseBuilder()
                    .addName("設(shè)計模式")
                    .addPPT("【PPT課件】")
                    .addVideo("【回放視頻】")
                    .addNote("【課堂筆記】")
                    .addHomework("【課后作業(yè)】");
        System.out.println(builder.build());
    }
}

這樣寫法是不是很眼熟,好像在哪見過呢?后面我們分析建造者模式在源碼中的應(yīng)用大家就會明白。
接下來,我們再來看一下類圖的變化:

<img src="https://gitee.com/woshiamiaojiang/image-hosting/raw/master/image-20200301184733802.png" alt="image-20200301184733802" style="zoom: 50%;" />

6.5.建造者模式應(yīng)用案例

下面我們再來看一個實戰(zhàn)案例,這個案例參考了開源框架JPA的SQL構(gòu)造模式。是否記得我們在構(gòu)
造SQL查詢條件的時候,需要根據(jù)不同的條件來拼接SQL字符串。如果查詢條件復(fù)雜的時候,我們SQL
拼接的過程也會變得非常復(fù)雜,從而給我們的代碼維護帶來非常大的困難。因此,我們用建造者類
QueryRuleSqlBuilder 將復(fù)雜的構(gòu)造 SQL 過程進行封裝,用 QueryRule 對象專門保存 SQL 查詢時的
條件,最后根據(jù)查詢條件,自動生成SQL語句。來看代碼,先創(chuàng)建QueryRule類:

/**
 * QueryRule,主要功能用于構(gòu)造查詢條件
 */
public final class QueryRule implements Serializable
{
    ... 
    /**
     * 添加升序規(guī)則
     * @param propertyName
     * @return
     */
    public QueryRule addAscOrder(String propertyName) {
        this.ruleList.add(new Rule(ASC_ORDER, propertyName));
        return this;
    }
    
    public QueryRule andEqual(String propertyName, Object value) {
        this.ruleList.add(new Rule(EQ, propertyName, new Object[] { value }).setAndOr(AND));
        return this;
    }
    
    public QueryRule andLike(String propertyName, Object value) {
        this.ruleList.add(new Rule(LIKE, propertyName, new Object[] { value }).setAndOr(AND));
        return this;
    }
    ...
}

然后,創(chuàng)建QueryRuleSqlBuilder類:

/**
 * 根據(jù)QueryRule自動構(gòu)建sql語句
 */
public class QueryRuleSqlBuilder {
    ...   
   /**
    * 處理like
    * @param rule
    */
   private  void processLike(QueryRule.Rule rule) {
      if (ArrayUtils.isEmpty(rule.getValues())) {
         return;
      }
      Object obj = rule.getValues()[0];

      if (obj != null) {
         String value = obj.toString();
         if (!StringUtils.isEmpty(value)) {
            value = value.replace('*', '%');
            obj = value;
         }
      }
      add(rule.getAndOr(),rule.getPropertyName(),"like","%"+rule.getValues()[0]+"%");
   }
    
    /**
     * 處理 =
     * @param rule
     */
    private  void processEqual(QueryRule.Rule rule) {
        if (ArrayUtils.isEmpty(rule.getValues())) {
            return;
        }
        add(rule.getAndOr(),rule.getPropertyName(),"=",rule.getValues()[0]);
    }
    
    /**
     * 處理 order by
     * @param rule 查詢規(guī)則
     */
    private void processOrder(Rule rule) {
        switch (rule.getType()) {
        case QueryRule.ASC_ORDER:
            // propertyName非空
            if (!StringUtils.isEmpty(rule.getPropertyName())) {
                orders.add(Order.asc(rule.getPropertyName()));
            }
            break;
        case QueryRule.DESC_ORDER:
            // propertyName非空
            if (!StringUtils.isEmpty(rule.getPropertyName())) {
                orders.add(Order.desc(rule.getPropertyName()));
            }
            break;
        default:
            break;
        }
    }
    ...
}

創(chuàng)建Order類:

/**
 * sql排序組件
 */
public class Order {
   private boolean ascending; //升序還是降序
   private String propertyName; //哪個字段升序,哪個字段降序
   
   public String toString() {
      return propertyName + ' ' + (ascending ? "asc" : "desc");
   }

   /**
    * Constructor for Order.
    */
   protected Order(String propertyName, boolean ascending) {
      this.propertyName = propertyName;
      this.ascending = ascending;
   }

   /**
    * Ascending order
    *
    * @param propertyName
    * @return Order
    */
   public static Order asc(String propertyName) {
      return new Order(propertyName, true);
   }

   /**
    * Descending order
    *
    * @param propertyName
    * @return Order
    */
   public static Order desc(String propertyName) {
      return new Order(propertyName, false);
   }
}

編寫測試代碼:

public class Test {
    public static void main(String[] args) {
        QueryRule queryRule = QueryRule.getInstance();
        queryRule.addAscOrder("age");
        queryRule.andEqual("addr","Changsha");
        queryRule.andLike("name","Tom");
        QueryRuleSqlBuilder builder = new QueryRuleSqlBuilder(queryRule);
        System.out.println(builder.builder("t_member"));
        System.out.println("Params: " + Arrays.toString(builder.getValues()));
    }
}

這樣一來,我們的客戶端代碼就非常清朗,來看運行結(jié)果:

select * from t_member where  addr = ?  and name like ?  order by age asc
Params: [Changsha, %Tom%]

6.6.建造者模式在源碼中的體現(xiàn)

下面來看建造者模式在哪些源碼中有應(yīng)用呢?首先來看JDK的StringBuilder,它提供append()方
法,給我們開放構(gòu)造步驟,最后調(diào)用toString()方法就可以獲得一個構(gòu)造好的完整字符串,源碼如下:

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
    public StringBuilder append(StringBuffer sb) {
        super.append(sb);
        return this;
    }
}

在MyBatis中也有體現(xiàn),比如CacheBuilder類。

同樣在 MyBatis 中,比如 SqlSessionFactoryBuilder 通過調(diào)用 build()方法獲得的是一個
SqlSessionFactory 類。

當然,在 Spring中自然也少不了,比如 BeanDefinitionBuilder 通過調(diào)用getBeanDefinition()方法獲得一個BeanDefinition對象。

6.7.建造者模式的優(yōu)缺點

建造者模式的優(yōu)點:

1、封裝性好,創(chuàng)建和使用分離;

2、擴展性好,建造類之間獨立、一定程度上解耦。

建造者模式的缺點:

1、產(chǎn)生多余的Builder對象;

2、產(chǎn)品內(nèi)部發(fā)生變化,建造者都要修改,成本較大。

6.8.建造者模式和工廠模式的區(qū)別

建造者模式和工廠模式的區(qū)別

1、建造者模式更加注重方法的調(diào)用順序,工廠模式注重于創(chuàng)建對象。

2、創(chuàng)建對象的力度不同,建造者模式創(chuàng)建復(fù)雜的對象,由各種復(fù)雜的部件組成,工廠模式創(chuàng)建出來
的都一樣。

3、關(guān)注重點不一樣,工廠模式模式只需要把對象創(chuàng)建出來就可以了,而建造者模式中不僅要創(chuàng)建出
這個對象,還要知道這個對象由哪些部件組成。

4、建造者模式根據(jù)建造過程中的順序不一樣,最終的對象部件組成也不一樣。

可以理解為工廠創(chuàng)建過程是靜態(tài)的,構(gòu)建者模式創(chuàng)建過程經(jīng)過外放而變成動態(tài)的。

6.9.總結(jié)

7.0.作業(yè)

1.用JSON方式實現(xiàn)一個原型模式的深克隆,并畫出UML圖。

一行代碼,比IO流簡單。

public ConcretePrototype deepCloneByJSON(){
    try {
        return JSON.parseObject(JSON.toJSONString(this), ConcretePrototype.class);
    }catch (Exception e){
        e.printStackTrace();
        return null;
    }
}

<img src="https://gitee.com/woshiamiaojiang/image-hosting/raw/master/image-20200301182057508.png" alt="image-20200301182057508" style="zoom: 33%;" />

2.請列舉1-3個需要用到建造者模式的業(yè)務(wù)場景。

建造者模式:適用于對象創(chuàng)建需要動態(tài)拼接復(fù)雜屬性值的業(yè)務(wù)場景。

例如:SQL拼接,鏈式編程,NIO。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容