前言
原型模式同属创建型模式的一种,目的也是为了创建对象。与一般创建对象方式不同的是,原型模式是使用一个对象的实例通过clone的方式,克隆出一个与原型相同的对象,说的简单点原型模式就是通过拷贝原型对象生成新对象,故名原型模式。在Java语言中,可以利用Object类提供的clone方法实现该拷贝流程。
原型模式示例
以一个简单的实例说明原型设计模式
首先定义两个需要拷贝的对象,Person和Computer
public class Person implements Cloneable {
private String name;
private int age;
private String gender;
private Computer computer;
public Person(String name, int age, String gender, Computer computer) {
this.name = name;
this.age = age;
this.gender = gender;
this.computer = computer;
}
public Person(){}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//getter & setter方法省略
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", computer=" + computer +
'}';
}
}
public class Computer implements Cloneable {
private String brand;
private int size;
private String cpu;
private String disk;
public Computer(){}
public Computer(String brand, int size, String cpu, String disk) {
this.brand = brand;
this.size = size;
this.cpu = cpu;
this.disk = disk;
}
/*
重写Object中的clone方法
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Computer{" +
"brand='" + brand + '\'' +
", size=" + size +
", cpu='" + cpu + '\'' +
", disk='" + disk + '\'' +
'}';
}
}
首先要求被拷贝的原型类必须实现Cloneable 接口,然后重写object中的clone方法,如果不重写Cloneable接口,直接重写clone方法,则会抛出CloneNotSupportedException异常。
这里要提一下深拷贝和浅拷贝的知识,对于一个类来讲,属性分为基本数据类型和引用数据类型, int,double,char,String…都属于基本数据类型,一共八种,而一般的对象类型、数组类型都属于引用类型。
- 浅拷贝:
对于这八种数据类型在浅拷贝下会直接对他们的内存空间进行拷贝,但是引用类型数据不会被拷贝,换句话说浅拷贝执行完成后,引用指向依然是原型对象中引用指向的堆内存空间,这个时候的情况就是原型对象里的引用属性和拷贝对象的引用属性指向了同一快内存空间,是一个对象
- 深拷贝:
深拷贝不仅会拷贝基本数据类型,引用数据类型指向的对象空间也会被拷贝,这个时候原型对象和拷贝对象的空间是完全隔离独立的,没有任何关系。
在Java中,默认是浅拷贝,如果需要进行深拷贝,则需要根据实际的需求对clone方法做实现,实现方式当然是自由的。
以上边的代码实例,这是一个典型的浅拷贝,Person类clone实现调用了默认的基类clone方法,自然是浅拷贝,以一段测试代码为例
/**
* @description: test
* @version: 1.0
*/
public class Test {
public static void main(String[] args) throws CloneNotSupportedException, InterruptedException {
Computer computer = new Computer("macintosh",14,"M1","west digital");
Person person = new Person("王大锤",18,"male",computer);
Person clone = (Person) person.clone();
clone.setName("王二锤");
clone.getComputer().setBrand("thinkpad");
System.out.println(person);
System.out.println(clone);
}
}
结果:
Person{name=‘王大锤’, age=18, gender=‘male’, computer=Computer{brand=‘thinkpad’, size=14, cpu=‘M1’, disk=‘west digital’}}
Person{name=‘王二锤’, age=18, gender=‘male’, computer=Computer{brand=‘thinkpad’, size=14, cpu=‘M1’, disk=‘west digital’}}
以上代码以王大锤作为原型对象clone出了一个新的王大锤,然后将克隆结果更名为王二锤,Computer对象也更名为thinkpad,但是通过结果发现,王大锤的Computer也变成了thinkpad,以此说明,王大锤和王二锤的Computer是同一个对象,该对象并没有发生拷贝行为。
如果你有兴趣debug一下,他们的computer引用一定是完全一致的。
接下来对Person的clone方法重写,如下
@Override
protected Object clone() throws CloneNotSupportedException {
Person p1 = (Person) super.clone();
Computer c1 = (Computer) p1.getComputer().clone();
p1.setComputer(c1);
return p1;
}
Person{name=‘王大锤’, age=18, gender=‘male’, computer=Computer{brand=‘macintosh’, size=14, cpu=‘M1’, disk=‘west digital’}}
Person{name=‘王二锤’, age=18, gender=‘male’, computer=Computer{brand=‘thinkpad’, size=14, cpu=‘M1’, disk=‘west digital’}}
再来看结果,修改了王二锤并没有影响到王二锤,说明两者Computer是完全的两个对象,也就是发生了内存的拷贝行为。
前边提到深拷贝行为既然留给了用户扩展,那这个具体实现行为自然是相对自由的,你甚至可以在clone中创建直接通过new创建对象,也可以利用字节码创建,比如很地方提到了用序列化的方式,无论是二进制还是JSON,这就看想象力和具体做法了,只要你认为合理就行。下边以常见的序列化方式创建简单演示以下深拷贝,具体做法就是先将对象进行序列化,二进制或者json,然后通再对其进行反序列化,反序列化将会对序列化的对象重新分配内存空间,写入该对象,自然也就实现了深拷贝。
重写clone方法,利用序列化方式拷贝
/*
使用这种方法,参与序列化类序实现序列化接口
*/
@Override
protected Object clone() throws CloneNotSupportedException {
Person p1 = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
p1 = (Person) ois.readObject();
ois.close();
}catch (IOException e){
//Exception handler
}
return p1;
}
结果:
Person{name=‘王大锤’, age=18, gender=‘male’, computer=Computer{brand=‘macintosh’, size=14, cpu=‘M1’, disk=‘west digital’}}
Person{name=‘王二锤’, age=18, gender=‘male’, computer=Computer{brand=‘thinkpad’, size=14, cpu=‘M1’, disk=‘west digital’}}
使用jdk原生序列化的时候,请一定要记的参与序列化对象类一定要实现Serializable接口,否则一个简单NPE一头雾水。
总结
一句话总结下来 ,原型模式就是通过一些手段对原型对象进行拷贝。简化了对象创建初始化的流程,直接通过运行时的对象状态在内存中复制一个对象的副本,对于需要创建大量复杂相似对象的场景,可以考虑使用原型模式。