Prototype模式的使用场景


当我们生成一个 Something 类的实例时,通常会使用以下形式:

new Something()

在 Java 中,我们可以使用 new 关键字指定类名来生成类的实例。像这样使用 new 来生成时,是必须指定类名的

当我们在开发中,需要“不指定类名的前提下生成实例”时,比如:

对象种类繁多,无法将他们整合到一个类中时

第一种情况就是当需要处理的对象过多,如果将他们分别作为一个类,那么必须编写多个类。

难以根据类生成实例时

当生成实例的过程太过复杂,很难根据类来生成实例。例如:“我们生成一个 用户在图形编辑器中使用鼠标制作出的图形的实例 ”,我们如果想要创建出和操作一样的实例时,会比较困难。

通常,我们会实现操作出的实例保存,然后在需要的时候通过复制来生成新的实例。

想解耦框架与生成的实例时

如果想要让生成实例的框架不依赖于具体的类。此时,不能指定类名来生成实例,而是事先“注册”一个“原型“实例,然后通过复制该实例来生成新的实例。

示例程序


类与接口一览表:

示例程序的类图:

Product接口

Product接口是复制功能的接口。该接口继承了 java.lang.Cloneable 接口。

  • use:用于“使用”的方法。具体交给子类实现。
  • createClone:用于复制实例的方法。
public interface Product  extends Cloneable {  
    void use(String s);  
  
    Product createClone();  
}

Manager类

使用 Product接口 来复制实例。

  • showcase字段:java.util.HashMap 类型,它保存了实例的“名字”和“实例“之间的对应关系。
  • register方法:将会接受到1组”名字”和“Product 接口” 注册到showcase中。

也就是说它实现了 Product 接口的类的实例,而不是实现了Product。这意味着,它可以单独的修改Product和Manager,不受MessageBox和UnderlinePen类的影响。

public class Manager {  
    private HashMap<String, Product> showcase = new HashMap<>();  
  
    public void register(String name, Product proto) {  
        showcase.put(name, proto);  
    }  
  
    public Product create(String protoname) {  
        Product p = showcase.get(protoname);  
        return p.createClone();  
    }  
  
}

MessageBox类

MessageBox类实现了Product接口。

  • decochar字段:特殊的字符(即中心字符串四周的边框)
  • use方法:使用decochar字段中的字符把要显示的字符串框起来

比如,让显示字符串“hello”时,将会显示:

*********
* Hello *
*********
  • createClone方法:用于复制自己。它内部所调用的 clone 方法是Java定义的方法,用于复制自己。
public class MessageBox implements Product {  
    private char decochar;  
  
    public MessageBox(char decochar) {  
        this.decochar = decochar;  
    }  
  
    public void use(String s) {  
        int length = s.getBytes().length;  
        for (int i = 0; i < length + 4; i++)  
            System.out.print(decochar);  
        System.out.println();  
        System.out.println(decochar + " " + s + " " + decochar);  
        for (int i = 0; i < length + 4; i++)  
            System.out.print(decochar);  
        System.out.println();  
    }  
  
    public Product createClone() {  
        Product p = null;  
        try {  
            p = (Product) clone();  
        } catch (CloneNotSupportedException e) {  
            e.printStackTrace();  
        }  
        return p;  
    }  
}

UnderlinePen类

与MessageBox类似

public class UnderlinePen implements Product {  
    private char ulchar;  
  
    public UnderlinePen(char ulchar) {  
        this.ulchar = ulchar;  
    }  
  
    public void use(String s) {  
        int length = s.getBytes().length;  
        System.out.println("\"" + s + "\"");  
        System.out.print(" ");  
        for (int i = 0; i < length; i++)  
            System.out.print(ulchar);  
        System.out.println();  
    }  
  
    public Product createClone() {  
        Product p = null;  
        try {  
            p = (Product) clone();  
        } catch (CloneNotSupportedException e) {  
            e.printStackTrace();  
        }  
        return p;  
    }  
}

Main

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, World.");  
        Product p2 = manager.create("warning box");  
        p2.use("Hello, World.");  
        Product p3 = manager.create("slash box");  
        p3.use("Hello, World.");  
    }  
}

登场角色


Prototype(原型)

Product 扮演,用于定义复制现有实例来生成新实例的方法

ConcretePrototype(实现的原型)

MessageBox类UnderlinePen类 扮演,负责实现复制现有实例生成新实例的方法。

Client(使用者)

Manager类 扮演,负责使用复制实例的方法生成新的实例。

Prototype模式类图

拓展思路


不能根据类来生成实例吗

回到最初的使用场景:

  1. 对象种类繁多,无法整合到一个类中

根据示例程序,我们每个类都有不同的样式,如 '~', ‘*’
如果这些样式都要编写为一个类,那么类的数量将会非常庞大。

  1. 难以根据类生成实例时

还是和上面说的一样,我们如果想要创建出和操作一样的实例时,会比较困难

  1. 想解耦框架与生成的实例时

在Manager的create方法中,我们没有使用类名来为生成的实例明明,这种方式具有更好的通用性,并且将框架从类名的束缚中解脱。