原型模式(Prototype)——对象创建型模式

177 阅读4分钟

模式意图

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象

模式结构

image.png

代码示例

class Location implements Cloneable {
    String street;
    String email;

    public Location(String street, String email) {
        this.street = street;
        this.email = email;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

// 继承 Cloneable
class Person implements Cloneable {
    String name;
    int age;
    Location location;

    public Person(String name, int age, Location location) {
        this.name = name;
        this.age = age;
        this.location = location;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        Person p = (Person) super.clone();
        p.location = (Location) p.location.clone();
        return p;
    }
}

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Location location = new Location("street1", "email");
        Person p1 = new Person("gao", 25, location);

        Person p2 = (Person) p1.clone();

        p1.location.street = "street2";

        System.out.println(p2.location.street);
    }
}

适用场景

  1. 如果你需要复制一些对象,同时又希望代码独立于这些对象的所属类,可以使用原型模式(一般出现在客户代码需要处理第三方代码接口传递过来的对象)
  2. 当要实例化的类是在运行时指定时,例如,通过动态装载
  3. 为了避免创建一个与产品类层次平行当工厂类层次时(避免像 Factory Method 那样产生与产品类平行的工厂类层次,减少类爆炸)
  4. 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些

常见案例

  1. Java 中 Object clone 方法

模式效果(优缺点)

  1. 跟工厂方法、抽象工厂、生成器模式一样,原型模式对客户隐藏了具体的产品类,减少了客户需要知道的产品的名字的数目
  2. 运行时增加和删除产品(允许只通过客户注册原型实例就将一个新的具体产品类并入系统。它比其他创建型模式更为灵活,因为客户可以在运行时建立和删除原型)
  3. 改变值以指定新对象(通过为一个对象变量指定值——并且不定义新的类,客户可以动态的改变产品的行为)
  4. 改变结构以指定新对象(许多应用是由部件和子部件构成的,组合模式和装饰者模式通常会被用于实例化这种复杂的、用户自定义的结构。原型模式也可以很好的支持这个场景,我们仅需要将子电路作为一个原型就可以避免一次次重复创建相同结构的子电路)
  5. 减少子类的构造(工厂方法中的工厂类通常需要与产品成对出现,会导致系统类爆炸,而原型模型不需要创建平行的工厂类)
  6. 用类动态配置应用(一些运行时环境允许你动态地将类装载到应用中,比如说 C++。一个希望创建动态载入类的实例的应用不能静态引用类的构造器,应该用运行环境在载入时自动创建每个类的实例,并用原型管理器管理这个实例)
  7. 原型模式的主要缺陷就是每个子类都必须要实现 Clone 操作,那些已经存在的类可能会很难操作,并且当类的内部有循环引用的对象时,实现克隆也会比较困难
  8. 你经常可以创建一个中心化原型注册表,用于存储常用原型。而注册表通常可以使用工厂类来实现,找到合适的原型后,注册表对原型进行克隆,并将复制后的对象返回给客户端

模式扩展

  1. 抽象工厂通常基于一组工厂方法,但是你也可以使用原型模式来生成这些类的方法(抽象工厂往往和原型模式是相互竞争的,但是也可以一起使用,比如说,抽象工厂可以存储一个被克隆的原型的集合,就像原型注册表一样,并且返回产品对象)
  2. 原型模式并不基于继承,因此它不具有继承的缺点,但是原型模式需要被复制的对象有大量的初始化步骤;工厂方法基于继承,但是不需要初始化步骤
  3. 抽象工厂生成器原型模式都可以通常单例模式来实现
  4. 原型可用于保存命令模式的历史记录
  5. 大量使用组合装饰的设计通常可以从对于原型的使用中获益,通过这种模式来复制复杂结构而非从头开始创建
  6. 有时原型可以作为备忘录的一个简化版本,其条件是你需要在历史中存储的对象的状态比较简单,不需要链接其他外部资源,或者可以方便地重建