在java中,正常都是使用new关键字来生成类的实例,这样的情况下,是必须指定类名。但是某些情况下,我们不能根据类来生成实例,而是要根据现有的实例开生成新的实例。

  • 对象种类繁多,无法将它们整合到一个类中
    • 需要处理的对象太多,如果将它们分别作为一个类,必须要编写很多个类文件。
  • 难以根据类生成实例时
    • 生成实例的过程过于复杂,难以复现。
  • 需要解耦合框架和生成的实例
    • 想要让生成实例的框架不依赖于具体的类,这个时候不能指定类名来生成实例,可以通过复制该实例来生成新的实例。
      1. 首先程序中需要一个接口来提供复制功能:
        1. public interface Product extends Cloneable{
        2. public abstract void use(String s);//use方法代表着使用
        3. public abstract Product createClone();//clone代表复制实例
        4. }
  1. 其次需要一个类来管理所有的实例对象,内部可以使用哈希Map去保存实例的名字和实例之间的对应关系。而且Manage和复制接口和具体的类之间解耦合,这点非常重要。 ```java package prototype;

import java.util.HashMap;

public class Manager { private HashMap showcase = new HashMap(); public void register(String name,Product prod){ showcase.put(name,prod); } //从hashMap中取出一个相同类型的实例,并复制后返回 public Product create(String protoName){ Product p=(Product)showcase.get(protoName); return p.createClone(); } }

  1. 3. 具体的子类,需要去实现Product接口
  2. ```java
  3. package prototype;
  4. public class MessageBox implements Product{
  5. private char decoChar;
  6. public MessageBox(char decoChar) {
  7. this.decoChar = decoChar;
  8. }
  9. @Override
  10. public void use(String s) {
  11. int length = s.getBytes().length;
  12. for (int i = 0; i < length + 4; i++) {
  13. System.out.print(decoChar);
  14. }
  15. System.out.println("");
  16. System.out.println(decoChar+" "+s+" "+decoChar);
  17. for (int i = 0; i < length + 4; i++) {
  18. System.out.print(decoChar);
  19. }
  20. System.out.println("");
  21. }
  22. @Override
  23. public Product createClone() {
  24. Product p = null;
  25. try {
  26. p = (Product) clone();
  27. } catch (CloneNotSupportedException e) {
  28. e.printStackTrace();
  29. }
  30. return p;
  31. }
  32. }
  1. package prototype;
  2. public class UnderlinePen implements Product{
  3. private char ulChar;
  4. public UnderlinePen(char ulChar) {
  5. this.ulChar = ulChar;
  6. }
  7. @Override
  8. public void use(String s) {
  9. int length = s.getBytes().length;
  10. System.out.println("\\"+s+"\\");
  11. System.out.println(" ");
  12. for (int i = 0; i < length; i++) {
  13. System.out.println(ulChar);
  14. }
  15. System.out.println("");
  16. }
  17. @Override
  18. public Product createClone() {
  19. Product p = null;
  20. try {
  21. p = (Product) clone();
  22. } catch (CloneNotSupportedException e) {
  23. e.printStackTrace();
  24. }
  25. return p;
  26. }
  27. }
  1. 对上述的类进行使用的Main类 ```java package prototype;

public class Main { public static void main(String[] args) { //准备 Manager manager = new Manager(); UnderlinePen upen = new UnderlinePen(‘~’); MessageBox mbox = new MessageBox(‘*’); MessageBox sbox = new MessageBox(‘/‘); manager.register(“Strong message”,upen); manager.register(“Warning box”,mbox); manager.register(“Slash box”,sbox); //生成 Product p1 = manager.create(“Strong message”); p1.use(“Hello”); Product p2 = manager.create(“Warning box”); p2.use(“Hello”); Product p3 = manager.create(“Slash box”); p3.use(“Hello”); } } ``` 这里需要注意的是:必须要先进行准备工作,将实例对象存储起来,才能进行生成,否则没有对象进行clone会直接报错,所以其实这个的代码可以进一步优化。
ok,对上述的代码进行总结,可以看出Prototype中有三个角色:

Prototype(原型):

负责定义用于复制现有实例来生成新实例的方法。

ConcretePrototype(具体的原因)

复制实现用于复制现有实例来生成新实例的方法。

Client(使用者)

负责存储现有实例和使用生成新实例的方法。

总结:

  • 这样的程序设计可以使得我们在增加新功能的并不需要再去写一个新的类,导致类数量过大时候,对于源的管理会比较困难。
  • 解决了生成实例时较为繁琐的问题。
  • 可以用HashMap来管理某一类型的全部实例,解耦合。

类名对于程序设计的麻烦:

在代码中出现要使用的类的名字并不是坏事,不过,一旦在代码出现要使用的类的名字就意味着无法将该类从中分离开,也就无法完成复用。
当多个类必须紧密结合的时候,代码中出现这些类的名字时没有问题的,但是如果那些需要被独立出来的作为组件复用的类的名字出现在代码中,就违背了程序设计的原则。

相关的设计模式:

Flyweight模式:

使用Prototype模式可以生成一个与当前实例的状态完全相同的实例,而使用Flyweight模式则可以在不同的地方使用同一个实例;

Memento模式:

使用Memento模式可以保存当前实例的状态,以实现快照和撤销功能;

ComPosite模式以及Decorator模式:

这两个模式需要能够动态地创建复杂结构的实例。这时可以使用Prototype来帮助快速生成实例;

Command模式:

想要复制Command模式中出现的命令时,可以使用Prototype模式。

需要注意的点:

clone方法所进行的复制仅仅是将复制实例的字段值直接复制到新的实例当中。这种复制方式在当字段中保存是数组时,只会复制该数组的引用,而不是将数组中的每个元素都复制过去。这种也被称作是浅复制。可以通过重写clone方法的方式实现自己需要的功能。
另外,clone方法只会进行复制,而不会调用被复制实例的构造函数。对于生成实例时需要进行特殊的初始化处理的类,需要自己去实现clone方法,在其内部进行这些初始化处理。