原型模式(Prototype):用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
一、原型模式
原型模式其实就是从一个对象中再创建另一个对象,而且不需要知道详细的创建细节,其UML图如下:
原型模式由以下部分组成:
抽象原型(Prototype)角色:这是一个抽象角色,通常由一个Java接口或Java抽象类实现。此角色给出所有的具体原型类所需的接口clone()。
具体原型(ConcretePrototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。
Prototype类需要具备以下两个条件:
实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了该接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public。
二、案例分析
在《西游记》中,孙悟空,拔一根毫毛,吹一口气,就能变出成千上万的小猴子,这是孙悟空的分身术,作者在定义孙悟空(Prototype)原型的时候,就想好了其可以拥有分身术(clone()),这里面就隐含了一个原型模式,其UML图如下:
代码如下:
原型类–孙悟空:
class Monkey implements Cloneable {
private String name;
public Prototype clone(){
Prototype prototype= null;
try {
prototype = (Prototype)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
故事上演:
public class Test1{
public static void main(String[] args) {
Monkey swk = new Monkey();
swk.setName("悟空");
Monkey swk2 = swk.clone();
System.out.println("孙悟空和小猴子是同一个对象么?"+ (swk==swk2));
}
}
运行结果:
从运行结果看出,复制出的对象是一个新的对象,达到了以原型复制的目的,这样孙悟空就能变出好多猴子了。
三、浅复制和深复制
接着让我们还是以孙悟空的分身术为例,我们规定,每个猴子不仅有名字,还得有武器,扩展上面的类,名字就是一个Sring类型,而武器有名字和重量,作为一个类,扩展如下:
武器类:
class Weapon implements Cloneable {
private String name;
private String weight;
public Weapon(String name, String weight) {
this.name = name;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getWeight() {
return weight;
}
public void setWeight(String weight) {
this.weight = weight;
}
}
Prototype–孙悟空:
class Monkey implements Cloneable {
private String name;
private Weapon Weapon;
public Monkey clone() {
Monkey monkey = null;
try {
monkey = (Monkey) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return monkey;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Weapon getWeapon() {
return Weapon;
}
public void setWeapon(Weapon Weapon) {
this.Weapon = Weapon;
}
}
故事上演:
public class Story{
Monkey swk = new Monkey();
swk.setName("悟空");
swk.setWeapon(new Weapon("如意金箍棒","一万三千五百斤"));
Monkey swk2 = (Monkey) swk.clone();
System.out.println("孙悟空和小猴子是同一个对象么?回答:"+(swk == swk2));
System.out.println("孙悟空和小猴子的武器一样么?回答:"+(swk.getWeapon() == swk2.getWeapon()));
}
运行结果如下:
这个结果不是我们想要的呀,一万三千五百斤的金箍棒孙悟空能扛得住,但小猴子们就不行了。况且,小猴子应该有自己的武器啊,那为什么会出现上面的结果呢?
我们先认识一下“浅复制”和“深复制”:
浅复制:
浅复制,用来创建一个新的对象,然后将当前对象的非静态字段复制到新对象,如果该字段是值类型的,则对该字段执行逐位复制,如果该字段是引用类型的,则复制引用但不复制引用的对象。
深复制:
用来创建一个新的对象,都含有与原来对象相同的值,除了那些引用其他对象的变量。那些引用其他对象的变量将指向一个被复制的新对象,而不再是原有那些被引用对象。即深复制把要复制的对象所引用的对象也都复制了一次,而这种对被引用到的对象复制叫做间接复制。
这样看来,名字是一个String类型的,就会复制成功,而武器类,是一个引用类型的,复制的是其引用,指向了同一个对象。
那又该怎么实现我们的需求呢? 孙悟空因为有了分身的方法,才可以变出很多小猴子,那我们为什么不让武器类也能够分身呢,这样岂不是就会出现我们想要的结果!
为做到深度克隆,所有需要复制的对象都需要实现java.io.Serializable接口。
代码如下:
武器类:实现clone()方法
class Weapon implements Cloneable,Serializable {
private String name;
private String weight;
public Weapon(String name, String weight) {
this.name = name;
this.weight = weight;
}
public Weapon clone() {
Weapon weapon = null;
try {
weapon = (Weapon) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return weapon;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getWeight() {
return weight;
}
public void setWeight(String weight) {
this.weight = weight;
}
}
原型类–孙悟空:提供一个深复制的方法:deepClone()
class Monkey implements Cloneable,Serializable {
private String name;
private Weapon weapon;
public Monkey(String name, Weapon weapon) {
this.name = name;
this.weapon = weapon;
}
public Monkey clone() {
Monkey Monkey = null;
try {
Monkey = (Monkey) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return Monkey;
}
public Monkey deepClone() throws IOException, ClassNotFoundException{
Monkey monkey = null;
try {
monkey = (Monkey) super.clone();
monkey.setWeapon(monkey.getWeapon().clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return monkey;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Weapon getWeapon() {
return weapon;
}
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
}
故事上演:
public class Story {
public static void main(String[] args) {
Monkey swk = new Monkey("悟空",new Weapon("如意金箍棒","一万三千五百斤"));
Monkey monkey1 = null;
try {
monkey1 = (Monkey) swk.deepClone();
System.out.println("孙悟空和小猴子是同一个对象么?回答:"+(swk == monkey1));
System.out.println("孙悟空和小猴子的武器一样么?回答:"+(swk.getWeapon() == monkey1.getWeapon()));
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果如下:
武器类和孙悟空类都实现了ICloneable接口,实现了深复制的效果。
以上内容,代码地址:Prototype
四、模式结语
深复制与浅复制问题中,会发生深复制的有java中的8种基本类型以及他们的封装类型,另外还有String类型。其余的都是浅复制。
原型模型的使用场景:
当一个系统应该独立于它的产品创建、构成和表示时,要使用 Prototype模式;
当要实例化的类是在运行时刻指定时,例如,通过动态装载;
为了避免创建一个与产品类层次平行的工厂类层次时;
当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
欢迎大家评论指正,点击查看其他设计模式。