第7章 原型模式

7.1 原型模式概述

image

原型模式(Prototype Pattern):使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。

7.2 原型模式结构与实现

7.2.1 原型模式结构

image

  1. Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是抽象类也可以是接口,甚至还可以是具体实现类。
  2. ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
  3. Client(客户类):在客户类中,让一个原型对象克隆自身从而创建一个新的对象,只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。

7.2.2 浅克隆与深克隆

浅克隆

image

深克隆

image

7.2.3 原型模式实现

通用实现方法

Prototype:

public abstract class Prototype {
    public abstract Prototype clone();
}

ConcretePrototype:

public class ConcretePrototype extends Prototype {
    private String attr;

    public void setAttr(String attr) {
        this.attr = attr;
    }

    public String getAttr() {
        return this.attr;
    }

    //克隆方法
    public Prototype clone() {
        Prototype prototype = new ConcretePrototype();
        prototype.setAttr(this.attr);
        return prototype;
    }
}

Client:

...
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAttr("Sunny");
ConcretePrototype copy = (ConcretePrototype) prototype.clone();
...

Java语言中的clone()方法和Cloneable接口

ConcretePrototype:

public class ConcretePrototype implements Cloneable {
    ...

    public Prototype clone() {
        Object object = null;
        try {
            object = super.clone();        //浅克隆
        } catch (CloneNotSupportedException exception) {
            System.err.println("Not support cloneable");
        }
        return (Prototype) object;
    }
}

Client:

Prototype protptype = new ConcretePrototype();
Prototype copy = protptype.clone( );

Java语言中的clone()方法满足以下几点:

  1. 对任何对象x,都有x.clone() !=x,即克隆对象与原型对象不是同一个对象。
  2. 对任何对象x,都有x.clone().getClass()==x.getClass(),即克隆对象与原型对象的类型一样。
  3. 自定义x的equals()方法,使得x.clone().equals(x)成立

在Java中获得一个对象的克隆

  1. 派生类需实现Cloneable接口
  2. 在派生类中覆盖基类的clone()方法,并声明为public
  3. 在派生类的clone()方法中调用super.clone()

7.3 原型模式应用实例

实例说明

       在使用某OA系统时,有些岗位的员工发现他们每周的工作都大同小异﹐因此在填写工作周报时很多内容都是重复的,为了提高工作周报的创建效率,大家迫切希望有一种机制能够快速创建相同或者相似的周报,包括创建周报的附件。
试使用原型模式对该OA系统中的工作周报创建模块进行改进。

实例类图

image

  • 抽象原型类
    • Object
  • 具体原型类
    • WeeklyLog
      • Attachment

实例代码

浅克隆解决方案

Attachment
package designpatterns.prototype.shallowclone;

public class Attachment {
    private String name;

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

    public String getName() {
        return name;
    }


    public void download() {
        System.out.println("下载附件,文件名为" + name);
    }
}
WeeklyLog
package designpatterns.prototype.shallowclone;

public class WeeklyLog {
    private Attachment attachment;
    private String name;
    private String data;
    private String content;


    public void setAttachment(Attachment attachment) {
        this.attachment = attachment;
    }

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

    public void setData(String data) {
        this.data = data;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Attachment getAttachment() {
        return attachment;
    }

    public String getName() {
        return name;
    }


    public String getData() {
        return data;
    }


    public String getContent() {
        return content;
    }

    public WeeklyLog clone() {
        Object obj = null;
        try {
            obj = super.clone();
            return (WeeklyLog) obj;
        }catch (CloneNotSupportedException e){
            System.out.println("不支持被复制!");
            return null;
        }
    }
}
Client
package designpatterns.prototype.shallowclone;

public class Client {
    public static void main(String[] args) {
        WeeklyLog log_previous, log_new;
        log_previous = new WeeklyLog();
        Attachment attachment = new Attachment();
        log_previous.setAttachment(attachment);
        log_new = log_previous.clone();
        //比较
        System.out.println("周报是否相同?" + (log_previous == log_new));
        System.out.println("附件是否相同?" + (log_previous.getAttachment() == log_new.getAttachment()));
    }
}

深克隆解决方案

通过序列化(Serialization) 来实现深克隆。

  1. 将原对象写入流
  2. 从流中读取出现对象

能够实现序列化的对象类必须实现Serialization接口,否则无法序列化操作。

image

Attachment
package designpatterns.prototype.deepclone;

import java.io.Serializable;

public class Attachment implements Serializable {
    private String name;

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

    public String getName() {
        return name;
    }


    public void download() {
        System.out.println("下载附件,文件名为" + name);
    }
}
WeeklyLog
package designpatterns.prototype.shallowclone;

import java.io.*;

public class WeeklyLog {
    private Attachment attachment;
    private String name;
    private String data;
    private String content;


    public void setAttachment(Attachment attachment) {
        this.attachment = attachment;
    }

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

    public void setData(String data) {
        this.data = data;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Attachment getAttachment(){
        return attachment;
    }

    public String getName() {
        return name;
    }


    public String getData() {
        return data;
    }


    public String getContent() {
        return content;
    }

    public WeeklyLog deepClone() throws IOException, ClassNotFoundException, OptionalDataException {
        //对象写入流中
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bao);
        oos.writeObject(this);
        ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (WeeklyLog) ois.readObject();
    }
}

Client
package designpatterns.prototype.shallowclone;

public class Client {
    public static void main(String[] args){
        WeeklyLog log_previous, log_new = null;
        log_previous = new WeeklyLog();
        Attachment attachment = new Attachment();
        log_previous.setAttachment(attachment);
        try {
            log_new = log_previous.deepClone();
        } catch (Exception e) {
            System.out.println("克隆失败!");
        }
        //比较
        System.out.println("周报是否相同?" + (log_previous == log_new));
        System.out.println("附件是否相同?" + (log_previous.getAttachment() == log_new.getAttachment()));
    }
}

原型管理器

       将多个原型对象存储在一个集合中供客户端使用。如果需要某个原型对象的一个克隆,通过复制集合中对应的原型对象来获得。
image

package designpatterns.prototype;

import java.util.Hashtable;

public class PrototypeManage {
    private Hashtable prototypeTable = new Hashtable();

    public PrototypeManage() {
        prototypeTable.put("A", new ConcretePrototypeA());
        prototypeTable.put("B", new ConcretePrototypeB());
    }

    public void add(String key, Prototype prototype) {
        prototypeTable.put(key, prototype);
    }

    public Prototype get(String key, Prototype prototype) {
        Prototype clone = null;
        clone = ((Prototype) prototypeTable.get(key)).clone();
        return clone;
    }
}

原型模式优/缺点与适用环境

原型模式优点

  1. 当创建新的对象实例较为复杂,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
  2. 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统对原有系统的影响。
  3. 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制型类中的克隆方法实现的,无须专门的工厂类来创建产品。
  4. 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(例如恢复到某一历史状态),可辅助实现撤销操作。

原型模式缺点

  1. 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时需要修改源代码,违背了开闭原则。
  2. 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。

原型模式适用环境

  1. 创建新对象成本较大(例如初始化需要占用较长的时间、占用太多的CPU资源或网络资源),新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改。
  2. 系统要保存对象的状态,而对象的状态变化很小。
  3. 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
posted @ 2022-04-06 16:02  手持六脉神剑的外星人  阅读(49)  评论(0编辑  收藏  举报