文章目录

  1. 1. 如何理解原型模式
    1. 1.1. Prototype - 抽象原型类
    2. 1.2. ConcretePrototype - 具体原型类
    3. 1.3. Client - 客户类
  2. 2. 简要定义
  3. 3. Java 的原型模式
  4. 4. 案例分析
  5. 5. 思维发散
    1. 5.1. 原型模式 vs 根据类创建实例
    2. 5.2. 原型模式创建的对象是全新的对象吗
    3. 5.3. 浅克隆 vs 深克隆
  6. 6. 总结
  7. 7. 参考文章
  8. 8. 源代码

在 Java 中,我们可以使用 new 关键字指定类名来生成类的实例。但是,有的时候,我们也会在不指定类名的前提下生成实例,例如像图形编辑器中拖动现有的模型工具制作图形的实例,这种是非常典型的生成实例的过程太过复杂,很难根据类来生成实例场景,因此需要根据现有的实例来生成新的实例。

像这样根据实例来生成新的实例的模式,我们称之为 原型模式

在软件开发过程中,我们经常会遇到需要创建多个相同或者相似对象的情况,因此 原型模式 的使用频率还是很高的。

如何理解原型模式

在原型模式中涉及 Prototype、 ConcretePrototype、 Client 三个角色。

Prototype - 抽象原型类

Prototype,即抽象原型类。声明一个 clone 自己的方法,是所有具体原型类的公共父类,可以是抽象类或者是接口,甚至还可以是具体实现类。

public interface Prototype {
    public Prototype clone();
}

ConcretePrototype - 具体原型类

ConcretePrototype, 即具体原型类。实现 clone 自己的实例并生成新的实例的方法。

public class ConcretePrototype implements Prototype{

    private String attr; // 成员属性

    /**
     * 克隆方法
     */
    @Override
    public ConcretePrototype clone() {
        // 创建新对象
        ConcretePrototype prototype = new ConcretePrototype(); 
        prototype.setAttr(this.attr);
        return prototype;
    }

    @Override
    public String toString() {
        return "ConcretePrototype[attr="+this.attr+"]";
    }

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

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

Client - 客户类

负责使用复制实例的方法生成新的实例。客户类基于接口编程,用户可以根据需要选择具体原型类,系统具有较好的可扩展性。

public class Client {
    public static void main(String[] args) {
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAttr("梁桂钊");

        ConcretePrototype prototype2 = prototype.clone();

        System.out.println(prototype.toString());
        System.out.println(prototype2.toString());
    }
}

简要定义

使用原型实例指定创建对象的种类,并且拷贝这个原型来创建新的对象。

Java 的原型模式

Java 中使用原型模式很简单, 它为我们提供了复制实例的 clone() 方法。

实际上,所有的 Java 类都继承自 java.lang.Object。Object 类提供一个 clone() 方法,因此,在 Java 中可以直接使用 Object 提供的 clone() 方法来实现对象的克隆。

值得注意的是,被复制对象的类必须实现 java.lang.Cloneable 接口,如果没有实现 java.lang.Cloneable 接口的实例调用了 clone() 方法,会在运行时抛出 CloneNotSupportedException 异常。

Cloneable 是一个标记接口,在 Cloneable 接口中并没有声明任何方法,它只是被用来标记可以使用 clone() 方法进行复制。

public interface Cloneable {
}

案例分析

现在,我有一个日报系统的需求,希望复制原先的日报内容进行快速创建,然后可以进行修改后保存。

下面,我们通过 Java 的 clone()方法来实现对象的克隆。

DailyModel 就是具体原型类,复制实现 clone() 方法。

public class DailyModel implements Cloneable{

    private String author;
    private String content;

    @Override
    public DailyModel clone(){
        try {
            DailyModel dailyModel = (DailyModel) super.clone();
            return dailyModel;
        } catch (CloneNotSupportedException e) {
            System.out.println("不支持复制!");
            return null;
        }
    }

    @Override
    public String toString() {
        StringBuffer content = new StringBuffer();
        content.append("Daily[");
        content.append("author = ").append(this.author).append(", ");
        content.append("content = ").append(this.content).append("]");
        return content.toString();
    }

    // Getters and Setters omitted for brevity
}

客户类负责使用复制实例的方法生成新的实例。

public class Client {

    public static void main(String[] args) {
        DailyModel dailyModel = new DailyModel();
        dailyModel.setAuthor("梁桂钊");
        dailyModel.setContent("2017-01-22 【今日工作要点】");

        DailyModel dailyModel2 = dailyModel.clone();
        dailyModel2.setContent("2017-01-23 【今日工作要点】");

        System.out.println(dailyModel.toString());
        System.out.println(dailyModel2.toString());
    }
}

思维发散

原型模式 vs 根据类创建实例

既然要创建新的实例,为什么不直接使用 new XXX(),而要设计出一个原型模式进行实例的复制呢?

有的时候,我们也会在不指定类名的前提下生成实例,例如像图形编辑器中拖动现有的模型工具制作图形的实例,这种是非常典型的生成实例的过程太过复杂,很难根据类来生成实例场景,因此需要根据现有的实例来生成新的实例。

还比如,类初始化需要消化非常多的资源,我们也可以考虑使用原型模式。因为,原型模式是在内存进行二进制流的拷贝,要比直接 new 一个对象性能好很多。

原型模式创建的对象是全新的对象吗

原型模式通过 clone() 方法创建的对象是全新的对象,它在内存中拥有新的地址。

浅克隆 vs 深克隆

clone() 方法使用的是浅克隆。浅克隆对于要克隆的对象, 会复制其基本数据类型和 String 类型的属性的值给新的对象. 而对于非基本数据类型的属性,例如数组、集合, 仅仅复制一份引用给新产生的对象, 即新产生的对象和原始对象中的非基本数据类型的属性都指向的是同一个对象。

此外,还存在深克隆。深克隆对于要克隆的对象, 对于非基本数据类型的属性,例如数组、集合支持复制。换句话说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

浅克隆和深克隆的主要区别在于,是否支持引用类型的成员变量的复制。

在 Java 中,如果需要实现深克隆,可以通过序列化等方式来实现。

@SuppressWarnings("serial")
public class AttachmentModel implements Serializable{

    private String name;

    @Override
    public String toString() {
        StringBuffer content = new StringBuffer();
        content.append("Attachment[");
        content.append("name = ").append(this.name).append("] ");
        return content.toString();
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
@SuppressWarnings("serial")
public class DailyModel implements Serializable {

    private String author;
    private String content;
    private AttachmentModel attachment;

    /**
     * 使用序列化技术实现深克隆
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws OptionalDataException
     */
    public DailyModel deepClone() throws Exception{
        //将对象写入流中
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bao);
        oos.writeObject(this);

        //将对象从流中取出
        ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (DailyModel)ois.readObject();
    }

    // Getters and Setters omitted for brevity
}
public class Client {

    public static void main(String[] args) {
        DailyModel dailyModel = new DailyModel();         
        DailyModel dailyModel2 = null;
        // 创建附件对象
        AttachmentModel attachment = new AttachmentModel(); 
        attachment.setName("附件");
        dailyModel.setAttachment(attachment); 

        try {
            // 调用深克隆方法创建克隆对象
            dailyModel2 = dailyModel.deepClone(); 
        } catch (Exception e) {
            System.err.println("克隆失败!");
        }

        System.out.println("日报是否相同? " + (dailyModel == dailyModel2));
        System.out.println("附件是否相同? " + (dailyModel.getAttachment() == dailyModel2.getAttachment()));
    }
}

总结

原型模式的目的在于,根据实例来生成新的实例,我们可以很方便的快速的创建实例。

在 Java 中使用原型模式很简单, 被复制对象的类必须实现 java.lang.Cloneable 接口,并重写 clone() 方法。

使用原型模式的时候,尤其需要注意浅克隆和深克隆问题。在 Java 中,如果需要实现深克隆,可以通过序列化等方式来实现。

参考文章

(文)对象的克隆——原型模式(一)

(书)「图解设计模式」(结城浩)

源代码

相关示例完整代码: design-pattern-action

(完)

微信公众号

文章目录

  1. 1. 如何理解原型模式
    1. 1.1. Prototype - 抽象原型类
    2. 1.2. ConcretePrototype - 具体原型类
    3. 1.3. Client - 客户类
  2. 2. 简要定义
  3. 3. Java 的原型模式
  4. 4. 案例分析
  5. 5. 思维发散
    1. 5.1. 原型模式 vs 根据类创建实例
    2. 5.2. 原型模式创建的对象是全新的对象吗
    3. 5.3. 浅克隆 vs 深克隆
  6. 6. 总结
  7. 7. 参考文章
  8. 8. 源代码