原型模式定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建
原型模式通用源码:
public class PrototypeClass implements Cloneable {
@Override
protected PrototypeClass clone(){
// TODO Auto-generated method stub
PrototypeClass prototypeClass = null;
try {
prototypeClass = (PrototypeClass)super.clone();
} catch (CloneNotSupportedException e) {
// TODO: handle exception
//process CloneNotSupportedException
}
return prototypeClass;
}
}
原型模式的实现
下面以一个英雄的样例来实现原型模式:
import java.util.ArrayList;
public class Hero implements Cloneable{
private String name;
private ArrayList heroSkills = new ArrayList();
public Hero() {
System.out.println(Hero 构造方法);
}
@Override
protected Hero clone(){
// TODO Auto-generated method stub
try {
Hero hero = (Hero) super.clone();
hero.name = this.name;
hero.heroSkills = this.heroSkills;
return hero;
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ArrayList getHeroSkills() {
return heroSkills;
}
public void addHeroSkills(String skillName) {
if(!heroSkills.contains(skillName)){
this.heroSkills.add(skillName);
}
}
public void showInfo() {
System.out.println(----------- Hero Start -----------);
System.out.println(name : + name);
System.out.println(heroSkills: );
for (String heroSkill : heroSkills) {
System.out.println(hero Skill: + heroSkill);
}
System.out.println(-----------Hero End -----------);
}
}
Hero类是一个英雄类,定义了名称和技能二个变量,Hero实现Cloneable接口的clone的方法。
Client端的使用方法:
public class Prototype {
public static void main(String[] args) {
// TODO Auto-generated method stub
Hero hero = new Hero();
hero.setName(Sniper--火枪);
hero.addHeroSkills(榴霰弹);
hero.addHeroSkills(爆头);
hero.addHeroSkills(瞄准);
hero.addHeroSkills(暗杀);
hero.showInfo();
Hero heroClone = hero.clone();
heroClone.showInfo();
heroClone.setName(Sniper--火枪--第一把);
heroClone.showInfo();
hero.showInfo();
}
}
运行方法,输入如下:
Hero 构造方法
----------- Hero Start -----------
name : Sniper--火枪
heroSkills:
hero Skill: 榴霰弹
hero Skill: 爆头
hero Skill: 瞄准
hero Skill: 暗杀
-----------Hero End -----------
----------- Hero Start -----------
name : Sniper--火枪
heroSkills:
hero Skill: 榴霰弹
hero Skill: 爆头
hero Skill: 瞄准
hero Skill: 暗杀
-----------Hero End -----------
----------- Hero Start -----------
name : Sniper--火枪--第一把
heroSkills:
hero Skill: 榴霰弹
hero Skill: 爆头
hero Skill: 瞄准
hero Skill: 暗杀
-----------Hero End -----------
----------- Hero Start -----------
name : Sniper--火枪
heroSkills:
hero Skill: 榴霰弹
hero Skill: 爆头
hero Skill: 瞄准
hero Skill: 暗杀
-----------Hero End -----------
从输出的信息,我们可以看出我们通过new一个对象和通过hero.clone()方法产生对象是一致的,并且通过clone方法产生的对象是不跑构造方法的。
深复制和浅复制
我们把Client改为:
public static void main(String[] args) {
// TODO Auto-generated method stub
Hero hero = new Hero();
hero.setName(Sniper--火枪);
hero.addHeroSkills(榴霰弹);
hero.addHeroSkills(爆头);
hero.addHeroSkills(瞄准);
hero.addHeroSkills(暗杀);
hero.showInfo();
Hero heroClone = hero.clone();
heroClone.showInfo();
heroClone.addHeroSkills(火枪的第五个技能);
heroClone.showInfo();
hero.showInfo();
}
输出信息:
Hero 构造方法
----------- Hero Start -----------
name : Sniper--火枪
heroSkills:
hero Skill: 榴霰弹
hero Skill: 爆头
hero Skill: 瞄准
hero Skill: 暗杀
-----------Hero End -----------
----------- Hero Start -----------
name : Sniper--火枪
heroSkills:
hero Skill: 榴霰弹
hero Skill: 爆头
hero Skill: 瞄准
hero Skill: 暗杀
-----------Hero End -----------
----------- Hero Start -----------
name : Sniper--火枪
heroSkills:
hero Skill: 榴霰弹
hero Skill: 爆头
hero Skill: 瞄准
hero Skill: 暗杀
hero Skill: 火枪的第五个技能
-----------Hero End -----------
----------- Hero Start -----------
name : Sniper--火枪
heroSkills:
hero Skill: 榴霰弹
hero Skill: 爆头
hero Skill: 瞄准
hero Skill: 暗杀
hero Skill: 火枪的第五个技能
-----------Hero End -----------
从此输出信息,我们发现在通过hero.clone()产生的对象中添加了火枪的第五个技能,但是这个会存在原生的hero对象中。这个什么原因呢?
在clone方法中,我们采用的是:
hero.heroSkills = this.heroSkills;
这种方法,这是浅复制的方法。也就是说这种引用类型的新对象hero的变量heroSkills 单纯的指向了this.heroSkills 引用,而并没有进行复制。那要怎么样才能解决这个问题,主要是采取深复制的方法:即在复制对象时,对于引用型的字段也要采用copy的形式,而不是单纯引用的形式。
示例如下 :
protected Hero clone(){
// TODO Auto-generated method stub
try {
Hero hero = (Hero) super.clone();
hero.name = this.name;
//hero.heroSkills = this.heroSkills;
hero.heroSkills = (ArrayList) this.heroSkills.clone();
return hero;
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
修改后的输出信息:
Hero 构造方法
----------- Hero Start -----------
name : Sniper--火枪
heroSkills:
hero Skill: 榴霰弹
hero Skill: 爆头
hero Skill: 瞄准
hero Skill: 暗杀
-----------Hero End -----------
----------- Hero Start -----------
name : Sniper--火枪
heroSkills:
hero Skill: 榴霰弹
hero Skill: 爆头
hero Skill: 瞄准
hero Skill: 暗杀
-----------Hero End -----------
----------- Hero Start -----------
name : Sniper--火枪
heroSkills:
hero Skill: 榴霰弹
hero Skill: 爆头
hero Skill: 瞄准
hero Skill: 暗杀
hero Skill: 火枪的第五个技能
-----------Hero End -----------
----------- Hero Start -----------
name : Sniper--火枪
heroSkills:
hero Skill: 榴霰弹
hero Skill: 爆头
hero Skill: 瞄准
hero Skill: 暗杀
-----------Hero End -----------
通过这种深复制的方法,可以让hero.heroSkills的变量也复制,从而让clone的方法复制的对象完全拥有一个完全独立于原型对象的heroSkills变量。
原型模式的优点
性能优良原型模式是在内存二进制流的复制,要比new一个对象性能要好许多,特别是在一个循环体内产生大量的对象时,原型模式就可以更加显示其性能的优势 避免构造函数的约束
这既是它的优点也是缺点,直接在内存中复制对象,构造方法是不会执行的,优点是减少了约束,缺点也是减少了约束,大家要根据实际的情况来权衡。
原型模式的使用场景
资源优化场景类初始化需要消耗非常多的资源,包括数据,硬件资源等 性能与安全要求的场景
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式 一个对象多个修改者的场景
一个对象需要提供给其它对象访问,而且各个访问者可能都需要修改其值时,可以考虑使用原型模式复制多个对象供访问者使用。
原型模式注意事项
构造函数不会被执行对象复制时构造函数没有执行,Object类的clone方法的原理是从内存中以二进制流 深复制和浅复制
使用原型模式时,引用的成员变量必须满足两个条件才不会被复制。一是类的成员变量,而不是方法内变量,二是必须是一个可变的引用对象,而不是一个原始类型或不可变的对象