问题

一个游戏程序例如微信的打飞机游戏,游戏中需要多个敌机实例,假如需要1000个敌机实例对象,我们该怎么创建这些对象?

一种可行的做法是:游戏还未开始之前,也就是游戏的加载阶段我们就实例化了这一关卡的所有1000架敌机,这种做法看似没有任何问题,然而效率却是非常低的。我们知道在游戏画面上根本没必要同时出现这么多敌机,而在游戏还未开始之前,也就是游戏的加载阶段我们就实例化了这一关卡的所有1000架敌机,这不但使加载速度变慢,而且是对有限内存资源的一种浪费。

另一种方法是:“懒加载”,随着游戏的进行,需要新的敌机出现时,再New一个敌机实例出来。然而这种方法依然可能有性能问题,基于类的实例化过程需要调用类的构造方法,如果这个构造方法很复杂或者需要大量的CPU资源(在大型游戏中,这个假设是很有可能的),那么可能会造成游戏卡顿。

一个很好的解决方法是使用——原型模式

定义

原型模式(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式。

说人话就是:用实例对象创建实例对象。

这么做的好处在于:当一个类的初始化方法很复杂或者需要耗费大量CPU资源,并且我们还需要多个这个类的对象时,用实例对象克隆出新的实例对象的方法可以节省资源,提高系统的性能。

原型模式

我们继续以打飞机游戏为例,运用原型模式的做法是,敌机类提供一个克隆自己的接口,具体看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//EnemyPlane敌机类实现了java.lang包中的克隆接口Cloneable
public class EnemyPlane implements Cloneable{
private int x;//敌机横坐标
private int y=0;//敌机纵坐标,假设敌机只能从y=0处出发纵向移动

//构造方法
public EnemyPlane(int x) {
this.x = x;
}

public int getX() {
return x;
}

public int getY() {
return y;
}

public void fly() {
//敌机纵向移动
y++;
}

//复制的实例可以通过此方法变更x坐标
public void setX(int x) {
this.x = x;
}

@Override
public EnemyPlane clone() throws CloneNotSupportedException{
return (EnemyPlane)super.clone();
}
}

/**
* EnemyPlaneFactory敌机工厂类,负责克隆敌机实例
*/
public class EnemyPlaneFactory {

//用单例饿汉模式,创造一个敌机原型
private static EnemyPlane protoType = new EnemyPlane(109);

//获取克隆的敌机实例
public static EnemyPlane getInstance(int x){
//克隆原型
EnemyPlane clonePlane = protoType.clone();
//个性化设置克隆出来的敌机的坐标
clonePlane.setX(x);
return clonePlane;
}
}

深拷贝与浅拷贝

上述拷贝方法有一个问题,如果上述敌机类有一个属性是引用类型,比如属性是另一个类的实例化对象,如敌机类有一个子弹属性private Bullet bullet = new Bullet();

Java中的变量分为原始类型和引用类型,所谓浅拷贝是指只复制原始类型的值,比如横坐标x与纵坐标y这种以原始类型int定义的值,它们会被复制到新克隆出的对象中。而引用类型同样会被拷贝,但是请注意这个操作只是拷贝了地址引用(指针),也就是说克隆出的副本敌机与原型敌机中的子弹是同一颗,因为两个同样的地址实际指向的内存对象是同一个bullet对象。

那么上述代码的克隆方法就需要修改一下了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//EnemyPlane敌机类实现了java.lang包中的克隆接口Cloneable
public class EnemyPlane implements Cloneable{
private int x;//敌机横坐标
private int y=0;//敌机纵坐标,假设敌机只能从y=0处出发纵向移动
private Bullet bullet;//子弹实例

//构造方法
public EnemyPlane(int x, Bullet bullet {
this.x = x;
this.bullet = bullet
}

public int getX() {
return x;
}

public int getY() {
return y;
}

public void fly() {
//敌机纵向移动
y++;
}

//复制的实例可以通过此方法变更x坐标
public void setX(int x) {
this.x = x;
}

public void setBullet(Bullet bullet){
this.bullet = bullet;
}

@Override
public EnemyPlane clone() throws CloneNotSupportedException{
//克隆敌机
EnemyPlane clonePlane = (EnemyPlane)super.clone();
//对子弹克隆
clonePlane.setBullet(this.bullet.clone())
return clonePlane;
}
}

Python实现原型模式

参考:python-原型模式

参考文献:
《秒懂设计模式》刘韬