github仓库地址:github.com/qiudao12345…
0. 引言
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象
工厂模式属于创建型模式,那么它跟我上一篇文章所提到的也是属于创建型模式的单例模式【设计模式】--可能是最详细的单例模式说明 有什么区别呢?
答案:单例模式的侧重点在于对象在内存中的唯一性,而工厂模式的侧重点则在于隐藏复杂对象的创建过程和细节,使构建对象的过程透明化、简单化。
ps:工厂模式的核心思想在于对调用端隐藏构建过程与细节,所以只要满足了这个核心思想的模块或者代码都可以说是用到了工厂模式。工厂模式可以有不同的实现方式,而下面提到的三种工厂只是在不同场景下,对工厂模式进行了简单的区分而已
1. 简单工厂
GOF中谈及到的23种设计模式其实是不包含简单工厂模式的,但是这个模式也是经常会遇到的
1.1 场景引入
1.11小明买手机
小明想要买两部手机,一部小米手机、一部华为手机。正常情况下,小明如果想买到这两部手机,那他可能需要做的事情就是
- 1.打开手机,输入小米商城,进入小米商城,找到对应型号的手机,下单
- 2.换衣服出门,坐车,进华为手机店,挑选对应型号的手机,购买 问题来了,这多麻烦了,如果他要是想再买一部OPPO手机呢?那岂不是还得去OPPO商城下单?
所以能不能搞一个公用平台,买了所有牌子的手机,只需要说出你要什么手机什么型号,然后这个平台就自动把手机发给你?
这就是工厂模式的思想了!
通过工厂模式提供的统一接口,我们就能很方便的获取到所需的对象,而不需要关注其具体的细节
下面看一下简单工厂模式的UML类图
1.2 UML图
这里主要有三种角色
- 1.抽象产品(Phone):对工厂产出产品的一种抽象,为了便于扩展功能功能
- 2.具体产品(XiaomiPhone):抽象产品的具体实现
- 3.工厂(SimpleFactory):根据传入参数构建并返回具体的产品
在这种模式下,调用端只需要持有工厂对象,同时传入对应的产品信息,就能够在不关心其具体创建过程和创建细节的情况下,很轻松的获取到所需的对象。
下面我们看看代码实现
1.3 代码实现
(示例中没有加入很复杂的业务逻辑,只是单纯用来展示这个模式)
1.31抽象产品
/**
* 手机
* @author qiudao
*/
public interface Phone {
// 打印品牌
void printBrand();
}
复制代码
1.32 具体产品
华为手机
/**
* 华为手机
* @author qiudao
*/
public class HuaweiPhone implements Phone{
@Override
public void printBrand() {
//todo 复杂逻辑
System.out.println("华为牌手机!");
}
}
复制代码
小米手机
/**
* 小米手机
* @author qiudao
*/
public class XiaomiPhone implements Phone{
@Override
public void printBrand() {
//todo 复杂逻辑
System.out.println("小米牌手机!");
}
}
复制代码
1.33 工厂
/**
* 简单工厂
*
* @author qiudao
*/
public class SimpleFactory {
// 小米的名
public final static String XIAOMI_NAME = "xiaomi";
//华为的名字
public final static String HUAWEI_NAME = "huawei";
/**获得手机**/
public Phone getPhone(String name) {
Phone phone;
// 静态编码校验
if (XIAOMI_NAME.equals(name)) {
//todo 复杂逻辑
phone = new XiaomiPhone();
} else if (HUAWEI_NAME.equals(name)) {
//todo 复杂逻辑
phone = new HuaweiPhone();
} else {
throw new NullPointerException("无对应手机");
}
return phone;
}
}
复制代码
1.34 测试
/**
* 测试简单工厂
* @author qiudao
*/
public class TestSimpleFactory {
public static void main(String[] args) {
// 创建工厂
SimpleFactory factory = new SimpleFactory();
Phone huaweiPhone = factory.getPhone(SimpleFactory.HUAWEI_NAME);
huaweiPhone.printBrand();
Phone xiaomiPhone = factory.getPhone(SimpleFactory.XIAOMI_NAME);
xiaomiPhone.printBrand();
}
}
复制代码
2. 工厂方法
上面我们介绍了简单工厂模式,虽然这种方式可以让我们很轻松就能获取到所需的对象,但是有一个非常严重的问题,那就是扩展性差,如果想要新增一个VIVO手机的话,那我们就需要
- 新建VIVO手机类VivoPhone
- 修改SimpleFactory.getPhone() 方法 这样就违背了开闭原则了,一方面,人是很容易犯错的动物,对总的逻辑流程进行修改扩展的话,很容易引发问题。另一方面,全部逻辑耦合在一个类里面,代码的耦合度会非常高,不符合系统设计中的 高内聚 低耦 合的原则
所以这个时候,就需要祭出我们的工厂方法模式了
为了把产品的实例化操作延迟到子类工厂中完成,通过工厂子类来决定究竟应该实例化哪一个产品具体对象.工厂父类(接口)负责定义产品对象的公共接口,而子类工厂则负责创建具体的产品对象。
2.1 场景的引入
在上个场景中,公用平台把所有手机厂商的手机都买下来了,这样如果有人要买手机的话,他就可以立刻把手机给到他。但是问题来了,随着手机牌子越来越多,这个平台的老板发现,他们平台不够钱买手机了!这该怎么办呢?
这时候,就有人提出了一种新的模式。
- 搞一个营销中心,所有手机厂商都入驻这个中心,如果小明有需要的话,直接进这个营销中心,找到对应品牌的驻点。然后跟他们买手机就可以了,如果有新的手机厂商出现的话,那么只要让他们在这个营销中心设置一个驻点就行了,这样平台就不用每个牌子的手机全都买一遍了
这就是工厂方法的核心思想,我们来看看uml图
2.2 UML图与说明
这里有四种角色:- 1.抽象产品(Phone):同简单工厂
- 2.具体产品(HuaweiPhone):同简单工厂
- 3.抽象工厂(Factory):根据职责抽象出创建产品的接口
- 4.具体工厂(HuaweiPhoneFactory):具体产品的创建工厂,由调用方决定使用哪个工厂
如果有需要新增具体的产品的话,只需要
- 1.创建具体产品实现抽象产品接口
- 2.创建具体的产品工厂,并实现抽象工厂接口
- 3.调用端决定调用哪个工厂
2.3 代码实现
2.31 抽象产品
/**
* 手机
* @author qiudao
*/
public interface Phone {
// 打印品牌
void printBrand();
}
复制代码
2.32 具体产品
华为手机
/**
* 华为手机
* @author qiudao
*/
public class HuaweiPhone implements Phone{
@Override
public void printBrand() {
System.out.println("华为牌手机!");
}
}
复制代码
小米手机
/**
* 小米手机
* @author qiudao
*/
public class XiaomiPhone implements Phone{
@Override
public void printBrand() {
System.out.println("小米牌手机!");
}
}
复制代码
2.33 抽象工厂
/**
* 工厂接口
* @author qiudao
*/
public interface Factory {
// 拿到手机
Phone getPhone();
}
复制代码
2.34 具体工厂
华为手机
/**
* 工厂方法的具体工厂
* @author qiudao
*/
public class HuaweiPhoneFactory implements Factory{
@Override
public Phone getPhone() {
//todo 复杂逻辑
return new HuaweiPhone();
}
}
复制代码
小米手机
/**
* 工厂方法的具体工厂
* @author qiudao
*/
public class XiaomiPhoneFactory implements Factory{
@Override
public Phone getPhone() {
//todo 复杂逻辑
return new XiaomiPhone();
}
}
复制代码
2.35 测试
/**.
* 测试工厂方法
* @author qiudao
*/
public class TestFactoryMethod {
public static void main(String[] args) {
System.out.println("工厂方法测试.......");
Factory xiaomiPhoneFactory = new XiaomiPhoneFactory();
Phone xiaomiPhoe = xiaomiPhoneFactory.getPhone();
xiaomiPhoe.printBrand();
HuaweiPhoneFactory huaweiPhoneFactory = new HuaweiPhoneFactory();
Phone huaweiPhone = huaweiPhoneFactory.getPhone();
huaweiPhone.printBrand();
}
}
复制代码
3. 抽象工厂
上面我们我们介绍了工厂方法模式,对比简单工厂,工厂方法很好地遵守了开闭原则,在扩展工厂的情况下,不会对原有代码造成任何破坏。
3.1 场景的引入
现在平台的资金问题解决了,但是又产生了一个新的问题:客户买了新手机之后,还需要根据型号买对应的手机套。这可怎么办啊?营销中心里面的驻点只卖手机啊,如果客户要买手机套的话,那岂不是还会发生在引入工厂模式之前的问题?
所以负责人新增了一条规则:每个入驻的驻点必须提供销售对应手机套的服务
这样就很好的解决了刚才出现的问题
3.2 UML图与说明
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。可以理解成是多个工厂方法的组合。
工厂方法创建的是同等级产品,而抽象工厂则是提供了创建产品族功能
- 什么是同等级?什么是产品族呢?
代入这个场景的话,小米手机跟华为手机就是同一个等级的产品,小米手机套跟华为手机套都是手机套,所以它们也是同等级的产品。
而小米手机跟小米手机套则是同一个产品族的东西,华为手机跟华为手机套虽然不是同一种东西,但是它们都是华为牌的,所以它们属于同一个产品族。
可以用下面这个图来表示
理解了产品等级跟产品族之后,我们来看一下抽象工厂的uml图
他的角色种类跟工厂方法是一样的,唯一一点不同的就是抽象工厂的工厂不生产单一产品,而是生产一个产品族的东西
3.3 代码实现
3.31 抽象产品
/**
* 手机
* @author qiudao
*/
public interface Phone {
// 打印品牌
void printBrand();
}
/**
* 手机套
* @author qiudao
*/
public interface PhoneCases {
// 打印手机套名字
void printCasesName();
}
复制代码
3.32 具体产品
/**
* 华为手机
* @author qiudao
*/
public class HuaweiPhone implements Phone{
@Override
public void printBrand() {
System.out.println("华为牌手机!");
}
}
/**
* 小米手机
* @author qiudao
*/
public class XiaomiPhone implements Phone{
@Override
public void printBrand() {
System.out.println("小米牌手机!");
}
}
/**
* 华为手机壳
* @author qiudao
*/
public class HuaweiPhoneCases implements PhoneCases{
@Override
public void printCasesName() {
System.out.println("华为手机套");
}
}
/**
* 小米手机壳
*
* @author qiudao
*/
public class XiaomiPhoneCases implements PhoneCases {
@Override
public void printCasesName() {
System.out.println("小米手机套");
}
}
复制代码
3.33 抽象工厂
/**
* 抽象工厂
*
* @author qiudao
*/
public interface AbstractFactory {
// 获取手机
Phone getPhone();
// 获取手机套
PhoneCases getPhoneCases();
}
复制代码
3.34 具体工厂
/**
* 华为工厂
* @author qiudao
*/
public class HuaweiFactory implements AbstractFactory {
@Override
public Phone getPhone() {
//todo 复杂逻辑
return new HuaweiPhone();
}
@Override
public PhoneCases getPhoneCases() {
//todo 复杂逻辑
return new HuaweiPhoneCases();
}
}
/**
* 小米工厂
* @author qiudao
*/
public class XiaomiFactory implements AbstractFactory {
@Override
public Phone getPhone() {
//todo 复杂逻辑
return new XiaomiPhone();
}
@Override
public PhoneCases getPhoneCases() {
//todo 复杂逻辑
return new XiaomiPhoneCases();
}
}
复制代码
3.35 测试
/**
* 测试抽象工厂
* @author qiudao
*/
public class TestAbstractFacoty {
public static void main(String[] args) {
System.out.println("抽象工厂测试");
AbstractFactory xiaomiFactory = new XiaomiFactory();
Phone xiaomiPhone = xiaomiFactory.getPhone();
PhoneCases xiaomiPhoneCases = xiaomiFactory.getPhoneCases();
xiaomiPhone.printBrand();
xiaomiPhoneCases.printCasesName();
AbstractFactory huaweiiFactory = new HuaweiFactory();
Phone huaweiPhone = huaweiiFactory.getPhone();
PhoneCases huaweiPhoneCases = huaweiiFactory.getPhoneCases();
huaweiPhone.printBrand();
huaweiPhoneCases.printCasesName();
}
}
复制代码
4. 工厂模式总结
需要说明的是,上面三种工厂模式的UML图以及代码实现并不是一成不变的,只是说上面的示例是比较经典比较具有代表性的。很多时候我们都会根据业务需求去自定义扩展或者稍微更改它的结构以便我们实现功能需求。
举个例子,上面的抽象工厂模式中,抽象工厂可以定义成抽象类,然后在抽象类中定义一个组装手机的方法assemblePhoe()
public void assemblePhone(){
System.out.println("开始组装");
this.getPhone().printBrand();
this.getPhoneCases().printCasesName();
System.out.println("组装完成");
}
复制代码
这样就可以实现一些扩展功能!
总结一下三种工厂模式的优劣
简单工厂
优点
工厂类是整个模式的关键.包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象.通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的.明确了各自的职责和权利,有利于整个软件体系结构的优化。
缺点
由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利
工厂方法
优点
对比简单工厂,工厂方法模式的最大优点是扩展的时候不需要对原有结构进行改动,符合了开闭原则,降低了系统的耦合。
缺点
每次新增产品都需要新增一个类,长期下来容易造成类泛滥,增加了系统的复杂度
抽象工厂
优点
增加产品族的概念,一个工厂可以生产多种产品,一定程度上可以减少工厂类的数量,避免类泛滥。同一产品族之间的产品可以进行一些联动操作(例如上面提到的组装手机例子),减少编码量。
缺点
缺点基本跟工厂方法是一样的,同时抽象工厂还有一个缺点就是不够灵活,比如说营销中心的华为摊位只想卖手机不想卖手机套的话,使用抽象工厂不仅会生成无用代码,还会让工厂显得不伦不类
5. 源码应用
JDK或者很多优秀开源框架中都有用到工厂方法。下面稍微列举一下,就不深入解读了
线程池框架Executors
Executors提供了一系列快速创建线程池的方法,虽然它的uml结构图并没有在上面提到的三种uml之中,但是它毫无疑问是使用了工厂模式,因为它为调用者隐藏了创建线程池的复杂过程与细节。只暴露出一个简单的接口。
Spring Bean 容器
相信很多同学在初学spring的时候,都会有见过这样一段代码
BeanFactory beanFactory = new ClassPathXmlApplicationContext("xxx");
Object obj = beanFactory.getBean("car");
复制代码
可以看出来,这段代码也用到了工厂模式,实际上,spring的核心就是bean容器,整个容器就是一个工厂,在启动时将对象实例化存放在工厂中,然后通过getBean(String beanName) 或者 getBean(Class clazz) 来获取工厂中的实例,而不是通过new XXX()的方式来获取实例对象
Collection中的iterator方法
java.util.Collection接口中定义了一个抽象的iterator()方法,该方法就是一个工厂方法。
对于iterator()方法来说Collection就是一个根抽象工厂,下面还有List等接口作为抽象工厂,再往下有ArrayList等具体工厂。
java.util.Iterator接口是根抽象产品,下面有ListIterator等抽象产品,还有ArrayListIterator等作为具体产品。
使用不同的具体工厂类中的iterator方法能得到不同的具体产品的实例。
Mybatis中的SqlSessionFactory
SqlSessionFactory主要用来从指定数据库中建立SqlSession,它的默认实现类是DefaultSqlSessionFactory,如果有需要的话,开发者也可以自己实现SqlSessionFactory,这也是经典的工厂方法体现
总而言之,工厂模式还是非常常见的,理解它不难,但是代入到各种框架的源码中的时候,阅读起来还是会比较有难度的。
文章如果有错误的话,望大家不吝赐教,评论指出!
github仓库地址:github.com/qiudao12345…