详解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类提供了工厂方法,用于返回一个克隆对象。

相关文章
|
2月前
|
设计模式 Java Spring
Java 设计模式之责任链模式:优雅处理请求的艺术
责任链模式通过构建处理者链,使请求沿链传递直至被处理,实现发送者与接收者的解耦。适用于审批流程、日志处理等多级处理场景,提升系统灵活性与可扩展性。
305 2
|
2月前
|
设计模式 网络协议 数据可视化
Java 设计模式之状态模式:让对象的行为随状态优雅变化
状态模式通过封装对象的状态,使行为随状态变化而改变。以订单为例,将待支付、已支付等状态独立成类,消除冗长条件判断,提升代码可维护性与扩展性,适用于状态多、转换复杂的场景。
337 0
|
4月前
|
设计模式 缓存 Java
Java设计模式(二):观察者模式与装饰器模式
本文深入讲解观察者模式与装饰器模式的核心概念及实现方式,涵盖从基础理论到实战应用的全面内容。观察者模式实现对象间松耦合通信,适用于事件通知机制;装饰器模式通过组合方式动态扩展对象功能,避免子类爆炸。文章通过Java示例展示两者在GUI、IO流、Web中间件等场景的应用,并提供常见陷阱与面试高频问题解析,助你写出灵活、可维护的代码。
|
2月前
|
设计模式 算法 搜索推荐
Java 设计模式之策略模式:灵活切换算法的艺术
策略模式通过封装不同算法并实现灵活切换,将算法与使用解耦。以支付为例,微信、支付宝等支付方式作为独立策略,购物车根据选择调用对应支付逻辑,提升代码可维护性与扩展性,避免冗长条件判断,符合开闭原则。
382 35
|
2月前
|
设计模式 消息中间件 传感器
Java 设计模式之观察者模式:构建松耦合的事件响应系统
观察者模式是Java中常用的行为型设计模式,用于构建松耦合的事件响应系统。当一个对象状态改变时,所有依赖它的观察者将自动收到通知并更新。该模式通过抽象耦合实现发布-订阅机制,广泛应用于GUI事件处理、消息通知、数据监控等场景,具有良好的可扩展性和维护性。
294 8
|
4月前
|
设计模式 安全 Java
Java设计模式(一):单例模式与工厂模式
本文详解单例模式与工厂模式的核心实现及应用,涵盖饿汉式、懒汉式、双重检查锁、工厂方法、抽象工厂等设计模式,并结合数据库连接池与支付系统实战案例,助你掌握设计模式精髓,提升代码专业性与可维护性。
|
4月前
|
设计模式 XML 安全
Java枚举(Enum)与设计模式应用
Java枚举不仅是类型安全的常量,还具备面向对象能力,可添加属性与方法,实现接口。通过枚举能优雅实现单例、策略、状态等设计模式,具备线程安全、序列化安全等特性,是编写高效、安全代码的利器。
|
设计模式 缓存 安全
Java设计模式的单例模式应用场景
Java设计模式的单例模式应用场景
288 4
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
208 4
|
设计模式 安全 Java
Java 编程中的设计模式:单例模式的深度解析
【9月更文挑战第22天】在Java的世界里,单例模式就像是一位老练的舞者,轻盈地穿梭在对象创建的舞台上。它确保了一个类仅有一个实例,并提供全局访问点。这不仅仅是代码优雅的体现,更是资源管理的高手。我们将一起探索单例模式的奥秘,从基础实现到高级应用,再到它与现代Java版本的舞蹈,让我们揭开单例模式的面纱,一探究竟。
117 11