概述
原型模式解决的主要问题是如何快速的复制一个已经存在的对象,一个普遍的做法是构建一个属于相同类的对象,然后遍历原始对象的所有属性值并复制到新对象中。这样的做法有一些问题,不是每一个对象都可以通过这种方式进行复制,且这么做的编程代价过高,比方说:
class Main{
public static void main(String[] args) {
Sheep sheep = new Sheep("tom", 1, "red");
new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
}
}
这样做比较好理解,简单易于操作,但是复制对象的效率很低(现在只有三个参数需要处理)。而原型模式就可以解决这个问题,原型模式是用于创建重复的对象,同时又能保证性能的一种模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式的角色如下:
- 抽象原型类:规定了具体原型对象必须实现的
clone()
方法,一般也只有这么一个方法。 - 具体原型类:实现抽象原型类的
clone()
方法,它是可被复制的对象。 - 访问类:使用具体原型类中的
clone()
方法来复制新的对象。
原型模式的优点在于:
- 你可以克隆对象,而无需与它们所属的具体类相耦合;
- 你可以克隆预生成原型,避免反复运行初始化代码;
- 你可以更方便地生成复杂对象;
- 你可以用继承以外的方式来处理复杂对象的不同配置;
原型模式的缺点在于:
- 克隆包含循环引用的复杂对象可能会非常麻烦。
实现
原型模式的克隆分为浅克隆和深克隆:
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
Java 中的 Object类提供了 clone()
方法,该方法实现了浅克隆,在 Java 中 Cloneable 接口就是原型模式中的抽象原型类,而任何实现了 Cloneable 接口的类就是具体原型类,代码如下:
public class Realizetype implements Cloneable {
public Realizetype() {
System.out.println("具体原型创建完成");
}
@Override
protected Realizetype clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功");
return (Realizetype) super.clone();
}
}
测试类:
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Realizetype prototye = new Realizetype();
Realizetype clonedObject = prototye.clone();
// false
System.out.println(prototye == clonedObject);
}
}
上面的拷贝方式是浅拷贝,如果需要实现深拷贝,可以考虑使用序列化、反序列化的方式进行实现,示例代码如下:
public class Sheep implements Cloneable, Serializable {
...
// 深克隆方法
public Sheep deepClone() throws IOException {
//创建对象流对象
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("a.txt")));
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("a.txt")));
try {
//序列化
oos.writeObject(this);
//反序列化
Sheep newSheep = (Sheep) ois.readObject();
return newSheep;
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
} finally {
oos.close();
ois.close();
}
}
}
小结一下,原型模式的实现思想如下:
- 创建抽象原型类,声明克隆方法;
- 创建具体原型类,实现抽象原型类,重写克隆方法;
- 客户端使用原型对象;
应用
- 如果你需要复制一些对象,同时又希望代码独立于这些对象所属的具体类,可以使用原型模式;
- 如果子类的区别仅在于其对象的初始化方式,那么你可以使用该模式来减少子类的数量,这么做的话客户端不必根据子类进行实例化,只需要找到合适的原型然后进行克隆即可;
- 当一个对象的构建代价过高时。例如某个对象里面的数据需要访问数据库才能拿到,而我们却要多次构建这样的对象;
- 当构建的多个对象,均需要处于某种原始状态时,就可以先构建一个拥有此状态的原型对象,其他对象基于原型对象来修改。
个人认为原型模式的一个重要应用在于减少保护性拷贝的代码量,保护性拷贝是指为了防止客户端对类的约束条件产生破坏,传递对象的时候要进行拷贝,一个简单的示例如下:
public class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (this.start.compareTo(end) > 0){
throw new IllegalArgumentException(this.start + "after" + this.end);
}
}
public Date getStart() {
// 直接return会破坏类
return new Date(start.getTime());
}
public Date getEnd() {
// 直接return会破坏类
return new Date(end.getTime());
}
}
类的约束条件是开始时间要小于结束时间,且这两个成员变量是私有的,但是如果 getter
方法中直接返回原始对象,那么就会破坏原本的约束,也就是说如果一个类从客户端得到或者返回一个可变组件,那么就必须进行保护性拷贝。如果使用了原型模式,直接返回要进行保护对象的 clone()
方法的返回值即可,这样大大减少了代码的书写量。
除此之外还可以创建一个中心化原型注册表,用于存储常用原型。可以新建一个工厂类来实现注册表,或者添加一个静态方法,不管是哪一种方式,这些方法必须能够根据客户端代码设定的条件进行搜索。搜索条件可以是简单的字符串,或者是一组复杂的搜索参数。找到合适的原型后,注册表应对原型进行克隆,并将复制生成的对象返回给客户端。
往期回顾
- 【Java设计模式002】工厂模式
- 【Java设计模式001】单例模式
文中难免会出现一些描述不当之处(尽管我已反复检查多次),欢迎在留言区指正,相关的知识点也可进行分享,希望大家都能有所收获!!如果觉得我的文章写得还行,不妨支持一下。你的每一个转发、关注、点赞、评论都是对我最大的支持!