重温23种设计模式(11):原型模式
来源:mikechen的互联网架构
为什么要学设计模式?设计模式有哪些优点?
提升查看框架源码能力提升对复杂业务的代码设计能力以及 code 能力为今后的面试以及进阶之路夯实基础今天我们要讲的是设计模式中的原型模式(Prototype Pattern)。
原型模式提供了创建对象的最佳方式,主要解决对象复制的问题,适用于创建复杂对象图、实现对象的快照和恢复等场景中。
原型模式通过克隆现有对象来创建新对象,避免了频繁的对象实例化过程,属于创建型模式。
简单理解,就是定义一个原型对象作为创建其他对象的基础,通过克隆原型对象,我们可以创建多个具有相同属性和行为的新对象。
例如:
孙悟空有个十分牛逼的绝活儿,叫分身术,可以幻化出多个相同的孙悟空。
这个幻化出的新的分身,和设计模式中的原型模式是相似的。
原型模式中的 3 个重要角色:
客户端(Client):使用具体原型类中的 clone() 方法,来复制新的对象。
抽象原型(Prototype):可以是抽象类或接口,规定了具体原型对象必须实现的 clone() 方法。
具体原型类(ConcretePrototype):实现抽象原型类的 clone() 方法,它是可被复制的对象。
Prototype 通常不用自己定义,因为拷贝这个操作十分常用。
在 Java 中,提供了Cloneable 接口来支持拷贝操作,它就是原型模式中的 Prototype 。
当然了,原型模式也未必非得去实现 Cloneable 接口,也有其他的实现方式。
03原型模式的两种实现原型模式有两种拷贝方式:浅拷贝、深拷贝。
1)浅拷贝
创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
简单理解,就是只复制所考虑的对象,而不复制它所引用的对象。
Object 类提供的方法 clone ,只是拷贝本对象 , 其对象内部的数组、引用对象等都不拷贝。
2)深拷贝
创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
简单理解,就是复制一切,把要复制的对象所引用的对象都拷贝了一遍。
相比于浅拷贝,深拷贝速度慢并且开销大,但是拷贝前后两个对象互不影响。
04原型模式的实现示例源码示例:
将名片拷贝到自己的名片库中。
我们先实现名片类。
具体的原型类:public class BusinessCard implements Cloneable { private String name; private String company; public BusinessCard(){ System.out.println("执行构造函数BusinessCard"); } public void setName(String name) { this.name = name; } public void setCompany(String company) { this.company = company; } @Override public BusinessCard clone() { BusinessCard businessCard = null; try { businessCard = (BusinessCard) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return businessCard; } public void show() { System.out.println("name:" + name); System.out.println("company:" + company); }}BusinessCard 类实现了 Cloneable 接口,它是一个标识接口,表示这个对象是可拷贝的。
只要重写 clone 方法,就可以实现拷贝。如果实现了 Cloneable 接口、却没有重写 clone 方法,就会报错。
需要注意的是:
clone 方法不是在 Cloneable 接口中定义的(Cloneable 接口中没有定义任何方法),而是在 Object 中定义的。
客户端调用:public class Client { public static void main(String[] args) { BusinessCard businessCard = new BusinessCard(); businessCard.setName("钱三"); businessCard.setCompany("阿里"); //拷贝名片 BusinessCard cloneCard1 = businessCard.clone(); cloneCard1.setName("赵四"); cloneCard1.setCompany("百度"); BusinessCard cloneCard2 = businessCard.clone(); cloneCard2.setName("孙五"); cloneCard2.setCompany("腾讯"); businessCard.show(); cloneCard1.show(); cloneCard2.show(); }}除了第一个名片,其他两个名片都是通过 clone 方法得到的。
但是,clone 方法并不会执行 cloneCard1 和 cloneCard2 的构造函数。
运行结果:
执行构造函数 BusinessCardname:钱三company:阿里name:赵四company:百度name:孙五company:腾讯1)实现浅拷贝上述的例子中,BusinessCard 的字段都是 String 类型的,如果字段是引用的类型的,会出现什么情况呢?public class DeepBusinessCard implements Cloneable { private String name; private Company company = new Company(); public void setName(String name) { this.name = name; } public void setCompany(String name, String address) { this.company.setName(name); this.company.setAddress(address); } @Override public DeepBusinessCard clone() { DeepBusinessCard businessCard = null; try { businessCard = (DeepBusinessCard) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return businessCard; } public void show() { System.out.println("name:" + name); System.out.println("company:" + company.getName() + "-address-" + company.getAddress()); }}我们定义了 DeepBusinessCard 类,它的字段 company 是引用类型的。
Company 类:
public class Company { private String name; private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getName() { return name; } public void setName(String name) { this.name = name; }在客户端使用 DeepBusinessCard :
public class Client { public static void main(String[] args) { DeepBusinessCard businessCard=new DeepBusinessCard(); businessCard.setName("钱三"); businessCard.setCompany("阿里","北京望京"); DeepBusinessCard cloneCard1=businessCard.clone(); cloneCard1.setName("赵四"); cloneCard1.setCompany("百度","北京西二旗"); DeepBusinessCard cloneCard2=businessCard.clone(); cloneCard2.setName("孙五"); cloneCard2.setCompany("腾讯","北京中关村"); businessCard.show(); cloneCard1.show(); cloneCard2.show(); }}运行结果:
name:钱三company:腾讯-address-北京中关村name:赵四company:腾讯-address-北京中关村name:孙五company:腾讯-address-北京中关村Object 类提供的 clone 方法,不会拷贝对象中的内部数组和引用对象,导致它们仍旧指向原来对象的内部元素地址。
因此,company 字段为最后设置的”腾讯”、”北京中关村”。
这种拷贝方式,我们就称为浅拷贝。
company 字段是引用类型,businessCard 被拷贝后,company 字段仍旧指向原来的 businessCard 对象的 company 字段的地址。
我们每次设置 company 字段,都会覆盖上一次设置的值,最终留下的就是最后一次设置的值:腾讯、北京中关村。
浅拷贝引用关系:
有多个对象可以修改 company ,显然,这样的引用关系是不符合需求的。
为了解决这个问题,我们引入了深拷贝模式。
修改引用关系为深拷贝:
拷贝 businessCard 对象的同时,也将它内部的引用对象 company 进行拷贝,使得每个拷贝的对象之间无任何关联,都指向了自身对应的 company。
这种拷贝方式,我们就称为深拷贝。
深拷贝模式把要复制的对象所引用的对象都拷贝了一遍。
2)实现深拷贝首先需要修改 Company 类:public class Company implements Cloneable{ private String name; private String address; ... public Company clone(){ Company company=null; try { company= (Company) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return company; }}为了实现 Company 类能被拷贝,Company 类也需要实现 Cloneable 接口、并且覆写 clone 方法。
接着,修改 DeepBusinessCard 的 clone 方法:
public class DeepBusinessCard implements Cloneable { private String name; private Company company = new Company(); ... @Override public DeepBusinessCard clone() { DeepBusinessCard businessCard = null; try { businessCard = (DeepBusinessCard) super.clone(); businessCard.company = this.company.clone();//1 } catch (CloneNotSupportedException e) { e.printStackTrace(); } return businessCard; } ...}注释 1 增加了对 company 字段的拷贝处理,最后在客户端调用。
输出结果:
name:钱三company:阿里-address-北京望京name:赵四company:百度-address-北京西二旗name:孙五company:腾讯-address-北京中关村可见,采用深拷贝模式修改一个对象的引用类型的成员时,不会再影响另外对象的该成员。
05原型模式的优缺点优点:
克隆对象时,对客户端隐藏实现细节,避免了代码耦合。
逃避构造函数的约束。
可以动态增加或减少产品类。
原型模式提供了简化的创建结构。
缺点:
必须实现 Cloneable 接口;
clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
在实现深拷贝时,需要编写较为复杂的代码。
06原型模式的使用场景不管是复杂还是简单的对象,只要存在对象复制的场景,都适合使用原型模式。
类的初始化需要耗费较多的资源时。
通过 new 产生一个对象、需要非常繁琐的数据准备或访问权限时。
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时。
07原型模式与工厂方法模式的区别工厂方法模式是一种创建型设计模式,它提供了一种在不指定具体类的情况下创建对象的方法。
关于工厂方法模式,看这篇:【重温23种设计模式】之工厂方法模式
原型模式与工厂方法模式的主要区别是:对象创建的方式。
原型模式通过复制现有的对象来创建新对象,适用于以下场景:
需要创建大量具有相似属性的对象。
对象的创建成本很高,例如需要执行复杂的初始化逻辑。
需要在运行时动态地创建和修改对象。
工厂方法模式通过调用工厂方法来创建新对象,适用于以下场景:
需要创建具有不同类型或不同实现的对象。
对象创建逻辑较为复杂,不适合在客户端代码中直接实例化。
需要将对象创建过程与客户端代码解耦。
08原型模式与单例模式的区别单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。
关于单例模式,看这篇:8大单例模式实现方式总结
原型模式与单例模式的主要区别在于它们的目的。
原型模式用于通过复制现有对象来创建新对象,适用于以下场景:
需要创建大量具有相似属性的对象。
对象的创建成本很高,例如需要执行复杂的初始化逻辑。
需要在运行时动态地创建和修改对象。
单例模式用于确保一个类只有一个实例,适用于以下场景:
需要确保一个类只有一个实例。
需要全局访问点以方便地访问该实例。
需要全局共享资源或配置信息。
总结通过本文,我们了解并掌握了原型模式的概念、原理、应用场景、优缺点、实现方式等。
原型模式是一种强大而灵活的设计模式,它可以简化对象的创建过程、提高性能,具备更好的可维护性。
在实现原型模式时,需要根据具体需求选择使用深拷贝或浅拷贝。原型模式适用于在处理对象创建、动态修改对象结构、高性能缓存等场景中。
以上,就是关于原型模式的全部介绍。
扫一扫,关注我们