原型模式 Prototype
- 原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- Java中Object类中提供了clone方法,可以用于实现原型模式,但使用clone方法必须实现Cloneable接口,表示该类可以被克隆
核心角色
- Prototype(原型类)被克隆的对象 顶级接口
- ConcretePrototype(具体原型类): 实现接口,实现克隆方法
- Client(调用者)
UML 示意图
代码实现
// 实现Cloneable接口 最简单的原型实现 这里不能直接用Lombok@Data 因为@Data重写了hashCode方法导致Clone出来的hashCode一样
@Setter
@Getter
@ToString
public class PrototypeClass implements Cloneable {
private String name;
private InnerClass innerClass;
@Override
protected Object clone() {
try {
PrototypeClass clone = (PrototypeClass) super.clone();
// clone.innerClass = (InnerClass) innerClass.clone();
return clone;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return new PrototypeClass();
}
}
// 内部引用类型属性
@Setter
@Getter
@ToString
public class InnerClass implements Cloneable {
private String innerName;
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return new InnerClass();
}
}
// 客户端调用 发现他们的属性 和hashcode 都是一样的
public class PrototypeTest {
public static void main(String[] args) {
PrototypeClass prototypeClass1 = new PrototypeClass();
prototypeClass1.setName("test");
InnerClass innerClass = new InnerClass();
innerClass.setInnerName("innerClass");
prototypeClass1.setInnerClass(innerClass);
System.out.println(prototypeClass1.toString() + prototypeClass1.getInnerClass().hashCode());
PrototypeClass prototypeClass2 = (PrototypeClass) prototypeClass1.clone();
System.out.println(prototypeClass2.toString() + prototypeClass2.getInnerClass().hashCode());
PrototypeClass prototypeClass3 = (PrototypeClass) prototypeClass1.clone();
System.out.println(prototypeClass3.toString() + prototypeClass3.getInnerClass().hashCode());
PrototypeClass prototypeClass4 = (PrototypeClass) prototypeClass1.clone();
System.out.println(prototypeClass4.toString() + prototypeClass4.getInnerClass().hashCode());
}
}
//PrototypeClass(name=test, innerClass=InnerClass(innerName=innerClass))1836019240
//PrototypeClass(name=test, innerClass=InnerClass(innerName=innerClass))1836019240
//PrototypeClass(name=test, innerClass=InnerClass(innerName=innerClass))1836019240
//PrototypeClass(name=test, innerClass=InnerClass(innerName=innerClass))1836019240
以上是最简单实现原型模式克隆的最简单的方法,存在的问题是浅拷贝,无法真正拷贝为对象的属性。
简单介绍一下深浅拷贝的问题
-
浅拷贝
对于基本数据类型的成员变量,浅拷贝直接是值传递,将值复制一份给新对象
对于引用类型的成员变量,比如数组、对象等,浅拷贝可以理解为直接拷贝属性内存地址(引用)实际上还是指向的同一个对象,这样的情况下如果任意一个修改了引用对象里面的属性,其他所有克隆出现的对象都被改变 Object的clone方法就是用的浅拷贝方式
-
深拷贝
复制对象的所有基本类型的成员变量
对于引用类型重新申请一块内存空间来专门存储新的对象,对引用类型内部的成员变量用一样的方法进行。
实现方式主要有两种 重写clone方法 或者 使用对象序列化反序列来实现(推荐使用)
// 模拟引用类型实现Cloneable接口
public class InnerClass implements Cloneable {
private String innerName;
@Override
protected Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return new InnerClass();
}
}
// 含有引用类型的类
public class PrototypeClass implements Cloneable {
private String name;
private InnerClass innerClass;
@Override
protected Object clone() {
try {
PrototypeClass clone = (PrototypeClass) super.clone();
// 重新调用引用类型的克隆方法进行赋值
clone.innerClass = (InnerClass) innerClass.clone();
return clone;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return new PrototypeClass();
}
}
上面这种方法实现简单只是把对象的innerClass属性再次clone,但缺点也很明显,如果对象有多个引用属性,每一个都要去重写方法很麻烦。下面使用序列化和反序列化实现虽然代码麻烦一点,但是使用简单
@Setter
@Getter
@ToString
public class PrototypeClass implements Cloneable,Serializable {
private String name;
private InnerClass innerClass;
@Override
protected Object clone() {
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);
return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- 首先所有类包括引用成员类型的变量都实现序列化接口
- 将对象序列化写出到流,然后用输入流读回来返回