设计模式实践笔记第二节:抽象工厂模式
Posted 滕哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式实践笔记第二节:抽象工厂模式相关的知识,希望对你有一定的参考价值。
抽象工厂模式
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
抽象工厂模式(Abstract Factory)也是一种创建型设计模式,通常用来解决「产品族」的创建问题。
抽象工厂模式结构
工厂模式的三要素是产品定义(即接口)、产品实现和工厂,抽象工厂模式是工厂模式的升级,核心是对工厂进行抽象,区分工厂定义和具体实现。抽象工厂模式主要解决的是 接口选择 的问题。
那么什么是产品族呢?
产品族可以理解为一个工厂生产的一系列产品。抽象工厂定义一系列产品(产品族)的创建方法,具体工厂实现各自的产品族的生产过程。
于是客户端在使用抽象工厂的时候无需关心具体的实现。
电子设备工厂
假设你要为自己的系统开发一款代工厂模拟程序,其中包括电子设备的生产服务。
定义抽象工厂接口和产品接口。
public interface AbstractFactory
/**
* 创建手机
*/
Phone createPhone();
/**
* 创建平板电脑
*/
Pad createPad();
public interface Phone
/**
* 打电话
*/
void call(String phoneNumber);
public interface Pad
/**
* 看视频
*/
void watchVideo();
抽象工厂定义了Phone
和 Pad
两种产品的生产方法,我们规定手机可以打电话,平板电脑可以看视频,客户端只依赖以上三个类便可以实现自己的业务。此时,我们应该给每种产品找到他们的代工厂,这里无需担心的是由于客户端对代工厂是无感知的,只要你找到新的供应商,客户端总能拿到最新的具体产品。
我们准备占领高端电子消费市场,所以先找了华为帮我们生产设备。
public class HuaweiFactory implements AbstractFactory
public Phone createPhone()
return new Mate40Pro();
public Pad createPad()
return new MatePad();
public class Mate40Pro implements Phone
@Override
public void call(String phoneNumber)
System.out.println("使用华为 Mate 40 Pro 手机给" + phoneNumber + "打电话");
public class MatePad implements Pad
@Override
public void watchVideo()
System.out.println("使用华为Mate平板看视频就是爽");
后来我们发现下沉市场也潜力无穷,又抓紧找了小米代工厂。
public class XiaomiFactory implements AbstractFactory
public Phone createPhone()
return new Xiaomi6();
public Pad createPad()
return new XiaomiPad();
public class Xiaomi6 implements Phone
@Override
public void call(String phoneNumber)
System.out.println("使用小米6手机给" + phoneNumber + "打电话");
public class XiaomiPad implements Pad
@Override
public void watchVideo()
System.out.println("使用小米平板看视频就是爽");
工厂和产品都有了,我们如何选择具体工厂可以交给客户端去处理,但更合理的做法一般是通过读取系统配置来实现。
public class DeviceClient
public static void main(String[] args)
// 实例化一个工厂
AbstractFactory factory = new HuaweiFactory();
// 生产一台手机
Phone phone = factory.createPhone();
phone.call("13100001111");
// 生产一台平板
Pad pad = factory.createPad();
phone.watchVideo();
输出:
使用华为 Mate 40 Pro 手机给13100001111打电话
使用华为Mate平板看视频就是爽
从例子中看起来似乎使用抽象工厂模式的收益并不高,反而增加了大量的代码导致更难理解,这是因为示例的主要目的是方便了该模式的结构,所以使用了过于简单场景来进行阐述。事实上在实际开发过程中我们会遇到更为复杂的业务场景和需求,如果不选择一种合适的开发方式,不光会产生大量的重复代码,还会打破开闭原则,最终导致系统难以维护。
Spring中的抽象工厂
在 Spring
框架中使用了大量的设计模式,其中 BeanFactory
和 FactoryBean
经常被拉出来解读。
阅读 FactoryBean
的源码,通过第一句 Interface to be implemented by objects used within a @link BeanFactory whichare themselves factories for individual objects. (由 BeanFactory
中使用的对象实现的接口,这些对象本身就是各个对象的工厂)很容易知道这是一个提供各种 Bean
的抽象工厂。具体工厂实现有很多:
通过这些不同的工厂实现,让 Spring
框架的基础架构变得非常稳定。下图是 FactoryBean
的类头注释,写得相当清楚。留一个小思考,为什么这个工厂的命名叫 xxxBean
而不是 xxxFactory
?
重构发货模块!
如果我们的代码需要与不同的产品族打交道,或者考虑到未来扩展性,那么使用抽象工厂模式将是一个好的选择。
假如我们现在维护一个电商平台,我们卖的不光有日用百货,还有水果生鲜,那这两个品类的派送我们都委托给第三方物流公司来做。最开始规模小,只跟顺丰一家物流商合作各方面成本最低,这时候代码随便写,怎么都能跑起来,但是随着贸易规模的扩大,要全国各地发货,不同地区的时效要求也不一样,那么接入更多的物流公司来参与合作无异更好。但是系统发展往往是跟业务发展脚步不齐的,日久天长以后,我们系统里充斥着大量的「兼容性」代码,每新增加一个物流商,就要把之前的程序员拉出来鞭一次尸。。。
原发货代码
1.发货服务代码:
public interface ShippingService
/**
* 普通快递发货
*/
void shipNormal(String goodsName, String logisticsChannel);
/**
* 冷链物流发货
*/
void shipFresh(String goodsName, String logisticsChannel);
public class ShippingServiceImpl implements ShippingService
private ShunfengLogisticsAdapter shunfengLogisticsAdapter;
private JingdongLogisticsAdapter jingdongLogisticsAdapter;
// 省略构造器
@Override
public void shipNormal(String goodsName, String logisticsChannel)
String orderNo;
int logisticsFee;
if ("shunfeng".equalsIgnoreCase(logisticsChannel))
JingdongOrder order = jingdongLogisticsAdapter.createOrder(goodsName);
orderNo = order.getOrderNo();
logisticsFee = order.getLogisticsFee();
else if ("jingdong".equalsIgnoreCase(logisticsChannel))
ShunfengOrder order = shunfengLogisticsAdapter.createOrder(goodsName);
orderNo = order.getOrderNo();
logisticsFee = order.getLogisticsFee();
else
throw new RuntimeException("物流渠道错误");
System.out.println(goodsName + "发货完成,运单号=" + orderNo + ",费用=" + logisticsFee + "元.");
@Override
public void shipFresh(String goodsName, String logisticsChannel)
String orderNo;
int logisticsFee;
if ("shunfeng".equalsIgnoreCase(logisticsChannel))
JingdongFreshOrder order = jingdongLogisticsAdapter.createFreshOrder(goodsName);
orderNo = order.getOrderNo();
logisticsFee = order.getLogisticsFee();
else if ("jingdong".equalsIgnoreCase(logisticsChannel))
ShunfengFreshOrder order = shunfengLogisticsAdapter.createFreshOrder(goodsName);
orderNo = order.getOrderNo();
logisticsFee = order.getLogisticsFee();
else
throw new RuntimeException("物流渠道错误");
System.out.println(goodsName + "发货完成,运单号=" + orderNo + ",费用=" + logisticsFee + "元.");
2.不同物流商的订单服务代码
/**
* 顺丰订单服务
*/
public class ShunfengLogisticsAdapter
/**
* 创建速运订单
*/
public ShunfengOrder createOrder(String goodsName)
return new ShunfengOrder("SF" + System.currentTimeMillis());
/**
* 创建生鲜订单
*/
public ShunfengFreshOrder createFreshOrder(String goodsName)
return new ShunfengFreshOrder("SF" + System.currentTimeMillis());
/**
* 京东物流订单服务
*/
public class JingdongLogisticsAdapter
/**
* 创建物流订单
*/
public JingdongOrder createOrder(String goodsName)
return new JingdongOrder("JD" + System.currentTimeMillis());
/**
* 创建生鲜订单
*/
public JingdongFreshOrder createFreshOrder(String goodsName)
return new JingdongFreshOrder("JD" + System.currentTimeMillis());
3.不同物流商的订单模型代码
// 顺丰速运订单
public class ShunfengOrder
private String orderNo;
private int logisticsFee = 10;
// ...省略getter/setter
// 顺丰生鲜订单
public class ShunfengFreshOrder extends ShunfengOrder
/**
* 生鲜订单配送费要贵
*/
@Override
public int getLogisticsFee()
return super.getLogisticsFee() * 2;
// 京东速运订单
public class JingdongOrder
private String orderNo;
private int logisticsFee = 8;
// ...省略getter/setter
// 京东生鲜订单
public class JingdongFreshOrder extends JingdongOrder
/**
* 京东生鲜配送费固定价15块
*/
private int logisticsFee = 15;
测试代码:
public class ShippingServiceTest
@Test
public void shipNormal()
ShunfengLogisticsAdapter shunfengLogisticsAdapter = new ShunfengLogisticsAdapter();
JingdongLogisticsAdapter jingdongLogisticsAdapter = new JingdongLogisticsAdapter();
ShippingService shippingService = new ShippingServiceImpl(shunfengLogisticsAdapter, jingdongLogisticsAdapter);
shippingService.shipNormal("高端智能空调", "shunfeng");
shippingService.shipNormal("低端不智能只制冷空调", "jingdong");
@Test
public void shipFresh()
ShunfengLogisticsAdapter shunfengLogisticsAdapter = new ShunfengLogisticsAdapter();
JingdongLogisticsAdapter jingdongLogisticsAdapter = new JingdongLogisticsAdapter();
ShippingService shippingService = new ShippingServiceImpl(shunfengLogisticsAdapter, jingdongLogisticsAdapter);
shippingService.shipFresh("新鲜海底捞出来大螃蟹", "shunfeng");
shippingService.shipFresh("新鲜獐子岛逃跑扇贝", "jingdong");
输出:
// 普通快递发货
新鲜海底捞出来大螃蟹发货完成,运单号=JD1657108945504,费用=10元.
新鲜獐子岛逃跑扇贝发货完成,运单号=SF1657108945505,费用=20元.
// 冷链物流发货
高端智能空调发货完成,运单号=JD1657108945510,费用=8元.
低端不智能只制冷空调发货完成,运单号=SF1657108945510,费用=10元.
虽然代码跑起来也没问题,但是只要增加新的供应商,就难免要对 ShippingService
里每个发货的业务代码都改一遍,这是一件极其恶心的事情,而且随着系统越来越庞大、复杂,随意改动老代码将变得很危险。
所以我们一定要赶在业务起飞之前重构掉它,解决历史包袱,提高系统的扩展性和可维护性。
按照业务规则进行分析,ShippingService
是系统的业务入口,对具体物流服务和订单有直接依赖,那我们第一步就是先抽象出物流服务和订单,使ShippingService
只依赖接口。
代码如下:
// 物流服务
public interface LogisticsService
Order createOrder(String goodsName);
Order createFreshOrder(String goodsName);
// 订单
public interface Order
String getOrderNo();
int getLogisticsFee();
在设计完抽象工厂和抽象产品之后,由于 ShippingService
已经不对实现有依赖,所以完全可以把原来用于选择物流服务的 logisticsChannel
字段从方法签名中移除。那么优化以后的发货代码如下:
// 发货服务
public interface ShippingService
void shipNormal(String goodsName);
void shipFresh(String goodsName);
// 发货服务实现
public class ShippingServiceImpl implements ShippingService
private LogisticsService logisticsService;
// ...省略构造器
@Override
public void shipNormal(String goodsName)
Order order = logisticsService.createOrder(goodsName);
String orderNo = order.getOrderNo();
int logisticsFee = order.getLogisticsFee();
System.out.println(goodsName + "发货完成,运单号=" + orderNo + ",费用=" + logisticsFee + "元.");
@Override
public void shipFresh(String goodsName)
Order order = logisticsService.createFreshOrder(goodsName);
String orderNo = order.getOrderNo();
int logisticsFee = order.getLogisticsFee();
System.out.println(goodsName + "发货完成,运单号=" + orderNo + ",费用=" + logisticsFee + "元.");
此时业务代码已经相当简洁,不过由于我们把抽象工厂作为发货服务的成员变量,所以在实际的使用过程中会比原来稍微复杂一些。
测试代码:
public class ShippingServiceImplTest
@Test
public void shipNormal()
LogisticsService logisticsService = new ShunfengLogisticsAdapter();
ShippingService shippingService = new ShippingServiceImpl(logisticsService);
shippingService.shipNormal("高端智能空调");
logisticsService = new JingdongLogisticsAdapter();
shippingService = new ShippingServiceImpl(logisticsService);
shippingService.shipNormal("低端不智能只制冷空调");
@Test
public void shipFresh()
LogisticsService logisticsService = new ShunfengLogisticsAdapter();
ShippingService shippingService = new ShippingServiceImpl(logisticsService);
shippingService.shipFresh("新鲜海底捞出来大螃蟹");
logisticsService = new ShunfengLogisticsAdapter();
shippingService = new ShippingServiceImpl(logisticsService);
shippingService.shipFresh("新鲜獐子岛逃跑扇贝");
输出:
// 普通快递发货
新鲜海底捞出来大螃蟹发货完成,运单号=SF1657110011628,费用=20元.
新鲜獐子岛逃跑扇贝发货完成,运单号=SF1657110011628,费用=20元.
// 冷链物流发货
高端智能空调发货完成,运单号=SF1657110011635,费用=10元.
低端不智能只制冷空调发货完成,运单号=JD1657110011649,费用=8元.
重构完成以后,如果增加新的物流供应商,完全无需对发货服务做任何改变。当然代码优化到这里并不算完,现在一个物流供应商会对应生成一个 ShippingService
实例,是完全没有必要的,不过这不算个大问题,我们有很多的方法来让代码变得更优雅,这里不做赘述。
小结
抽象工厂模式和工厂方法模式的差异:
构成\\模式 | 工厂方法模式 | 抽象工厂模式 |
---|---|---|
工厂接口 | 无 | 有 |
具体工厂实现 | 有1个 | 有多个 |
产品接口 | 有1个 | 有多个产品接口构成产品族 |
具体产品实现 | 有多个 | 每个产品都有多个实现 |
前面提到抽象工厂模式主要用来解决「产品族」的创建问题,核心点是产品族和对工厂进行抽象。当抽象工厂的实现类只创建一种产品,也就是不存在「产品族」的时候,抽象工厂会退化成工厂方法模式,值得注意的是并不是把工厂方法模式里的工厂加上 Abstract
前缀就变成抽象工厂模式了。
设计模式虽然看着复杂,其实并不难学,当你带着设计模式的思想再去review实际情况,往往很容易就完成代码的优化工作,还是那句话,实践出真知,一起加油吧!
关注「Java实践笔记」公众号获取全部源代码!
版权声明:本文为公众号「Java实践笔记」的原创文章,转载请联系作者,附上原文出处链接及本声明。
原文链接:https://mp.weixin.qq.com/s/33YfZdDnIFJfDw5wDaa0bA
以上是关于设计模式实践笔记第二节:抽象工厂模式的主要内容,如果未能解决你的问题,请参考以下文章