引子
小帅就职于一家游戏公司,参与开发一款RPG游戏,他负责设计游戏里的怪物。有些大场面需要成百上千的怪物,如果用new的方法创建每一个怪物,需要初始化的参数很多,会比较耗时间,而且也比较麻烦。
小帅决定用原型模式快速地克隆怪物,让怪物大军迅速集结。
原型模式
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型是一种创建型设计模式, 使你能够复制对象, 甚至是复杂对象, 而又无需使代码依赖它们所属的类。
对应我们的代码,类图如下:
怪物类:
/**
* 怪物类
*/
public class Monster implements Cloneable{
/**
* 名称
*/
String name;
/**
* 攻击力
*/
int attackPower;
/**
* 生命值
*/
int hp;
public Monster(String name, int attackPower, int hp) {
this.name = name;
this.attackPower = attackPower;
this.hp = hp;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "怪物名称:" + name + ",攻击力:" + attackPower + ",生命值:" + hp;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAttackPower() {
return attackPower;
}
public void setAttackPower(int attackPower) {
this.attackPower = attackPower;
}
public int getHp() {
return hp;
}
public void setHp(int hp) {
this.hp = hp;
}
}
客户端类:
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
List<Monster> monsterList = new ArrayList<Monster>();
Monster monster = new Monster("飞龙", 200, 100);
for(int i = 0; i < 10; i++) {
monsterList.add((Monster)monster.clone());
}
monsterList.stream().forEach(f -> System.out.println(f));
}
}
输出:
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
注意这里的Monster是要实现Cloneable接口才能使用clone()方法,如果把Cloneable接口去掉是会报错的:
Exception in thread "main" java.lang.CloneNotSupportedException: prototype.monster.normal.Monster
at java.lang.Object.clone(Native Method)
at prototype.monster.normal.Monster.clone(Monster.java:31)
at prototype.monster.normal.Client.main(Client.java:13)
Object类中clone()方法中已经说明了:
浅拷贝和深拷贝
如果每个怪物都有自己的宠物,宠物有自己的名称和技能,我们再来看看下面的例子。
浅拷贝
怪物类:
/**
* 怪物类
*/
public class Monster implements Cloneable{
/**
* 名称
*/
String name;
/**
* 攻击力
*/
int attackPower;
/**
* 生命值
*/
int hp;
/**
* 宠物
*/
Pet pet;
public Monster(String name, int attackPower, int hp, Pet pet) {
this.name = name;
this.attackPower = attackPower;
this.hp = hp;
this.pet = pet;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "怪物名称:" + name + ",攻击力:" + attackPower + ",生命值:" + hp + ", 宠物名称:" + pet.name + ",技能:" + pet.skill;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAttackPower() {
return attackPower;
}
public void setAttackPower(int attackPower) {
this.attackPower = attackPower;
}
public int getHp() {
return hp;
}
public void setHp(int hp) {
this.hp = hp;
}
public Pet getPet() {
return pet;
}
public void setPet(Pet pet) {
this.pet = pet;
}
}
宠物类:
/**
* 宠物类
*/
public class Pet {
/**
* 名称
*/
String name;
/**
* 技能
*/
String skill;
public Pet(String name, String skill) {
this.name = name;
this.skill = skill;
}
@Override
public String toString() {
return "宠物名称:" + name + ",技能:" + skill;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
}
客户端类:
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
// 宠物
Pet pet = new Pet("小石头人", "飞石");
// 怪兽
Monster monster = new Monster("山岭巨人", 300, 500, pet);
// 怪兽副本
Monster monsterClone = (Monster)monster.clone();
System.out.println("monster :" + monster);
System.out.println("monsterClone :" + monsterClone);
System.out.println("----------------------------------------------------------------------------------------------");
// 只是修改怪兽副本的宠物属性
monsterClone.pet.setName("飞鹰");
monsterClone.pet.setSkill("俯冲");
System.out.println("monster :" + monster);
System.out.println("monsterClone :" + monsterClone);
}
}
输出:
monster :怪物名称:山岭巨人,攻击力:300,生命值:500, 宠物名称:小石头人,技能:飞石
monsterClone :怪物名称:山岭巨人,攻击力:300,生命值:500, 宠物名称:小石头人,技能:飞石
----------------------------------------------------------------------------------------------
monster :怪物名称:山岭巨人,攻击力:300,生命值:500, 宠物名称:飞鹰,技能:俯冲
monsterClone :怪物名称:山岭巨人,攻击力:300,生命值:500, 宠物名称:飞鹰,技能:俯冲
从上面的例子可以看到,复制出来的怪物对象monsterClone修改了自己的宠物pet的属性,同时也修改了原型怪物的宠物属性。
这是因为在 Java 语言中,Object 类的 clone() 方法执行的就是上面的浅拷贝。它只会拷贝对象中的基本数据类型的数据(比如,int、long),以及引用对象(pet)的内存地址,不会递归地拷贝引用对象本身。
所以,monster和monsterClone对象引用的是同一个pet对象。
深拷贝
下面我们来看看深拷贝的例子:
宠物类:
客户端类:
输出:
可以看到,复制的怪物只是修改了自己的宠物,原型怪物的宠物没有改变。
深拷贝就是把对象里的对象都一一拷贝了,每个对象里所有的数据都有独立的副本。
下面两张图描述了浅拷贝和深拷贝的区别:
还有一种实现深拷贝的方法就是序列化和反序列化:
public Object deepCopy(Object object) {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(object);
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return oi.readObject();
}
总结
如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(拷贝、克隆)的方式,来创建新对象,以达到节省创建时间的目的。
原型模式是在内存二进制层面拷贝对象,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更加高效。
这就是原型模式,是不是很简单?
最后,我们看看原型模式的优点和缺点:
优点
- 可以克隆对象, 而无需与它们所属的具体类相耦合。
- 可以克隆预生成原型, 避免反复运行初始化代码。
- 可以更方便地生成复杂对象。
- 可以用继承以外的方式来处理复杂对象的不同配置。
缺点
- 克隆包含循环引用的复杂对象可能会非常麻烦。