一、原型模式
在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。
1、传统方法
Sheep sheep = new Sheep("tom", 1, "白色");
// 传统方法复制
Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
优缺点:
- 好理解,易操作
- 创建新的对象总是需要重新获取原始对象的属性,如果对象比较复杂,效率就会变低
- 总是需要重新初始化对象,而不是动态的获得对象,不够灵活
- 改进:
- 思路:Java Object 类提供了一个
clone()
方法,该方法可以将 Java 对象复制一份,但是需要实现 clone 的 Java 类必须实现Cloneable
接口(原型模式)
- 思路:Java Object 类提供了一个
2、原型模式(优缺点)
基本介绍:
- 原型模式(Prototype)是指:用原型实例创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如如何创建的细节
- 工作原理:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实现创建,即:对象.clone()
结构:
实现:
- Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。
说明:
- 抽象原型类:规定了具体原型对象必须实现的接口。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
优缺点:
- 优点:
- Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
- 缺点:
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
3、Spring 源码应用
Spring 中原型 bean 的创建,就使用了原型模式
<bean id="people" class="com.naivekyo.springanalysis.People" scope="prototype">
<property name="name" value="张三"/>
<property name="age" value="21"/>
</bean>
4、浅拷贝和深拷贝
浅拷贝
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因此实际上两个对象的该成员变量都指向同一个实例,这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量
- Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。
深拷贝
- 复制对象的所有基本数据类型的成员变量值
- 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝。
- 深拷贝实现方式:
- 1:重写 clone 方法实现深拷贝
- 2:通过对象序列化实现深拷贝
5、深拷贝详解
方式一:重写 clone 方法
// 深拷贝:clone()
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
// 完成对基本数据类型的克隆
deep = super.clone();
// 单独处理引用类型
DeepPrototype deepPrototype = (DeepPrototype) deep;
deepPrototype.deepCloneableTarget = (DeepCloneableTarget) this.deepCloneableTarget.clone();
return deepPrototype;
}
方式二:序列化实现深拷贝(推荐使用)
// 深拷贝:序列化(推荐)
public Object deepClone() {
// 创建流对象
ByteArrayOutputStream bos = null;
ByteArrayInputStream bis = null;
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
// 序列化操作
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); // 把当前对象转换成字节流
// 反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepPrototype copyObj = (DeepPrototype) ois.readObject();
return copyObj;
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
} finally {
// 关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
6、原型模式注意事项
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能提高效率
- 不用重新初始化对象,而是动态的获得对象运行时的状态
- 如果原始对象发生变化(增加或者减少属性),其他克隆对象也会发生相应的变化,无需修改代码
- 再实现深克隆的时候可能需要比较复杂的代码(重写 clone 方法)
- 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很复杂,但对已有的类进行改造时,需要修改源代码,违背了 ocp 原则。
7、原型模式的应用场景
原型模式通常适用于以下场景。
- 对象之间相同或相似,即只是个别的几个属性不同的时候。
- 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
- 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
- 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。
二、建造者模式
建造者模式将复杂对象中变化与不变的部分分离开
举个例子:盖房子问题
盖房子的步骤是确定的,打地基、砌墙、封底
但是建成房子的种类有很多:普通房子、高楼 等等
1、传统方式
AbstractHouse
— CommonHouse
Client
- 优点:实现比较简单
- 设计的程序结构:没有设计缓存层对象,程序的扩展和维护不好,也就是说,这种设计方式:将产品和创建产品的过程封装在一起,增强了耦合性
- 解决方案:将产品和产品建造过程解耦 =》 建造者模式
2、建造者模式
基本介绍
- 建造者模式(Builder Pattern)又叫生成器模式,是一种对象构建模式,它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方式可以构造出不同表现(属性)的对象
- 建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体实现细节
模式的结构和实现
建造者(Builder)模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成,现在我们来分析其基本结构和实现方法。
模式的结构:
建造者(Builder)模式的主要角色如下。
- 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口 / 抽象类,通常还包含一个返回复杂产品的方法 getResult()。
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
类图:
图中抽象建造者和指挥者之间应该是 组合 关系。箭头用错了。
Java 的 StringBuilder 使用了建造者模式的思想
注意事项
- 客户端(实用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品
- 每一个具体的建造者都相对独立,而与其他的具体建造者无关,因此可以很方便的替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象
- 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 增加新的具体建造者无需修改原有的代码,指挥者类针对抽象建造者编程,系统扩展方便,符合 ocp 原则。
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围收到一定的限制
- 如果产品内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,如果系统变得很庞大,因此在这种情况下,要考虑是否使用建造者模式
- 抽象工厂模式 VS 建造者模式
- 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心创建过程,只关心什么产品由什么工程生产即可
- 而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件来生产另一个新产品。
3、模式的应用场景
建造者模式唯一区别于工厂模式的是针对复杂对象的创建。也就是说,如果创建简单对象,通常都是使用工厂模式进行创建,而如果创建复杂对象,就可以考虑使用建造者模式。
当需要创建的产品具备复杂创建过程时,可以抽取出共性创建过程,然后交由具体实现类自定义创建流程,使得同样的创建行为可以生产出不同的产品,分离了创建与表示,使创建产品的灵活性大大增加。
建造者模式主要适用于以下应用场景:
- 相同的方法,不同的执行顺序,产生不同的结果。
- 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。
- 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
- 初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。
4、建造者模式和工厂模式的区别
- 建造者模式更加注重方法的调用顺序,工厂模式注重创建对象。
- 创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的对象都一样
- 关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成。
- 建造者模式根据建造过程中的顺序不一样,最终对象部件组成也不一样。
5、模式的扩展
建造者(Builder)模式在应用过程中可以根据需要改变,如果创建的产品种类只有一种,只需要一个具体建造者,这时可以省略掉抽象建造者,甚至可以省略掉指挥者角色。