原型模式是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,属于创建型模式。
原型模式的核心在于拷贝原型对象。以系统中已存在的一个对象为原型,直接基于内存二进制流进行拷贝,无需经历耗时的对象初始化过程(不调用构造函数),性能提升了许多。当对象的构建过程比较耗时时,可以利用当前系统中已存在的对象作为原型,对其进行克隆(一般是基于二进制流的复制),躲避初始化过程,使得新对象的创建时间大大减少。
对不通过new关键字,而是通过对象拷贝来实现创建对象的模式就称作原型模式。
客户(Client):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。
抽象原型(Prototype):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。
具体原型(Concrete Prototype):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
1、类初始化消耗资源较多
2、new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
3、构造函数比较复杂
4、循环体中生产大量对象时
抽象原型接口:Prototype
public interface Prototype<T> {
T clone();
}
具体原型:ConcretePrototype
public class ConcretrPrototype implements Prototype {
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 ConcretrPrototype clone() {
ConcretrPrototype concretrPrototype = new ConcretrPrototype();
concretrPrototype.setAge(this.age);
concretrPrototype.setName(this.name);
return concretrPrototype;
}
@Override
public String toString() {
return "ConcretrPrototype{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
具体调用:Main
public class Main {
public static void main(String[] args) {
ConcretrPrototype prototype = new ConcretrPrototype();
prototype.setAge(18);
prototype.setName("Tom");
System.out.println(prototype);
ConcretrPrototype cloneType = prototype.clone();
System.out.println(cloneType);
}
}
众所周知,所有的Java类都继承自 java.lang.Object
。事实上,Object
类提供一个 clone()
方法,可以将一个Java对象复制一份。因此在Java中可以直接使用 Object
提供的 clone()
方法来实现对象的克隆,Java语言中的原型模式实现很简单。
需要注意的是能够实现克隆的Java类必须实现一个 标识接口 Cloneable
,表示这个Java类支持被复制。如果一个类没有实现这个接口但是调用了clone()
方法,Java编译器将抛出一个 CloneNotSupportedException
异常。
public class ConcretrPrototype implements Cloneable {
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 ConcretrPrototype clone() {
try {
return (ConcretrPrototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "ConcretrPrototype{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
(1)、在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
(2)、简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
(3)、在Java语言中,通过覆盖Object类
的clone()
方法可以实现浅克隆。
(1)、在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
(2)、简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
(3)、在Java语言中,如果需要实现深克隆,可以通过序列化(Serialization)等方式来实现。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
具体原型:ConcretePrototype
增加属性:List hobbies
public class ConcretrPrototype implements Cloneable {
private int age;
private String name;
private List<String> hobbies;
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 ConcretrPrototype clone() {
try {
return (ConcretrPrototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
@Override
public String toString() {
return "ConcretrPrototype{" +
"age=" + age +
", name='" + name + '\'' +
", hobbies=" + hobbies +
'}';
}
}
具体调用:Main
public class Main {
public static void main(String[] args) {
ConcretrPrototype prototype = new ConcretrPrototype();
prototype.setAge(18);
prototype.setName("Tom");
List<String> hobbies = new ArrayList<>();
hobbies.add("游戏");
hobbies.add("小说");
prototype.setHobbies(hobbies);
// 拷贝原型对象
ConcretrPrototype cloneType = prototype.clone();
cloneType.getHobbies().add("睡觉");
System.out.println("原型对象:" + prototype);
System.out.println("拷贝对象:" + cloneType);
}
}
运行结果:
具体原型:ConcretePrototype
改写clone()方法
public class ConcretrPrototype implements Cloneable, Serializable {
private int age;
private String name;
private List<String> hobbies;
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;
}
public ConcretrPrototype deepClone() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (ConcretrPrototype) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
@Override
public String toString() {
return "ConcretrPrototype{" +
"age=" + age +
", name='" + name + '\'' +
", hobbies=" + hobbies +
'}';
}
}
具体调用:Main
public class Main {
public static void main(String[] args) {
ConcretrPrototype prototype = new ConcretrPrototype();
prototype.setAge(18);
prototype.setName("Tom");
List<String> hobbies = new ArrayList<>();
hobbies.add("游戏");
hobbies.add("小说");
prototype.setHobbies(hobbies);
// 拷贝原型对象
ConcretrPrototype cloneType = prototype.deepClone();
cloneType.getHobbies().add("睡觉");
System.out.println("原型对象:" + prototype);
System.out.println("拷贝对象:" + cloneType);
System.out.println(prototype == cloneType);
System.out.println("原型对象的爱好:" + prototype.getHobbies());
System.out.println("拷贝对象的爱好:" + cloneType.getHobbies());
System.out.println(prototype.getHobbies() == cloneType.getHobbies());
}
}
运行结果:
public class HungrySingleton implements Serializable, Cloneable {
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton() {
if (hungrySingleton != null) {
throw new RuntimeException("单例构造器禁止反射调用");
}
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
private Object readResolve() {
return hungrySingleton;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
HungrySingleton hungrySingleton = HungrySingleton.getInstance();
Method method = hungrySingleton.getClass().getDeclaredMethod("clone");
method.setAccessible(true);
HungrySingleton cloneHungrySingleton = (HungrySingleton) method.invoke(hungrySingleton);
System.out.println(hungrySingleton);
System.out.println(cloneHungrySingleton);
}
}
由图可知,该单例模式被破坏,生成的是两个不同的对象。
如果我们克隆的目标的对象是单例对象,那意味着,深克隆就会破坏单例。实际上防止克隆破坏单例只需要禁止深克隆便可。要么单例类不实现Cloneable
接口;要么重写clone()
方法,在clone()
方法中返回单例对象即可。
@Override
protected Object clone() throws CloneNotSupportedException {
return hungrySingleton;
}
1、性能优良,Java自带的原型模式是基于内存二进制流的拷贝,比直接new一个对象性能上提升了许多
2、可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可以辅助实现撤销操作。
1、需要为每一个类配置一个克隆方法
2、克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违反了开闭原则。
3、在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深拷贝、浅拷贝需要运用得当。
链接: 七大设计原则的简单解释(包含合成复用原则),简单理解、快速入门,具备案例代码.
链接: 工厂模式详解附有代码案例分析(简单工厂,工厂方法,抽象工厂).
链接: 单例模式详解及代码案例与应用场景(饿汉式单例模式、懒汉式单例模式、注册式单例模式).
链接: 原型模式详解附有代码案例分析(浅克隆和深克隆的相关解析).
链接: 建造者模式详解附有代码案例分析(包含建造者模式与工厂模式的区别分析).
链接: 门面模式详解附有代码案例分析.
链接: 装饰者模式详解附有代码案例分析.
链接: 享元模式详解附有代码案例分析(包含享元模式的源码应用分析——String中的享元模式应用、Integer中的享元模式应用).
链接: 组合模式详解附有代码案例分析(包含透明组合模式、安全组合模式的代码示例).
链接: 桥接模式详解附有代码案例分析.
链接: 适配器模式详解附有代码案例分析(包含类适配器、对象适配器以及接口适配器的代码示例).
链接: 委派模式详解附有代码案例分析(包含委派模式在JDK中的源码示例解析).
链接: 模板方法模式详解附有代码案例分析(包含模板方法模式重构JDBC操作业务代码示例).
链接: 策略模式详解附有代码案例分析(包含策略模式在源码中的应用以及代码示例).
链接: 责任链模式详解附有代码案例分析(包含责任链模式与建造者模式的结合代码案例).
链接: 迭代器模式详解附有代码案例分析(包含迭代器模式的源码应用分析).
链接: 命令模式详解附有代码案例分析(包含命令模式的源码应用分析).
链接: 状态模式详解附有代码案例分析(包含状态模式与其他相关设计模式的对比).
链接: 备忘录模式详解附有代码案例分析.
链接: 中介者模式详解附有代码案例分析.
链接: 解释器模式详解附有代码案例分析.
链接: 观察者模式详解附有代码案例分析(包含观察者模式使用JDK方式实现).
链接: 访问者模式详解附有代码案例分析.