概念
原型模式(Prototype Pattern)是一种创建型设计模式,其目的是通过复制现有对象来创建新对象,而不是通过实例化类。它允许我们通过使用原型对象作为蓝本来生成新的对象,从而避免了直接创建对象时所需的复杂初始化过程。
在原型模式中,我们首先创建一个原型对象,然后通过克隆该原型对象来创建新对象。这个克隆可以是浅克隆(Shallow Clone)或深克隆(Deep Clone)。
基本使用
示例一
手动编写克隆方法:
/**
* 原型抽象类
* */
public interface Prototype {
String attr = null; //成员属性
public String getAttr();
public void setAttr(String attr);
public Prototype clone();
}
/**
* 原型具体实现类
* */
public class ConcretePrototype implements Prototype {
private String attr; //成员属性
@Override
public String getAttr() {
return attr;
}
@Override
public void setAttr(String attr) {
this.attr = attr;
}
@Override
public Prototype clone() {
Prototype prototype = new ConcretePrototype();
prototype.setAttr(this.attr);
return prototype;
}
}
//客户端
Prototype prototype = new ConcretePrototype();
prototype.setAttr("123");
Prototype prototype1 = prototype.clone();
System.out.println(prototype == prototype1);//false
System.out.println(prototype1.getAttr());//123
示例二
基于Cloneable接口实现clone
/**
* 基于Cloneable接口实现
* */
public class ConcretePrototype1 implements Cloneable {
private String attr; //成员属性
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
this.attr = attr;
}
@Override
public Prototype clone() throws CloneNotSupportedException {
return (Prototype) super.clone();
}
}
//客户端
Prototype prototype2 = new ConcretePrototype();
prototype2.setAttr("456");
Prototype prototype3 = prototype2.clone();
System.out.println(prototype2 == prototype3);//false
System.out.println(prototype3.getAttr());//456
浅克隆与深克隆
浅克隆:在浅克隆中,对于对象的字段,只有对象的引用被复制,而不复制引用对象本身。这意味着原型对象和克隆对象共享相同的引用对象,如果其中一个对象修改了引用对象,另一个对象也会受到影响。简单来说,如果是值类型则将值复制一份,如果是引用类型,则复制该成员变量的地址。
示例一
我们定义一个子类
public class Child {
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
将Child子类作为ConcretePrototype1类的成员变量
/**
* 基于Cloneable接口实现(浅拷贝)
* */
public class ConcretePrototype1 implements Cloneable {
private String attr; //成员属性
private Child child;
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
this.attr = attr;
}
public Child getChild() {
return child;
}
public void setChild(Child child) {
this.child = child;
}
//浅拷贝
@Override
public ConcretePrototype1 clone() throws CloneNotSupportedException {
return (ConcretePrototype1) super.clone();
}
}
客户端测试克隆之后,子类是否为同一个对象地址
ConcretePrototype1 concretePrototype1,concretePrototype1New;
Child child = new Child();
concretePrototype1 = new ConcretePrototype1();//创建原型对象
concretePrototype1.setChild(child);
concretePrototype1New = concretePrototype1.clone();//浅拷贝原型对象
System.out.println(concretePrototype1.getChild() == concretePrototype1New.getChild()); //true
结果原型与拷贝之后的子类为同一个对象。
深克隆:在深克隆中,除了复制对象的引用,还会复制引用对象本身。这意味着即使一个对象修改了引用对象,另一个对象也不会受到影响。简而言之,除了值类型的变量被复制了一份之外,引用类型的值也会被复制一份,而不是共享同一个地址
实现方法:我们需要实现**(序列化)Serializable接口,序列化实际上就是将对象写到流中的过程,在这一过程中,不仅该对象会被拷贝一份到流中,其子类也会被拷贝一同写进流中,而原对象还在内存中,数据流中的对象及其子类相当于被拷贝了一份。在反序列化的过程中,会重新创建一个全新的对象,并将原始对象的属性值逐个复制到新对象中。
这种方式可以实现深拷贝的原因是它会对对象进行递归复制**,包括对象的属性、引用对象以及引用对象的属性等等。当序列化后再反序列化时,每个对象都会被单独创建,这样就避免了对象之间共享引用的问题。
示例二
我们定义一个子类,实现Serializable接口
public class Child implements Serializable {
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
将Child子类作为ConcretePrototype1类的成员变量,同样也实现Serializable接口
/**
* 通过序列化的方式读入流中 实现深拷贝
* */
public class ConcretePrototype1 implements Serializable {
private String attr; //成员属性
private Child child;
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
this.attr = attr;
}
public Child getChild() {
return child;
}
public void setChild(Child child) {
this.child = child;
}
//深拷贝
public ConcretePrototype1 deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(this);
ByteArrayInputStream bai = new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bai);
return (ConcretePrototype1) ois.readObject();
}
}
客户端测试克隆之后,子类是否为同一个对象地址
ConcretePrototype1 concretePrototype1,concretePrototype1New;
Child child = new Child();
concretePrototype1 = new ConcretePrototype1();//创建原型对象
concretePrototype1.setChild(child);
concretePrototype1New = concretePrototype1.deepClone();//深拷贝原型对象
System.out.println(concretePrototype1.getChild() == concretePrototype1New.getChild()); //false
结果原型与拷贝之后的子类为同一个对象。
原型管理器
原型管理器(Prototype Manager)是在原型模式中使用的一个重要组件。它负责管理和存储原型对象,客户端通过原型管理器获取需要的原型对象,然后进行克隆来创建新的对象。
原型管理器通常使用哈希表(Hash Table)或者其他数据结构来存储原型对象,以便能够方便地根据键值对来快速获取对应的原型对象。原型管理器还可以提供一些方法,用于注册、检索和删除原型对象。
使用原型管理器可以带来以下好处:
隐藏了原型对象的具体创建细节,客户端只需通过管理器获取对应的原型对象即可。
提高了对象的复用性和创建效率。通过原型管理器,可以在需要时直接克隆现有的对象,而不需要每次都重新实例化和初始化对象。
方便了添加、修改和删除原型对象。原型管理器集中管理了所有原型对象,可以灵活地进行原型对象的管理和维护。
下面是一个简单示例,展示如何实现一个原型管理器:
1、原型模板接口
public interface ProjectPattern extends Cloneable {
public ProjectPattern clone();
public void build();
}
2、原型实现类
public class Code1Project implements ProjectPattern{
@Override
public ProjectPattern clone() {
ProjectPattern project = null;
try {
project = (ProjectPattern) super.clone();
}catch (CloneNotSupportedException c) {
System.out.println("无法赋值");
}
return project;
}
@Override
public void build() {
System.out.println("构建代码1工程。。。");
}
}
public class Code2Project implements ProjectPattern{
@Override
public ProjectPattern clone() {
ProjectPattern project = null;
try {
project = (ProjectPattern) super.clone();
}catch (CloneNotSupportedException c) {
System.out.println("无法赋值");
}
return project;
}
@Override
public void build() {
System.out.println("构建代码2工程。。。");
}
}
3、原型管理器
/**
* 原型管理器
* 使用单例饿汉式实现
* */
public class ProjectManager {
private Hashtable ht = new Hashtable();
private static ProjectManager pm = new ProjectManager();
//初始化模板
private ProjectManager() {
//若想对Code1Project或Code2Project的成员变量初始化值,可以在这实现,然后在放入ht中
ht.put("code1",new Code1Project());
ht.put("code2",new Code2Project());
}
//获取到单例模式下唯一的原型管理器对象
public static ProjectManager getProjectManager() {
return pm;
}
//增加原型的可扩展性
public void addProject(String key, ProjectPattern project) {
ht.put(key, project);
}
//通过各具体实现类的克隆方法实现浅拷贝
public ProjectPattern getProjectByClone(String key) {
return ((ProjectPattern) (ht.get(key))).clone();
}
}
4、客户端
//获取到单例模式下唯一的原型管理器对象,此时此对象已经初始化完成
ProjectManager projectManager = ProjectManager.getProjectManager();
ProjectPattern code1,code2,code3,code4;
//验证实体类是否复制成功
code1 = projectManager.getProjectByClone("code1");
code1.build();
code2 = projectManager.getProjectByClone("code1");
code2.build();
//验证是否为同一个对象
code3 = projectManager.getProjectByClone("code2");
code4 = projectManager.getProjectByClone("code2");
System.out.println(code3 == code4);
总结
优点:
1、减少了对象的创建成本:原型模式通过克隆现有的对象来创建新的对象,避免了重复的初始化过程,提高了创建对象的效率。
2、提高了对象的复用性:通过原型模式可以方便地复制已有对象的状态,从而创建新的对象。这样可以避免创建大量相似对象的开销。
3、隐藏了对象创建的细节:客户端只需通过克隆方法即可获取新的对象,无需关心对象的具体实现和创建细节。
缺点:
1、深度克隆可能较为复杂:如果原型对象中包含引用类型的属性,并且需要进行深度克隆,那么克隆过程可能比较复杂。需要注意在克隆过程中处理好关联对象的克隆。
2、必须实现 Cloneable 接口:为了使用原型模式,原型类必须实现 Cloneable 接口并重写 clone 方法。这对一些不支持克隆或者不可修改的类是一种限制。
适用环境:
1、当对象的创建过程比较复杂且耗时时,可以使用原型模式来节省资源。
2、当需要创建大量相似对象时,可以使用原型模式来提高创建效率。
3、当希望对对象的某个部分进行修改而不影响其他部分时,可以使用原型模式来克隆一个对象然后进行修改。
4、当类中多层嵌套着类的时候,为了免去每层对象单独创建然后再赋值的麻烦,直接使用克隆即可。
总之,原型模式适用于创建成本较高、对象结构相对稳定且需要频繁创建或者修改的场景。它通过克隆已有的对象来创建新对象,从而提高了创建效率和对象的复用性。但是需要注意处理关联对象的克隆和实现 Cloneable 接口的限制。