Author:Gorit
Date:2021/1/13
Refer:《图解设计模式》

一、通过复制生成的实例

在 java 中,我们可以通过 new 关键字来生成实例。我们通常这样生成的实例都是要指定类名的。

但是有时候,我们在开放的过程中,也有不指定类名来生成实例的需求。

1.1 以下情况不根据类来生成实例

在以下情况中,我们不能根据类来生成实例,而要根据已有的实例来生成新的实例。

  1. 对象种类繁多,无法将他们整合到一个类中。
  2. 难以根据类生成实例。
  3. 解耦框架与生成的实例时。

1.2 如何实现不依赖类来生成实例?

不根据类来生成实例,而是根据实例来生成实例的设计模式叫 Prototype 模式

Prototype 有原型模型 的意思。在设计模式中,它是根据实例原型,实例模型来生成新的实例。

现在来回答标题提出的问题:

我们可以通过 clone 创建出实例的副本。我们还会用刀拍 clone 方法与 Cloneable 接口的使用方法。

二、示例程序

2.1 实现目标

我们要实现的程序功能是:将字符串放入方框,显示出来 或者 加上下划线显示出来

2.2 类和接口一览表

名字 说明
framework Product 声明了抽象方法 use 和 createClone 接口
framework Manager 调用 createClone 方法复制实例类
MessageBox 将字符串放入方框,并显示出来,实现 use 方法 和 createClone 方法
UnderlinePen 给字符串加上上下划线并显示出来的类,实现了 use 和 createClone 方法
Main 测试程序行为的类

Product 接口和 Message 类属于 framework包,负责复制实例。
Manager 类虽然会调用 createClone 方法,但是对于具体要复制哪个。并不知道。
但是只要实现了 Product 接口的类,调用它的 createClone 方法就可以复制出新的实例。

MessageBox 类 和 UnderlinePen 类是两个实现了 Product 接口的类。只用实现将这两个类“注册”到 Manager 类中,就可以随时复制新的实例。

2.3 代码实现

Product

  1. package Prototype.framework;
  2. // 继承复制的接口, Cloneable 通过调用 clone 实现对象的调用
  3. public interface Product extends Cloneable{
  4. public abstract void use(String s); // 具体是如何使用,交给子类去实现
  5. public abstract Product createClone();
  6. }

Manager

  1. package Prototype.framework;
  2. import java.util.*;
  3. /**
  4. * 使用 product 接口复制实例
  5. */
  6. public class Manager {
  7. // 保存名字和实例之间的对应关系
  8. private HashMap showcase = new HashMap();
  9. public void register(String name, Product proto) {
  10. showcase.put(name,proto);
  11. }
  12. public Product create(String protoname) {
  13. Product p = (Product) showcase.get(protoname);
  14. return p.createClone();
  15. }
  16. }

MessageBox

  1. package Prototype;
  2. import Prototype.framework.Product;
  3. public class MessageBox implements Product {
  4. private char decochar;
  5. public MessageBox(char decochar) {
  6. this.decochar = decochar;
  7. }
  8. public void use(String s) {
  9. int length = s.getBytes().length;
  10. for (int i=0;i<length+4;i++) {
  11. System.out.print(decochar);
  12. }
  13. System.out.println("");
  14. System.out.println(decochar + " " + s + " " + decochar);
  15. for (int i=0;i<length+4;i++) {
  16. System.out.print(decochar);
  17. }
  18. System.out.println("");
  19. }
  20. public Product createClone() {
  21. Product p = null;
  22. try {
  23. p = (Product) clone();
  24. } catch (CloneNotSupportedException e) {
  25. e.printStackTrace();
  26. }
  27. return p;
  28. }
  29. }

UnderlinePen

package Prototype;

import Prototype.framework.Product;

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.println(" ");
        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

package Prototype;

import Prototype.framework.Manager;
import Prototype.framework.Product;

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 msg",upen);
        manager.register("warning box",mbox);
        manager.register("slash box",sbox);

        // 生成
        Product p1 = manager.create("strong msg");
        p1.use("Hello World");
        Product p2 = manager.create("warning box");
        p2.use("Hello World");
        Product p3 = manager.create("slash box");
        p3.use("Hello World");

    }
}

执行效果
image.png

三、Prototype 模式中登场的角色-

  • Prototype(原型)

Product 角色负责定义用于复制现有实例来生成新实例的方法。 Product 接口扮演此角色

  • ConcretePrototupe(具体的原型)

ConcretePrototype 角色负责实现复制现有的实例并生成新实例的方法。在上述程序中, MessageBox类 和 UnderlinePen 类扮演此角色

  • Client (使用者)

Client 角色负责使用复制实例的方法生成新的实例。在示例中,由 Message类 扮演此角色。

四、补充

4.1 相关设计模式

  • Flyweight 模式(可以在不同地方使用同一个实例)
  • Memento 模式(可以保存当前实例状态,以实现快照和撤销功能)
  • Composite 模式 以及 Decorator 模式(动态创建复杂结构的实例,这里可以用 Prototype 模式快速生成实例)
  • Command 模式(该模式中出现的命令需要复制的时候,可以使用 Prototype)

4.2 clone 方法 和 java.lang.Cloneable 接口

要使用 clone 方法进行复制实例,就要实现 Cloneable 接口,否则就会出现 CloneNotSupportedException

java.lang 包是默认引入的。因此不必显示引入 java.lang 包调用 clone() 方法

4.3 clone() 方法在哪里定义的?

clone() 方法定义在 java.lang.Object 中,因为 Object 类是所有 java 类的父类。因此所有类都默认继承了 Object 类。