设计模式 #3 (原型模式、建造者模式)

设计模式 #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的细节、过程。