View on GitHub

designpattern

JAVA23种设计模式及面向对象设计的原则

原型模式(本工程为原型代码实例) 首页

原型(Prototype)模式的定义如下:

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。
(Specify the kind of objects to create using a prototypical instance, and create new objects by copying this prototype.)

在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。
原型模式作为一种快速创建大量相同或相似的对象方式,在软件开发种的应用较为广泛,很多软件提供的CTRL+C和CTRL+V操作的就是原型模式的典型应用!
例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

原型模式的优缺点

优点

当创建的对象实例较为复杂的时候,使用原型模式可以简化对象的创建过程! 扩展性好,由于写原型模式的时候使用了抽象原型类,在客户端进行编程的时候可以将具体的原型类通过配置进行读取。 可以使用深度克隆来保存对象的状态,使用原型模式进行复制。当你需要恢复到某一时刻就直接跳到。比如我们的idea种就有历史版本,或则SVN中也有这样的操作。非常好用!

缺点

需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的里面,当对已有的类经行改造时需要修改源代码,违背了开闭原则。 在实现深克隆的时需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用的时候,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现相对麻烦。

何时使用

 1、当一个系统应该独立于它的产品创建,构成和表示时。
 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 
 3、为了避免创建一个与产品类层次平行的工厂类层次时。 
 4、当一个类的实例只能有几个不同状态组合中的一种时。
 5、创建对象成本比较大,比如初始化要很长时间的,占用太多CPU的,新对象可以通过复制已有的对象获得的,如果是相似的对象,则可以对其成员变量稍作修改。
 6、系统要保存对象状态的,而对象的状态改变很小。
 7、需要避免使用分层次的工厂类来创建分层次的对象,并且类的对象就只用一个或很少的组合状态!
 
建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

使用场景

1、资源优化场景。
2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 
3、性能和安全要求的场景。 
4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 
5、一个对象多个修改者的场景。 
6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 
7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。
原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。

注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写clone(),深拷贝是通过实现 Serializable 读取二进制流。

原型模式的结构与实现

由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。
1 模式的结构 (原型模式分三个角色,抽象原型类,具体原型类,客户类)

抽象原型类(prototype):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是接口,抽象类甚至是一个具体的实现类。
具体原型类(concretePrototype):它实现了抽象原型类中声明的克隆方法,在克隆方法中返回一个自己的克隆对象。
客户类(Client):在客户类中,使用原型对象只需要通过工厂方式创建或者直接NEW(实例化一个)原型对象,然后通过原型对象的克隆方法就能获得多个相同的对象。由于客户端是针对抽象原型对象编程的所以还可以可以很方便的换成不同类型的原型对象!<p>

2 模式的实现

[手动实现] 原型模式的克隆分为浅克隆和深克隆,Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。 代码实例

[spring boot实现] 在类上加入 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
这里,使用ConfigurableBeanFactory类的SCOPE_PROTOTYPE常量设置了原型作用域。 你当然也可以使用@Scope(“prototype”) ,但是使用SCOPE_PROTOTYPE常量更加安全并且不易出错。 如果你使用组件扫描来发现和声明bean,那么你可以在bean的类上使用@Scope注解,将其声明为原型bean:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class XXXX {
... 

如果你想在Java配置中将Notepad声明为原型bean,那么可以组合使用@Scope和@Bean来指定所需的作用域:

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public XXX getXXX {
	return new XXX();
}

不管你使用哪种方式来声明原型作用域,每次注入或从Spring应用上下文中检索该bean的时候,都会创建新的实例。这样所导致的结果就是每次操作都能得到自己的Notepad实例。

返回