设计模式(1)--Prototype(原型模式)--创建型
1.模式定义:
原型模式就是用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
2.使用场景:
在原型模式中我们可以利用过一个原型对象来指明我们所要创建对象的类型,然后通过复制这个对象的方法来获得与该对象一模一样的对象实例。这就是原型模式的设计目的。
(1). 如果创建新对象成本较大,我们可以利用已有的对象进行复制来获得。
(2). 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不大的时候,也可以使用原型模式配合备忘录模式来应用。相反,如果对象的状态变化很大,或者对象占用的内存很大,那么采用状态模式会比原型模式更好。
(3).需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
3.模式实现:
(1)简单形式:
三个角色:
[1].客户(Client)角色:客户类提出创建对象的请求。
public class Client { // 持有需要使用的原型接口对象 private Prototype prototype; // 构造方法,传入需要使用的原型接口对象 public Client(Prototype prototype){ this.prototype = prototype; } public void operation(Prototype example){ //需要创建原型接口的对象 Prototype copyPrototype = prototype.clone(); } }
[2].抽象原型(Prototype)角色:这是一个抽象角色,通常由一个Java接口或Java抽象类实现。此角色给出所有的具体原型类所需的接口。
public interface Prototype{ public Object clone(); }
[3].具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。
public class ConcretePrototype1 implements Prototype { public Prototype clone(){ //最简单的克隆,新建一个自身对象,由于没有属性就不再复制值了 Prototype prototype = new ConcretePrototype1(); return prototype; } } public class ConcretePrototype2 implements Prototype { public Prototype clone(){ //最简单的克隆,新建一个自身对象,由于没有属性就不再复制值了 Prototype prototype = new ConcretePrototype2(); return prototype; } }
(2)登记形式
四个角色:
[1].客户(Client)角色:客户类提出创建对象的请求。
public class Client { public static void main(String[]args){ try{ Prototype p1 = new ConcretePrototype1(); PrototypeManager.setPrototype("p1", p1); //获取原型来创建对象 Prototype p3 = PrototypeManager.getPrototype("p1").clone(); p3.setName("张三"); System.out.println("第一个实例:" + p3); //有人动态的切换了实现 Prototype p2 = new ConcretePrototype2(); PrototypeManager.setPrototype("p1", p2); //重新获取原型来创建对象 Prototype p4 = PrototypeManager.getPrototype("p1").clone(); p4.setName("李四"); System.out.println("第二个实例:" + p4); //有人注销了这个原型 PrototypeManager.removePrototype("p1"); //再次获取原型来创建对象 Prototype p5 = PrototypeManager.getPrototype("p1").clone(); p5.setName("王五"); System.out.println("第三个实例:" + p5); }catch(Exception e){ e.printStackTrace(); } } }
[2].抽象原型(Prototype)角色:这是一个抽象角色,通常由一个Java接口或Java抽象类实现。此角色给出所有的具体原型类所需的接口。
public interface Prototype{ public Prototype clone(); public String getName(); public void setName(String name); }
[3].具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。
public class ConcretePrototype1 implements Prototype { private String name; public Prototype clone(){ ConcretePrototype1 prototype = new ConcretePrototype1(); prototype.setName(this.name); return prototype; } public String toString(){ return "Now in Prototype1 , name = " + this.name; } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } } public class ConcretePrototype2 implements Prototype { private String name; public Prototype clone(){ ConcretePrototype2 prototype = new ConcretePrototype2(); prototype.setName(this.name); return prototype; } public String toString(){ return "Now in Prototype2 , name = " + this.name; } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } }
[4].原型管理器(PrototypeManager)角色:创建具体原型类的对象,并记录每一个被创建的对象。
public class PrototypeManager { /** * 用来记录原型的编号和原型实例的对应关系 */ private static Map<String,Prototype> map = new HashMap<String,Prototype>(); /** * 私有化构造方法,避免外部创建实例 */ private PrototypeManager(){} /** * 向原型管理器里面添加或是修改某个原型注册 * @param prototypeId 原型编号 * @param prototype 原型实例 */ public synchronized static void setPrototype(String prototypeId , Prototype prototype){ map.put(prototypeId, prototype); } /** * 从原型管理器里面删除某个原型注册 * @param prototypeId 原型编号 */ public synchronized static void removePrototype(String prototypeId){ map.remove(prototypeId); } /** * 获取某个原型编号对应的原型实例 * @param prototypeId 原型编号 * @return 原型编号对应的原型实例 * @throws Exception 如果原型编号对应的实例不存在,则抛出异常 */ public synchronized static Prototype getPrototype(String prototypeId) throws Exception{ Prototype prototype = map.get(prototypeId); if(prototype == null){ throw new Exception("您希望获取的原型还没有注册或已被销毁"); } return prototype; }
}
(3).浅克隆
只负责克隆按值传递的数据(比如基本数据类型、String类型),而不复制它所引用的对象,换言之,所有的对其他对象的引用都仍然指向原来的对象。
示例:齐天大圣浅克隆: 拥有自己和金箍棒两个对象
[1].金箍棒:GoldRingedStaff.java
public class GoldRingedStaff { private float height = 100.0f; private float diameter = 10.0f; /** * 增长行为,每次调用长度和半径增加一倍 */ public void grow(){ this.diameter *= 2; this.height *= 2; } /** * 缩小行为,每次调用长度和半径减少一半 */ public void shrink(){ this.diameter /= 2; this.height /= 2; } }
[2].大圣本身:Monkey.java
public class Monkey implements Cloneable { //身高 private int height; //体重 private int weight; //生日 private Date birthDate; //金箍棒 private GoldRingedStaff staff; /** * 构造函数 */ public Monkey(){ this.birthDate = new Date(); this.staff = new GoldRingedStaff(); } /** * 克隆方法 */ public Object clone(){ Monkey temp = null; try { temp = (Monkey) super.clone(); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { return temp; } } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public Date getBirthDate() { return birthDate; } public void setBirthDate(Date birthDate) { this.birthDate = birthDate; } public GoldRingedStaff getStaff() { return staff; } public void setStaff(GoldRingedStaff staff) { this.staff = staff; } }
[3].克隆实现:TheGreatestSage.java
public class TheGreatestSage { private Monkey monkey = new Monkey(); public void change(){ //克隆大圣本尊 Monkey copyMonkey = (Monkey)monkey.clone(); System.out.println("大圣本尊的生日是:" + monkey.getBirthDate()); System.out.println("克隆的大圣的生日是:" + monkey.getBirthDate()); System.out.println("大圣本尊跟克隆的大圣是否为同一个对象 " + (monkey == copyMonkey)); System.out.println("大圣本尊持有的金箍棒 跟 克隆的大圣持有的金箍棒是否为同一个对象? " + (monkey.getStaff() == copyMonkey.getStaff())); } public static void main(String[]args){ TheGreatestSage sage = new TheGreatestSage(); sage.change(); } }
(4).深克隆
除了浅度克隆要克隆的值外,还负责克隆引用类型的数据。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。
换言之,深度克隆把要复制的对象所引用的对象都复制了一遍,而这种对被引用到的对象的复制叫做间接复制。
在采取深度克隆时,需要决定多深才算深。此外,在深度克隆的过程中,很可能会出现循环引用的问题,必须小心处理。
利用序列化实现深度克隆
把对象写到流里的过程是序列化(Serialization)过程;而把对象从流中读出来的过程则叫反序列化(Deserialization)过程。应当指出的是,写到流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。
在Java语言里深度克隆一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的拷贝)写到一个流里(序列化),再从流里读回来(反序列化),便可以重建对象。
示例:齐天大圣深克隆: 拥有自己和金箍棒两个对象
[1].金箍棒:GoldRingedStaff.java
public class GoldRingedStaff implements Serializable{ private float height = 100.0f; private float diameter = 10.0f; /** * 增长行为,每次调用长度和半径增加一倍 */ public void grow(){ this.diameter *= 2; this.height *= 2; } /** * 缩小行为,每次调用长度和半径减少一半 */ public void shrink(){ this.diameter /= 2; this.height /= 2; } }
[2].大圣本身:Monkey.java
public class Monkey implements Cloneable,Serializable { //身高 private int height; //体重 private int weight; //生日 private Date birthDate; //金箍棒 private GoldRingedStaff staff; /** * 构造函数 */ public Monkey(){ this.birthDate = new Date(); staff = new GoldRingedStaff(); } /** * 克隆方法 */ public Object clone(){ Monkey temp = null; try { temp = (Monkey) super.clone(); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { return temp; } } public Object deepClone() throws IOException, ClassNotFoundException{ //将对象写到流里 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); //从流里读回来 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public Date getBirthDate() { return birthDate; } public void setBirthDate(Date birthDate) { this.birthDate = birthDate; } public GoldRingedStaff getStaff() { return staff; } public void setStaff(GoldRingedStaff staff) { this.staff = staff; } }
[3].克隆实现:TheGreatestSage.java
public class TheGreatestSage { private Monkey monkey = new Monkey(); public void change() throws IOException, ClassNotFoundException{ Monkey copyMonkey = (Monkey)monkey.deepClone(); System.out.println("大圣本尊的生日是:" + monkey.getBirthDate()); System.out.println("克隆的大圣的生日是:" + monkey.getBirthDate()); System.out.println("大圣本尊跟克隆的大圣是否为同一个对象 " + (monkey == copyMonkey)); System.out.println("大圣本尊持有的金箍棒 跟 克隆的大圣持有的金箍棒是否为同一个对象? " + (monkey.getStaff() == copyMonkey.getStaff())); } public static void main(String[]args) throws IOException, ClassNotFoundException{ TheGreatestSage sage = new TheGreatestSage(); sage.change(); } }
4.优缺点:
(1)原型模式的优点
原型模式允许在运行时动态改变具体的实现类型。原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态地改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个类实例了。因为克隆一个原型就类似于实例化一个类。
(2)原型模式的缺点
原型模式最主要的缺点是每一个类都必须配备一个克隆方法。配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,而对于已经有的类不一定很容易,特别是当一个类引用不支持序列化的间接对象,或者引用含有循环结构的时候。