详解Java设计模式之原型模式(Prototype Pattern)

简介: 详解Java设计模式之原型模式(Prototype Pattern)

模式简介


原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。


在软件系统中,有时候需要多次创建某一类型的对象,为了简化创建过程,可以只创建一个对象,然后再通过克隆的方式复制出多个相同的对象,这就是原型模式的设计思想。


原型模式的基本工作原理是通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象复制原型自己来实现创建过程。


举例说明 👇


《西游记》中孙悟空拔毛变小猴的故事几乎人人皆知,孙悟空可以用猴毛根据自己的形象,复制出很多跟自己长得一模一样的身外身来。


22497cfa7912418cae07bdbf45b541e5.png

孙悟空这种复制出多个身外身的方式在面向对象设计领域里称为原型(Prototype)模式。在面向对象系统中,使用原型模式来复制一个对象自身,从而克隆出多个与原型对象一模一样的对象。


在软件系统中,有些对象的创建过程较为复杂,而且有时候需要频繁创建。原型模式通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象,这就是原型模式的意图所在。


模式结构

原型模式包含如下角色 👇

  1. Prototype(抽象原型类)

抽象原型类是定义具有克隆自己的方法的接口,是所有具体原型类的公共父类,可以是抽象类,也可以是接口。

  1. ConcretePrototype(具体原型类)

具体原型类实现具体的克隆方法,在克隆方法中返回自己的一个克隆对象。

  1. Client(客户类)

客户类让一个原型克隆自身,从而创建一个新的对象。在客户类中只需要直接实例化或通过工厂方法等方式创建一个对象,再通过调用该对象的克隆方法复制得到多个相同的对象。


模式案例

原型模式的克隆分为浅克隆和深克隆。

  • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。


cfca1cb908884a14aecabea71a6b1191.png


深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。


e3577561fd484eeab701efdcfe182174.png

下面通过两个分别实现浅克隆和深克隆的实例来进一步学习并理解原型模式。

案例1:原型模式实例之邮件复制(浅克隆)


18238b13154d4254912eff059474ddd6.png

9519fd7b59194844940bb4755a09637b.png

  1. 抽象原型类Object(无需创建)

Object作为抽象原型类,在Java语言中,所有的类都是Object的子类,在Object 中提供了克隆方法clone(),用于创建一个原型对象,其 clone()方法具体实现由JVM完成,用户在使用时无须关心。

3bdd7a0a341e4524b1a507073e210593.png

  1. 附件类 Attachment

为了更好地说明浅克隆和深克隆的区别,在本实例中引入了附件类Attachment,邮件类Email与附件类是组合关联关系,在邮件类中定义一个附件类对象,作为其成员对象。

package prototype;
/**
 * @author mengzhichao
 * @create 2021-11-10-22:20
 */
public class Attachment {
    public void download(){
        System.out.println("下载附件");
    }
}
  1. 具体原型类Email(邮件类)


Email类是具体原型类,也是Object类的子类。在Java语言中,只有实现了Cloneable接口的类才能够使用clone()方法来进行复制,因此Email类实现了Cloneable接口。在Email类中覆盖了Object的clone()方法,通过直接或者间接调用Object的clone()方法返回一个克隆的原型对象。在Email类中定义了一个成员对象attachment,其类型为Attachment。

package prototype;
/**
 * @author mengzhichao
 * @create 2021-11-10-22:18
 */
public class Email implements Cloneable {
    private Attachment attachment=null;
    public Email(){
        this.attachment=new Attachment();
    }
    @Override
    public Object clone(){
        Email clone =null;
        try {
            clone=(Email) super.clone();
        }catch (CloneNotSupportedException e){
            System.out.println("Clone failure!");
        }
        return clone;
    }
    public Attachment getAttachment(){
        return this.attachment;
    }
    public void display(){
        System.out.println("查看邮件");
    }
}
  1. 客户端测试类Client

在Client 客户端测试类中,比较原型对象和复制对象是否一致﹐并比较其成员对象attachment的引用是否一致。

package prototype;
/**
 * @author mengzhichao
 * @create 2021-11-10-22:29
 */
public class Client {
    public static void main(String[] args) {
        Email email,copyEmail;
        email=new Email();
        copyEmail= (Email) email.clone();
        System.out.println("email == copyEmail?");
        System.out.println(email == copyEmail);
        System.out.println("email.getAttachment() == copyEmail.getAttachment()?");
        System.out.println(email.getAttachment() == copyEmail.getAttachment());
    }
}

结果如下


903bb459492b406e80b2d360331d8087.png


通过结果可以看出,表达式(email==copyEmail)结果为false,即通过复制得到的对象与原型对象的引用不一致,也就是说明在内存中存在两个完全不同的对象,一个是原型对象,一个是克隆生成的对象。


但是表达式(email.getAttachment( ) == copyEmail.getAttachment())结果为true,两个对象的成员对象是同一个,说明虽然对象本身复制了一份,但其成员对象在内存中没有复制,原型对象和克隆对象维持了对相同的成员对象的引用。


案例2:原型模式实例之邮件复制(深克隆)

使用深克隆实现邮件复制,即复制邮件的同时复制附件。

dc084058c1a64e999fdcdc940570c64f.png

4afa78e5d3ea489eb0b74469e2a3904f.png


  1. 附件类 Attachment

作为Email类的成员对象,在深克隆中,Attachment类型的对象也将被写入流中,因此Attachment类也需要实现Serializable接口。

package prototype2;
import java.io.Serializable;
/**
 * @author mengzhichao
 * @create 2021-11-10-22:20
 */
public class Attachment implements Serializable {
    public void download(){
        System.out.println("下载附件");
    }
}
  1. 具体原型类Email(邮件类)

Email作为具体原型类,由于实现的是深克隆,无须使用Object的 clone()方法,因此无须实现Cloneable接口;可以通过序列化的方式实现深克隆(代码中粗体部分),由于要将Email类型的对象写入流中,因此Email类需要实现Serializable接口。

package prototype2;
import java.io.*;
/**
 * @author mengzhichao
 * @create 2021-11-10-22:18
 */
public class Email implements Serializable {
    private Attachment attachment=null;
    public Email(){
        this.attachment=new Attachment();
    }
    public Object 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 (ois.readObject());
    }
    public Attachment getAttachment(){
        return this.attachment;
    }
    public void display(){
        System.out.println("查看邮件");
    }
}
  1. 客户端测试类Client

在Client客户端测试类中,我们仍然比较深克隆后原型对象和拷贝对象是否一致,并比较其成员对象attachment的引用是否一致。

package prototype2;
/**
 * @author mengzhichao
 * @create 2021-11-11-22:28
 */
public class Client {
    public static void main(String[] args) {
        Email email,copyEmail = null;
        email=new Email();
        try {
            copyEmail = (Email) email.deepClone();
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("email==copyEmail?");
        System.out.println(email==copyEmail);
        System.out.println("email.getAttachment()==copyEmail.getAttachment()?");
        System.out.println(email.getAttachment()==copyEmail.getAttachment());
    }
}


401d085cae4e4da697a14620a420e4f4.png

通过结果可以看出,表达式( email==copyEmail)结果为false,即通过复制得到的对象与原型对象的引用不一致,表达式( email.getAttachment()==copyEmail.getAttachment())结果也为false,原型对象与克隆对象对成员对象的引用不相同,说明其成员对象也复制了一份。


模式总结


优缺点:原型模式最大的优点在于可以快速创建很多相同或相似的对象,简化对象的创建过程,还可以保存对象的一些中间状态。

其缺点在于需要为每一个类配备一个克隆方法,因此对已有类进行改造比较麻烦﹐需要修改其源代码,并且在实现深克隆时需要编写较为复杂的代码。


适用于:创建新对象成本较大,新的对象可以通过原型模式对已有对象进行复制来获得。系统要保存对象的状态,而对象的状态变化很小,需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便


拓展内容

带原型管理器的原型模式

  • 原型模式的一种改进形式是带原型管理器的原型模式。

ebda19f52afc473d846fb5cc0e7f1e52.png

原型管理器(Prototype Manager)角色创建具体原型类的对象,并记录每一个被创建的对象。原型管理器的作用与工厂相似,其中定义了一个集合用于存储原型对象,如果需要某个对象的一个克隆,可以通过复制集合中对应的原型对象来获得。在原型管理器中针对抽象原型类进行编程,以便扩展。


下面使用代码模拟演示一个颜色原型管理器的实现过程。


1.抽象原型类 MyColor


package prototypemanager;
/**
 * @author mengzhichao
 * @create 2021-11-14-10:30
 */
public interface MyColor extends Cloneable {
    public Object clone();
    public void display();
}
  1. 具体原型类 Red
package prototypemanager;
/**
 * @author mengzhichao
 * @create 2021-11-14-10:32
 */
public class Red implements MyColor {
    @Override
    public Object clone() {
        Red r=null;
        try {
            r = (Red) super.clone();
        }catch (Exception e){
        }
        return r;
    }
    @Override
    public void display() {
        System.out.println("This is Red");
    }
}
  1. 具体原型类 Blue
package prototypemanager;
/**
 * @author mengzhichao
 * @create 2021-11-14-10:36
 */
public class Blue implements MyColor {
    @Override
    public Object clone() {
        Blue b=null;
        try {
            b = (Blue) super.clone();
        }catch (Exception e){
        }
        return b;
    }
    @Override
    public void display() {
        System.out.println("This is Blue");
    }
}
  1. 原型管理器类 PrototypeManager
package prototypemanager;
import java.util.Hashtable;
/**
 * @author mengzhichao
 * @create 2021-11-14-10:37
 */
public class PrototypeManager {
    private Hashtable ht =new Hashtable();
    public PrototypeManager() {
        ht.put("red",new Red());
        ht.put("blue",new Blue());
    }
    public void addColor(String key,MyColor obj){
        ht.put(key,obj);
    }
    public MyColor getColor(String key){
        return (MyColor) ((MyColor)ht.get(key)).clone();
    }
}
  1. 客户端测试类 Client
package prototypemanager;
/**
 * @author mengzhichao
 * @create 2021-11-14-10:47
 */
public class Client {
    public static void main(String[] args) {
        PrototypeManager pm =new PrototypeManager();
        MyColor obj1=pm.getColor("red");
        obj1.display();
        MyColor obj2=pm.getColor("red");
        obj2.display();
        System.out.println(obj1==obj2);
    }
}

运行结果如下

64bfaa0cf7244feca407fd623a65daff.png


  • 在PrototypeManager中定义了一个 Hashtable类型的集合,使用“键值对”来存储原型对象,客户端可以通过Key来获取对应原型对象的克隆对象。PrototypeManager类提供了工厂方法,用于返回一个克隆对象。

相关文章
|
23天前
|
设计模式 Java 程序员
[Java]23种设计模式
本文介绍了设计模式的概念及其七大原则,强调了设计模式在提高代码重用性、可读性、可扩展性和可靠性方面的作用。文章还简要概述了23种设计模式,并提供了进一步学习的资源链接。
36 0
[Java]23种设计模式
|
7天前
|
设计模式 JavaScript Java
Java设计模式:建造者模式详解
建造者模式是一种创建型设计模式,通过将复杂对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。本文详细介绍了建造者模式的原理、背景、应用场景及实际Demo,帮助读者更好地理解和应用这一模式。
|
1月前
|
设计模式 监控 算法
Java设计模式梳理:行为型模式(策略,观察者等)
本文详细介绍了Java设计模式中的行为型模式,包括策略模式、观察者模式、责任链模式、模板方法模式和状态模式。通过具体示例代码,深入浅出地讲解了每种模式的应用场景与实现方式。例如,策略模式通过定义一系列算法让客户端在运行时选择所需算法;观察者模式则让多个观察者对象同时监听某一个主题对象,实现松耦合的消息传递机制。此外,还探讨了这些模式与实际开发中的联系,帮助读者更好地理解和应用设计模式,提升代码质量。
Java设计模式梳理:行为型模式(策略,观察者等)
|
1月前
|
设计模式 Java
Java设计模式
Java设计模式
28 0
|
1月前
|
设计模式 Java
Java设计模式之外观模式
这篇文章详细解释了Java设计模式之外观模式的原理及其应用场景,并通过具体代码示例展示了如何通过外观模式简化子系统的使用。
29 0
|
1月前
|
设计模式 Java
Java设计模式之桥接模式
这篇文章介绍了Java设计模式中的桥接模式,包括桥接模式的目的、实现方式,并通过具体代码示例展示了如何分离抽象与实现,使得两者可以独立变化。
43 0
|
1月前
|
设计模式 Java
Java设计模式之适配器模式
这篇文章详细讲解了Java设计模式中的适配器模式,包括其应用场景、实现方式及代码示例。
42 0
|
16天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
2月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
18天前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###