Prototype模式就是不根据类来生成实例,而是根据实例来生成新实例。至于为什么不能根据类来生成实例,在最后会讲到。
还是根据实例程序来理解这种设计模式吧。
下面是实例代码。
1 package BigJunOba.bjtu.framework; 2 3 public interface Product extends Cloneable{ 4 5 public abstract void use (String string); 6 public abstract Product createClone(); 7 }
Product接口是复制功能的接口。Product接口继承了Cloneable接口,也就是说实现了Cloneable接口的类的实例可以调用clone方法来自动复制实例。
1 package BigJunOba.bjtu.framework; 2 3 import java.util.HashMap; 4 5 public class Manager { 6 7 private HashMap<String, Object> showcase = new HashMap<>(); 8 9 public void register(String name, Product proto) { 10 showcase.put(name, proto); 11 } 12 13 public Product create(String protoname) { 14 Product product = (Product) showcase.get(protoname); 15 return product.createClone(); 16 } 17 }
Manager类使用了Product接口并且通过create方法来复制实例。
showcase字段是HashMap类型的,这里是用来保存实例的“名字”和“实例”之间的对应关系。
register方法将实例名字和实现了Product接口的类的实例注册到showcase当中。
1 package BigJunOba.bjtu.concretePrototype; 2 3 import BigJunOba.bjtu.framework.Product; 4 5 public class MessageBox implements Product{ 6 7 private char decochar; 8 9 public MessageBox(char decochar) { 10 this.decochar = decochar; 11 } 12 13 @Override 14 public void use(String string) { 15 int length = string.getBytes().length; 16 for (int i = 0; i < length + 3 ; i++) { 17 System.out.print(decochar); 18 } 19 System.out.println(""); 20 System.out.println(decochar + " " + string + decochar); 21 for (int i = 0; i < length + 3; i++) { 22 System.out.print(decochar); 23 } 24 System.out.println(""); 25 } 26 27 @Override 28 public Product createClone() { 29 Product product = null; 30 try { 31 product = (Product) clone(); 32 } catch (CloneNotSupportedException e) { 33 e.printStackTrace(); 34 } 35 return product; 36 } 37 38 }
MessageBox类实现了Product接口来对字符串进行加框操作。use方法用来进行加框操作。
createClone方法用来复制自己。它内部所调用的clone方法时java语言中定义的方法。在进行复制时,原来实例中的字段的值也会被复制到新的实例中。我们之所以可以调用clone方法进行复制,仅仅是因为该类实现了Cloneable接口。如果没有实现这个接口,在运行程序时将会抛出CloneNotSupportedException异常,因此必须捕获这个异常。在这里虽然 MessageBox类只实现了Product接口,但是Product接口继承了Cloneable接口,因此程序不会出现异常。只有类自己(或是它的子类)能够调用java语言中定义的clone方法,当其他类要求复制实例时,必须先调用createClone这样的方法,然后在该方法内部再调用clone方法。
1 package BigJunOba.bjtu.concretePrototype; 2 3 import BigJunOba.bjtu.framework.Product; 4 5 public class UnderlinePen implements Product { 6 7 private char ulchar; 8 9 public UnderlinePen(char ulchar) { 10 this.ulchar = ulchar; 11 } 12 13 @Override 14 public void use(String string) { 15 int length = string.getBytes().length; 16 // 这个转义字符"表示双引号 17 System.out.println(""" + string + """); 18 System.out.print(" "); 19 for (int i = 0; i < length; i++) { 20 System.out.print(ulchar); 21 } 22 System.out.println(""); 23 } 24 25 @Override 26 public Product createClone() { 27 Product product = null; 28 try { 29 product = (Product) clone(); 30 } catch (CloneNotSupportedException e) { 31 e.printStackTrace(); 32 } 33 return product; 34 } 35 36 }
UnderlinePen类实现了Product接口用来对字符串进行加波浪线操作。具体实现不多说。
1 package BigJunOba.bjtu.test; 2 3 import BigJunOba.bjtu.concretePrototype.MessageBox; 4 import BigJunOba.bjtu.concretePrototype.UnderlinePen; 5 import BigJunOba.bjtu.framework.*; 6 7 public class Main { 8 public static void main(String[] args) { 9 10 //准备工作 11 Manager manager = new Manager(); 12 UnderlinePen uPen = new UnderlinePen('~'); 13 MessageBox mBox = new MessageBox('*'); 14 MessageBox sBox = new MessageBox('/'); 15 manager.register("strong message", uPen); 16 manager.register("warning box", mBox); 17 manager.register("slash box", sBox); 18 19 //生成输出 20 Product product1 = manager.create("strong message"); 21 product1.use("Hello, world."); 22 Product product2 = manager.create("warning box"); 23 product2.use("Hello, world."); 24 Product product3 = manager.create("slash box"); 25 product3.use("Hello, world."); 26 } 27 }
main方法用来进行测试。分为两个部分。在准备工作中,首先生成了Manager的实例,然后通过生成了UnderlinePen的一个实例和MessageBox的两个实例,然后将这三个实例的名字和实例对象注册到manager中。在生成输出中,通过调用create方法,根据hashmap中的name属性,然后再通过clone方法来复制自己也就是现有的实例来得到复制之后的实例。
"Hello, world." ~~~~~~~~~~~~~ **************** * Hello, world.* **************** //////////////// / Hello, world./ ////////////////
测试结果如上图。
分析:总结来说,有三种不能根据类来生成实例的情况。
1.对象种类繁多,无法将它们整合到一个类中时。
这种情况是需要处理的对象太多,如果将它们分别作为一个类,必须要编写很多个类文件。在示例程序中,包括三种字符串样式,如果将每种样式都编写为一个类,类的数量会非常庞大。源程序的管理也会非常困难。
2.难以根据类生成实例时。
这种情况发生在生成实例的过程太过复杂,很难根据类来生成实例。比如一个用鼠标操作的应用程序,在创建一个通过一系列鼠标操作所创建出来的实例完全一样的实例时就会很复杂。
3.想解耦框架与生成的实例时。
这种情况是想要让生成实例的框架不依赖于具体的类。那么怎么能实现呢?思路就是不能指定类名来生成实例,而是要事先“注册”一个“原型”实例,然后通过复制该实例来生成新的实例。在示例程序中,将复制clone实例的部分封装在framework包中,在Manager类的create方法中,没有使用类名,而是使用“strong message”、“warning box”和“slash box”等字符串为生成的实例命名。相比较“new 类名()”这种方式具有更好地通用性。
这里要说明一下,一旦在代码中出现要使用的类的名字,就无法与该类分离开来,也就无法实现复用。原则就是,那些需要被独立出来作为组件复用的类的名字不要出现在代码中。
Prototype模式的类图如下:
类图说明:
Client:负责使用复制实例的方法生成新的示例。
Prototype:负责定义用于复制现有实例来生成新实例的方法。
ConcretePrototype:负责实现复制现有实例并生成新实例的方法。