1. 原型模式
1.1 定义
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式之一。
这种模式是实现了一个原型接口,该接口用于创建当前对象的拷贝。当直接创建对象的代价比较大时,则采用这种模式。
例如:淘宝。我们知道,淘宝在双十一期间与普通时间的工作量有很大差别,并且CPU的处理速度和数据库的处理速度不在一个级别,我们不能每次访问都去数据库查询,这可能会导致数据库宕机。所以,我们在这之前,就需要把数据库中的数据全部加载到缓存中,当每次访问时就去缓存中查,判断是否有这个对象,而不去数据库中查询。
1.2 介绍
原型模式主要是用原型实例指定创建对象的种类,通过拷贝原型来创建新的对象
。主要是解决在运行期的建立和删除原型
。
使用场景:1.当一个系统应该独立于它的产品创建,构成和表示时;
2.当要实例化的类是在运行时刻指定时;(如:动态重载)
3.为了避免创建一个与产品类层次平行的工厂类层次时;
4.当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并拷贝它们可能比每次用的合适状态手工实例化该类更方便一些。
原型模式可分为以下角色:
- 抽象原型类:规定了具体原型对象必须实现clone()方法;
- 具体原型类:实现了抽象原型类的clone()方法;
- 访问类:使用具体原型类的clone()方法拷贝对象。
原型模式中拷贝分为浅拷贝和深拷贝:
- 浅拷贝:浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是指向内存的地址 ,所以
如果其中一个对象改变了这个引用类型的值,就会影响到另一个对象
。浅拷贝使用默认的clone()
方法来实现。- 深拷贝:深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且
修改新对象不会影响原对象。
2.代码介绍
2.1 浅拷贝
抽象原型类
@Slf4j
public abstract class Commodity implements Cloneable {
protected abstract void purchase();
}
具体原型类
@Slf4j
public class Electronics extends Commodity {
protected String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected void purchase() {
log.info("购买了"+name+"~~~");
}
@Override
protected Electronics clone() throws CloneNotSupportedException {
return (Electronics) super.clone();
}
}
访问类
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Electronics electronics = new Electronics();
Electronics clone = (Electronics) electronics.clone();
electronics.setName("电脑");
clone.setName("手机");
electronics.purchase();
clone.purchase();
}
}
/**
* [main] INFO com.glw.designPattern.dp_5_prototypeMode.浅拷贝.基本类型.Electronics - 购买了电脑~~~
* [main] INFO com.glw.designPattern.dp_5_prototypeMode.浅拷贝.基本类型.Electronics - 购买了手机~~~
*/
2.2 浅拷贝出现的问题
浅拷贝具体原型对象的属性只能是基本数据类型和String类型, 那么其他类型会发生什么呢???
具体对象类
@Data
public class ConsumerCategory {
private String name;
}
抽象原型类
@Slf4j
public abstract class Commodity implements Cloneable {
abstract void purchase();
}
具体原型类
@Slf4j
public class Electronics extends Commodity {
private ConsumerCategory consumerCategory;
public ConsumerCategory getConsumerCategory() {
return consumerCategory;
}
public void setConsumerCategory(ConsumerCategory consumerCategory) {
this.consumerCategory = consumerCategory;
}
@Override
void purchase() {
log.info("购买了"+consumerCategory.getName()+"~~~");
}
@Override
protected Electronics clone() throws CloneNotSupportedException {
return (Electronics) super.clone();
}
}
访问类
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Electronics electronics = new Electronics();
ConsumerCategory consumerCategory = new ConsumerCategory();
consumerCategory.setName("电脑");
electronics.setConsumerCategory(consumerCategory);
Electronics clone = electronics.clone();
clone.getConsumerCategory().setName("手机");
electronics.purchase();
clone.purchase();
}
}
/**
* [main] INFO com.glw.designPattern.dp_5_prototypeMode.浅拷贝.对象类型.Electronics - 购买了手机~~~
* [main] INFO com.glw.designPattern.dp_5_prototypeMode.浅拷贝.对象类型.Electronics - 购买了手机~~~
*/
我们能看到,对于基本类型的数据,得到的是两个对象,但是对于对象类型或者其他类型的数据(这里的例子为对象类型),得到的确实一个结果,即得到的是一个对象,这是为什么呢???
因为Object类中提供了clone()方法实现的是浅拷贝,对于非基本类型属性仍指向原有属性所指向的内存地址,原型类中的ConsumerCategory对象和拷贝类中的对象是同一个对象,当修改拷贝类的ConsumerCategory对象时也会修改原型类中的ConsumerCategory对象。
深拷贝就可以解决这种问题…
2.2 深拷贝
深拷贝实现的两种方式:
-
重写clone()
方法; -
通过
对象序列化
实现,即实现java.io.Serializable
接口-
重写clone()方法
具体对象类
@Data public class ConsumerCategory implements Cloneable { private String name; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
抽象原型类
@Slf4j public abstract class Commodity implements Cloneable { abstract void purchase(); }
具体原型类
@Slf4j public class Electronics extends Commodity { private ConsumerCategory consumerCategory; public ConsumerCategory getConsumerCategory() { return consumerCategory; } public void setConsumerCategory(ConsumerCategory consumerCategory) { this.consumerCategory = consumerCategory; } @Override void purchase() { log.info("购买了"+consumerCategory.getName()+"~~~"); } @Override protected Electronics clone() throws CloneNotSupportedException { Electronics deepClone = (Electronics) super.clone(); deepClone.consumerCategory = (ConsumerCategory) consumerCategory.clone(); return deepClone; } }
访问类
public class Test { public static void main(String[] args) throws CloneNotSupportedException { Electronics electronics = new Electronics(); ConsumerCategory consumerCategory = new ConsumerCategory(); consumerCategory.setName("电脑"); electronics.setConsumerCategory(consumerCategory); Electronics clone = electronics.clone(); clone.getConsumerCategory().setName("手机"); electronics.purchase(); clone.purchase(); } } /** * [main] INFO com.glw.designPattern.dp_5_prototypeMode.深拷贝.对象类型.Electronics - 购买了电脑~~~ * [main] INFO com.glw.designPattern.dp_5_prototypeMode.深拷贝.对象类型.Electronics - 购买了手机~~~ */
-
对象序列化
我们先来了解一下什么是对象
序列化与反序列化
:序列化:将数据结构或对象转换成二进制字节流的过程。利用
FileOutputStream
。进行序列化操作就必须实现Serializable
接口。反序列化:将在序列化过程中所生成的二进制字节流的过程转换成数据结构或者对象的过程。利用
FileInputStream
在浅拷贝出现的问题的代码基础上进行修改
具体对象类
@Data public class ConsumerCategory implements Serializable {//...... }
抽象原型类
public abstract class Commodity implements Cloneable, Serializable {//...... }
访问类
public class Test { public static void main(String[] args) throws Exception { Electronics electronics = new Electronics(); ConsumerCategory consumerCategory = new ConsumerCategory(); consumerCategory.setName("电脑"); electronics.setConsumerCategory(consumerCategory); //序列化对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt")); oos.writeObject(electronics); //序列化对象 oos.close(); //关闭对象输出流 //反序列化对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt")); Electronics clone = (Electronics) ois.readObject(); //反序列化对象 ois.close(); //关闭对象输入流 clone.getConsumerCategory().setName("手机"); electronics.purchase(); clone.purchase(); } } /** * [main] INFO com.glw.designPattern.dp_5_prototypeMode.浅拷贝.重写clone方法.Electronics - 购买了手机~~~ * [main] INFO com.glw.designPattern.dp_5_prototypeMode.浅拷贝.重写clone方法.Electronics - 购买了手机~~~ */
-
3. 原型模型的优缺点
优点:
- 性能提高。
- 逃避构造函数的约束。
缺点:
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
- 必须实现 Cloneable 接口。