Java设计模式-原型模式

原型模式概述

使用原型模式时,我们需要首先创建一个原型对象,再通过复制这个原型对象来创建更多类型的对象。原型对象可以通过调用原型类中的克隆方法来克隆自身从而创建更多的对象。

原型类的核心是如何实现克隆方法,其中有两种常用的实现方法。

原型模式结构

1、Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。

2、ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。

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

原型模式代码

Java 中的 Object 类提供了一个 clone() 方法,可以将一个 Java 对象复制一份。因此 Java 中可以直接利用 Object 中的 clone() 方法克隆一份对象,但必须要实现 Cloneable 接口,表示自己可以被复制。

1、抽象原型类即为 Object 类,因为 Object 类中声明了 clone() 方法了,子类实现即可。

2、具体原型类代码

/**
 * Author: YiFan
 * Date: 2018/12/12 09:53
 * Description: 具体原型类-笔记本
 */
public class NoteBook implements Cloneable {

    // 笔记本ID
    private int id;

    // 笔记本标题
    private String title;
    
    // 笔记本模型
    private String model;
    
    // 笔记本联系人-引用数据类型
    private Contact contact;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }
    
    public void setContact(Contact contact) {
        this.contact = contact;
    }

    public Contact getContact() {
        return contact;
    }

    @Override
    public String toString() {
        return "笔记本ID:" + id + ",笔记本标题:" + title + ", 笔记本模型:" + model;
    }

    // 重写抽象原型类Object中的clone()方法-克隆NoteBook对象
    @Override
    public NoteBook clone() throws CloneNotSupportedException {
        Object object;
        object = super.clone();
        return (NoteBook) object;
    }
}

// 笔记本的联系人
class Contact {

    private int num;

    public void setNum(int num) {
        this.num = num;
    }

    public int getNum() {
        return num;
    }
}

3、客户端代码

/**
 * Author: YiFan
 * Date: 2018/12/12 10:02
 * Description: 客户端
 */
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {

        // 创建原型对象
        NoteBook noteBook1 = new NoteBook();

        // 创建contact对象
        Contact contact = new Contact();

        // 设置contact的num属性
        contact.setNum(1);

        // 设置noteBook1对象的属性
        noteBook1.setId(1);
        noteBook1.setTitle("2017");
        noteBook1.setModel("22x22");

        System.out.println(noteBook1);

        // 将联系人contact添加到notebook1中
        noteBook1.setContact(contact);

        // 创建noteBook2引用变量
        NoteBook noteBook2;

        // 克隆noteBook1对象
        noteBook2 = noteBook1.clone();

        // 判断noteBook1和noteBook2指向的是否为同一个对象
        System.out.println("noteBook1和noteBook2指向的是否为同一个对象:"
                + (noteBook1 == noteBook2));

        // 设置noteBook2对象的属性
        noteBook2.setId(2);
        noteBook2.setTitle("2018");

        System.out.println(noteBook2);

        // 判断noteBook1和noteBook2对象中的contact对象是否是同一个对象
        System.out.println("noteBook1和noteBook2对象中的contact对象是否是同一个对象:"
                + (noteBook1.getContact() == noteBook2.getContact()));

        // noteBook2对象将contact中的num设置为2
        noteBook2.getContact().setNum(2);

        // 打印noteBook1对象中contact对象的num值
        System.out.println(noteBook1.getContact().getNum());
    }
}

执行结果为:

笔记本ID:1,笔记本标题:2017, 笔记本模型:22x22
noteBook1和noteBook2指向的是否为同一个对象:false
笔记本ID:2,笔记本标题:2018, 笔记本模型:22x22
noteBook1和noteBook2对象中的contact对象是否是同一个对象:true
2

执行结果的最后一行为 true 表示 noteBook1 和 noteBook2 对象中的 contact 对象是同一个对象,也就表明 noteBook1 对象并没有将自己的 contact 变量拷贝给 noteBook2,只是拷贝 contact 对象的引用给 noteBook2。noteBook2 设置 contact 对象中的 num 值为 2 后 noteBook1.getContact().getNum() 为 2 了,表明两个对象中的 contact 是同一个对象。

浅克隆和深克隆

浅克隆

浅克隆就如同上述的代码一样,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

深克隆

在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

在Java语言中,如果需要实现深克隆,可以通过序列化(Serialization)等方式来实现。序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现 Serializable 接口,否则无法实现序列化操作。

深克隆代码

具体原型类代码

/**
 * Author: YiFan
 * Date: 2018/12/12 09:53
 * Description: 具体原型类-笔记本
 */

// 实现Serializable接口表示该类可以被序列化
public class NoteBook implements Serializable {

    // 笔记本ID
    private int id;

    // 笔记本标题
    private String title;

    // 笔记本模型
    private String model;

    // 笔记本联系人-引用数据类型
    private Contact contact;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public void setContact(Contact contact) {
        this.contact = contact;
    }

    public Contact getContact() {
        return contact;
    }

    @Override
    public String toString() {
        return "笔记本ID:" + id + ",笔记本标题:" + title + ", 笔记本模型:" + model;
    }

    // 使用序列化技术进行深克隆
    public NoteBook deepClone() throws IOException, ClassNotFoundException {
        // 将对象写入流中
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(this);

        // 将对象从流中取出
        ByteArrayInputStream byteArrayInputStream = new
                ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);

        return (NoteBook) objectInputStream.readObject();
    }
}

// 笔记本的联系人
class Contact implements Serializable {

    private int num;

    public void setNum(int num) {
        this.num = num;
    }

    public int getNum() {
        return num;
    }
}

客户端代码

/**
 * Author: YiFan
 * Date: 2018/12/12 10:02
 * Description: 客户端
 */
public class Client {

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        // 创建原型对象
        NoteBook noteBook1 = new NoteBook();

        // 创建contact对象
        Contact contact = new Contact();

        // 设置contact的num属性
        contact.setNum(1);

        // 设置noteBook1对象的属性
        noteBook1.setId(1);
        noteBook1.setTitle("2017");
        noteBook1.setModel("22x22");

        System.out.println(noteBook1);

        // 将联系人contact添加到notebook1中
        noteBook1.setContact(contact);

        // 创建noteBook2引用变量
        NoteBook noteBook2;

        // 克隆noteBook1对象
        // noteBook2 = noteBook1.clone();
        
        // 调用deepClone()方法进行深克隆
        noteBook2 = noteBook1.deepClone();

        // 判断noteBook1和noteBook2指向的是否为同一个对象
        System.out.println("noteBook1和noteBook2指向的是否为同一个对象:"
                + (noteBook1 == noteBook2));

        // 设置noteBook2对象的属性
        noteBook2.setId(2);
        noteBook2.setTitle("2018");

        System.out.println(noteBook2);

        // 判断noteBook1和noteBook2对象中的contact对象是否是同一个对象
        System.out.println("noteBook1和noteBook2对象中的contact对象是否是同一个对象:"
                + (noteBook1.getContact() == noteBook2.getContact()));

        // noteBook2对象将contact中的num设置为2
        noteBook2.getContact().setNum(2);

        // 打印noteBook1对象中contact对象的num值
        System.out.println(noteBook1.getContact().getNum());
    }
}

客户端代码只是将 noteBook2 = noteBook1.clone() 代码改成 noteBook2 = noteBook1.deepClone()。执行结果:

笔记本ID:1,笔记本标题:2017, 笔记本模型:22x22
noteBook1和noteBook2指向的是否为同一个对象:false
笔记本ID:2,笔记本标题:2018, 笔记本模型:22x22
noteBook1和noteBook2对象中的contact对象是否是同一个对象:false
1

可以看到这条语句: noteBook1 和 noteBook2 对象中的 contact 对象是否是同一个对象:false。表示 noteBook1 将值类型成员变量和引用类型成员变量都复制到了 noteBook2 中,进行了深克隆。

原型管理器的引入和实现

原型管理器是将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。在原型管理器中针对抽象原型类进行编程,以便扩展。

下面模拟一个简单的戴尔笔记本的设计与实现:戴尔公司需要生产很多型号的笔记本,例如外星人、XPS、laptop等,为了提高效率,已经为各型号创建了模板了,也就是搭建好了模型,生产厂可以通过这些模型快速生产新的笔记本,这些模板要统一管理,生产厂根据需求生产不同的新笔记本。如需 XPS 笔记本则调用 XPS 型号的模板生产即可。

原型管理器实现代码
/**
 * Author: YiFan
 * Date: 2018/12/12 19:52
 * Description: 抽象接口,提供clone()方法的实现
 */
public interface DellNoteBook extends Cloneable {

    DellNoteBook clone() throws CloneNotSupportedException;
    void desc();
}

/**
 * Author: YiFan
 * Date: 2018/12/12 19:56
 * Description: 外星人笔记本
 */
public class AlienNoteBook implements DellNoteBook {

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

    @Override
    public void desc() {
        System.out.println("外星人笔记本");
    }
}

/**
 * Author: YiFan
 * Date: 2018/12/12 19:58
 * Description: XPS笔记本
 */
public class XPSNoteBook implements DellNoteBook {

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

    @Override
    public void desc() {
        System.out.println("XPS笔记本");
    }
}

/**
 * Author: YiFan
 * Date: 2018/12/12 20:14
 * Description: 原型管理器(使用饿汉式单例实现)
 */
public class PrototypeManager {

    private HashMap<String, DellNoteBook> ht = new HashMap<>();
    private static PrototypeManager pm = new PrototypeManager();

    // 为HashTable增加笔记本对象
    private PrototypeManager() {
        ht.put("alien", new AlienNoteBook());
        ht.put("xps", new XPSNoteBook());
    }

    // 增加新的笔记本
    public void addDellNoteBook(String key, DellNoteBook dellNoteBook) {
        ht.put(key, dellNoteBook);
    }

    public DellNoteBook getDellNoteBook(String key) throws CloneNotSupportedException {
        return ht.get(key).clone();
    }

    public static PrototypeManager getInstance() {
        return pm;
    }
}

/**
 * Author: YiFan
 * Date: 2018/12/12 20:21
 * Description: 客户端
 */
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {

        // 获取原型管理器对象,只需要唯一一个,使用单例模式(饿汉式)创建
        PrototypeManager prototypeManager = PrototypeManager.getInstance();

        DellNoteBook book1, book2, book3, book4;

        book1 = prototypeManager.getDellNoteBook("alien");
        book1.desc();

        book2 = prototypeManager.getDellNoteBook("alien");
        book2.desc();

        System.out.println(book1 == book2);

        book3 = prototypeManager.getDellNoteBook("xps");
        book3.desc();

        book4 = prototypeManager.getDellNoteBook("xps");
        book4.desc();

        System.out.println(book3 == book4);
    }
}

执行结果:

外星人笔记本
外星人笔记本
false
XPS笔记本
XPS笔记本
false

原型模式总结

原型模式作为一种快速创建大量相同或相似对象的方式,在软件开发中应用较为广泛,很多软件提供的复制和粘贴操作就是原型模式的典型应用,下面对该模式的使用效果和适用情况进行简单的总结。

主要优点

(1) 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。

(2) 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。

(3) 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。

(4) 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。

主要缺点

(1) 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。

(2) 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。

适用场景

(1) 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。

(2) 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。

(3) 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。

练习

设计并实现一个客户类 Customer,其中包含一个名为客户地址的成员变量,客户地址的类型为 Address,用浅克隆和深克隆分别实现 Customer 对象的复制并比较这两种克隆方式的异同。

练习代码

结构图如下:

原型模式-Customer

Customer 具体原型类如下:

/**
 * Author: YiFan
 * Date: 2018/12/12 21:26
 * Description: 具体原型类
 */
public class Customer implements Cloneable, Serializable {

    private String name;

    private int age;

    private Address address;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    /**
     * 浅克隆
     * @return Customer
     * @throws CloneNotSupportedException
     */
    @Override
    public Customer clone() throws CloneNotSupportedException {
        Object object;
        object = super.clone();
        return (Customer) object;
    }
    
    /**
     * 深克隆
     * @return Customer
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public Customer deepClone() throws IOException, ClassNotFoundException {
        // 将对象写入流中
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(this);

        // 将对象从流中取出
        ByteArrayInputStream byteArrayInputStream = new
                ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);

        // 返回Customer对象
        return (Customer) objectInputStream.readObject();
    }
}

// 实现Serializable接口为了可以序列化
class Address implements Serializable {

    private String addressName;

    public void setAddressName(String addressName) {
        this.addressName = addressName;
    }

    public String getAddressName() {
        return addressName;
    }
}

客户端代码如下:

/**
 * Author: YiFan
 * Date: 2018/12/12 21:29
 * Description: 客户端
 */
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {

        // 创建customer1对象
        Customer customer1 = new Customer();

        // 创建address对象
        Address address = new Address();

        // 设置customer1的属性
        customer1.setName("a");
        customer1.setAge(1);
        customer1.setAddress(address);

        // 创建customer2和customer3引用变量
        Customer customer2, customer3;

        // 克隆customer1对象(浅克隆)
        customer2 = customer1.clone();

        // 浅克隆-所以address引用类型变量只是拷贝内存地址
        System.out.println("customer1中引用类型address变量是否与customer2中相同:"
                + (customer1.getAddress() == customer2.getAddress()));

        // 克隆customer1对象(深克隆)
        customer3 = customer1.deepClone();

        // 深克隆-即拷贝值类型变量,也拷贝引用类型变量
        System.out.println("customer1中引用类型address变量是否与customer3中相同:"
                + (customer1.getAddress() == customer3.getAddress()));
    }
}

直接结果:

customer1中引用类型address变量是否与customer2中相同:true
customer1中引用类型address变量是否与customer3中相同:false
posted @ 2018-12-27 11:43  扇影无风  阅读(438)  评论(0编辑  收藏  举报