设计模式-原型模式
本文章来源于网络,由本人整理,仅供参考:
原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
介绍
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
主要解决:在运行期建立和删除原型。
何时使用: 1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、为了避免创建一个与产品类层次平行的工厂类层次时。 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码: 1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
应用实例: 1、细胞分裂。 2、JAVA 中的 Object clone() 方法。
优点: 1、性能提高。 2、逃避构造函数的约束。
缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。
使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
抽象原型角色
package com.design.demo.protitype; /** * @author: GuanBin * @date: Created in 下午5:17 2019/8/5 */
//实现cloneable接口,重写clone接口
public abstract class Shape implements Cloneable { private String id; private String type; abstract void draw(); @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getType() { return type; } public void setType(String type) { this.type = type; } }
具体原型角色
package com.design.demo.protitype; /** * @author: GuanBin * @date: Created in 下午5:42 2019/8/5 */ public class Circle extends Shape { public Circle() { setType("Circle"); } @Override void draw() { System.out.println("Inside Circle::draw() method."); } }
package com.design.demo.protitype; /** * @author: GuanBin * @date: Created in 下午5:40 2019/8/5 */ public class Rectangle extends Shape { public Rectangle() { setType("Rectangle"); } @Override void draw() { System.out.println("Inside Rectangle::draw() method."); } }
package com.design.demo.protitype; /** * @author: GuanBin * @date: Created in 下午5:42 2019/8/5 */ public class Square extends Shape { public Square() { setType("Square"); } @Override void draw() { System.out.println("Inside Square::draw() method."); } }
原型管理器角色保持一个聚集,作为对所有原型对象的登记,这个角色提供必要的方法,供外界增加新的原型对象和取得已经登记过的原型对象。
package com.design.demo.protitype; import java.util.Hashtable; /** * @author: GuanBin * @date: Created in 下午5:44 2019/8/5 */ public class ShapeCache { private static Hashtable<String, Shape> shapeMap = new Hashtable<String, Shape>(); public static Shape getShape(String shapeId) { Shape clone = null; try { Shape cachedShape = shapeMap.get(shapeId); clone = (Shape) cachedShape.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } public static void loadCache() { Circle circle = new Circle(); circle.setId("1"); shapeMap.put(circle.getId(), circle); Rectangle rectangle = new Rectangle(); rectangle.setId("2"); shapeMap.put(rectangle.getId(), rectangle); Square square = new Square(); square.setId("3"); shapeMap.put(square.getId(), square); } }
客户端角色
package com.design.demo.protitype; /** * @author: GuanBin * @date: Created in 下午6:03 2019/8/5 */ public class PrototypePatternDemo { public static void main(String[] args) { ShapeCache.loadCache(); Shape cloneShape1 = (Shape) ShapeCache.getShape("1"); System.out.println(cloneShape1.getType()); Shape cloneShape2 = (Shape) ShapeCache.getShape("2"); System.out.println(cloneShape2.getType()); Shape cloneShape3 = (Shape) ShapeCache.getShape("3"); System.out.println(cloneShape3.getType()); } }
--------------------------------------------------------------
Java中的克隆方法
Java的所有类都是从java.lang.Object类继承而来的,而Object类提供protected Object clone()方法对对象进行复制,子类当然也可以把这个方法置换掉,提供满足自己需要的复制方法。对象的复制有一个基本问题,就是对象通常都有对其他的对象的引用。当使用Object类的clone()方法来复制一个对象时,此对象对其他对象的引用也同时会被复制一份
Java语言提供的Cloneable接口只起一个作用,就是在运行时期通知Java虚拟机可以安全地在这个类上使用clone()方法。通过调用这个clone()方法可以得到一个对象的复制。由于Object类本身并不实现Cloneable接口,因此如果所考虑的类没有实现Cloneable接口时,调用clone()方法会抛出CloneNotSupportedException异常。
克隆满足的条件
clone()方法将对象复制了一份并返还给调用者。所谓“复制”的含义与clone()方法是怎么实现的。一般而言,clone()方法满足以下的描述:
(1)对任何的对象x,都有:x.clone()!=x。换言之,克隆对象与原对象不是同一个对象。
(2)对任何的对象x,都有:x.clone().getClass() == x.getClass(),换言之,克隆对象与原对象的类型一样。
(3)如果对象x的equals()方法定义其恰当的话,那么x.clone().equals(x)应当成立的。
在JAVA语言的API中,凡是提供了clone()方法的类,都满足上面的这些条件。JAVA语言的设计师在设计自己的clone()方法时,也应当遵守着三个条件。一般来说,上面的三个条件中的前两个是必需的,而第三个是可选的。
浅克隆和深克隆
无论你是自己实现克隆方法,还是采用Java提供的克隆方法,都存在一个浅度克隆和深度克隆的问题。
- 浅度克隆
只负责克隆按值传递的数据(比如基本数据类型、String类型),而不复制它所引用的对象,换言之,所有的对其他对象的引用都仍然指向原来的对象。
- 深度克隆
除了浅度克隆要克隆的值外,还负责克隆引用类型的数据。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深度克隆把要复制的对象所引用的对象都复制了一遍,而这种对被引用到的对象的复制叫做间接复制。
深度克隆要深入到多少层,是一个不易确定的问题。在决定以深度克隆的方式复制一个对象的时候,必须决定对间接复制的对象时采取浅度克隆还是继续采用深度克隆。因此,在采取深度克隆时,需要决定多深才算深。此外,在深度克隆的过程中,很可能会出现循环引用的问题,必须小心处理。
利用序列化实现深度克隆
把对象写到流里的过程是序列化(Serialization)过程;而把对象从流中读出来的过程则叫反序列化(Deserialization)过程。应当指出的是,写到流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。
在Java语言里深度克隆一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的拷贝)写到一个流里(序列化),再从流里读回来(反序列化),便可以重建对象。
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(); }
这样做的前提就是对象以及对象内部所有引用到的对象都是可序列化的,否则,就需要仔细考察那些不可序列化的对象可否设成transient,从而将之排除在复制过程之外。
浅度克隆显然比深度克隆更容易实现,因为Java语言的所有类都会继承一个clone()方法,而这个clone()方法所做的正式浅度克隆。
有一些对象,比如线程(Thread)对象或Socket对象,是不能简单复制或共享的。不管是使用浅度克隆还是深度克隆,只要涉及这样的间接对象,就必须把间接对象设成transient而不予复制;或者由程序自行创建出相当的同种对象,权且当做复制件使用。
孙大圣的身外身法术
孙大圣的身外身本领如果在Java语言里使用原型模式来实现的话,会怎么样呢?首先,齐天大圣(The Greatest Sage)即TheGreatestSage类扮演客户角色。齐天大圣持有一个猢狲(Monkey)的实例,而猢狲就是大圣本尊。Monkey类具有继承自java.lang.Object的clone()方法,因此,可以通过调用这个克隆方法来复制一个Monkey实例。
孙大圣本人用TheGreatestSage类代表
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(); } }
大圣本尊由Monkey类代表,这个类扮演具体原型角色:
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; } }
大圣还持有一个金箍棒的实例,金箍棒类GoldRingedStaff:
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; } }
当运行TheGreatestSage类时,首先创建大圣本尊对象,而后浅度克隆大圣本尊对象。程序在运行时打印出的信息如下:
可以看出,首先,复制的大圣本尊具有和原始的大圣本尊对象一样的birthDate,而本尊对象不相等,这表明他们二者是克隆关系;其次,复制的大圣本尊所持有的金箍棒和原始的大圣本尊所持有的金箍棒为同一个对象。这表明二者所持有的金箍棒根本是一根,而不是两根。
正如前面所述,继承自java.lang.Object类的clone()方法是浅克隆。换言之,齐天大圣的所有化身所持有的金箍棒引用全都是指向一个对象的,这与《西游记》中的描写并不一致。要纠正这一点,就需要考虑使用深克隆。
为做到深度克隆,所有需要复制的对象都需要实现java.io.Serializable接口。
孙大圣的源代码:
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(); } }
在大圣本尊Monkey类里面,有两个克隆方法,一个是clone(),也即浅克隆;另一个是deepClone(),也即深克隆。在深克隆方法里,大圣本尊对象(一个拷贝)被序列化,然后又被反序列化。反序列化的对象就成了一个深克隆的结果。
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; } }
可以看到,大圣本尊持有一个金箍棒(GoldRingedStaff)的实例。在大圣复制件里面,此金箍棒实例是原大圣本尊对象所持有的金箍棒对象的一个拷贝。在大圣本尊对象被序列化和反序列化时,它所持有的金箍棒对象也同时被序列化和反序列化,这使得复制的大圣的金箍棒和原大圣本尊对象所持有的金箍棒对象是两个独立的对象。
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; } }
运行结果:
从运行的结果可以看出,大圣的金箍棒和他的身外之身的金箍棒是不同的对象。这是因为使用了深克隆,从而把大圣本尊所引用的对象也都复制了一遍,其中也包括金箍棒。
原型模式的优点
原型模式允许在运行时动态改变具体的实现类型。原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态地改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个类实例了。因为克隆一个原型就类似于实例化一个类。
原型模式的缺点
原型模式最主要的缺点是每一个类都必须配备一个克隆方法。配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,而对于已经有的类不一定很容易,特别是当一个类引用不支持序列化的间接对象,或者引用含有循环结构的时候。
作者:guanbin —— 纵码万里千山
出处:https://www.cnblogs.com/guanbin-529/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。