设计模式(八):原型模式(Prototype Pattern)

系统中可能需要创建大量相同或相似对象,如果用构造方法创建则会比较耗时耗费资源,这时就可以使用 原型模式 ,生成对象更高效。

原型模式 就像复印机,可以复印多份相同的;像孙悟空的猴毛,拨下一吹就变出很多个孙悟空一样简单快捷。

模式定义

原型模式(Prototype):用一个已创建的实例作为原型(原型实例),通过拷贝(复制)该原型对象来创建一个和原型相同或相似的对象。

使用原型模式创建对象非常高效,不需要知道对象创建的细节。例如 Windows 操作系统的 Ghost 安装,在安装一次操作系统后,做成 Ghost 镜像,下次安装就直接拷贝即可,无须再设置操作系统安装的每个细节。

模式分析

由于 Java 所有类默主继承了 Object 类,Object 类提供了 clone() 方法,所以用 Java 实现原型模式很简单。

模式结构

原型模式包含如下角色:

  • 抽象原型类(Prototype):规定了具体原型对象必须实现的接口。
  • 具体原型类(ConcretePrototype):实现提象原型类的 clone() 方法,是可被复制的对象。
  • 访问类(Client):调用具体原型类中的 clone() 方法来复制新的对象。

应用场景

  • 对象之间相同或相似,即只有个别的几个属性不同时。
  • 对象的创建过程比较麻烦,但复制比较简单的时候。

模式应用

对象克隆

原型模式克隆 分为 浅克隆深克隆,Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口,重写 clone() 方法时引用超父类(Object)的 clone() 方法,就可实现对象的 浅克隆,Cloneable 接口就是抽象原型类。

  • 浅克隆:创建一个新对象(副本),然后将非静态属性复制到新对象。如果属性是值类型,则对该属性执行逐位复制。如果属性是引用类型,则复制引用,但不复制引用的对象;因此,原始对像与副本引用同一个对象。
  • 深克隆:创建一个新对象(副本),属性是引用类型,引用的对象也会被克隆,不再指向原有对象地址。

对象实现 Cloneable 接口并重写 clone() 方法(引用超父类 Object 的 clone() 方法不进行任何额外操作),对象调用 clone() 方法进行的是 浅克隆若使用对象流将对象写入流然后再读出,则是深克隆

对象克隆是创建一个新对象,与原对象不是同一个地址,从而可以引申出,如果对象的引用类型属性对象也实现了 Cloneable 接口,重写了 clone() 方法,直到没有引用类型属性或者引用类型属性为 null 时,整体上就形成了 深克隆。即引用类型属性的引用类型属性都实现了 Cloneable 接口,重写 clone() 方法,并在 clone() 方法中进行调用。

浅克隆示例

  1. 原型对象实现 Cloneable 接口,重写 clone() 方法。

    Cloneable 是抽象原型类,默认提供由 Object 类实现的 clone() 方法,User 是具体原型类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    public class User implements Cloneable {

    private String name;
    private Integer age;

    private Student student;

    public User(String name, Integer age) {
    this.name = name;
    this.age = age;
    }

    public Student getStudent() {
    return student;
    }

    public User setStudent(Student student) {
    this.student = student;
    return this;
    }

    public String getName() {
    return name;
    }

    public User setName(String name) {
    this.name = name;
    return this;
    }

    public Integer getAge() {
    return age;
    }

    public User setAge(Integer age) {
    this.age = age;
    return this;
    }

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

  2. 客户端调用

    1
    2
    3
    4
    5
    6
    7
    8
    public class MainTest {
    public static void main(String[] args) throws CloneNotSupportedException {
    User user = new User("Kitty", 21).setStudent(new Student(3));
    User cloneUser = (User) user.clone();

    System.out.println(user == cloneUser);
    }
    }

    返回的是 false,表示是不同的两个对象。

深克隆示例

  1. 原型对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public class User implements Cloneable {

    private String name;
    private Integer age;

    private Student student;

    public User(String name, Integer age) {
    this.name = name;
    this.age = age;
    }

    public Student getStudent() {
    return student;
    }

    public User setStudent(Student student) {
    this.student = student;
    return this;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
    User result = (User) super.clone();
    if (result != null) {
    result.student = (Student) student.clone();
    }
    return result;
    }
    }
  2. 引用类型对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Student implements Cloneable{

    private Integer grade;

    public Student(Integer grade) {
    this.grade = grade;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
    return super.clone();
    }
    }
  3. 这样的话,引用对象的地址并不是原引用对象的地址,实现了深克隆。

原型模式扩展

原型模式可扩展为带原型管理器的原型模式,它在原型模式的基础上增加了一个原型管理器 PrototypeManager 类。该类用 HashMap 保存多个复制的原型,Client 类可以通过管理器的 get(String id) 方法从中获取复制的原型。

获取原型实际是在 PrototypeManager 类中读取 HashMap 中的具体原型对象,原型对象实例调用 clone() 方法克隆出一个新对象并新对象返回。

例如:发送多种类型或不同风格的营销邮件

  1. 原型抽象接口

    1
    2
    3
    public interface IMail extends Cloneable {
    Object clone() throws CloneNotSupportedException;
    }
  2. 具体原型对象,实现原型抽象接口,重写 clone() 方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class BirthdayGreetingMail implements IMail {

    private String name;
    private String msg = "生日快乐!";

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

    public class HappyNewYearMail implements IMail {

    private String name;
    private String msg = "新年快乐!";

    @Override
    public Object clone() throws CloneNotSupportedException {
    return super.clone();
    }
    }
  3. 原型管理器类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class ProtoTypeManager {

    private HashMap<String, IMail> map = new HashMap<>();

    public ProtoTypeManager() {
    map.put("newYear", new HappyNewYearMail());
    map.put("birthday", new BirthdayGreetingMail());
    }

    public void addMail(String key, IMail mail) {
    map.put(key, mail);
    }

    public IMail getMail(String key) throws CloneNotSupportedException {
    IMail mail = map.get(key);
    return (BirthdayGreetingMail) mail.clone();
    }
    }

相关参考

  1. 原型模式(原型设计模式)详解

设计模式(八):原型模式(Prototype Pattern)

http://blog.gxitsky.com/2019/10/06/DesignPatterns-08-Prototype/

作者

光星

发布于

2019-10-06

更新于

2022-06-17

许可协议

评论