Design Pattern - Prototype Pattern && Factory Method Pattern
Prototype Pattern
Intro
使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象
原型模式(Prototype Pattern)是创建型模式的一种。通过复制一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是原型。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。Prototype是原型类,ConcretePrototype是子类,Client是调用类。
Example
Prototype类需要具备两个条件
实现Cloneable接口
在Java中的Cloneable接口的作用只有一个:在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。
通过该方法进行对象的拷贝,Java提供了一个Cloneable接口来标示这个对象是可拷贝的。Cloneable里面一个方法都没有的,这个接口只是一个标记作用,在JVM中具有这个标记的对象有可能被拷贝。
在JVM中,只有实现了这个接口的类才可以被拷贝,否则会抛出
CloneNotSupportedException异常。重写Object类中的clone方法
Java中所有类的父类是Object类,Object有个clone方法,作用是返回对象的一个拷贝,但是其作用于是protected的,一般类无法调用,因此Prototype类需要将clone方法的作用于修改为public

浅拷贝:clone只是拷贝本对象,其对象内部的数组、引用对象、引用类型都不拷贝,还是指向原型的内部地址。两个对象的地址是一样的。
1 | public class Prototype implements Cloneable{ |
1 | public class ConcretePrototype1 extends Prototype{ |
1 | public class ConcretePrototype2 extends Prototype{ |
深拷贝:将对象复制后,基本数据类型学和引用类型都重新创建。两个对象的地址不一样。
1 | public interface Prototype{ |
1 | public class ConcretePrototype1 implements Prototype{ |
1 | public class ConcretePrototype2 implements Prototype{ |
使用原型的客户端
1 | public class Client{ |
如果一个对象中只有基本类型属性,那深拷贝和浅拷贝效果都是一样的,基本类型数据不管是用深还是浅都会被拷贝出一份,但如果对象中包含引用对象属性,那浅拷贝其实这是拷贝了一份引用,而深拷贝确实把整个引用对象都拷贝了一份。
Attention
Advantages
- 性能优良 原型模式是在内存二进制流的拷贝,比直接new一个对象性能要好很多
- 构造函数不会执行 因为对象的复制是通过调用Object类的clone方法来完成的,直接在内存中复制数据 因此不用调用到类的构造方法
- 根据客户端要求实现动态创建对象,客户端不需要知道对象的创建细节,便于代码的维护和拓展。
Usage Scenario
- 资源优化:类初始化需要消化非常多的资源(包括数据、硬件资源等) 通过原型拷贝避免消耗
- 通过new 产生的一个对象需要非常繁琐的数据准备或者权限
- 一个对象多个修改者 :一个对象需要提供给其他对象访问,而且各个调用者都需要修改其值 (保护性拷贝)
final关键字
final类型是不能重新赋值的,所以要使用clone() 类的成员变量上不要加final关键字
在实际项目中,原型模式一般是和工厂方法模式同时出现 通过clone方法创建一个对象,然后由工厂方法提供给调用者。
Factory Method Pattern
Intro
定义一个用于创建对象的接口,让子类决定实例化哪一个类
工厂模式有三种:简单工厂模式 工厂方法模式 抽象工厂模式 这里讲的是第二种
工厂方法(Factory method pattern)使一个类的实例延迟到其子类。封装类中不变的部分,提取其中个性化善变的部分为独立类。
创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;Creator为抽象创建类,具体如何创建产品类是由具体的实现工厂ConcreteProduct完成的。

Example
抽象产品类:具体产品继承的父类或者实现的接口
1 | public abstract class Product { |
具体产品类:具体工厂角色所创建的对象就是该角色的实例
1 | public class ConcreteProduct extends Product { |
抽象工厂类:工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。
1 | public abstract class Creator { |
具体工厂类:与具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。负责每个产品。
1 | public class ConcreteCreator extends Creator { |
场景类
1 | public class Client { |
- 一个抽象产品类,可以派生出多个具体产品类。
- 一个抽象工厂类,可以派生出多个具体工厂类。
- 每个具体工厂类,只能创建一个具体产品类的实例。
当有新的产品产生时,只要创建并继承抽象产品 而不用修改任何一个类。比如说抽象产品是tp,具体产品类是tpT470、tpE470 当新产品T480出现时,只需要继承tp这个类,然后定义新的工厂,同样是实现工厂接口,生产具体的产品。
符合开闭原则。对拓展开放;对修改封闭。
Attention
使用场景
- 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
- 数据库访问:当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
- 设计一个连接服务器的框架,需要三个协议 “POP3” “IMAP” “HTTP” 可以把这三个作为产品类,共同实现一个接口
优点
- 良好的封装性,代码结构清晰
- 良好的拓展性,在增加产品类的情况下,只要适当地修改具体或扩展工厂类即可
- 屏蔽产品类。调用者只需要关心产品的接口。如果从数据Mysql切换到Oracle,只要切换驱动名称就行。
- 典型的解耦框架。高层模块值需要知道产品的抽象类,其他的实现类都不用关心,符合迪米特法则,我们不需要的就是不要去交流;也符合依赖倒置原则,只依赖产品类的抽象;也符合里氏替换原则,使用产品子类替换产品父类。
缺点
- 每增加一个产品都需要增加一个具体类和对象实现工厂,使系统中类的个数成倍增加。当产品种类非常多的时候会出现大量的与之对应的工厂对象。在一定程度上增加了系统的复杂度,也增加了系统具体类的依赖。
参考链接:
拓展阅读: