简说设计模式——原型模式
一、什么是原型模式
还记不记得初高中学生物的时候,细胞分裂时怎么实现的,一个细胞无论是细胞核分裂还是细胞质分裂,是不是都是通过克隆自身实现的。或者说我们去复印资料的时候,是不是直接对原本的资料进行复印,得到了一个一模一样的资料,这些都可以说是原型模式,下面看一下定义。
原型模式(Prototype),用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。UML结构图如下:
其中,Prototype为原型类,声明一个克隆自身的接口;ConcretePrototype为具体实现类,实现一个克隆自身的操作;而客户端Client只需让一个原型克隆自身,从而创建一个新的对象。
1. Prototype
1 public abstract class Prototype implements Cloneable { 2 3 private String id; 4 5 public Prototype(String id) { 6 this.id = id; 7 } 8 9 public String getId() { 10 return id; 11 } 12 13 @Override 14 public Prototype clone() { 15 Prototype prototype = null; 16 17 try { 18 prototype = (Prototype) super.clone(); 19 } catch (CloneNotSupportedException e) { 20 e.printStackTrace(); 21 } 22 23 return prototype; 24 } 25 26 }
2. ConcretePrototype
创建当前对象的浅表副本。
1 public class ConcretePrototype extends Prototype { 2 3 public ConcretePrototype(String id) { 4 super(id); 5 } 6 7 }
3. Client
1 public class Client { 2 3 public static void main(String[] args) { 4 ConcretePrototype p1 = new ConcretePrototype("Hello"); 5 ConcretePrototype c1 = (ConcretePrototype) p1.clone(); 6 System.out.println(c1.getId()); 7 } 8 9 }
运行结果为“Hello”,此时ConcretePrototype的对象p1得到了新的实例c1。
二、原型模式的应用
1. 何时使用
- 当一个系统应该独立于它的产品创建、构成和表示时。
- 当要实例化的类是在运行时刻指定时(如动态装载)。
- 为了避免创建一个与产品类层次平行的工厂类层次时。
- 当一个类的实例只能有几种几个不同状态组合中的一种时。
2. 优点
- 性能优良。不用重新初始化对象,而是动态地获取对象运行时的状态。
- 逃避构造函数的约束。
3. 缺点
- 配置克隆方法需要对类的功能进行通盘考虑。
- 必须实现Cloneable接口。
4. 使用场景
- 资源优化场景。
- 性能和安全要求的场景。
- 一个对象多个修改者的场景。
- 一般与工厂方法模式一起出现,通过clone方法创建一个对象,然后由工厂方法提供给调用者。
5. 应用实例
- 细胞分裂
- Java中的Object.clone()方法
- 复印
三、原型模式的实现
下面我们创建一个抽象类和扩展了它的实体类,即图形类与圆形、矩形、三角形。之后再通过一个cache类将对象存储在一个Hashtable中,并在请求的时候返回它们的克隆。UML图如下:
1. Shape类
创建一个实现了Cloneable接口的抽象类。
1 public abstract class Shape implements Cloneable { 2 3 private String id; 4 protected String type; 5 6 public abstract void draw(); 7 8 public String getId() { 9 return id; 10 } 11 12 public void setId(String id) { 13 this.id = id; 14 } 15 16 public String getType() { 17 return type; 18 } 19 20 @Override 21 public Shape clone() { 22 Shape prototype = null; 23 24 try { 25 prototype = (Shape) super.clone(); 26 } catch (CloneNotSupportedException e) { 27 e.printStackTrace(); 28 } 29 30 return prototype; 31 } 32 33 }
2. 实现类
创建扩展了上面抽象类的实现类,这里以Circle为例。
1 public class Circle extends Shape { 2 3 public Circle() { 4 type = "圆形"; 5 } 6 7 @Override 8 public void draw() { 9 System.out.println("圆形类的draw方法"); 10 } 11 12 }
3. ShapeCache
获取实体类,并存于Hashtable中。
1 public class ShapeCache { 2 3 private static Hashtable<String, Shape> shapeMap = new Hashtable<>(); 4 5 public static Shape getShape(String shapeId) { 6 Shape shape = shapeMap.get(shapeId); 7 8 return shape.clone(); 9 } 10 11 //添加三种图形 12 public static void loadCache() { 13 Circle circle = new Circle(); 14 circle.setId("1"); 15 shapeMap.put(circle.getId(), circle); 16 17 Triangle triangle = new Triangle(); 18 triangle.setId("2"); 19 shapeMap.put(triangle.getId(), triangle); 20 21 Rectangle rectangle = new Rectangle(); 22 rectangle.setId("3"); 23 shapeMap.put(rectangle.getId(), rectangle); 24 } 25 26 }
4. Client客户端
1 public class Client { 2 3 public static void main(String[] args) { 4 ShapeCache.loadCache(); 5 6 Shape clonedShape = ShapeCache.getShape("1"); 7 System.out.println("图形:" + clonedShape.getType()); 8 9 Shape clonedShape2 = ShapeCache.getShape("2"); 10 System.out.println("图形:" + clonedShape2.getType()); 11 12 Shape clonedShape3 = ShapeCache.getShape("3"); 13 System.out.println("图形:" + clonedShape3.getType()); 14 15 } 16 17 }
运行结果如下:
四、浅复制与深复制
在上述代码中,对象里的数据都是String类型的,而String是一种拥有值类型特点的特殊引用类型,在使用clone()方法时,原始对象及其复本引用同一对象。也就是说,如果类中有对象引用,那么引用的对象数据时不会被克隆过来的。这就叫做“浅复制”。
“浅复制”指,被复制的对象的所有变量都含有与原来的对象相同的值,而所有的其他对象的引用都仍然指向原来的对象。
但我们可能更需要这样一种需求,把要赋值的对象所引用的对象都复制一遍。这种方式就是“深复制”。方法是对私有变量进行独立的复制。
“深复制”指,把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。
下面看一个例子。
1. Client客户端
先将客户端内容放上,深复制与浅复制只需修改注释即可。
1 public class TestClient { 2 3 public static void main(String[] args) { 4 //产生对象 5 // ShallowCopy copy = new ShallowCopy(); 6 DeepCopy copy = new DeepCopy(); 7 copy.setValue("张三"); 8 9 //拷贝对象 10 // ShallowCopy cloneCopy = copy.clone(); 11 DeepCopy cloneCopy = copy.clone(); 12 cloneCopy.setValue("李四"); 13 14 System.out.println(copy.getvalue()); 15 } 16 17 }
2. 浅复制
1 public class ShallowCopy implements Cloneable { 2 3 private ArrayList<String> arrayList = new ArrayList<String>(); 4 5 @Override 6 public ShallowCopy clone() { 7 ShallowCopy sCopy = null; 8 9 try { 10 sCopy = (ShallowCopy) super.clone(); 11 } catch (CloneNotSupportedException e) { 12 // TODO Auto-generated catch block 13 e.printStackTrace(); 14 } 15 16 return sCopy; 17 } 18 19 //set 20 public void setValue(String value) { 21 this.arrayList.add(value); 22 } 23 24 //get 25 public ArrayList<String> getvalue() { 26 return this.arrayList; 27 } 28 }
在客户端中运行结果如下:
这里之所以出现了“李四”,是因为Java做了一个偷懒的复制动作,Object类提供的方法clone只是复制本对象,其对象内部的数组、引用对象等都不复制,还是指向原先对象的内部元素地址,这种复制就叫浅复制。两个对象共享了一个私有变量,是一种非常不安全的方式,大家都可以进行更改。
3. 深复制
1 public class DeepCopy implements Cloneable { 2 3 private ArrayList<String> arrayList = new ArrayList<String>(); 4 5 @Override 6 public DeepCopy clone() { 7 DeepCopy dCopy = null; 8 9 try { 10 dCopy = (DeepCopy) super.clone(); 11 //增加这行 12 dCopy.arrayList = (ArrayList<String>) this.arrayList.clone(); 13 } catch (CloneNotSupportedException e) { 14 // TODO Auto-generated catch block 15 e.printStackTrace(); 16 } 17 18 return dCopy; 19 } 20 21 //set 22 public void setValue(String value) { 23 this.arrayList.add(value); 24 } 25 26 //get 27 public ArrayList<String> getvalue() { 28 return this.arrayList; 29 } 30 31 }
在客户端中运行结果如下:
仅仅增加了注释下的那一行代码,对私有的类变量进行独立的复制,这样就完成了完全的复制,两个对象间没有任何联系,各自修改互不影响,这就叫深复制。深复制还有一种实现方式是通过自己写二进制流来操作对象,然后实现对象的深复制。