04.原型模式设计思想

简介: 本文详细介绍了原型模式的设计思想,包括其定义、应用场景、实现原理及优缺点。通过邮件复制的例子,阐述了原型模式如何通过克隆现有对象来创建新对象,从而提高性能和减少代码复杂度。文章还对比了原型模式与工厂模式的区别,并讨论了深克隆和浅克隆的实现方式。最后,总结了原型模式在特定场景下的应用价值和局限性。

04.原型模式设计思想

目录介绍

  • 01.原型模式介绍
    • 1.1 原型模式由来
    • 1.2 原型模式定义
    • 1.3 原型模式场景
    • 1.4 原型模式思考
  • 02.原型模式原理与实现
    • 2.1 罗列一个场景
    • 2.2 用例子理解原型
    • 2.3 案例演变分析
    • 2.4 原型模式基本实现
  • 03.原型模式分析
    • 3.1 原型模式VS工厂模式
    • 3.2 原型模式VS深拷贝
  • 04.原型模式应用解析
    • 4.1 使用clone方法
    • 4.2 实现接口Cloneable
    • 4.3 深克隆和浅克隆
  • 05.原型模式总结
    • 5.1 优缺点分析
    • 5.2 有哪些弊端
    • 5.3 应用环境说明
  • 06.原型模式拓展应用
    • 6.1 模式扩展
    • 6.2 原型模式总结
    • 6.3 更多内容推荐

01.原型模式介绍

1.0 AI生成博客摘要

1.1 原型模式由来

我们来看一个例子——邮件。由于邮件对象包含的内容较多(如发送者、接收者、标题、内容、日期、附件等),某系统中现需要提供一个邮件复制功能,对于已经创建好的邮件对象,可以通过复制的方式创建一个新的邮件对象,如果需要改变某部分内容,无须修改原始的邮件对象,只需要修改复制后得到的邮件对象即可。

如果在每次复制的时候重新写一遍这个代码,可想而知是多么的复杂,所以这个时候需要封装一个基类,把这些赋值的过程全部封装好,由此可见,原型模式利用的无非就是面向对象编程的封装、继承特点。

1.2 原型模式定义

原型模式是通过给出一个原型对象来指明所创建的对象的类型,然后使用自身实现的克隆接口来复制这个原型对象,该模式就是用这种方式来创建出更多同类型的对象。

使用这种方式创建新的对象的话,就无需再通过 new 实例化来创建对象了。这是因为 Object 类的 clone 方法是一个本地方法,它可以直接操作内存中的二进制流,所以性能相对 new 实例化来说,更佳。

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

1.3 原型模式场景

原型模式在以下情况下特别有用:

  1. 当对象的创建过程比较复杂或耗时时,可以通过复制现有对象来提高性能。
  2. 当需要创建多个相似对象,但又不希望与它们的具体类耦合时,可以使用原型模式。

原型模式的应用场景,以及它的两种实现方式:深拷贝和浅拷贝。虽然原型模式的原理和代码实现非常简单。

1.4 原型模式思考

提供了一种灵活的方式来创建对象,通过复制现有对象来创建新对象,从而避免了昂贵的对象创建过程。该模式的核心思想是通过复制现有对象来创建新对象,而不是通过实例化类来创建。

02.原型模式原理与实现

2.1 罗列一个场景

邮件例子代码来说,由于展示,我们封装邮件中的标题、内容和附件三个字段,其中附件是一个类,包含了名字、类型和文件大小。

首先采用默认的clone方式(浅克隆),即复制后的邮件中的附件与原邮件中的附件是同一对象;然后实现深克隆,即复制后的邮件中的附件与原邮件中的附件不是同一对象。

2.2 用例子理解原型

1.基类——为了方便子类调用clone不需要捕获异常

public class Prototype implements Cloneable {
   
    public Prototype cloneMe() {
   
        Prototype prototype = null;
        try {
   
            prototype = (Prototype) this.clone();
        } catch (CloneNotSupportedException exception) {
   
        }
        return prototype;
    }
}

2.附件子类

public class Attachment extends Prototype {
   

    // 附件名字
    private String name;
    // 附件文档类型
    private String type;
    // 附件大小
    private long length;

    public Attachment(String name, String type, long length) {
   
        super();
        this.name = name;
        this.type = type;
        this.length = length;
    }

    public void download() {
   
        System.out.println("下载了附件:" + name);
    }

    public String display() {
   
        return "Attachment [name=" + name + ", type=" + type + ", length=" + length + "]";
    }

    @Override
    public boolean equals(Object obj) {
   
        Attachment a = (Attachment) obj;
        if(a.name.equals(name) && a.type.equals(type) && a.length == length) {
   
            return true;
        }
        return false;
    }
}

3.邮件子类

public class Email extends Prototype {
   

    // 标题
    private String title;
    // 内容
    private String content;
    // 附件
    private Attachment attachment;

    public Email(String title, String content, Attachment attachment) {
   
        super();
        this.title = title;
        this.content = content;
        this.attachment = attachment;
    }

    public String display() {
   
        return "Email [title=" + title + ", content=" + content + ", attachment=" + attachment.display();
    }

    public Attachment getAttachment() {
   
        return attachment;
    }

    @Override
    public boolean equals(Object obj) {
   
        Email e = (Email) obj;
        if(e.title.equals(title) && e.content.equals(content) && e.attachment.equals(attachment)) {
   
            return true;
        }
        return false;
    }
}

4.客户端类

private void test() {
   
    Email email = new Email("邮件标题", "邮件内容,哈哈哈..", new Attachment("附件标题", "文档", 45987));
    System.out.println(email.display());
    Email copyEmail = (Email) email.cloneMe();
    System.out.println("邮件复制状态:" + (email != copyEmail && email.equals(copyEmail) ? "成功" : "失败") );
    Attachment attachment = email.getAttachment();
    Attachment copyAttachment = copyEmail.getAttachment();
    if(attachment.equals(copyAttachment)) {
   
        System.out.println("邮件附件内容一致");
    }
    if(attachment == copyAttachment) {
   
        System.out.println("邮件附件未复制");
    } else {
   
        System.out.println("邮件附件已复制");
    }
}

最后运行结果如下所示:

Email [title=邮件标题, content=邮件内容,哈哈哈.., attachment=Attachment [name=附件标题, type=文档, length=45987]
邮件复制状态:成功
邮件附件内容一致
邮件附件未复制

可以看到,由于重写了子类的equals方法,所以当每个字段的内容相等时,认为该类的内容相同,但是进行复制后,地址是不同的,最后一个判断是用于判断邮件类调用cloneMe方法后,当中的附件对象是否也进行了复制,若地址相同,所以未进行复制,此时是浅克隆。

2.3 案例演变分析

那么如何进行深克隆呢?很简单,只需要重写邮件对象的cloneMe方法,先调用父类的克隆方法获得自身拷贝的对象,然后自身的附件对象也调用cloneMe方法,将得到的对象再赋值给拷贝后的邮件对象。

public class EmailNew extends Prototype {
   

    // 标题
    private String title;
    // 内容
    private String content;
    // 附件
    private Attachment attachment;

    public EmailNew(String title, String content, Attachment attachment) {
   
        super();
        this.title = title;
        this.content = content;
        this.attachment = attachment;
    }

    public String display() {
   
        return "EmailNew [title=" + title + ", content=" + content + ", attachment=" + attachment.display();
    }

    public Attachment getAttachment() {
   
        return attachment;
    }

    @Override
    public boolean equals(Object obj) {
   
        EmailNew e = (EmailNew) obj;
        if(e.title.equals(title) && e.content.equals(content) && e.attachment.equals(attachment)) {
   
            return true;
        }
        return false;
    }

    @Override
    public EmailNew cloneMe() {
   
        EmailNew e = (EmailNew) super.cloneMe();
        e.attachment = (Attachment) attachment.cloneMe();
        return e;
    }
}

最后运行结果如下所示:

EmailNew [title=邮件标题, content=邮件内容,哈哈哈.., attachment=Attachment [name=附件标题, type=文档, length=45987]
邮件复制状态:成功
邮件附件内容一致
邮件附件已复制

2.4 原型模式基本实现

原型模式包含如下角色:

  1. Prototype:抽象原型类。声明一个克隆自己的接口。
  2. ConcretePrototype:具体原型类。实现原型类的 clone() 方法,实现克隆自己的操作。它是可被复制的对象,可以有多个。
  3. Client:客户类。

要实现一个原型类,需要具备三个条件:

  1. 实现 Cloneable 接口:Cloneable 接口与序列化接口的作用类似,它只是告诉虚拟机可以安全地在实现了这个接口的类上使用 clone 方法。在 JVM 中,只有实现了 Cloneable 接口的类才可以被拷贝,否则会抛出 CloneNotSupportedException 异常。
  2. 重写 Object 类中的 clone 方法:在 Java 中,所有类的父类都是 Object 类,而 Object 类中有一个 clone 方法,作用是返回对象的一个拷贝。
  3. 在重写的 clone 方法中调用 super.clone():默认情况下,类不具备复制对象的能力,需要调用 super.clone() 来实现。

现在通过一个简单的例子来实现一个原型模式:

//实现Cloneable 接口的原型抽象类Prototype 
class Prototype implements Cloneable {
   
    //重写clone方法
    public Prototype clone(){
   
        Prototype prototype = null;
        try{
   
            prototype = (Prototype)super.clone();
        }catch(CloneNotSupportedException e){
   
            e.printStackTrace();
        }
        return prototype;
    }
}
//实现原型类
class ConcretePrototype extends Prototype{
   
    public void show(){
   
        System.out.println("原型模式实现类");
    }
}

public class Client {
   
    public static void main(String[] args){
   
        ConcretePrototype cp = new ConcretePrototype();
        for(int i=0; i< 10; i++){
   
            ConcretePrototype clonecp = (ConcretePrototype)cp.clone();
            clonecp.show();
        }
    }
}

03.原型模式分析

3.1 原型模式VS工厂模式

目的和使用场景区别:

  1. 原型模式的主要目的是通过复制现有对象来创建新对象,而不是通过实例化类来创建。它适用于需要创建多个相似对象,但又不希望与具体类耦合的情况。
  2. 工厂模式的主要目的是封装对象的创建过程,通过一个工厂类来统一创建对象。它适用于需要根据不同的条件或参数创建不同类型的对象的情况。

创建方式区别:

  1. 原型模式通过复制现有对象来创建新对象,可以是浅克隆(只复制基本类型属性)或深克隆(复制所有属性,包括引用类型属性)。
  2. 工厂模式通过一个工厂类来创建对象,根据不同的条件或参数调用不同的工厂方法来创建不同类型的对象。

灵活性区别:

  1. 原型模式在运行时动态确定对象的类型,可以根据需要克隆不同类型的对象。
  2. 工厂模式在编译时确定对象的类型,需要在工厂类中定义不同的工厂方法来创建不同类型的对象。

关注点不同:

  1. 原型模式关注对象的复制和克隆过程,通过复制现有对象来创建新对象。
  2. 工厂模式关注对象的创建过程,通过工厂类来封装对象的创建逻辑。

3.2 原型模式VS深拷贝

原型模式(Prototype Pattern)和深拷贝(Deep Copy)是两个概念,它们在对象复制和克隆方面有一些区别。

目的和使用场景:

  1. 原型模式的主要目的是通过复制现有对象来创建新对象,而不是通过实例化类来创建。它适用于需要创建多个相似对象,但又不希望与具体类耦合的情况。
  2. 深拷贝的主要目的是创建一个新对象,并将原始对象的所有属性都复制到新对象中,包括引用类型的属性。它适用于需要完全独立的对象副本,而不是共享引用的情况。

复制方式:

  1. 原型模式通过复制现有对象来创建新对象,可以是浅克隆(只复制基本类型属性)或深克隆(复制所有属性,包括引用类型属性)。
  2. 深拷贝是一种复制方式,它会递归地复制对象的所有属性,包括引用类型属性,确保新对象和原始对象完全独立。

实现方式的差异:

  1. 原型模式可以通过实现Cloneable接口并重写clone()方法来实现对象的复制和克隆。
  2. 深拷贝可以通过自定义的复制方法或使用序列化和反序列化来实现。

04.原型模式应用解析

4.1 使用clone方法

在原型模式结构中定义了一个抽象原型类,所有的Java类都继承自java.lang.Object,而Object类提供一个clone()方法,可以将一个Java对象复制一份。

因此在Java中,可以直接使用Object提供的clone()方法来实现对象的克隆,Java语言中的原型模式实现很简单。

4.2 实现接口Cloneable

能够实现克隆的Java类必须实现一个标识接口Cloneable,表示这个Java类支持复制。

如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常。

因此在Java中,Object类和Cloneable接口共同充当着抽象原型类,无需再次定义,不过由于调用clone方法需要捕获异常,每次调用的时候做处理比较麻烦,所以可以抽象出一个基类,来捕获,那么在具体子类中就不需要再次捕获(仅限于子类不需要处理异常的时候)。

4.3 深克隆和浅克隆

通常情况下,一个类包含一些成员对象,在使用原型模式克隆对象时,根据其成员对象是否也克隆,原型模式可以分为两种形式:深克隆和浅克隆。

  1. 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
  2. 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

Java中的clone方法默认是浅克隆。

关于数据深克隆和浅克隆,我这边有一篇文章专门详细介绍其案例和原理。具体可以看:各种拷贝数据比较

05.原型模式总结

5.1 优缺点分析

优点

  1. 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率。
  2. 可以动态增加或减少产品类。
  3. 原型模式提供了简化的创建结构。
  4. 可以使用深克隆的方式保存对象的状态。

5.2 有哪些弊端

缺点

  1. 需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。
  2. 在实现深克隆时需要编写较为复杂的代码。要修改clone方法的实现,有一定复杂。

5.3 应用环境说明

在以下情况下可以使用原型模式:

  1. 创建新对象成本较大,新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其属性稍作修改。
  2. 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不大的时候,也可以使用原型模式配合备忘录模式来应用。相反,如果对象的状态变化很大,或者对象占用的内存很大,那么采用状态模式会比原型模式更好。
  3. 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。

06.原型模式拓展应用

6.1 模式扩展

6.2 原型模式总结

1.什么是原型模式?

如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式,来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式,简称原型模式。

2.原型模式的两种实现方法

原型模式有两种实现方法,深拷贝和浅拷贝。浅拷贝只会复制对象中基本数据类型数据和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象……而深拷贝得到的是一份完完全全独立的对象。所以,深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间。

如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的,但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,也就变得复杂多了。

6.3 更多内容推荐

模块 描述 备注
GitHub 多个YC系列开源项目,包含Android组件库,以及多个案例 GitHub
博客汇总 汇聚Java,Android,C/C++,网络协议,算法,编程总结等 YCBlogs
设计模式 六大设计原则,23种设计模式,设计模式案例,面向对象思想 设计模式
Java进阶 数据设计和原理,面向对象核心思想,IO,异常,线程和并发,JVM Java高级
网络协议 网络实际案例,网络原理和分层,Https,网络请求,故障排查 网络协议
计算机原理 计算机组成结构,框架,存储器,CPU设计,内存设计,指令编程原理,异常处理机制,IO操作和原理 计算机基础
学习C编程 C语言入门级别系统全面的学习教程,学习三到四个综合案例 C编程
C++编程 C++语言入门级别系统全面的教学教程,并发编程,核心原理 C++编程
算法实践 专栏,数组,链表,栈,队列,树,哈希,递归,查找,排序等 Leetcode
Android 基础入门,开源库解读,性能优化,Framework,方案设计 Android

23种设计模式

23种设计模式 & 描述 & 核心作用 包括
创建型模式
提供创建对象用例。能够将软件模块中对象的创建和对象的使用分离
工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
结构型模式
关注类和对象的组合。描述如何将类或者对象结合在一起形成更大的结构
适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
过滤器模式(Filter、Criteria Pattern)
组合模式(Composite Pattern)
装饰器模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
行为型模式
特别关注对象之间的通信。主要解决的就是“类或对象之间的交互”问题
责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
空对象模式(Null Object Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
访问者模式(Visitor Pattern)
目录
相关文章
|
6天前
|
编解码 Java 程序员
写代码还有专业的编程显示器?
写代码已经十个年头了, 一直都是习惯直接用一台Mac电脑写代码 偶尔接一个显示器, 但是可能因为公司配的显示器不怎么样, 还要接转接头 搞得桌面杂乱无章,分辨率也低,感觉屏幕还是Mac自带的看着舒服
|
8天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1563 10
|
1月前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
11天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
738 27
|
8天前
|
存储 SQL 关系型数据库
彻底搞懂InnoDB的MVCC多版本并发控制
本文详细介绍了InnoDB存储引擎中的两种并发控制方法:MVCC(多版本并发控制)和LBCC(基于锁的并发控制)。MVCC通过记录版本信息和使用快照读取机制,实现了高并发下的读写操作,而LBCC则通过加锁机制控制并发访问。文章深入探讨了MVCC的工作原理,包括插入、删除、修改流程及查询过程中的快照读取机制。通过多个案例演示了不同隔离级别下MVCC的具体表现,并解释了事务ID的分配和管理方式。最后,对比了四种隔离级别的性能特点,帮助读者理解如何根据具体需求选择合适的隔离级别以优化数据库性能。
225 3
|
15天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
787 5
|
2天前
|
Python
【10月更文挑战第10天】「Mac上学Python 19」小学奥数篇5 - 圆和矩形的面积计算
本篇将通过 Python 和 Cangjie 双语解决简单的几何问题:计算圆的面积和矩形的面积。通过这道题,学生将掌握如何使用公式解决几何问题,并学会用编程实现数学公式。
108 60
|
1天前
|
人工智能
云端问道12期-构建基于Elasticsearch的企业级AI搜索应用陪跑班获奖名单公布啦!
云端问道12期-构建基于Elasticsearch的企业级AI搜索应用陪跑班获奖名单公布啦!
115 1
|
3天前
|
Java 开发者
【编程进阶知识】《Java 文件复制魔法:FileReader/FileWriter 的奇妙之旅》
本文深入探讨了如何使用 Java 中的 FileReader 和 FileWriter 进行文件复制操作,包括按字符和字符数组复制。通过详细讲解、代码示例和流程图,帮助读者掌握这一重要技能,提升 Java 编程能力。适合初学者和进阶开发者阅读。
104 61
|
14天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】