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.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 原型模式基本实现
原型模式包含如下角色:
- Prototype:抽象原型类。声明一个克隆自己的接口。
- ConcretePrototype:具体原型类。实现原型类的 clone() 方法,实现克隆自己的操作。它是可被复制的对象,可以有多个。
- Client:客户类。
要实现一个原型类,需要具备三个条件:
- 实现 Cloneable 接口:Cloneable 接口与序列化接口的作用类似,它只是告诉虚拟机可以安全地在实现了这个接口的类上使用 clone 方法。在 JVM 中,只有实现了 Cloneable 接口的类才可以被拷贝,否则会抛出 CloneNotSupportedException 异常。
- 重写 Object 类中的 clone 方法:在 Java 中,所有类的父类都是 Object 类,而 Object 类中有一个 clone 方法,作用是返回对象的一个拷贝。
- 在重写的 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工厂模式
目的和使用场景区别:
- 原型模式的主要目的是通过复制现有对象来创建新对象,而不是通过实例化类来创建。它适用于需要创建多个相似对象,但又不希望与具体类耦合的情况。
- 工厂模式的主要目的是封装对象的创建过程,通过一个工厂类来统一创建对象。它适用于需要根据不同的条件或参数创建不同类型的对象的情况。
创建方式区别:
- 原型模式通过复制现有对象来创建新对象,可以是浅克隆(只复制基本类型属性)或深克隆(复制所有属性,包括引用类型属性)。
- 工厂模式通过一个工厂类来创建对象,根据不同的条件或参数调用不同的工厂方法来创建不同类型的对象。
灵活性区别:
- 原型模式在运行时动态确定对象的类型,可以根据需要克隆不同类型的对象。
- 工厂模式在编译时确定对象的类型,需要在工厂类中定义不同的工厂方法来创建不同类型的对象。
关注点不同:
- 原型模式关注对象的复制和克隆过程,通过复制现有对象来创建新对象。
- 工厂模式关注对象的创建过程,通过工厂类来封装对象的创建逻辑。
3.2 原型模式VS深拷贝
原型模式(Prototype Pattern)和深拷贝(Deep Copy)是两个概念,它们在对象复制和克隆方面有一些区别。
目的和使用场景:
- 原型模式的主要目的是通过复制现有对象来创建新对象,而不是通过实例化类来创建。它适用于需要创建多个相似对象,但又不希望与具体类耦合的情况。
- 深拷贝的主要目的是创建一个新对象,并将原始对象的所有属性都复制到新对象中,包括引用类型的属性。它适用于需要完全独立的对象副本,而不是共享引用的情况。
复制方式:
- 原型模式通过复制现有对象来创建新对象,可以是浅克隆(只复制基本类型属性)或深克隆(复制所有属性,包括引用类型属性)。
- 深拷贝是一种复制方式,它会递归地复制对象的所有属性,包括引用类型属性,确保新对象和原始对象完全独立。
实现方式的差异:
- 原型模式可以通过实现Cloneable接口并重写clone()方法来实现对象的复制和克隆。
- 深拷贝可以通过自定义的复制方法或使用序列化和反序列化来实现。
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 深克隆和浅克隆
通常情况下,一个类包含一些成员对象,在使用原型模式克隆对象时,根据其成员对象是否也克隆,原型模式可以分为两种形式:深克隆和浅克隆。
- 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
- 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
Java中的clone方法默认是浅克隆。
关于数据深克隆和浅克隆,我这边有一篇文章专门详细介绍其案例和原理。具体可以看:各种拷贝数据比较
05.原型模式总结
5.1 优缺点分析
优点
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率。
- 可以动态增加或减少产品类。
- 原型模式提供了简化的创建结构。
- 可以使用深克隆的方式保存对象的状态。
5.2 有哪些弊端
缺点
- 需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。
- 在实现深克隆时需要编写较为复杂的代码。要修改clone方法的实现,有一定复杂。
5.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) |