之前已经讲过「简单工厂」,所以按照进阶,我们再来看一下工厂方法模式是如何在简单工厂模式的基础之上对其进行改善和丰富的。其实工厂方法模式本质是设计了抽象工厂的角色,来满足对于「开放-封闭原则」的要求。工厂方法模式属于六个创建型模式之一,其余五个为:
- 简单工厂模式
- 抽象工厂模式
- 单例模式
- 原型模式
- 建造者模式
简单工厂模式、工厂方法模式、抽象工厂模式属于依次递进,层层抽象的,完全可以放在一起循序渐进式的掌握,从入门到进阶。
工厂方法模式的定义
定义一个用于创建对象的接口,让子类决定将哪一个 类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。
工厂方法模式的结构及角色
- Product(抽象产品):定义产品或者业务行为的接口,是工厂方法模式所创建对象的父类型。
- ConcreteProduct(具体产品):也就是抽象产品或者业务行为的具体实现,不过这种具体的产品类型实例要由对应的具体工厂来创建。
- Factory(抽象工厂):抽象工厂中声明的就是工厂方法,可以理解为返回的是上面的抽象产品,抽象工厂是工厂方法模式的核心,所有创建对象的具体工厂都必须实现该抽象工厂。
- ConcreteFactory(具体工厂):抽象工厂的具体实现,实现抽象工厂中定义的工厂方法
所以,根简单工厂模式相比,工厂方法模式引入了抽象工厂。
举一个具体的例子,我们继续使用简单工厂方法的那篇文章中的「飞行」行为来举例,将简单工厂模式优化为工厂方法模式。首先还是老样子,创建抽象鸟类接口,然后创建具体的鸟实现类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package learn.design.patterns.factory.method; /** * 抽象接口类: 鸟类 * * @ClassName: Bird * @author: 高老四博客 * @since: 2020/4/23 17:43 */ public interface Bird { /** * 鸟类的飞行行为 * @Title: fly * @return void */ void fly(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package learn.design.patterns.factory.method; /** * 具体鸟类: 老鹰类 * * @ClassName: Eagle * @author: 高老四博客 * @since: 2020/4/23 17:45 */ public class Eagle implements Bird { public Eagle() { System.out.println("创建一只老鹰!"); } @Override public void fly() { System.out.println("我要去运动达人罗志祥家啄米,起飞!"); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package learn.design.patterns.factory.method; /** * 具体鸟类: 信鸽类 * * @ClassName: Letter * @author: 高老四博客 * @since: 2020/4/23 17:45 */ public class Letter implements Bird { public Letter() { System.out.println("创建一只信鸽!"); } @Override public void fly() { System.out.println("我要送一封信,起飞!"); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package learn.design.patterns.factory.method; /** * 具体鸟类: 知更鸟类 * * @ClassName: Robin * @author: 高老四博客 * @since: 2020/4/23 17:45 */ public class Robin implements Bird { public Robin() { System.out.println("创建一只知更鸟!"); } @Override public void fly() { System.out.println("有人要杀死一只知更鸟,起飞!"); } } |
然后我们还是继续创建鸟类工厂类,不过这个工厂类是抽象接口,声明具体的鸟类的创建,而不是直接写判断条件的业务逻辑,具体的鸟类实例用抽象工厂的子类工厂类来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package learn.design.patterns.factory.method; /** * 飞行行为抽象工厂 * * @InterfaceName: FlyFactory * @author: glorze.com * @since: 2020/7/23 23:05 */ public interface BirdFactory { Bird createBird(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package learn.design.patterns.factory.method; /** * 老鹰工厂类 * * @ClassName: EagleFactory * @author: 高老四博客 * @since: 2020/7/23 23:09 */ public class EagleFactory implements BirdFactory { @Override public Bird createBird() { Bird bird = new Eagle(); return bird; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package learn.design.patterns.factory.method; /** * 信鸽工厂类 * * @ClassName: EagleFactory * @author: 高老四博客 * @since: 2020/7/23 23:09 */ public class LetterFactory implements BirdFactory { @Override public Bird createBird() { Bird bird = new Letter(); return bird; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package learn.design.patterns.factory.method; /** * 知更鸟工厂类 * * @ClassName: EagleFactory * @author: 高老四博客 * @since: 2020/7/23 23:09 */ public class RobinFactory implements BirdFactory { @Override public Bird createBird() { Bird bird = new Robin(); return bird; } } |
接着就是客户端的对具体鸟类的实例化了,将以前在鸟类工厂的各种条件判断来实例化具体的鸟类转移到客户端中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package learn.design.patterns.factory.method; /** * 工厂方法模式客户端 * * @ClassName: Client * @author: glorze.com * @since: 2020/7/23 23:14 */ public class Client { public static void main(String[] args) { BirdFactory birdFactory = new EagleFactory(); Bird bird = birdFactory.createBird(); bird.fly(); } } |
你看,就这样实现了更高一级的工厂抽象,这样,当我们想要再增加一个麻雀类的飞行实现的时候,只需要创建一个麻雀类去实现 Bird 接口,然后在创建一个麻雀工厂类实现 BirdFactory,在客户端就能继续使用新的麻雀类实例化了,不影响其余产品和工厂类的修改,实现了对修改关闭,对扩展开放,不违背「开放-封闭原则」。
工厂方法模式优缺点以及适用场景
优点
- 用户只需要关心产品对应的工厂,不需要关心产品的创建细节,甚至不需要知道具体的产品名称。
- 符合面向对象编程中的多态定义
- 完全符合「开放-封闭原则」
缺点
- 新增产品的时候还需要增加对应的工厂类,对于大型系统设计,类的数量指数级增加
- 多层抽象固有的理解难度。
适用场景
- 客户端不知道他所需要的对象的类。
- 利用面向对象和里氏代换原则,程序运行时,子类对象可以覆盖父类对象,从而对系统进行扩展。
工厂方法模式在 Spring 中的应用
Spring 中其实将简单工厂模式、工厂方法模式、抽象工厂模式用到了极致,其中体现为工厂方法模式的是 FactoryBean 这个类,我们知道该类经常拿来与 BeanFactory 拿来作比较的。
BeanFactory
BeanFactory 算是 Spring 的最高级抽象,允许我们通过名称或者类型创建和检索对象,并且也负责管理对象之间的关系。BeanFactory 底层支持单例和原型模式,是 IoC 容器的根基,属于简单工厂模式的体现。BeanFactory 有三个比较重要的实现,分别是 ListableBeanFactory、HierarchicalBeanFactory、AutowireCapableBeanFactory 和最终的默认实现类 DefaultListableBeanFactory。ListableBeanFactory 代表 Bean 的可列表化,HierarchicalBeanFactory 表示 Bean 之间的集继承,AutowireCapableBeanFactory 定义 Bean 的装配规则。
FactoryBean
FactoryBean 与 BeanFactory 是完全不同的两个类,BeanFactory 是 Bean 的工厂,贯穿 Spring 管理 Bean 的整个生命周期,而 FactoryBean 其实就是一个 Bean,在 Spring 中,FactoryBean 可以作为 xml 或者注解直接进行生成一种对 Bean 的补充,我们可以通过 FactoryBean 返回自己想要的 Bean 或者对自己的 Bean 进行复杂的扩展操作,FactoryBean 源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package org.springframework.beans.factory; import org.springframework.lang.Nullable; /** * 工厂 Bean * @param <T> the bean type */ public interface FactoryBean<T> { /** * 返回此工厂管理的对象的实例 * @return an instance of the bean (can be {@code null}) * @throws Exception in case of creation errors */ @Nullable T getObject() throws Exception; /** * 返回此 FactoryBean 创建的对象的类型 * @return the type of object that this FactoryBean creates, * or {@code null} if not known at the time of the call */ @Nullable Class<?> getObjectType(); /** * 这个工厂返回的对象是否是单例,默认返回 true; * 如果是单例则由 getObject 返回,相同的对象可以起到缓存的引用 * @return whether the exposed object is a singleton */ default boolean isSingleton() { return true; } } |
工厂方法模式在 MyBatis 中的应用
工厂方法模式在 MyBatis 中体现在基础支持层中的数据源 DataSource 配置,MyBatis 使用不同的 DataSourceFactory 接口实现创建不同类型的 DataSource,分别是连接池数据资源、非连接池数据资源。所以我们在 MyBatis 的源码中主要看 DataSourceFactory、PooledDataSourceFactory、UnpooledDataSourceFactory、DataSource、PooledDataSource、UnpooledDataSource 这些接口/类,在这些类中,DataSourceFactory 就是抽象工厂的角色,PooledDataSourceFactory 和 UnpooledDataSourceFactory 属于具体的工厂,负责对应的产品的创建,而 DataSource 就属于抽象产品,PooledDataSource 和 UnpooledDataSource 就是具体的产品。
相关文章阅
更博不易,如果觉得文章对你有帮助并且有能力的老铁烦请捐赠盒烟钱,点我去赞助。或者扫描文章下面的微信/支付宝二维码打赏任意金额(点击「给你买杜蕾斯」),也可以加入本站封闭式交流论坛「DownHub」开启新世界的大门,老四这里抱拳谢谢诸位了。捐赠时请备注姓名或者昵称,因为您的署名会出现在赞赏列表页面,您的捐赠钱财也会被用于小站的服务器运维上面,再次抱拳感谢。