1 定义
原型模式是使用一个类实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。原型模式要求对象实现一个可以“克隆”自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。
2 类图
抽象原型类(Prototype):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是接口,抽象类甚至是一个具体的实现类。
具体原型类(ConcretePrototype):它实现了抽象原型类中声明的克隆方法,在克隆方法中返回一个自己的克隆对象。
客户类(Client):在客户类中,使用原型对象只需要通过工厂方式创建或者直接NEW(实例化一个)原型对象,然后通过原型对象的克隆方法就能获得多个相同的对象。由于客户端是针对抽象原型对象编程的所以还可以可以很方便的换成不同类型的原型对象!
3 原型模式优缺点
原型模式作为一种快速创建大量相同或相似的对象方式,在软件开发中的应用较为广泛,很多软件提供的CTRL+C和CTRL+V操作的就是原型模式的典型应用!
3.1 优点
当创建的对象实例较为复杂的时候,使用原型模式可以简化对象的创建过程!
扩展性好,由于写原型模式的时候使用了抽象原型类,在客户端进行编程的时候可以将具体的原型类通过配置进行读取。
可以使用深度拷贝来保存对象的状态,使用原型模式进行复制。当你需要恢复到某一时刻就直接跳到。比如我们的idea种就有历史版本,git中也有这样的操作。非常好用!
3.2 缺点
需要为每一个类配备一个拷贝方法,而且该拷贝方法位于一个类的里面,当对已有的类经行改造时需要修改源代码,违背了开闭原则。
在实现深拷贝的时需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用的时候,为了实现深拷贝,每一层对象对应的类都必须支持深拷贝,实现相对麻烦。
4 浅拷贝与深拷贝
浅拷贝深拷贝是对于引用类型拷贝的概念。在利用类实例调用clone方法生成新的对象时,值类型(八大基本类型,byte,short,int,long,char,double,float,boolean)是直接复制一份,新对象属性值改变时,老对象不会跟着改变。
对于引用对象类型,若是浅拷贝,新的类实例和原来的类实例对于引用对象类型是相同的,即改变新对象引用类型的值,老对象的引用类型的值也会改变。深拷贝是对于引用对象也完全的单独的拷贝一份,不再指向原有的那些被引用的对象,新实例对于引用对象的改变不会影响老的类实例。
String类型不是基本类型,是引用类型,但是String类型是不可变类型,他指向的值为常量,克隆出来的对象改变他的值实际上是改变了克隆出来对象String类型成员的指向,不会影响被克隆对象的值及其指向。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
}
例如现在有两个类,Teacher和Student,Teacher类中引用了Student类。
@Data
public class Teacher{
private int number;
private String name;
private Student student;
}
@Data
public class Student{
private String name;
}
public class client{
Student stu = new Student("zhangsan");
Teacher t = new Teacher(1, "lisi", stu);
}
下图展示了浅拷贝与深拷贝中关于值类型与引用类型的不同之处。
5 Java的clone方法
Java的Object类有一个方法clone,为protected,需要覆盖此方法重写clone方法。Java所有的类都默认继承Object类,所以所有的类都能实现clone方法。
protected native Object clone() throws CloneNotSupportedException;
Java的clone()方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足:
1.对任何的对象x,都有x.clone() !=x//克隆对象与原对象不是同一个对象
2.对任何的对象x,都有x.clone().getClass()= =x.getClass(),即克隆对象与原对象的类型一样
3.如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。
实现clone方法只是其中之一,还需要继承Cloneable接口,否则会抛出CloneNotSupportedException异常。
package java.lang;
/**
* A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.
* <p>
* Invoking Object's clone method on an instance that does not implement the
* <code>Cloneable</code> interface results in the exception
* <code>CloneNotSupportedException</code> being thrown.
* <p>
* By convention, classes that implement this interface should override
* <tt>Object.clone</tt> (which is protected) with a public method.
* See {@link java.lang.Object#clone()} for details on overriding this
* method.
* <p>
* Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
* Therefore, it is not possible to clone an object merely by virtue of the
* fact that it implements this interface. Even if the clone method is invoked
* reflectively, there is no guarantee that it will succeed.
*
* @author unascribed
* @see java.lang.CloneNotSupportedException
* @see java.lang.Object#clone()
* @since JDK1.0
*/
public interface Cloneable {
}
Java中对象的克隆基本做法如下:
1.为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。
2.在派生类中覆盖基类的clone()方法,并声明为public。
3.在派生类的clone()方法中,调用super.clone()。
4.在派生类中实现Cloneable接口。
6 代码示例
孙悟空,有72般变化,孙悟空由原型变为另外一种形式时,就相当于是自身的一个拷贝,其中引用一个金箍棒的类型。
6.1 孙悟空原型SunWuKong
@Data
public class SunWuKong implements Cloneable{
/**
* 表示孙悟空几号
*/
private int number;
/**
* 孙悟空技能描述
*/
private String desc;
/**
* 兵器尺寸
*/
private Staff staff;
/**
* 浅复制
* @return 孙悟空
* @throws CloneNotSupportedException 不支持复制异常
*/
@Override
public Object clone() throws CloneNotSupportedException {
SunWuKong sunWuKong = (SunWuKong)super.clone();
return sunWuKong;
}
}
6.2 金箍棒Staff
@Data
@AllArgsConstructor
public class Staff implements Cloneable{
/**
* 金箍棒的尺寸
*/
private int size;
}
6.3 主函数MainClass
/**
* @program: design-pattern-learning
* @author: zgr
* @create: 2021-09-15 09:48
**/
public class MainClass {
public static void main(String[] args) {
try {
Staff staff = new Staff(200);
SunWuKong sunWuKong1 = new SunWuKong();
sunWuKong1.setNumber(1);
sunWuKong1.setDesc("我是1号");
sunWuKong1.setStaff(staff);
System.out.println(sunWuKong1);
System.out.println("*****************浅拷贝*******************");
SunWuKong sunWuKong2 = (SunWuKong) sunWuKong1.clone();
System.out.println(sunWuKong2);
System.out.println("*****************改变属性*******************");
staff.setSize(300);
sunWuKong2.setNumber(2);
sunWuKong2.setDesc("我原来是1号,我现在变了");
System.out.println(sunWuKong1);
System.out.println(sunWuKong2);
}catch (CloneNotSupportedException e){
System.out.println("对象不能被克隆");
}
}
}
6.4 运行结果
6.5 结果分析
注意引用对象Staff,我们在改变staff属性时,孙悟空原型与拷贝类型都发生了改变,这说明是浅复制。
7 利用Java序列化实现深拷贝
7.1 原型代码
主要代码不变,只需要在Staff类和SunWuKong类添加继承Serializable接口,在SunWuKong添加deepClone方法。
@Data
public class SunWuKong implements Cloneable, Serializable{
/**
* 表示孙悟空几号
*/
private int number;
/**
* 孙悟空技能描述
*/
private String desc;
/**
* 兵器尺寸
*/
private Staff staff;
/**
* 浅复制
* @return 孙悟空
* @throws CloneNotSupportedException 不支持复制异常
*/
@Override
public Object clone() throws CloneNotSupportedException {
SunWuKong sunWuKong = (SunWuKong)super.clone();
return sunWuKong;
}
public Object 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 ois.readObject();
}
}
@Data
@AllArgsConstructor
public class Staff implements Cloneable, Serializable {
/**
* 金箍棒的尺寸
*/
private int size;
}
7.2 主函数代码
public class MainClass {
public static void main(String[] args) {
try {
Staff staff = new Staff(200);
SunWuKong sunWuKong1 = new SunWuKong();
sunWuKong1.setNumber(1);
sunWuKong1.setDesc("我是1号");
sunWuKong1.setStaff(staff);
System.out.println(sunWuKong1);
System.out.println("*****************浅拷贝*******************");
SunWuKong sunWuKong2 = (SunWuKong) sunWuKong1.clone();
staff.setSize(300);
sunWuKong2.setNumber(2);
sunWuKong2.setDesc("我原来是1号,我现在变2号");
System.out.println(sunWuKong1);
System.out.println(sunWuKong2);
System.out.println("*****************深拷贝*******************");
SunWuKong sunWuKong3 = (SunWuKong) sunWuKong1.deepClone();
staff.setSize(400);
sunWuKong3.setNumber(3);
sunWuKong3.setDesc("我是深拷贝3号");
System.out.println(sunWuKong1);
System.out.println(sunWuKong2);
System.out.println(sunWuKong3);
}catch (CloneNotSupportedException e){
System.out.println("对象不能被克隆");
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
7.3 运行结果
7.4 结果分析
在改变staff的size属性后,1号2号都改变的其属性,3号还是克隆之前的300属性,这说明引用对对象被重新拷贝的了一份。
8 引用
1.《大话设计模式》
2.JAVA原型模式
9 源代码
https://github.com/airhonor/design-pattern-learning/tree/main/src/com/hz/design/pattern/prototype