原型模式
原型模式
一、什么是原型模式?
原型设计模式(Prototype Design Pattern),是指根据实例原型、实例模型来生成新的实例对象。
也就是用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。
重点在于新实例对象,不是创建出来的,而是基于已有的原型对象复制的。
二、为什么要用原型模式?
- 对象之间相同或相似,即只是个别的几个属性不同的时候
- 有些对象的创建过程较为复杂,而且有时候需要频繁创建,创建新对象还不如复制已有对象来得快
- 对象中的数据需要经过复杂的计算才能得到(比如排序、计算哈希值)
- 对象中的数据需要从 RPC、网络、数据库、文件系统等非常慢速的 IO 中读取
- 想要复制当前的对象,但是对象内部的数据异常复杂,通过构造函数创建难以实现
- 比如一个图形,之前做了很多处理,比一开始创建的时候变动了很多,此时想要复制它,通过构造函数新建是难以实现的
- 想要让生成的实例的框架不依赖于具体的类
- 框架中不能指定类名来生成实例,所以会先“注册”一个“原型实例”,然后通过复制该“原型”来生成新实例
三、模型的结构是怎么样的?
原型模式主要包含以下几种角色:
- 抽象原型类(Prototype):负责定义用于复制现有实例来生成新实例的方法
- 具体原型类(ConcretePrototype):负责实现复制现有实例并生成新实例的方法
- 访问类(Client):负责使用复制实例的方法生成新的实例
四、怎么实现原型模式?
原型模式有两种实现方法:深拷贝和浅拷贝。
- 浅拷贝只会复制对象中基本数据类型数据和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象……
- 深拷贝得到的是一份完完全全独立的对象。所以,深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间
深拷贝和浅拷贝的一些注意事项:
- 如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的
- 但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,也就变得复杂多了
- 没有充分的理由,不要为了一点点的性能提升而使用浅拷贝
示例程序的类结构图:
其中 Product 代表的是原型结构中的 Propotype,UnderlinePen 和 MessageBox 代表的是原型结构中的 ConcretePrototype,而 ProductManager 代表的是原型结构中的 Client。
4.1 Product 接口
1 | public interface Product extends Cloneable { |
4.2 UnderlinePen 和 MessageBox 具现类
UnderlinePen 是为输出字符串在左右两端添加双引号,并在下边添加下划符号。代码如下:
1 | /** |
MessageBox 是为输出字符串添加一个输出框。代码如下:
1 | /*** |
4.3 ProductManager 访问类
1 | public class ProductManager { |
五、原型模式有什么优缺点?
原型模式的优点:
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率
- 原型模式提供了简化的创建结构,可以使用深克隆方式保存对象的状态,以便在需要的时候使用(例如恢复到历史某一状态)
原型模式的缺点:
- 需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,必须修改其源代码,违背了“开闭原则”
- 当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦
- 深克隆、浅克隆需要运用得当
扩展
- Java 中提供了用于复制实例的方法
clone()
clone()
方法定义在java.lang.Object
中,也就是说,所有 Java 类都有clone()
方法clone()
方法内部所执行的是基于内存二进制流的复制,复制了一份和原型对象一样的内存数据clone()
方法复制的只是对象的内存数据,所以clone()
方法是浅复制clone()
方法只会进行复制,不会调用实例的构造函数- 重写
clone()
方法时,记得调用父类的super.clone()
方法
- 类使用
clone()
方法,这个类必须实现java.lang.Clonable
接口(自己实现或者父类实现都行) Clonable
是一个标记接口,用于标记该类可以执行clone()
方法,自身没有声明任何方法