目录
- 原型模式概述
- 原型模式的结构与实现
- 原型模式的应用实例
- 原型管理器
- 原型模式的优缺点与适用环境
原型模式概述
孙悟空“拔毛变小猴”实现自我复制:
- 孙悟空:根据自己的形状复制(克隆)出多个身外身
- 软件开发:通过复制一个原型对象得到多个与原型对象一模一样的新对象
原型模式的定义:
- 原型模式:使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。
- Prototype Pattern: Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
- 对象创建型模式
注:
- 工作原理:将一个原型对象传给要发动创建的对象(即客户端对象),这个要发动创建的对象通过请求原型对象复制自己来实现创建过程
- 创建新对象(也称为克隆对象)的工厂就是原型类自身,工厂方法由负责复制原型对象的克隆方法来实现
- 通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,每一个克隆对象都是独立的
- 通过不同的方式对克隆对象进行修改以后,可以得到一系列相似但不完全相同的对象
否定原型模式的误区:
- New一个对象很方便?
- 工厂帮我搞定?
原型模式的应用场景:
场景一:如果说我们的对象类型不是刚开始就能确定,而是这个类型是在运行期确定的话,那么我们通过这个类型的对象克隆出一个新的类型更容易。
场景二:我们可能在实际的项目中需要一个对象在某个状态下的副本,例如有的时候我们需要对比一个对象经过处理后的状态和处理前的状态是否发生过改变,可能我们就需要在执行某段处理之前,克隆这个对象此时状态的副本,然后等执行后的状态进行相应的对比
ORM数据更新:
场景三:当我们在处理一些对象比较简单,并且对象之间的区别很小,使用原型模式更合适。
例如七彩的颜色,我们只需要根据现有的一个颜色对象,克隆一个新的颜色对象,然后修改具体的颜色的值就可以满足要求,如果通过我们之前讲述的创建型工厂,抽象工厂模式等相对来说就引入新的依赖,并且复杂度也有所提高。
原型模式的结构与实现
原型模式的结构:
原型模式包含以下3个角色:
- Prototype(抽象原型类)
抽象原型类是定义具有克隆自己的方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口。
- ConcretePrototype(具体原型类)
具体原型类实现具体的克隆方法,在克隆方法中返回自己的一个克隆对象。
- Client(客户类)
让一个原型克隆自身,从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个对象,在通过调用该对象的克隆方法得到多个相同的对象。
浅克隆和深克隆
- 浅克隆(Shallow Clone):当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制
Java实现:在Java中可以直接使用Object提供的clone方法实现对象的克隆。但需要注意的是,能够实现克隆的java类必须实现一个标识接口:Cloneable,标识这个Java类支持复制。如果一个类没有实现这个接口而调用了clone方法,java编译器将抛出一个CloneNotSupportedException异常。
代码如下:
package com.zyt.designpatterns.prototype;
public class PrototypeDemo implements Cloneable {
public Object clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return object;
}
public static void main(String[] args) {
PrototypeDemo obj1 = new PrototypeDemo();
PrototypeDemo obj2 = (PrototypeDemo) obj1.clone();
}
}
- 深克隆(Deep Clone):除了对象本身被复制外,对象所包含的所有成员变量也将被复制
java实现:在Java语言中,序列化(Serialization)就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,在从流里将其读出来,从而实现深克隆。实现深克隆的类需要实现Serializable标识接口。
Java语言原型模式的实现:
Java语言提供的clone方法将对象复制了一份并返回给调用者。一般而言,clone方法满足:
- 对任何对象x,都有x.clone() != x,即克隆对象与原有对象不是同一个对象。
- 对任何的对象x,都有x.clone().getClass()==x.getClass(),即克隆对象与原对象的类型一样。
- 如果对象x的equals方法定义恰当,那么x.clone().equals(x)应该成立。
为了获取对象的一份拷贝,我们可以利用Object类的clone方法,具体步骤如下:
- 在派生类中覆盖类的clone方法,并声明为public
- 在派生类的clone方法中,调用super.clone()
- 在派生类中实现Cloneable接口
原型模式的应用实例
在使用某OA系统时,有些岗位的员工发现他们每周的工作都大同小异,因此在填写工作周报时很多内容都是重复的,为了提高工作周报的创建效率,大家迫切地希望有一种机制能够快速创建相同或者相似的周报,包括创建周报的附件。试使用原型模式对该OA系统中的工作周报创建模块进行改进。
- (1) WeeklyLog:周报类,充当原型角色
- (2) Attachment:附件类
- (3) Program:客户端测试类
结果与分析:
- 周报是否相同?否
- 附件是否相同?是
周报对象被成功复制,但是附件对象并没有复制,实现了浅克隆
深克隆解决方案
1. 将周报类WeeklyLog和附件类Attachment标记为可序列化(Serializable)
[Serializable]
class WeeklyLog
{
private Attachment attachment;
……
}
[Serializable]
class Attachment
{
……
}
2. 修改周报类WeeklyLog的Clone()方法
//使用序列化方式实现深克隆
public WeeklyLog Clone()
{
WeeklyLog clone = null;
FileStream fs = new FileStream("Temp.dat", FileMode.Create);
BinaryFormatter formatter = new BinaryFormatter();
try {
formatter.Serialize(fs, this); //序列化
} catch (SerializationException e) {
Console.WriteLine("Failed to serialize. Reason: " + e.Message);
throw;
} finally {
fs.Close();
}
FileStream fs1 = new FileStream("Temp.dat", FileMode.Open);
BinaryFormatter formatter1 = new BinaryFormatter();
try {
clone = (WeeklyLog)formatter1.Deserialize(fs1); //反序列化
} catch (SerializationException e) {
Console.WriteLine("Failed to deserialize. Reason: " + e.Message);
throw;
} finally {
fs1.Close();
}
return clone;
}
原型管理器
定义:原型管理器(Prototype Manager)将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得
结构:
下面代码实现一个颜色原型管理器:
抽象原型类MyColor:
/**
* 原型模式中,抽象原型类(这里使用的是接口)
* Created by yitian.z on 2016/3/21.
*/
public interface MyColor extends Cloneable {
public Object clone() throws CloneNotSupportedException;
public void display();
}
具体原型类Red:
/**
* 原型模式中,具体原型类A
* Created by yitian.z on 2016/3/21.
*/
public class Red implements MyColor {
public Object clone() {
Red red = null;
try {
red = (Red) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return red;
}
@Override
public void display() {
System.out.println("This is Red...");
}
}
具体原型类Blue:
/**
* 原型模式中,具体原型类B
* Created by yitian.z on 2016/3/21.
*/
public class Blue implements MyColor {
public Object clone() {
Blue blue = null;
try {
blue = (Blue) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return blue;
}
@Override
public void display() {
System.out.println("This is blue...");
}
}
原型管理器类PrototypeManager:
/**
* 原型管理器
* Created by yitian.z on 2016/3/21.
*/
public class PrototypeManager {
/**
* 私用成员
*/
private Hashtable hashtable = new Hashtable();
/**
* 默认构造方法
*/
public PrototypeManager() {
hashtable.put("red", new Red());
hashtable.put("blue", new Blue());
}
/**
* 添加颜色
* @param key colorName
* @param color MyColor
*/
public void addColor(String key, MyColor color) {
hashtable.put(key, color);
}
/**
* 获取颜色
* @param key colorName
* @return MyColor
* @throws CloneNotSupportedException
*/
public MyColor getColor(String key) throws CloneNotSupportedException {
return (MyColor) ((MyColor) hashtable.get(key)).clone();
}
}
客户端测试类Client:
/**
* Created by yitian.z on 2016/3/21.
*/
public class Client {
public static void main(String args[]) throws CloneNotSupportedException {
PrototypeManager prototypeManager = new PrototypeManager();
MyColor color1 = prototypeManager.getColor("red");
color1.display();
MyColor color2 = prototypeManager.getColor("blue");
color2.display();
System.out.println(color1 == color2);
}
}
原型模式的优缺点与适用环境
模式优点
- 简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率
- 扩展性较好
- 提供了简化的创建结构,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品
- 可以使用深克隆的方式保存对象的状态,以便在需要的时候使用,可辅助实现撤销操作
模式缺点
- 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了开闭原则
- 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦
模式适用环境
- 创建新对象成本较大,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改
- 系统要保存对象的状态,而对象的状态变化很小
- 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便