说明:本文介绍设计模式中,创建型中的原型模式;
飞机大战
创建型设计模式关注于对象的创建,原型模式也不例外。如简单工厂和工厂模式中提到过的飞机大战这个例子,游戏中飞机、坦克对象会创建许许多多的实例,每个实例除了坐标,是一模一样的,如果每次都用关键字new去创建,是非常消耗时间的。
(Enemy,敌人抽象类)
/**
* 敌人抽象类
*/
public abstract class Enemy {
/**
* 敌人的坐标
*/
protected int x;
/**
* 敌人的坐标
*/
protected int y;
/**
* 抽象方法
*/
public Enemy(int x, int y) {
this.x = x;
this.y = y;
}
/**
* 绘制方法
*/
public abstract void show();
}
(AirPlane,飞机类)
/**
* 飞机
*/
public class AirPlane extends Enemy {
public AirPlane(int x, int y) {
super(x, y);
}
@Override
public void show() {
System.out.println("飞机出现了,坐标是:" + x + "," + y);
}
}
(Client,客户端类,循环创建对象,浪费资源,影响效率)
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
// 屏幕宽度
int screenLength = 100;
// 定义飞机集合
List<AirPlane> airPlanes = new ArrayList<>();
// 创建飞机
for (int i = 0; i < 100000; i++) {
airPlanes.add(new AirPlane(new Random().nextInt(screenLength), 0));
}
}
}
原型模式
原型模式,是使用了Object类里面的clone()方法,对原型对象,也就是创建的第一个对象进行了克隆,避免重复创建对象。如下:
(AirPlane,飞机类,实现Cloneable接口)
/**
* 飞机
*/
public class AirPlane extends Enemy implements Cloneable {
public AirPlane(int x, int y) {
super(x, y);
}
@Override
public void show() {
System.out.println("飞机出现了,坐标是:" + x + "," + y);
}
/**
* clone方法,调用父类的clone方法
*/
public AirPlane clone() {
Object obj = null;
try {
obj = super.clone();
return (AirPlane) obj;
} catch (CloneNotSupportedException e) {
System.out.println("克隆失败");
return null;
}
}
}
(AirPlaneFactory,飞机工厂类,调用飞机的克隆方法)
/**
* 飞机工厂
*/
public class AirPlaneFactory {
/**
* 加载一个原型对象
*/
private static AirPlane airPlane = new AirPlane(0, 0);
/**
* 获取飞机
* @param x
* @return
*/
public static AirPlane getAirPlane(int x) {
AirPlane clone = airPlane.clone();
clone.setX(x);
return clone;
}
}
(客户端类,创建飞机实例对象)
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
// 屏幕宽度
int screenLength = 100;
// 定义飞机集合
List<AirPlane> airPlanes = new ArrayList<>();
// 创建飞机
for (int i = 0; i < 100000; i++) {
airPlanes.add(AirPlaneFactory.getAirPlane(new Random().nextInt(screenLength)));
}
// 比较两个飞机是否相等
System.out.println(airPlanes.get(0) == airPlanes.get(1));
}
}
克隆的实例,是不相等的;
以上就是通过原型模式实例化对象的过程,可以节约内存空间。
浅拷贝和深拷贝
在使用了clone()方法时,我们需要知道浅拷贝和深拷贝的概念。在Java中有基本数据类型和引用数据类型,其中基本数据类型有8种,分别是byte、short、int、long、float、double、char、boolean,除此之外的都是引用数据类型。
对于基本数据类型,浅拷贝和深拷贝都是创建一个新对象,而对于引用数据类型,浅拷贝是拷贝一个对象的指针,深拷贝是拷贝一个一模一样的对象。
例如,在上面的基础上,我创建一个机长类,在飞机类里面注入一个机长类对象,克隆之后,对飞机里面的机长对象进行判断,如下:
(机长类)
/**
* 机长
*/
public class Captain implements Cloneable{
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
(注入一个机长类对象)
/**
* 飞机
*/
public class AirPlane extends Enemy implements Cloneable {
/**
* 机长
*/
private Captain captain;
public AirPlane(int x, int y, Captain captain) {
super(x, y);
this.captain = captain;
}
@Override
public void show() {
System.out.println("飞机出现了,坐标是:" + x + "," + y);
}
/**
* clone方法,调用父类的clone方法
*/
public AirPlane clone() {
Object obj = null;
try {
obj = super.clone();
return (AirPlane) obj;
} catch (CloneNotSupportedException e) {
System.out.println("克隆失败");
return null;
}
}
public Captain getCaptain() {
return captain;
}
}
(克隆对象后,对飞机对象和飞机对象里面的机长对象进行判断)
// 比较两个飞机是否相等
System.out.println(airPlanes.get(0) == airPlanes.get(1));
// 比较两个飞机的机长是否相等
System.out.println(airPlanes.get(0).getCaptain() == airPlanes.get(1).getCaptain());
结果是飞机对象是互不相同的,但是飞机对象中的引用数据类型,是相同的,是浅拷贝;
这肯定是不行的,每个实例的数据应该是封装独有的,不能“克隆了,但没完全克隆”。我们可以采用字节流的方式实现“深拷贝”,如下:
(敌人抽象类)
/**
* 敌人抽象类
*/
public abstract class Enemy {
/**
* 敌人的坐标
*/
protected int x;
/**
* 敌人的坐标
*/
protected int y;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
/**
* 绘制方法
*/
public abstract void show();
}
(飞机类,使用IO流的方式来实现深拷贝)
import java.io.*;
/**
* 飞机
*/
public class AirPlane extends Enemy implements Serializable {
/**
* 机长
*/
private Captain captain;
public Captain getCaptain() {
return captain;
}
public void setCaptain(Captain captain) {
this.captain = captain;
}
@Override
public void show() {
System.out.println("飞机出现了,坐标是:" + x + "," + y);
}
/**
* 获取飞机
* @param x
* @return
*/
public AirPlane getAirPlane(int x) throws IOException, ClassNotFoundException {
this.setX(x);
// 创建对象输出流
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(this);
// 返回克隆对象
ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (AirPlane) ois.readObject();
}
}
(机长类)
import java.io.Serializable;
/**
* 机长
*/
public class Captain implements Serializable {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
创建对象,调用“克隆”方法,比较两个对象的地址和对象内的引用类型数据的地址;
import java.io.IOException;
import java.util.Random;
/**
* 客户端
*/
public class Client {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 屏幕宽度
int screenLength = 100;
AirPlane airPlane = new AirPlane();
airPlane.setCaptain(new Captain());
AirPlane airPlaneNew = airPlane.getAirPlane(new Random().nextInt(screenLength));
// 比较两个飞机是否相等
System.out.println(airPlane == airPlaneNew);
// 比较两个飞机的机长是否相等
System.out.println(airPlane.getCaptain() == airPlaneNew.getCaptain());
}
}
都为false,说明“深拷贝”已经实现。
深克隆的实现并未用到Object的clone方法,而是使用了IO流中的Object流的方式,对对象进行序列化来实现的。
原型管理器
原型管理器的构想是,创建一个管理器类,包含了所有需要克隆的对象,需要的时候可以直接调用管理器中的方法,取一个克隆的对象出来。代码如下:
(定义一个敌人接口,一个克隆方法,一个展示方法)
/**
* 敌人接口
*/
public interface Enemy extends Cloneable{
/**
* 克隆方法
* @return
*/
Enemy clone();
/**
* 显示方法
*/
void show();
}
(分别定义敌人飞机、敌人坦克对象)
/**
* 敌人飞机
*/
public class AirPlane implements Enemy{
@Override
public Enemy clone() {
AirPlane airPlane = null;
try {
airPlane = (AirPlane) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("克隆失败");
}
return airPlane;
}
@Override
public void show() {
System.out.println("敌人飞机");
}
}
/**
* 敌人坦克
*/
public class Tank implements Enemy{
@Override
public Enemy clone() {
Tank tank = null;
try {
tank = (Tank) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("克隆失败");
}
return tank;
}
@Override
public void show() {
System.out.println("敌人坦克");
}
}
(定义原型管理器,通过getKey()的方式获取到对应对象的克隆实例)
import java.util.Hashtable;
/**
* 原型管理类
*/
public class PrototypeManagement {
private Hashtable ht = new Hashtable();
private static PrototypeManagement pm = new PrototypeManagement();
/**
* 添加敌人
* @param key
* @param enemy
*/
public void addEnemy(String key, Enemy enemy) {
ht.put(key, enemy);
}
/**
* 获取敌人
* @param key
* @return
*/
public Enemy getEnemy(String key) {
Enemy enemy = (Enemy) ht.get(key);
return enemy.clone();
}
public static PrototypeManagement getPrototypeManagement() {
return pm;
}
}
(客户端测试)
/**
* 原型管理器客户端
*/
public class Client {
public static void main(String[] args) {
PrototypeManagement pm = PrototypeManagement.getPrototypeManagement();
// 添加敌人
pm.addEnemy("airplane", new AirPlane());
pm.addEnemy("tank", new Tank());
AirPlane airplane = (AirPlane) pm.getEnemy("airplane");
airplane.show();
AirPlane airplaneNew = (AirPlane) pm.getEnemy("airplane");
airplaneNew.show();
System.out.println(airplane == airplaneNew);
System.out.println("====================================");
Tank tank = (Tank) pm.getEnemy("tank");
tank.show();
Tank tankNew = (Tank) pm.getEnemy("tank");
tankNew.show();
System.out.println(tank == tankNew);
}
}
通过克隆获取实例,地址不相等;
此时,如果需要增加一个Boss,非常简单,只需要新增一个类实现Enemy接口即可;
总结
本文参考《设计模式的艺术》、《秒懂设计模式》两书