原型模式(Prototype pattern)
定义
通过已有对象,拷贝出一个新对象,具有一模一样的属性,存放在不同的内存地址上
模式总结过程
游戏DOTA,简化规则如下:
- 分上中下三路,每路、每30秒就出现一波小兵,各路小兵沿种路向对方进发。
- 每波小兵由一个远程小兵和三个进战小兵组成。
- 当攻破一路兵营后,小兵会升级成中后,当攻破三路兵营后,会升级成大兵。
- 小中大兵的区别在于血量、攻击力和体积不同。
- 每个小兵有自己的状态,所以得有一个小兵一个对象。
- 游戏分近卫、天灾两大阵营。
- 初步的设计,可能是这样子的
// 士兵类
public class Soldier {
// 等级,1 2 3级
private int level;
// 血量
private double blood;
// 类型,进战、远程
private String classify;
// 攻击力
private int attack;
// 体积,大、中、小
private String display;
// 阵营,近卫、天灾
private String camp;
// 路线,上路、中路、下路
private String route;
// 其它共有属性没有列出,如速度、仇恨等
// get set constructor toString
}
// 客户端,以近卫中路作为演示
public class Client {
// 以近卫中路作为演示
public static void main(String[] args) {
System.out.println("游戏开始,时间:00:00");
System.out.println("近卫中路创建一波小兵,3个近战、1个远程");
Soldier soldier0001 = new Soldier(1, 600, "进战", 30, "小兵", "近卫", "中路");
Soldier soldier0002 = new Soldier(1, 600, "进战", 30, "小兵", "近卫", "中路");
Soldier soldier0003 = new Soldier(1, 600, "进战", 30, "小兵", "近卫", "中路");
Soldier soldier0004 = new Soldier(1, 300, "远程", 28, "小兵", "近卫", "中路");
print(soldier0001, soldier0002, soldier0003, soldier0004);
System.out.println("游戏时间:00:30");
System.out.println("近卫中路创建一波小兵,3个近战、1个远程");
Soldier soldier0005 = new Soldier(1, 600, "进战", 30, "小兵", "近卫", "中路");
Soldier soldier0006 = new Soldier(1, 600, "进战", 30, "小兵", "近卫", "中路");
Soldier soldier0007 = new Soldier(1, 600, "进战", 30, "小兵", "近卫", "中路");
Soldier soldier0008 = new Soldier(1, 300, "远程", 28, "小兵", "近卫", "中路");
print(soldier0005, soldier0006, soldier0007, soldier0008);
System.out.println("每30秒创建一波小兵,每波小兵会进行攻击、也会被攻击,血量会减少、增多(加血)");
System.out.println("...");
System.out.println("当天灾中路兵营被攻破后,近卫中路的小兵升级为中兵(新创建的)");
System.out.println("近卫中路创建一波中兵,3个近战、1个远程");
Soldier soldier0009 = new Soldier(2, 1200, "进战", 60, "中兵", "近卫", "中路");
Soldier soldier0010 = new Soldier(2, 1200, "进战", 60, "中兵", "近卫", "中路");
Soldier soldier0011 = new Soldier(2, 1200, "进战", 60, "中兵", "近卫", "中路");
Soldier soldier0012 = new Soldier(2, 600, "远程", 56, "中兵", "近卫", "中路");
print(soldier0009, soldier0010, soldier0011, soldier0012);
System.out.println("...");
System.out.println("当天灾三路兵营被攻破后,近卫中路的中兵升级为大兵(新创建的)");
System.out.println("近卫中路创建一波大兵,3个近战、1个远程");
Soldier soldier0013 = new Soldier(3, 2400, "进战", 60, "大兵", "近卫", "中路");
Soldier soldier0014 = new Soldier(3, 2400, "进战", 60, "大兵", "近卫", "中路");
Soldier soldier0015 = new Soldier(3, 2400, "进战", 60, "大兵", "近卫", "中路");
Soldier soldier0016 = new Soldier(3, 1200, "远程", 56, "大兵", "近卫", "中路");
print(soldier0013, soldier0014, soldier0015, soldier0016);
}
// 方便打印
private static void print(Soldier... soldiers) {
for (Soldier s : soldiers) {
System.out.println(s);
}
System.out.println();
}
}
/*
游戏开始,时间:00:00
近卫中路创建一波小兵,3个近战、1个远程
Soldier{level=1, blood=600.0, classify='进战', attack=30, display='小兵', camp='近卫', route='中路'}
Soldier{level=1, blood=600.0, classify='进战', attack=30, display='小兵', camp='近卫', route='中路'}
Soldier{level=1, blood=600.0, classify='进战', attack=30, display='小兵', camp='近卫', route='中路'}
Soldier{level=1, blood=300.0, classify='远程', attack=28, display='小兵', camp='近卫', route='中路'}
游戏时间:00:30
近卫中路创建一波小兵,3个近战、1个远程
Soldier{level=1, blood=600.0, classify='进战', attack=30, display='小兵', camp='近卫', route='中路'}
Soldier{level=1, blood=600.0, classify='进战', attack=30, display='小兵', camp='近卫', route='中路'}
Soldier{level=1, blood=600.0, classify='进战', attack=30, display='小兵', camp='近卫', route='中路'}
Soldier{level=1, blood=300.0, classify='远程', attack=28, display='小兵', camp='近卫', route='中路'}
每30秒创建一波小兵,每波小兵会进行攻击、也会被攻击,血量会减少、增多(加血)
...
当天灾中路兵营被攻破后,近卫中路的小兵升级为中兵(新创建的)
近卫中路创建一波中兵,3个近战、1个远程
Soldier{level=2, blood=1200.0, classify='进战', attack=60, display='中兵', camp='近卫', route='中路'}
Soldier{level=2, blood=1200.0, classify='进战', attack=60, display='中兵', camp='近卫', route='中路'}
Soldier{level=2, blood=1200.0, classify='进战', attack=60, display='中兵', camp='近卫', route='中路'}
Soldier{level=2, blood=600.0, classify='远程', attack=56, display='中兵', camp='近卫', route='中路'}
...
当天灾三路兵营被攻破后,近卫中路的中兵升级为大兵(新创建的)
近卫中路创建一波大兵,3个近战、1个远程
Soldier{level=3, blood=2400.0, classify='进战', attack=60, display='大兵', camp='近卫', route='中路'}
Soldier{level=3, blood=2400.0, classify='进战', attack=60, display='大兵', camp='近卫', route='中路'}
Soldier{level=3, blood=2400.0, classify='进战', attack=60, display='大兵', camp='近卫', route='中路'}
Soldier{level=3, blood=1200.0, classify='远程', attack=56, display='大兵', camp='近卫', route='中路'}
*/
可以看到,对象的创建很频繁、复杂,而且容易出错,这时,原型模式就有用武之地了
- 在Soldier类中,提供对象复制的功能,再加一个管理类,管理常用的对象(如大中小兵)
// 士兵类
public class Soldier implements Cloneable {
// 等级
private int level;
// 血量
private double blood;
// 类型,进战、远程
private String classify;
// 攻击力
private int attack;
// 体积
private String display;
// 阵营
private String camp;
// 路线
private String route;
// 其它共有属性没有列出,如速度、仇恨等
// 提供对象复制的方法
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
// get set constructor toString
}
// 士兵管理类
public class SoldierManager {
// 存放各类小兵,开始就把士兵定义好,后面都要从这里取
private Map<String, Soldier> map = new HashMap<>();
// 添加士兵
public void setSoldier(String classify, Soldier soldier) {
map.put(classify, soldier);
}
// 获取士兵
public Soldier getSoldier(String classify) {
return map.get(classify);
}
// 移除士兵,并把目标返回
public Soldier removeSoleier(String classify) {
return map.remove(classify);
}
}
// 客户端,以近卫中路作为演示
public class Client {
// 以近卫中路作为演示
public static void main(String[] args) throws CloneNotSupportedException {
System.out.println("游戏开始前,先把各类士兵定义好,并保存到士兵管理类中");
SoldierManager manager = new SoldierManager();
Soldier soldier1 = new Soldier(1, 600, "进战", 30, "小兵", "近卫", "中路");
Soldier soldier2 = new Soldier(1, 300, "远程", 30, "小兵", "近卫", "中路");
manager.setSoldier("1级近战小兵", soldier1);
manager.setSoldier("1级远程小兵", soldier2);
Soldier soldier3 = new Soldier(2, 1200, "进战", 30, "中兵", "近卫", "中路");
Soldier soldier4 = new Soldier(2, 600, "远程", 30, "中兵", "近卫", "中路");
manager.setSoldier("2级近战中兵", soldier3);
manager.setSoldier("2级远程中兵", soldier4);
Soldier soldier5 = new Soldier(3, 2400, "进战", 30, "大兵", "近卫", "中路");
Soldier soldier6 = new Soldier(3, 1200, "远程", 30, "大兵", "近卫", "中路");
manager.setSoldier("3级近战大兵", soldier5);
manager.setSoldier("3级远程大兵", soldier6);
System.out.println("游戏开始,时间:00:00");
System.out.println("近卫中路创建一波小兵,3个近战、1个远程");
Soldier soldier0001 = manager.getSoldier("1级近战小兵");
Soldier soldier0002 = (Soldier) soldier0001.clone(); // 这里为什么不用manager.getSoldier("1级近战小兵")?因为这样返回的对象就是同一个了(引用指向一个内存地址)
Soldier soldier0003 = (Soldier) soldier0001.clone();
Soldier soldier0004 = manager.getSoldier("1级远程小兵");
print(soldier0001, soldier0002, soldier0003, soldier0004);
System.out.println("游戏时间:00:30");
System.out.println("近卫中路创建一波小兵,3个近战、1个远程");
Soldier soldier0005 = (Soldier) soldier0001.clone();
Soldier soldier0006 = (Soldier) soldier0001.clone();
Soldier soldier0007 = (Soldier) soldier0001.clone();
Soldier soldier0008 = (Soldier) soldier0004.clone();
print(soldier0005, soldier0006, soldier0007, soldier0008);
System.out.println("每30秒创建一波小兵,每波小兵会进行攻击、也会被攻击,血量会减少、增多(加血)");
System.out.println("...");
System.out.println("当天灾中路兵营被攻破后,近卫中路的小兵升级为中兵(新创建的)");
System.out.println("近卫中路创建一波中兵,3个近战、1个远程");
Soldier soldier0009 = manager.getSoldier("2级近战中兵");
Soldier soldier0010 = (Soldier) soldier0009.clone();
Soldier soldier0011 = (Soldier) soldier0009.clone();
Soldier soldier0012 = manager.getSoldier("2级远程中兵");
print(soldier0009, soldier0010, soldier0011, soldier0012);
System.out.println("...");
System.out.println("当天灾三路兵营被攻破后,近卫中路的中兵升级为大兵(新创建的)");
System.out.println("近卫中路创建一波大兵,3个近战、1个远程");
Soldier soldier0013 = manager.getSoldier("3级近战大兵");
Soldier soldier0014 = (Soldier) soldier0013.clone();
Soldier soldier0015 = (Soldier) soldier0013.clone();
Soldier soldier0016 = manager.getSoldier("3级远程大兵");
print(soldier0013, soldier0014, soldier0015, soldier0016);
}
// 方便打印
private static void print(Soldier... soldiers) {
for (Soldier s : soldiers) {
System.out.println(s);
}
System.out.println();
}
}
/*
游戏开始前,先把各类士兵定义好,并保存到士兵管理类中
游戏开始,时间:00:00
近卫中路创建一波小兵,3个近战、1个远程
Soldier{level=1, blood=600.0, classify='进战', attack=30, display='小兵', camp='近卫', route='中路'}
Soldier{level=1, blood=600.0, classify='进战', attack=30, display='小兵', camp='近卫', route='中路'}
Soldier{level=1, blood=600.0, classify='进战', attack=30, display='小兵', camp='近卫', route='中路'}
Soldier{level=1, blood=300.0, classify='远程', attack=30, display='小兵', camp='近卫', route='中路'}
游戏时间:00:30
近卫中路创建一波小兵,3个近战、1个远程
Soldier{level=1, blood=600.0, classify='进战', attack=30, display='小兵', camp='近卫', route='中路'}
Soldier{level=1, blood=600.0, classify='进战', attack=30, display='小兵', camp='近卫', route='中路'}
Soldier{level=1, blood=600.0, classify='进战', attack=30, display='小兵', camp='近卫', route='中路'}
Soldier{level=1, blood=300.0, classify='远程', attack=30, display='小兵', camp='近卫', route='中路'}
每30秒创建一波小兵,每波小兵会进行攻击、也会被攻击,血量会减少、增多(加血)
...
当天灾中路兵营被攻破后,近卫中路的小兵升级为中兵(新创建的)
近卫中路创建一波中兵,3个近战、1个远程
Soldier{level=2, blood=1200.0, classify='进战', attack=30, display='中兵', camp='近卫', route='中路'}
Soldier{level=2, blood=1200.0, classify='进战', attack=30, display='中兵', camp='近卫', route='中路'}
Soldier{level=2, blood=1200.0, classify='进战', attack=30, display='中兵', camp='近卫', route='中路'}
Soldier{level=2, blood=600.0, classify='远程', attack=30, display='中兵', camp='近卫', route='中路'}
...
当天灾三路兵营被攻破后,近卫中路的中兵升级为大兵(新创建的)
近卫中路创建一波大兵,3个近战、1个远程
Soldier{level=3, blood=2400.0, classify='进战', attack=30, display='大兵', camp='近卫', route='中路'}
Soldier{level=3, blood=2400.0, classify='进战', attack=30, display='大兵', camp='近卫', route='中路'}
Soldier{level=3, blood=2400.0, classify='进战', attack=30, display='大兵', camp='近卫', route='中路'}
Soldier{level=3, blood=1200.0, classify='远程', attack=30, display='大兵', camp='近卫', route='中路'}
*/
这样,不用每个士兵对象都new出来,不容易出错,也提高了程序效率;还有,游戏开始前对各类士兵的定义,可以放在manager类,这样可以对客户端隐藏对象的创建过程,简化使用。
例子仅仅为了体现原型模式,没有做线程安全、代码优化、抽取接口等操作,就是怎么方便怎么来
注意
这个例子通过重写Object类的clone方法,实现对象的拷贝,这是一种方法,但是,当对象的属性为引用类型时,需要层层重写clone,这样,会很麻烦。所以,实现对象的拷贝,还可以通过对象序列化进行实现。具体可了解另一篇文章:java浅克隆与深克隆详解
优点
- 提供创建相同对象的方法,简单易用、安全高效
- 提供管理类,让对象可以理好地分类管理
- 让代码更简洁,提高易读性和可维护性
缺点
需要对对象序列化、深克隆和浅克隆的一定理解
应用
在一个程序中,需要大量的对象时,采用原型模式是不错的选择