创建型模式-原型模式
一、相关介绍
- 原型模式定义:使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
- 工作原理:将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。原型模式是一种“另类”的创建型模式,创建克隆对象的工厂就是原型类自身,工厂方法由克隆方法来实现。
二、角色介绍
-
Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类(java中直接使用
Cloneable
,Serializable
即可)。 - ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
- Client(客户类):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。
三、浅克隆
Java的
Object
类提供了一个clone()
方法,可以实现对象的浅克隆。需要注意的是,调用此方法的对象必须实现Cloneable
接口。否则Java编译器将会抛出一个CloneNotSupportedException
异常。
浅克隆.png
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Liucheng
* @since 2019-07-06
*/
@Data // lombok插件支持
@NoArgsConstructor
@AllArgsConstructor
public class ConcretePrototype implements Cloneable {
private Attachment attachment;
private String name;
private Integer size;
// 重写父类的clone方法,主要是进行类型转换
@Override
public ConcretePrototype clone() {
try {
return (ConcretePrototype)(super.clone());
} catch (CloneNotSupportedException e) {
System.out.println("Not support cloneable");
}
return null;
}
}
@Data // lombok插件支持
@NoArgsConstructor
@AllArgsConstructor
class Attachment {
private String name;
private Integer size;
}
class Client {
public static void main(String[] args) {
ConcretePrototype obj1 = new ConcretePrototype(new Attachment(), "Hello Word", 10);
ConcretePrototype clone = obj1.clone();
System.out.println("x == x.clone()? :" + (obj1 == clone));
System.out.println("类型是否一样:" + (obj1.getClass() == clone.getClass()));
System.out.println("引用成员地址是否相等:" + (obj1.getName() == clone.getName()));
System.out.println("包装类是否相等:" + (obj1.getName() == clone.getName()));
/*
x == x.clone()? :false
类型是否一样:true
引用成员地址是否相等:true
包装类是否相等:true
*/
}
}
Java语言的clone()方法满足
- 对任何对象x,
x != x.clone()
- 对任何对象x, 都有
x.getClass() == x.clone().getClass()
- 如果x的
equals()
方法定义恰当,那么x.clone().equals(x) == true
四、深克隆
深克隆.pngJava中,可以通过序列化(Serialization)等方式来实现。将对象写入流,流中的为内存的一个拷贝,而源对象任在内存中,而且可以拷贝引用属性。(被序列化的对象需实现Serialization接口,包括引用对象)
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.*;
/**
* @author Liucheng
* @since 2019-07-06
*/
@Data // lombok
@NoArgsConstructor
@AllArgsConstructor
public class DeepCopy implements Serializable {
private InnerOne innerOne;
private String name;
public DeepCopy deepClone() throws Exception {
// 将对象写入流中
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(this);
// 将对象从流中取出
ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (DeepCopy)ois.readObject();
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class InnerOne implements Serializable {
private InnerTwo innerTwo;
private String name;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class InnerTwo implements Serializable {
String name;
}
class ClientDeep {
public static void main(String[] args) throws Exception{
DeepCopy deepCopy = new DeepCopy(
new InnerOne(
new InnerTwo("innerTwo"), "innerOne"
),
"deepCopy");
DeepCopy deepClone = deepCopy.deepClone();
System.out.println("对象是否相同:" + (deepCopy == deepClone));
System.out.println("普通成员属性是否相同:" + (deepCopy.getName() == deepClone.getName()));
System.out.println("一级引用是否相同:" + (deepCopy.getInnerOne() == deepClone.getInnerOne()));
System.out.println("二级引用是否相同:" + (deepCopy.getInnerOne().getInnerTwo() == deepClone.getInnerOne().getInnerTwo()));
/*
对象是否相同:false
普通成员属性是否相同:false
一级引用是否相同:false
二级引用是否相同:false
*/
}
}
Java提供的的
Cloneable
接口和Serializable
接口的代码非常简单,都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。
五、原型管理器
原型管理器是将多个原型对戏那个存储在一个集合中供客户端使用,是一个专门负责克隆对象的工厂,一下以一个公文管理器为例实现原型管理器
公文管理器.png
注意:以下代码有所改动
/**
* @author Liucheng
* @since 2019-07-06
*/
import java.io.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 抽象公文接口
*/
interface OfficialDocument extends Cloneable {
public OfficialDocument clone();
public void display();
}
/**
* 可行性分析报告
*/
class FAR implements OfficialDocument, Serializable {
@Override
public OfficialDocument clone() {
try {
return (OfficialDocument)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
@Override
public void display() {
System.out.println("可行性分析报告");
}
}
/**
* 软件需求规格说明书
*/
class SRS implements OfficialDocument, Serializable {
@Override
public OfficialDocument clone() {
try {
return (OfficialDocument)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
@Override
public void display() {
System.out.println("软件需求规格说明书");
}
}
/**
* 原型管理器
*/
public class PrototypeManager {
private final ConcurrentHashMap<String, Object> hashMap = new ConcurrentHashMap<>();
private static final PrototypeManager pm = new PrototypeManager();
private PrototypeManager() {
hashMap.put("far", new FAR());
hashMap.put("srs", new SRS());
}
public void addOfficialDocument(String key, OfficialDocument document) {
hashMap.put(key, document);
}
// 通过浅克隆获取新的公文对象
public OfficialDocument getOfficialDocumentShallowCopy(String key) {
return ((OfficialDocument)hashMap.get(key)).clone();
}
// 通过深克隆获取新的公文对象
public OfficialDocument getOfficialDocumentDeepCopy(String key) {
try {
// 将对象写入流中
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(byteOut);
objOut.writeObject(this.hashMap.get(key));
// 从流中获取对象
ByteArrayInputStream byteIn= new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream objIn = new ObjectInputStream(byteIn);
return (OfficialDocument)objIn.readObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("深拷贝失败");
}
}
public static PrototypeManager getPrototypeManager(){
return pm;
}
}
class Client {
public static void main(String[] args) {
PrototypeManager pm = PrototypeManager.getPrototypeManager();
OfficialDocument far = pm.getOfficialDocumentShallowCopy("far");
OfficialDocument far1 = pm.getOfficialDocumentDeepCopy("far");
}
}
六、原型模式总结
1). 优点
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
- 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。
- 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
- 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。
2).主要缺点
- 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。
- 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦(被引用的对象得实现Serializable接口)。
3).适用场景 在以下情况下可以考虑使用原型模式:
- 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
- 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
- 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
网友评论