设计模式-原型模式、建造者模式

六个创建型模式【额外包括不属于设计模式的简单工厂模式】说完了四个,还剩下两个: 原型模式和建造者模式。


一、原型模式

引入:将一个羊对象一毛一样的复制十份, 最朴素的想法:使用同样的构造参数new十个对象。

缺点很明显,写起来很麻烦。。。

另一种思路:
Object类提供了一个clone()方法,可以将当前对象完整的复制一份。
但是要使用这个方法要求类必须实现Cloneable接口【able型接口是表示这个类具有某项能力。
这个当然来自英语词根-able】
这种想法实现起来就是原型模式的初始模型


原型模式是指,提供一个对象,无需知晓如何复制的细节就能得到和这个对象一毛一样的复制。

孙大圣拔一根猴毛,再造一群孙大圣。

原型模式类图:
在这里插入图片描述
Client是指想克隆自己的类,而ConcretePrototype是指帮助其创建复制的类。

好处:

  • 模板对象改变时,克隆对象跟着改变,而不必去一个一个去动手修改。
  • 动态获取运行状态,指的就是自动修改这一点。
    在这里插入图片描述
    原型模式需要对新加入的属性修改提供方源码,因此违反了开闭原则

1.1 实现Cloneable方式

羊的实体类【需要重写clone()方法】


    class Sheep implements Cloneable {

        private String name;
        private int age;

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

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

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();//无需任何操作,默认就好
        }
    }

//测试对羊类的复制
    @Test
    public void test() throws CloneNotSupportedException {

        Sheep sheep1 = new Sheep("小红", 2);
        Sheep sheep2 = (Sheep)sheep1.clone();
        Sheep sheep3 = (Sheep)sheep1.clone();
        Sheep sheep4 = (Sheep)sheep1.clone();
        Sheep sheep5 = (Sheep)sheep1.clone();
        Sheep sheep6 = (Sheep)sheep1.clone();

        System.out.println(sheep1);
        System.out.println(sheep2);
        System.out.println(sheep3);
        System.out.println(sheep4);
        System.out.println(sheep5);
        System.out.println(sheep6);
    }

1.2 原型模式在Spring中的使用

使用过spring框架的我们,在配置bean标签的时候,会发现有一个scope的标签,他的默认值是singleton(单例),若我们设置为prototype,就会变成原型模式创建对象【即每次getBean()都会得到一个完整的拷贝】。此时,若我们使用==运算符去比较,就会得到false的结果。因为得到的并不是同一个对象了。
在这里插入图片描述

在getBean()的方法栈调用中,会进入一个BeanFactory的实现类,最终会通过方法isPrototype()判断对象是否设置为原型模式,若是,按照原型模式创建对象。
在这里插入图片描述

1.3 深拷贝与浅拷贝【重点】

通过(一)中的方式只能实现对象的浅拷贝:
即对象的基本数据类型以及String类型的变量可以完整的赋值一个新的值,但是其引用类型变量不能复制,而是会将该对象的引用复制一遍放到这个克隆体中来敷衍一下。
但是有时候又确实有复制完整对象的需求。

如,对上面的羊类,多了一个属性Master(主人),现在需要将其主人也完整复制一份。


实现完整带引用的复制有两种写法: (1)深度重写clone()方法实现深拷贝 (一)的实现中,我们让clone()保持默认实现,在这里,我们需要将其稍微改进一点,不但要 使用clone()方法super.clone的到对其普通变量的克隆,还需要对其引用类型进行克隆(直接让引用类型调用自己的克隆方法即可)。【PS:如果该引用类型类也有引用对象是不是也要对他进行同样处理呢?太套娃了】 (2)通过虚拟化/反序列化实现深拷贝 让引用类和本类都实现Serializable接口,使其具有序列化能力。 定义一个普通方法,先将原对象进行序列化写入硬盘,再通过反序列化得到新的对象。

由于对象序列化流/反序列化流都是包装类,因此这个操作共需要四个类型的流对象。
这里有个技巧,可以使用一个特殊的流类:ByteArrayOutputStream/...iutpu...
这个流可以将读取的直接暂存到缓存,而不是真的写到硬盘
这样做,既可以节省硬盘空间,又可以让只在内存操作,增快效率。


以下是代码实现:
public class Demo2 implements Serializable{
    class Master implements Cloneable, Serializable {
        private String name;
        private int age;

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

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

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

    class Sheep implements Cloneable,Serializable {

        private String name;
        private int age;
        private Master master;

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

        @Override
        public String toString() {
            return "Sheep{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", master=" + master +
                    '}';
        }
        @Override
//         会返回不同master.hashCode()的深拷贝方式1:重写clone()
        protected Object clone() throws CloneNotSupportedException {
            //复制普通类型和String类型
            Sheep sheep = (Sheep)super.clone();

            master = (Master)this.master.clone();
            sheep.master = master;
            return sheep;
        }

//使用序列化实现深拷贝
        public Object deepClone() throws IOException, ClassNotFoundException {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            Sheep sheep = (Sheep) ois.readObject();
            bos.close();
            oos.close();
            bis.close();
            ois.close();
            return sheep;

        }


/*        @Override
//        会返回同样master.hashCode()的浅拷贝
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }*/
    }


需要注意的是,我为了方便定义了好几个内部类,序列化时必须要将外部类也给实现序列化接口,否则序列化会抛出异常。

二、建造者模式

2.1 引入案例

需求:设计一个盖房子的程序。

现实中盖房子的流程是:

  • 打地基;
  • 砌墙;
  • 封顶
    由于不同房子的墙、屋顶都不相同,因此需要设计为抽象方法交由子类来实现
    将这三个方法封装到一起,形成一个盖房子的方法build();

下面是设计类图:

在这里插入图片描述
代码实现:


  abstract class AbsractHouse{
        public abstract void buildBasic();
        public abstract void buildWall();
        public abstract void roofed();

        public void build() {
            buildBasic();
            buildWall();
            roofed();
        }
    }

    class LittleHouse extends AbsractHouse{

        @Override
        public void buildBasic() {
            System.out.println("小房子地基");
        }

        @Override
        public void buildWall() {
            System.out.println("小房子墙");
        }

        @Override
        public void roofed() {
            System.out.println("小房子屋顶");
        }
    }

    @Test
    public void test() {
        AbsractHouse littleHouse = new LittleHouse();
        littleHouse.build();
    }

这样写,可以满足一般的需求。
但是,房子是一个静态的物体,而建造这件事是一个动态的行为,将动态的行为和静态实体放在一个类中非常不利用拓展,而且在实际使用中也会遇到耦合度高的问题。

我们要做的是将行为与具体的产品进行解耦,这就需要建造者模式。

2.2 建造者模式

在这里插入图片描述
建造者模式将产品的部件实现与建造过程隔离开来,通过一些复杂对象的构成部件就可以很轻松的创建这个复杂对象。

在现在开发的应用中,建造者模式更大的作用是帮助用户非常快的初始化一个复杂对象:
试想一个场景:
一个类有十几个属性,其中可能只有一两个属性是必需的,而大部分属性都是不必须的。
以前我们的做法是设计重载的构造器去创建对象,或者利用JavaBean的setter方法去设置这几个必须变量。
但如果这样的场景呢?
这几个必须的变量通常值是固定的,而其他才是真正需要设置的变量,这时候就很麻烦了。
遇到这种情况,正是建造者模式应用的最佳场合。

如,若用户想建造一台汽车,用户不需要知道轮胎,方向盘,发动机这些配件是怎么设计出来的,而只需要关心怎么把它们组装起来即可。

建造者模式具有四个角色:

  • Production,产品,就好像上面的汽车;
  • Builder,抽象的建造接口或者抽象类,用来设计创建每个部件的轮廓,就好像汽车每个部件的图纸一样;
  • ConcreteBuilder:具体的建造类,实现抽象的部件创建方法,就好像根据图纸实际创建出来的部件;
  • Director:具体协调上面各角色工作的中间层类,解耦的关键所在。
    在这里插入图片描述

Director类中聚合了一个Builder实现类实例,Builder生成产品并通过Director这个中介交于客户端。

可能这样还是太抽象,转化为开头那个例子中的角色:

在这里插入图片描述
与最开始的设计相比,多了一层中间层HouseDirector,看看有什么好处:
再看类图,会发现Client不仅依赖Director类,还依赖Builder实现类。
不是说应该面向接口吗,为什么还要依赖实现类?

这正是创建者模式最方便的地方,具体实现类就好像是一套完整的套餐,而客户端只需要将这个套餐告诉Director,他就能用他聚合的Builder来无缝连接“这个套餐”,生成用户订购的那个产品。

比如说:我们在购买电脑时,不知道具体买哪些部件组装,这个时候可能有的地方有现成的组装好的主机,我们可以按照自己想要的配置挑选套餐,最后商家会为我们组装好我们就能获取到了。
或许这个例子比之前的盖房子更容易理解?
那就把这个例子实现了吧。。。

在这里,为了突出建造者模式的好处(配置默认的属性),我就将CPU默认为Intel的了,A厨别见怪。

    /**
     * 电脑类:大部分配置都是用户设置的
     * 这个类作为真正的产品
     * 其中,CPU使用默认配置,减少使用端的属性配置量
     */
    class Computer{

        private String CPU;
        private String memory;
        private String operatingSystem;
        private String hardDisk;

        public Computer() {
            this.CPU = "Intel";
        }

        @Override
        public String toString() {
            return "Computer{" +
                    "CPU='" + CPU + '\'' +
                    ", memory='" + memory + '\'' +
                    ", operatingSystem='" + operatingSystem + '\'' +
                    ", hardDisk='" + hardDisk + '\'' +
                    '}';
        }

    }

    /**
     * Builder抽象类,只负责创建部件,不负责创建产品本身
     */
    abstract class ComputerBuilder{
         abstract void  builderMemory();
         abstract void  builderOperatingSystem();
         abstract void  builderHardDisk();

         abstract Computer createComputer();
    }

    /**
     * 建造者实现类
     * 相当于一套完整的配置套餐
     * 可以有很多套这样的套餐,我就不一一写了
     */
    class WindowsComputerBuilder extends ComputerBuilder{

        private Computer computer = new Computer();

        @Override
        void builderMemory() {
            this.computer.memory = "kingston";
        }

        @Override
        void builderOperatingSystem() {
            this.computer.operatingSystem = "windows10";
        }

        @Override
        void builderHardDisk() {
            this.computer.hardDisk = "ADATA";
        }

        @Override
        Computer createComputer() {
            return this.computer;
        }
    }

    /**
     * 负责接收用户的订单请求:即ComputerBuilder实现类
     * 调用ComputerBuilder的建造部件的方法返还一个Computer对象给用户
     */
    class ComputerDirector{
        private ComputerBuilder computerBuilder;

        public ComputerDirector(ComputerBuilder computerBuilder) {
            this.computerBuilder = computerBuilder;
        }

        public Computer buildComputer() {
            computerBuilder.builderMemory();
            computerBuilder.builderOperatingSystem();
            computerBuilder.builderHardDisk();
            return computerBuilder.createComputer();
        }
    }


    @Test
    public void test() {
        System.out.println(new ComputerDirector(new WindowsComputerBuilder()).buildComputer());
    }

可以看到,时至今日,建造者模式的设计已经变得非常的麻烦和不适宜,其实可以有其他的简化版本,以后有机会再补充.

2.3 JDK中对建造者模式的使用

典型应用:StringBuilder类

查看一下StringBuilder类的类图:
在这里插入图片描述
查看Appendable接口,他已经设计了建造的构想,即Appendable是一个抽象Builder类。

而AbstractStringBuilder类,虽然叫做抽象类,但是已经实现了大量的构建字符串方法,因此可以说他是一个Builder实现类(抽象的为什么还是实现类,听起来很怪,其实是因为这是建造者模式的一种魔改,或者说不完全遵守建造者模式规范)
在这里插入图片描述
最后的StringBuilder类,他解决了AbstractStringBuilder不能实例化的问题,并且提供对父类AbstractStringBuilder的调用,即他充当了Director的作用。

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值