Prototype(原型)模式---通过复制生成实例

1.Prototype 模式
   》》 在 Java 中,我们可以使用 new 关键字指定类名来生成类的实例。像这样使用 new 来生成实例时,
      是必须指定类名的。但是,在开发过程中,有时候也会有“在不指定类名的前提下生成实例”的需求。
      例如,在以下情况,我们就不能根据类来生成实例,而要根据现有的实例来生成新的实例。
       (1)、对象种类繁多,无法将它们整合到一个类中时
             这种情况是需要处理的对象太多,如果将它们分别作为一个类,必须要编写很多个类文件。
       (2)、难以根据类生成实例时
             这种情况是生成实例的过程太复杂,很难根据类来生成实例。例如,我们假设这里有一个实例,
            即表示用户在图形编辑器中使用鼠标制作出的图形的实例。想在程序中创建这样的实例是非常
            困难的。通常,在想生成一个和之前用户通过操作所创建出的实例完全一样的实例的时候,我们
            会事先将用户通过操作所创建出来实例保存起来,然后在需要时通过复制来生成新的实例。
       (3)、想解耦框架与生成的实例时
             这种情况是想要让生成实例的框架不依赖于具体的类。这时,不能指定类名来生成实例,而要
            事先“注册”一个“原型”实例,然后通过复制该实例来生成新的实例。
             根据实例生成实例与使用复印机复印文档相类似。即使不知道原来的文档中的内容,我们也可以
            使用复印机复制出完全相同的文档,无论多少份都行。

     》》Prototype 模式:根据实例来生成新的实例。Prototype 有“原型”、“模型”的意思。
     》》在 Java 语言中,我们可以使用 clone 创建出实例的副本。

2.示例程序:
     》》以下这段示例程序的功能是将字符串放入框中显示出来或是加上下划线显示出来。
     》》示例程序中的类和接口的一览表:

  

  

 说明:(1)、 Product 接口和 Manager 类属于 framework 包,负责复制实例。
                       虽然 Manager 类会调用 createClone 方法,但是对于具体要复制哪个类一无所知。不过,只要是实现了 Product
                   接口的类,调用它的 createClone 方法就可以复制出新的实例。
               (2)、MessageBox 类和  UnderlinePen 类是两个实现了 Product 接口的类。只要是实现将这两个类“注册”到
                   Manager 类中,就可以随时复制新的实例。

      》》 示例程序的类图:

 

 1)、Product 接口
        》》 Product 接口是复制功能的接口。该接口继承了 java.lang.Cloneable 接口。现在大家只需要知道实现了该接口的类的实例可以
            调用 clone 方法来自动复制实例即可。
        》》  use 方法是用于“使用”的方法。具体怎么“使用”,则被交给子类去实现。
        》》  createClone 方法是用于复制实例的方法。
        》》 代码清单:
              package module.prototype.framework;

public interface Product extends  Cloneable{
    /**
     * 用于“使用”的方法
     * @param s
     */
    public abstract void use(String s);

    /**
     * 用于复制实例的方法
     * @return
     */
    public abstract Product createClone();
}

 2)、Manager 类
          》》 Manager类使用 Product 接口来复制实例。
          》》 showcase 字段是 java.util.HashMap 类型,它保存了实例的“名字”和“实例”之间的对应关系。
          》》 register 方法会将接收到的 1 组“名字”和“Product 接口”注册到 showcase 中。
          》》请注意,在 Product 接口和 Manager 类的代码中完全没有出现 MessageBox 类和 UnderlinePen 类
              的名字,这也意味着我们可以独立地修改 Product 和 Manager ,不受 MessageBox 类和 UnderlinePen 类
              的影响。这是非常重要的,因为一旦在类中使用了别的类名,这就意味着该类与其他类紧密地耦合在了一起。
              在 Manager 类中,并没有写明具体的类名,仅仅使用了 Product 这个接口名。也就是说, Product
              接口成为了 Manager 类与其他具体类之间的桥梁。
          》》代码清单:
               package module.prototype.framework;

import java.util.HashMap;

public class Manager {
    private  HashMap showcase = new HashMap<>();

    /**
     * 实现注册
     * @param name
     * @param proto
     */
    public void register(String name , Product proto){
        showcase.put(name , proto);
    }

    public Product create(String protoname){
        Product p = (Product)showcase.get(protoname);
        return p.createClone();
    }
}
      3)、MessageBox 类
             》》 MessageBox 类实现了  Product  接口
             》》 decochar 字段中保存的是像装饰方框那样的环绕着字符串的字符。
                  use 方法会使用 decochar 字段中保存的字符把要显示的字符串框起来。
             》》 createClone 方法用于复制自己。它内部调用的 clone 方法是 Java 语言中定义的方法,用于复制自己。
                 在进行复制时,原来实例中的字段的值也会被复制到新的实例中。我们之所以可以调用 clone 方法进行复制,
                 仅仅是因为该类实现了 java.lang.Cloneable 接口。如果没有实现这个接口,在运行时会将会抛出
                 CloneNotSupportException 异常,因此必须用 try...catch... 语句块捕捉这个异常。
              》》需要注意的是, java.lang.Cloneable  接口只是起到告诉程序可以调用 clone 方法的作用,它自身并
                没有定义任何方法。
              》》 只有类自己(或是它的子类)能够调用 Java 语言中定义的 clone 方法。当其他类要求复制实例时,
                 必须先调用 createClone 这样的方法,然后在该方法内部再调用 clone() 方法。
              》》 代码清单:
                  package module.prototype.framework;

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

        System.out.println("");
        System.out.println(decochar + ""+ s + "" + decochar);
        for(int i = 0 ; i < length + 4 ; i++){
            System.out.println(decochar);
        }

        System.out.println("");

    }
    public Product createClone(){
        Product p = null;
        try{
            p = (Product)clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return p;
    }
}

  4)、UnderlinePen 类
       》》 UnderlinePen 类的实现与 MessageBox 几乎完全相同,不同的是在 ulchar 字符中把保存的是修饰下划线样式的字符。
       》》 use 方法的作用是将字符串用双引号括起来显示,并在字符串下面加上下划线。
       》》 代码清单:
            package module.prototype.framework;

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;
    }
}

  5)、Main 类
      》》Main 类首先生成了 Manager 的实例。接着,在 Manager 实例中注册了 UnderlinePen 的实例(带名字)和 MessageBox 的实例(带名字)
      》》 代码清单:
          package module.prototype.framework;

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('/');
        /**
         * w完成注册
         */
        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");

 

    }
}
    
  6)、上面程序的执行效果如下:

3.Prototype 模式中的登场角色
     在 Prototype 模式中有以下登场角色:
     1)、Prototype (原型)
          Product 角色负责定义用于复制现有实例来生成新实例的方法。在示例程序中,由  Product 接口扮演此角色。
     2)、ConcretePrototype (具体的原型)
          ConcretePrototype 角色负责实现复制现有实例并生成新实例的方法。在示例程序中,由 MessageBox 类和
         UnderlinePen 类扮演此角色。
     3)、Client(使用者)
          Client 角色负责使用复制实例的方法生成新的实例。在示例程序中,由 Manager 类扮演此角色。
     
     补充:Prototype 模式的类图

 4.扩张思路的要点
  1)、不能根据类来生成实例吗?
      (1)、对象种类繁多,无法将它们整合到一个类中时
              在示例程序中,一共出现了如下 3 种样式:
              a. 使用“~”为字符添加下划线
              b. 使用“*”为字符添加边框
              c. 使用“/”为字符添加边框
      (2)、难以根据类生成实例时
              》》假设我们想生成一个和用户通过一系列鼠标操作所创建出来的实例完全一样的实例。这个时候,与根据类
            来生成实例相比,根据实例来生成实例要简单得多。
      (3)、想解耦框架与生成的实例时
              》》 在示例程序中,我们将复制(clone)实例的部分封装在了 framework 包中了。
              》》 在 Manager 类的 create 方法中,我们并没有使用类名,取而代之使用了"strong message" 和 "slash box"
            等字符串为生成的实例命名。与 Java 语言自带的生成实例的 new Something() 方式相比,这种方式具有更好的
            通用性,而且将框架从类名的束缚中解脱出来了。
  2)、类名是束缚吗?
        》》面向对象编程的目标之一,即“作为组件复用”。
        》》在代码中出现要使用的类的名字并非总是坏事。不过,一旦在代码中出现要使用的类的名字,就无法与该类分离开来,
            也就无法实现复用。
        》》 以 Java 来说,重要的是当手边只有 class 文件(.class )时,该类能否被复用。即使没有 Java 文件(.java)
             也能复用该类才是关键。
        》》 当多个类必须紧密结合时,代码中出现这些类的名字是没有问题的。但是如果那些需要被独立出来作为组件复用的类
             的名字出现在代码中,那就有问题了。

5.相关的设计模式
        》》 Flyweight 模式
             使用 Prototype 模式可以生成一个与当前实例的状态完全相同的实例。
             而使用 Flyweight 模式可以在不同的地方使用同一个实例。
        》》 Memento 模式
             使用 Prototype 模式可以生成一个与当前实例的状态完全相同的实例。
             而使用 Memento 模式可以保存当前实例的状态,以实现快照和撤销功能。
        》》 Composite 模式以及Decorator 模式
             经常使用 Composite 模式与 Decorator 模式时,需要能够动态地创建复杂结构的实例。这时可以使用 Prototype 模式,
             以帮助我们方便地生成实例。
        》》 Command 模式
             想要复制 Command 模式中出现的命令时,可以使用 Prototype 模式。

6.延伸阅读:clone 方法和 java.lang.Cloneable 接口
  1)、Java 语言的 clone
        》》 Java 语言为我们准备了用于复制实例的 clone() 方法。
            **** 请注意,要想调用 clone() 方法,被复制对象的类必须实现 java.lang.Cloneable 接口,不论是被复制对象的类
                实现 java.lang.Cloneable 接口还是其某个父类实现 Cloneable 接口,亦或是被复制对象的类实现了 Cloneable 接口
                的子接口都可以。
            **** 实现了 Cloneable 接口的类的实例可以调用 clone 方法进行复制,clone 方法的返回值是复制出的新的实例
                (clone 方法内部所进行的处理是分配与要复制的实例同样大小的内存空间,接着将要复制的实例中的字段的值复制到
                所分配的内存空间中去)
            **** 如果没有实现了 Cloneable 接口的类的实例调用了 clone 方法,则会在运行时抛出 CloneNotSupportException (不支持 clone 方法)
                异常。
  2)、clone 方法是在哪里定义的
         》》 clone 方法定义在 java.lang.Object 中,因为 Object 类是所有 Java 类的父类,因此所有的 Java 类都继承了 clone() 方法。
  3)、需要实现 Cloneable 的哪些方法
         》》 在 Cloneable 接口中并没有声明任何方法。它只是被用来标记“可以使用 clone() 方法进行复制”的。这样的接口被
             称为标记接口(marker interface)。
  4)、clone 方法进行的是浅复制
         》》 clone() 方法所进行的复制只是将被复制实例的字段值直接复制到新的实例中。换言之,它并没有考虑到字段中保存的实例的内容。
            例如,当字段中保存的是数组时,如果使用 clone() 方法进行复制,则只会复制该数组的引用,并不会一一复制数组中的元素。
         》》 当使用 clone() 方法进行浅复制无法满足需求时,类的设计者可以实现重写 clone() 方法,实现自己需要的复制功能。
            (重写 clone() 方法时,别忘了使用 super.clone() 来调用父类的 clone() 方法)。
         》》 需要注意的是, clone() 方法只会进行复制,并不会调用被复制实例的构造函数。此外, 对于在生成实例时需要进行特殊的初始化
            处理的类,需要自己去实现 clone() 方法,在其内部进行这些初始化处理。
         补充: 详细信息请参考 Java  的 API 参考资料中 java.lang.Object 类的 clone() 方法和 Cloneable 接口这两个相关条目。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小达人Fighting

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值