設計模式 #3 (原型模式、建造者模式)

凌丹妙耀發表於2020-09-16

設計模式 #3 (原型模式、建造者模式)


文章中所有工程程式碼和UML建模檔案都在我的這個GitHub的公開庫--->DesignPatternStar來一個好嗎?秋梨膏!


原型模式

簡述:用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。

反例 #1 :

public class negtive {
    /*==============服務端=======================*/
    static class Resume{
        private String name;
        private Integer age;

        public String getName() {
            return name;
        }

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

        public Integer getAge() {
            return age;
        }

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

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

    /*==============客戶端=========================*/
    public static void main(String[] args) {
        Resume resume01= new Resume();
        resume01.setName("ling001");
        resume01.setAge(20);

        System.out.println(resume01);

        Resume resume02= new Resume();
        resume02.setName("ling002");
        resume02.setAge(23);

        System.out.println(resume02);
    }
}

複製多份簡歷需要一個個去new。我們們都是IT人士了,得專業點,重複無用功怎麼能做呢?程式設計師要說最熟的,難道不是Ctrl+C+Ctrl+V嗎?(手動滑稽保命)Java就提供了這種複製貼上的辦法,不過他有自己的名字--Clone

正例 #1:

public class postvie_01 {
        /*==============服務端=======================*/
        static class Resume implements Cloneable{
            private String name;
            private Integer age;

            public String getName() {
                return name;
            }

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

            public Integer getAge() {
                return age;
            }

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

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

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

        /*==============客戶端=========================*/
        public static void main(String[] args) throws CloneNotSupportedException {
            Resume resume01= new Resume();
            resume01.setName("ling001");
            resume01.setAge(20);

            Resume resume02= (Resume) resume01.clone();
            resume02.setName("li666");

            System.out.println(resume01);
            System.out.println(resume02);
            System.out.println(resume01.equals(resume02));
        }
}

不需要new,只需要服務端先實現一個Cloneable介面,並重寫clone方法即可。而且作用堪比new一個新的物件,因為克隆和被克隆的物件並不是同一個,equals的時候得到的是false的。

這時候,新需求來了(這次沒有產品經理,別拿刀K自己):為簡歷增加一個工作經歷的內容,這時候:

反例 #2:

public class negtive_02 {
    /*==============服務端=======================*/
    static class Resume implements Cloneable{
        private String name;
        private Integer age;
        private Date workExperience;

        public Date getWorkExperience() {
            return workExperience;
        }

        public void setWorkExperience(Date workExperience) {
            this.workExperience = workExperience;
        }

        public String getName() {
            return name;
        }

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

        public Integer getAge() {
            return age;
        }

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

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

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

    /*==============客戶端=========================*/
    public static void main(String[] args) throws CloneNotSupportedException{
        Resume resume01= new Resume();
        Date date = new Date();
        resume01.setName("ling001");
        resume01.setAge(20);
        resume01.setWorkExperience(date);

        Resume resume02= (Resume) resume01.clone();
        resume02.getWorkExperience().setTime(0);

        System.out.println(resume02);
        System.out.println(resume01);

        System.out.println(resume01.equals(resume02));
    }
}

image-20200915174612254

這是一個關於深拷貝、淺拷貝的問題。

深拷貝與淺拷貝

淺拷貝(淺複製):clone得到的物件a其實只是對被clone物件b的引用,即物件a是指向b物件上的。

image-20200915174807631

深拷貝(深複製):clone得到的物件a是對被clone物件b的複製,即物件ab是兩個不同的物件,a只複製了b的內容。

image-20200915175541062

Java中,對一物件的clone深拷貝物件的基本型別欄位,淺拷貝引用型別欄位。

這時候要將淺拷貝改為深拷貝。

正例 #2:只需要重寫物件的clone方法即可。

		@Override
        public Object clone() throws CloneNotSupportedException {
            Resume clone = (Resume) super.clone();
            Date cloneDate = (Date) clone.getWorkExperience().clone();
            clone.setWorkExperience(cloneDate);
            return clone;
        }

image-20200915185444082

其實就是對淺拷貝的欄位再進行深拷貝

以上面用到的Date引用型別物件為例:

image-20200915185501800

可以看到Date是實現了Cloneable介面的,即表示Date也是可以進行clone(克隆)的。只需要將淺拷貝的Date再使用clone方法進行一次深拷貝,再賦值給clone的物件即可。具體參照上面重寫的clone方法。

總結:

這種模式是實現了一個原型介面,該介面用於建立當前物件的克隆。當直接建立物件的代價比較大時,則採用這種模式。例如,一個物件需要在一個高代價的資料庫操作之後被建立。我們可以快取該物件,在下一個請求時返回它的克隆,在需要的時候更新資料庫,以此來減少資料庫呼叫。

建造者模式

簡述:建造者模式(Builder Pattern)使用多個簡單的物件一步一步構建成一個複雜的物件。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。

先明確實體類

public class Computer {
        private  String cpu;
        private String gpu;
        private  String Hd;
        private String RAM;

    public String getCpu() {
        return cpu;
    }

    public void setCpu(String cpu) {
        this.cpu = cpu;
    }

    public String getGpu() {
        return gpu;
    }

    public void setGpu(String gpu) {
        this.gpu = gpu;
    }

    public String getHd() {
        return Hd;
    }

    public void setHd(String hd) {
        Hd = hd;
    }

    public String getRAM() {
        return RAM;
    }

    public void setRAM(String RAM) {
        this.RAM = RAM;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "cpu='" + cpu + '\'' +
                ", gpu='" + gpu + '\'' +
                ", Hd='" + Hd + '\'' +
                ", RAM='" + RAM + '\'' +
                '}';
    }
}

反例 #1 :

廢話不多說,建立物件。

public class negtive_01 {
    public static void main(String[] args) {
        Computer computer_01 = new Computer();
        computer_01.setCpu("9700k");
        computer_01.setGpu("gtx2080ti");
        computer_01.setHd("SSD--1T");
        computer_01.setRAM("32G");

        Computer computer_02 = new Computer();
        computer_02.setCpu("9600k");
        computer_02.setGpu("gtx1080ti");
        computer_02.setHd("SSD--500G");
        computer_02.setRAM("16G");

        System.out.println(computer_02);
        System.out.println(computer_01);
    }
}

缺陷:建造複雜物件的時候,客戶端程式猿要炸,造成客戶端程式碼臃腫,且違反迪米特法則

反例 #2:

public class negtive_02 {
    /*=============服務端==========================*/
    static class HighComputerBuilder {
        private Computer computer = new Computer();

        public Computer build() {
            computer.setCpu("9700k");
            computer.setGpu("gtx2080ti");
            computer.setHd("SSD--1T");
            computer.setRAM("32G");
            return computer;
        }
    }

    static class High_02ComputerBuilder {
        private Computer computer = new Computer();

        public Computer build() {
            computer.setCpu("9600k");
            computer.setGpu("gtx1080ti");
            computer.setHd("SSD--500G");
            computer.setRAM("16G");
            return computer;
        }
    }
/*=====================客戶端===============================*/
    public static void main(String[] args) {
        HighComputerBuilder builder_01 = new HighComputerBuilder();
        Computer computer_01 =builder_01.build();

        High_02ComputerBuilder builder_02 = new High_02ComputerBuilder();
        Computer computer_02 =builder_02.build();

        System.out.println(computer_01);
        System.out.println(computer_02);
    }
}

創造了建造者類,用於建立複雜物件。

UML類圖如下:

image-20200916133832600

缺陷:建造者遺漏部分建造步驟編譯也會通過,會造成建造出來的物件不符合要求。比如,漏執行某一步驟時,使得部分值為null,後續物件屬性被呼叫時,可能會丟擲空指標NullPointerException異常,會造成程式崩潰。

反例 #3:

public class negtive_03 {
    /*=============服務端==========================*/
    interface ComputerBuilder{
        Computer build();
        void setCpu();
        void setGpu();
        void setHd();
        void setRAM();
    }

    static class HighComputerBuilder implements ComputerBuilder{
        private Computer computer = new Computer();

        @Override
        public Computer build() {
            return computer;
        }

        @Override
        public void setCpu() {
            computer.setCpu("9700k");
        }

        @Override
        public void setGpu() {
            computer.setGpu("gtx2080ti");
        }

        @Override
        public void setHd() {
            computer.setHd("SSD--1T");
        }

        @Override
        public void setRAM() {
            computer.setRAM("32G");
        }
    }

    static class High_02ComputerBuilder implements ComputerBuilder{
        private Computer computer = new Computer();

        @Override
        public Computer build() {
            return computer;
        }

        @Override
        public void setCpu() {
            computer.setCpu("9600k");
        }

        @Override
        public void setGpu() {
            computer.setGpu("gtx1080ti");
        }

        @Override
        public void setHd() {
            computer.setHd("SSD--500G");
        }

        @Override
        public void setRAM() {
            computer.setRAM("16G");
        }
    }
    /*==============客戶端=====================================*/
    public static void main(String[] args) {
        HighComputerBuilder builder_01 = new HighComputerBuilder();
        builder_01.setCpu();
        builder_01.setGpu();
        builder_01.setHd();
        builder_01.setRAM();
        Computer computer_01 =builder_01.build();

        High_02ComputerBuilder builder_02 = new High_02ComputerBuilder();
        builder_02.setCpu();
        builder_02.setGpu();
        builder_02.setHd();
        builder_02.setRAM();
        Computer computer_02 =builder_02.build();

        System.out.println(computer_01);
        System.out.println(computer_02);
    }
}

創造了建造者介面,建立者不會再遺漏步驟。

UML類圖如下:

image-20200916161454045

缺陷

  • 每一個builder都要自己去呼叫setXXX方法進行建立,造成程式碼重複。

  • 需要客戶端自己執行建立步驟,建造複雜物件的時候,容易造成客戶端程式碼臃腫,且違反迪米特法則。而且客戶端會出現遺漏步驟的情況。又回到了原點的感覺???

正例 #1:

public class postive {
    /*=============服務端==========================*/
    interface ComputerBuilder{
        Computer getComputer();
        void setCpu();
        void setGpu();
        void setHd();
        void setRAM();
    }

    static class HighComputerBuilder implements ComputerBuilder {
        private Computer computer = new Computer();

        @Override
        public Computer getComputer() {
            return computer;
        }

        @Override
        public void setCpu() {
            computer.setCpu("9700k");
        }

        @Override
        public void setGpu() {
            computer.setGpu("gtx2080ti");
        }

        @Override
        public void setHd() {
            computer.setHd("SSD--1T");
        }

        @Override
        public void setRAM() {
            computer.setRAM("32G");
        }
    }

    static class High_02ComputerBuilder implements ComputerBuilder {
        private Computer computer = new Computer();

        @Override
        public Computer getComputer() {
            return computer;
        }

        @Override
        public void setCpu() {
            computer.setCpu("9600k");
        }

        @Override
        public void setGpu() {
            computer.setGpu("gtx1080ti");
        }

        @Override
        public void setHd() {
            computer.setHd("SSD--500G");
        }

        @Override
        public void setRAM() {
            computer.setRAM("16G");
        }
    }
	//指揮者
    static class Director {

        public Computer build(ComputerBuilder builder){
            builder.setCpu();
            builder.setGpu();
            builder.setRAM();
            builder.setHd();
            return builder.getComputer();
        }
    }
    /*==============客戶端=====================================*/
    public static void main(String[] args) {
        Director director = new Director();

        Computer computer_01 =director.build(new HighComputerBuilder());
        Computer computer_02 =director.build(new High_02ComputerBuilder());

        System.out.println(computer_01);
        System.out.println(computer_02);
    }
}

UML類圖如下:

image-20200916162337824

此時在需要增加一個不同配置的A_Computer型別計算機,只需要編寫一個A_Builder類實現ComputerBuilder介面,再傳給指揮者Director進行建立即可得到一個A_Computer型別的Computer物件。符合開閉原則

總結一下建造者模式的優點

  • 建立物件的過程保持穩定。(通過ComputerBuilder介面保持穩定)

  • 建立過程只需要編寫一次(通過實現ComputerBuilder介面的類保持穩定)

  • 保持擴充套件性,需要建立新型別的物件時,只需要建立新的Builder,再使用指揮者Director進行呼叫進行建立即可。

  • 增加指揮者Director保證步驟穩定執行,客戶端不需要知道建立物件的具體步驟,符合迪米特法則


建造者模式和工廠模式的區別

  • 工廠模式注重new一個物件就可以,是否得到了這一物件,更多地關注new的結果。
  • 建造者模式注重保證new的物件穩定可用,保證不出現細節缺漏,更多關注new的細節、過程。

相關文章