设计模式_原型模式
一、定义
原型模式(Prototype Pattern)是指原型实例指定创建并且通过拷贝这些原型创建新的对象。调用者不需要知道任何创建细节,不调用构造函数。原型模式属于创建型模式。
二、应用场景
1、类初始化消耗资源过多
2、new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
3、构造函数比较复杂
4、循环体重生产大量对象时,如:Spring中 scope="prototype"
三、原型模式的常见写法
1、通用写法
public interface IPrototype<T> {
T clone();
}
public class ConCreatePrototype implements IPrototype{
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public ConCreatePrototype clone() {
ConCreatePrototype conCreatePrototype = new ConCreatePrototype();
conCreatePrototype.setAge(this.age);
conCreatePrototype.setName(this.name);
return conCreatePrototype;
}
@Override
public String toString() {
return "ConCreatePrototype{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
测试代码:
public class ConTest {
public static void main(String[] args) {
//创建原型对象
ConCreatePrototype prototype = new ConCreatePrototype();
prototype.setAge(18);
prototype.setName("eleven-smile");
System.out.println(prototype);
//拷贝原型对象
ConCreatePrototype clonePrototype = prototype.clone();
System.out.println(clonePrototype);
}
}
测试结果:
ConCreatePrototype{age=18, name='eleven-smile'}
ConCreatePrototype{age=18, name='eleven-smile'}
2、浅克隆
JDK层面已经帮我们实现了一个线程API,我们只需要实现Cloneable
接口即可.
public class Prototype implements Cloneable{
private String name;
private int age;
private List<String> hobbies = new ArrayList<>();
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public List<String> getHobbies() {
return hobbies;
}
public void addHobby(String hobby){
hobbies.add(hobby);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Prototype() {
super();
}
@Override
public String toString() {
return "Prototype{" +
"name='" + name + '\'' +
", age=" + age +
", hobbies=" + hobbies +
'}';
}
}
测试代码:
public static void main(String[] args) {
Prototype prototypeA = new Prototype();
prototypeA.setName("张三");
prototypeA.setAge(18);
prototypeA.addHobby("学习");
System.out.println(prototypeA);
System.out.println("---------浅克隆--------");
try {
Prototype prototypeB = (Prototype) prototypeA.clone();
prototypeB.setName("李四");
prototypeB.setAge(20);
prototypeB.addHobby("玩");
System.out.println(prototypeB);
//再次打印prototypeA
System.out.println(prototypeA);
System.out.println("prototypeA:"+prototypeA.getHobbies()+",prototypeB:"+prototypeB.getHobbies());
System.out.println(prototypeA.getHobbies()==prototypeB.getHobbies());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
测试结果:
Prototype{name='张三', age=18, hobbies=[学习]}
---------浅克隆--------
Prototype{name='李四', age=20, hobbies=[学习, 玩]}
Prototype{name='张三', age=18, hobbies=[学习, 玩]}
prototypeA:[学习, 玩],prototypeB:[学习, 玩]
true
分析: 原型对象是 张三 18 学习(引用类型) 克隆对象是 李四 20 玩(引用类型) 结果却变成了学习 玩 同时 张三也是学习 玩 这就是浅克隆带来的问题,原型对象和克隆对象共用同一个引用类型,都指向该引用类型的地址。
3、深克隆
3.1 硬编码
public class Prototype implements Cloneable{
private String name;
private int age;
private ArrayList<String> hobbies = new ArrayList<>();
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setHobbies(ArrayList<String> hobbies) {
this.hobbies = hobbies;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public List<String> getHobbies() {
return hobbies;
}
public void addHobby(String hobby){
hobbies.add(hobby);
}
@Override
protected Object clone() throws CloneNotSupportedException {
Prototype prototype = (Prototype) super.clone();
//对引用类型hobbies 也做clone
prototype.hobbies = (ArrayList<String>) this.hobbies.clone();
return prototype;
}
public Prototype() {
super();
}
@Override
public String toString() {
return "Prototype{" +
"name='" + name + '\'' +
", age=" + age +
", hobbies=" + hobbies +
'}';
}
}
测试代码:
public static void main(String[] args) {
Prototype prototypeA = new Prototype();
prototypeA.setName("张三");
prototypeA.setAge(18);
prototypeA.addHobby("学习");
System.out.println(prototypeA);
System.out.println("---------深克隆--------");
try {
//调用clone方法,根据原型A 克隆 B
Prototype prototypeB = (Prototype) prototypeA.clone();
prototypeB.setName("李四");
prototypeB.setAge(20);
prototypeB.addHobby("玩");
System.out.println(prototypeB);
System.out.println(prototypeA);
//发现了这里就是浅克隆和深克隆的差异点,深克隆相当于重新new了一个hobbies的ArrayList对象 即 每当根据原型A
//克隆了 B 甚至 C、D 等时 就会new 一个属于克隆对象的hobbies的ArrayList对象
System.out.println("prototypeA:" + prototypeA.getHobbies() + ",prototypeB:"
+ prototypeB.getHobbies());
System.out.println(prototypeA.getHobbies() == prototypeB.getHobbies());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
测试结果:
Prototype{name='张三', age=18, hobbies=[学习]}
---------深克隆--------
Prototype{name='李四', age=20, hobbies=[学习, 玩]}
Prototype{name='张三', age=18, hobbies=[学习]}
prototypeA:[学习],prototypeB:[学习, 玩]
false
分析:
造成这样的结果差异,因为这是深克隆,之前的是浅克隆,在深克隆的Prototype
类中的clone
方法中对引用类型List<String>
也做了clone(这里就是用了ArrayList.clone)。深克隆相当于重新new了一个hobbies的ArrayList对象 即 每当根据原型A
克隆了 B 甚至 C、D 等时 就会new 一个属于克隆对象的hobbies的ArrayList对象
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
这就是ArrayList.clone源码,其实只是将List的元素循环遍历了一遍,这种就是硬编码,试想一下,如果在对象重声明了各种几何类型,那每种情况都需要单独处理,那岂不是很麻烦。
3.2 序列化
对上述浅克隆代码进行改良。
public class Prototype implements Cloneable, Serializable {
private String name;
private int age;
private List<String> hobbies;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public List<String> getHobbies() {
return hobbies;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//序列化 深克隆
public Prototype deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Prototype)ois.readObject();
}
@Override
public String toString() {
return "Prototype{" +
"name='" + name + '\'' +
", age=" + age +
", hobbies=" + hobbies +
'}';
}
}
测试代码:
public static void main(String[] args) {
Prototype prototypeA = new Prototype();
prototypeA.setName("张三");
prototypeA.setAge(18);
List<String> hobbies = new ArrayList<>();
hobbies.add("学习");
prototypeA.setHobbies(hobbies);
System.out.println(prototypeA);
System.out.println("---------序列化深克隆--------");
try {
Prototype prototypeB = (Prototype) prototypeA.deepClone();
prototypeB.getHobbies().add("玩");
System.out.println(prototypeB);
//再次打印prototypeA
System.out.println(prototypeA);
System.out.println("prototypeA:"+prototypeA.getHobbies()+",prototypeB:"+prototypeB.getHobbies());
System.out.println(prototypeA.getHobbies()==prototypeB.getHobbies());
} catch (Exception e) {
e.printStackTrace();
}
}
测试结果:
Prototype{name='张三', age=18, hobbies=[学习]}
---------序列化深克隆--------
Prototype{name='张三', age=18, hobbies=[学习, 玩]}
Prototype{name='张三', age=18, hobbies=[学习]}
prototypeA:[学习],prototypeB:[学习, 玩]
false
四、克隆破坏单例模式
思考:假如我们克隆的目标的对象是单例对象,那意味着,深克隆就会破坏单例。 实际上防止克隆破坏单例的解决思路非常简单,禁止深克隆即可。
1、单例类不实现Cloneable接口
public class Prototype implements Serializable{
}
2、重写clone()方法,在clone方法中返回单例对象即可。
protected Object clone() throws CloneNotSupportedException {
return INSTANCE;
}
五、原型模式的优缺点
优点:
1、性能优良,Java自带的。原型模式是基于内存二进制流的拷贝,比直接new一个对象性能上提升了许多。 2、可以使用深克隆方法保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建对象的过程,以便在需要的时候(例如回复到历史某一状态),可辅助实现撤销操作。
缺点:
1、需要为每一个类配置一个克隆方法。 2、克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代,违反了开闭原则。 3、在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦,因此,深拷贝、浅拷贝都需要运用得当。
- 感谢你赐予我前进的力量