设计模式 - 创建型模式_工厂方法模式
文章目录
- 创建型模式
- 概述
- Case
- Bad Impl
- Better Impl (⼯⼚模式优化代码)
- 接口定义
- 实现奖品发放接⼝
- 创建商店⼯⼚
- 单元测试
- 小结
创建型模式
创建型模式提供创建对象的机制, 能够提升已有代码的灵活性和可复⽤性。
类型 | 实现要点 |
---|---|
工厂方法 | 定义⼀个创建对象的接⼝,让其⼦类⾃⼰决定实例化哪⼀个⼯⼚类,⼯⼚模式使其创建过程延迟到⼦类进⾏。 |
抽象工厂 | 提供⼀个创建⼀系列相关或相互依赖对象的接⼝,⽽⽆需指定它们具体的类。 |
建造者 | 将⼀个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示 |
原型 | ⽤原型实例指定创建对象的种类,并且通过拷⻉这些原型创建新的对象。 |
单例 | 保证⼀个类仅有⼀个实例,并提供⼀个访问它的全局访问点。 |
概述
优秀的代码在结构设计上松耦合易读易扩展,在领域实现上⾼内聚不对外暴漏实现细节不被外部⼲扰。
⼯⼚模式⼜称⼯⼚⽅法模式,是⼀种创建型设计模式,其在⽗类中提供⼀个创建对象的⽅法, 允许⼦类决定实例化对象的类型。
它的主要意图是定义⼀个创建对象的接⼝,让其⼦类⾃⼰决定实例化哪⼀个⼯⼚类,⼯⼚模式使其创建过程延迟到⼦类进⾏。
优点: 简单说就是为了提供代码结构的扩展性,屏蔽每⼀个功能类中的具体实现逻辑。让外部可以更加简单的只是知道调⽤即可,同时,这也是去掉众多 ifelse 的⽅式。
缺点: ⽐如需要实现的类⾮常多,如何去维护,怎样减低开发成本。但这些问题都可以在后续的设计模式结合使⽤中,逐步降低。
Case
模拟积分兑换中的发放多种类型商品,假如现在我们有如下三种类型的商品接⼝
- 优惠券 :
CouponResult sendCoupon(String uId, String couponNumber, String uuid)
- 实物商品 :
Boolean deliverGoods(DeliverReq req)
- 第三⽅爱奇艺兑换卡:
void grantToken(String bindMobileNumber, String cardId)
从以上接⼝来看有如下信息:
三个接⼝返回类型不同,有对象类型、布尔类型、还有⼀个空类型。也可能会随着后续的业务的发展,会新增其他种商品类型。
Bad Impl
不考虑任何扩展性,只为了尽快满⾜需求,那么对这么⼏种奖励发放只需使⽤ifelse
语句判断,调⽤不同的接⼝即可满⾜需求。
【if else 大法实现】
public class PrizeController {private Logger logger = LoggerFactory.getLogger(PrizeController.class);public AwardRes awardToUser(AwardReq req) {String reqJson = JSON.toJSONString(req);AwardRes awardRes = null;logger.info("奖品发放开始{}。req:{}", req.getuId(), reqJson);// 按照不同类型方法商品[1优惠券、2实物商品、3第三方兑换卡(爱奇艺)]if (req.getAwardType() == 1) {.....................awardRes = new AwardRes("0000", "发放成功");} else if (req.getAwardType() == 2) {.....................awardRes = new AwardRes("0000", "发放成功");} else if (req.getAwardType() == 3) {..................awardRes = new AwardRes("0000", "发放成功");}logger.info("奖品发放完成{}。", req.getuId());return awardRes;}..................}
这样的代码⽬前来看并不会有什么问题,但如果在经过⼏次的迭代和拓展,非常痛苦。
- 重构成本⾼,需要梳理之前每⼀个接⼝的使⽤;
- 测试回归验证时间⻓,需要全部验证⼀次。
这也就是很多⼈并不愿意接⼿别⼈的代码,如果接⼿了⼜被压榨开发时间。那么可想⽽知这样的 ifelse
还会继续增加。
【测试验证】
写⼀个单元测试来验证上⾯编写的接⼝⽅式
@Testpublic void test_awardToUser() {PrizeController prizeController = new PrizeController();System.out.println("\r\n模拟发放优惠券测试\r\n");// 模拟发放优惠券测试AwardReq req01 = new AwardReq();req01.setuId("10001");req01.setAwardType(1);req01.setAwardNumber("EGM1023938910232121323432");req01.setBizId("791098764902132");AwardRes awardRes01 = prizeController.awardToUser(req01);logger.info("请求参数:{}", JSON.toJSON(req01));logger.info("测试结果:{}", JSON.toJSON(awardRes01));System.out.println("\r\n模拟方法实物商品\r\n");// 模拟方法实物商品AwardReq req02 = new AwardReq();req02.setuId("10001");req02.setAwardType(2);req02.setAwardNumber("9820198721311");req02.setBizId("1023000020112221113");req02.setExtMap(new HashMap<String, String>() {{put("consigneeUserName", "谢飞机");put("consigneeUserPhone", "15200292123");put("consigneeUserAddress", "吉林省.长春市.双阳区.XX街道.檀溪苑小区.#18-2109");}});AwardRes awardRes02 = prizeController.awardToUser(req02);logger.info("请求参数:{}", JSON.toJSON(req02));logger.info("测试结果:{}", JSON.toJSON(awardRes02));System.out.println("\r\n第三方兑换卡(爱奇艺)\r\n");AwardReq req03 = new AwardReq();req03.setuId("10001");req03.setAwardType(3);req03.setAwardNumber("AQY1xjkUodl8LO975GdfrYUio");AwardRes awardRes03 = prizeController.awardToUser(req03);logger.info("请求参数:{}", JSON.toJSON(req03));logger.info("测试结果:{}", JSON.toJSON(awardRes03));}
日志输出
模拟发放优惠券测试14:16:29.947 [main] INFO com.artisan.PrizeController - 奖品发放开始10001。req:{"awardNumber":"EGM1023938910232121323432","awardType":1,"bizId":"791098764902132","uId":"10001"}
模拟发放优惠券一张:10001,EGM1023938910232121323432,791098764902132
14:16:29.951 [main] INFO com.artisan.PrizeController - 奖品发放完成10001。
14:16:29.953 [main] INFO com.artisan.ApiTest - 请求参数:{"uId":"10001","bizId":"791098764902132","awardNumber":"EGM1023938910232121323432","awardType":1}
14:16:29.955 [main] INFO com.artisan.ApiTest - 测试结果:{"code":"0000","info":"发放成功"}模拟方法实物商品14:16:29.956 [main] INFO com.artisan.PrizeController - 奖品发放开始10001。req:{"awardNumber":"9820198721311","awardType":2,"bizId":"1023000020112221113","extMap":{"consigneeUserName":"谢飞机","consigneeUserPhone":"15200292123","consigneeUserAddress":"吉林省.长春市.双阳区.XX街道.檀溪苑小区.#18-2109"},"uId":"10001"}
模拟发货实物商品一个:{"consigneeUserAddress":"吉林省.长春市.双阳区.XX街道.檀溪苑小区.#18-2109","consigneeUserName":"谢飞机","consigneeUserPhone":"15200292123","orderId":"1023000020112221113","sku":"9820198721311","userName":"花花","userPhone":"15200101232"}
14:16:29.959 [main] INFO com.artisan.PrizeController - 奖品发放完成10001。
14:16:29.959 [main] INFO com.artisan.ApiTest - 请求参数:{"extMap":{"consigneeUserName":"谢飞机","consigneeUserAddress":"吉林省.长春市.双阳区.XX街道.檀溪苑小区.#18-2109","consigneeUserPhone":"15200292123"},"uId":"10001","bizId":"1023000020112221113","awardNumber":"9820198721311","awardType":2}
14:16:29.959 [main] INFO com.artisan.ApiTest - 测试结果:{"code":"0000","info":"发放成功"}第三方兑换卡(爱奇艺)14:16:29.959 [main] INFO com.artisan.PrizeController - 奖品发放开始10001。req:{"awardNumber":"AQY1xjkUodl8LO975GdfrYUio","awardType":3,"uId":"10001"}
模拟发放爱奇艺会员卡一张:15200101232,AQY1xjkUodl8LO975GdfrYUio
14:16:29.960 [main] INFO com.artisan.PrizeController - 奖品发放完成10001。
14:16:29.960 [main] INFO com.artisan.ApiTest - 请求参数:{"uId":"10001","awardNumber":"AQY1xjkUodl8LO975GdfrYUio","awardType":3}
14:16:29.960 [main] INFO com.artisan.ApiTest - 测试结果:{"code":"0000","info":"发放成功"}
运⾏结果正常,满⾜当前所有业务产品需求,写的还很快。但实在难以为维护!
Better Impl (⼯⼚模式优化代码)
接下来使⽤⼯⼚⽅法模式来进⾏代码优化,也算是⼀次很⼩的重构。整理重构后代码结构清晰了、也具备了下次新增业务需求的扩展性。
相关类的具体作用如下:
代码目录如下:
从上⾯的⼯程结构中: 它看上去清晰了、这样分层可以更好扩展了、似乎可以想象到每⼀个类做了什么。
接口定义
public interface ICommodity {void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception;}
- 所有的奖品⽆论是实物、虚拟还是第三⽅,都需要实现此接⼝进⾏处理,以保证最终⼊参出参的统⼀性。
- 接⼝的⼊参包括; ⽤户ID 、 奖品ID 、 业务ID 以及 扩展字段 ⽤于处理发放实物商品时的收获地址
实现奖品发放接⼝
【优惠券】
public class CouponCommodityService implements ICommodity {private Logger logger = LoggerFactory.getLogger(CouponCommodityService.class);private CouponService couponService = new CouponService();public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {CouponResult couponResult = couponService.sendCoupon(uId, commodityId, bizId);logger.info("请求参数[优惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));logger.info("测试结果[优惠券]:{}", JSON.toJSON(couponResult));if (!"0000".equals(couponResult.getCode())) throw new RuntimeException(couponResult.getInfo());}}
【实物商品】
public class GoodsCommodityService implements ICommodity {private Logger logger = LoggerFactory.getLogger(GoodsCommodityService.class);private GoodsService goodsService = new GoodsService();public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {DeliverReq deliverReq = new DeliverReq();deliverReq.setUserName(queryUserName(uId));deliverReq.setUserPhone(queryUserPhoneNumber(uId));deliverReq.setSku(commodityId);deliverReq.setOrderId(bizId);deliverReq.setConsigneeUserName(extMap.get("consigneeUserName"));deliverReq.setConsigneeUserPhone(extMap.get("consigneeUserPhone"));deliverReq.setConsigneeUserAddress(extMap.get("consigneeUserAddress"));Boolean isSuccess = goodsService.deliverGoods(deliverReq);logger.info("请求参数[实物商品] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));logger.info("测试结果[实物商品]:{}", isSuccess);if (!isSuccess) throw new RuntimeException("实物商品发放失败");}private String queryUserName(String uId) {return "花花";}private String queryUserPhoneNumber(String uId) {return "15200101232";}}
【第三方兑换卡】
public class CardCommodityService implements ICommodity {private Logger logger = LoggerFactory.getLogger(CardCommodityService.class);// 模拟注入private IQiYiCardService iQiYiCardService = new IQiYiCardService();public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {String mobile = queryUserMobile(uId);iQiYiCardService.grantToken(mobile, bizId);logger.info("请求参数[爱奇艺兑换卡] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));logger.info("测试结果[爱奇艺兑换卡]:success");}private String queryUserMobile(String uId) {return "15200101232";}}
从上⾯可以看出
每⼀种奖品的实现都包括在⾃⼰的类中,新增、修改或者删除都不会影响其他奖品功能的测试,降低回归测试的可能。
如果有新增的奖品只需要按照此结构进⾏填充对应的实现类即可,易于维护和扩展。
在统⼀了⼊参以及出参后,调⽤⽅不在需要关⼼奖品发放的内部逻辑,按照统⼀的⽅式即可处理
创建商店⼯⼚
public class StoreFactory {/*** 奖品类型方式实例化* @param commodityType 奖品类型* @return 实例化对象*/public ICommodity getCommodityService(Integer commodityType) {if (null == commodityType) return null;if (1 == commodityType) return new CouponCommodityService();if (2 == commodityType) return new GoodsCommodityService();if (3 == commodityType) return new CardCommodityService();throw new RuntimeException("不存在的奖品服务类型");}/*** 奖品类信息方式实例化* @param clazz 奖品类* @return 实例化对象*/public ICommodity getCommodityService(Class<? extends ICommodity> clazz) throws IllegalAccessException, InstantiationException {if (null == clazz) return null;return clazz.newInstance();}}
这⾥我们定义了⼀个商店的⼯⼚类,在⾥⾯按照类型实现各种商品的服务,后续新增的商品在这⾥扩展即可。
提供了两种获取工厂实现类的方法: 一种是根据商品类型,另外一种是根据奖品类信息进行催熟啊,
如果不喜欢 if 判断,也可以使⽤ switch 或者 map (key是类型值,value是具体的实现逻辑) 配置结构,会让代码更加⼲净。
单元测试
@Testpublic void test_StoreFactory_01() throws Exception {StoreFactory storeFactory = new StoreFactory();// 1. 优惠券ICommodity commodityService_1 = storeFactory.getCommodityService(1);commodityService_1.sendCommodity("10001", "EGM1023938910232121323432", "791098764902132", null);// 2. 实物商品ICommodity commodityService_2 = storeFactory.getCommodityService(2);commodityService_2.sendCommodity("10001", "9820198721311", "1023000020112221113", new HashMap<String, String>() {{put("consigneeUserName", "谢飞机");put("consigneeUserPhone", "15200292123");put("consigneeUserAddress", "吉林省.长春市.双阳区.XX街道.檀溪苑小区.#18-2109");}});// 3. 第三方兑换卡(模拟爱奇艺)ICommodity commodityService_3 = storeFactory.getCommodityService(3);commodityService_3.sendCommodity("10001", "AQY1xjkUodl8LO975GdfrYUio", null, null);}@Testpublic void test_StoreFactory_02() throws Exception {StoreFactory storeFactory = new StoreFactory();// 1. 优惠券ICommodity commodityService = storeFactory.getCommodityService(CouponCommodityService.class);commodityService.sendCommodity("10001", "EGM1023938910232121323432", "791098764902132", null);}
可以看到在进⾏封装后可以⾮常清晰的看到⼀整套发放奖品服务的完整性,统⼀了⼊参、统⼀了结果。
小结
- ⼯⼚⽅法模式并不复杂,可以使开发结构更加简单。
- 避免创建者与具体的产品逻辑耦合 、 满⾜单⼀职责,每⼀个业务逻辑实现都在所属⾃⼰的类中完成 、 满⾜开闭原则,⽆需更改使⽤调⽤⽅就可以在程序中引⼊新的产品类型 。
- 也会带来⼀些问题,⽐如有⾮常多的奖品类型,那么实现的⼦类会极速扩张。因此也需要使⽤其他的模式进⾏优化.
设计模式 - 创建型模式_工厂方法模式相关推荐
- 工厂方法模式_工厂方法模式
工厂方法模式是简单工厂模式的升级版,简单工厂模式不符合设计模式的原则(即:单一职责,开闭原则) 优点: 职责明确,扩展方便 缺点:需要创建多个工厂 实现步骤: 1.将工厂通用方法抽取接口 (例如:IF ...
- 创建型模式之工厂方法模式
概述 在简单工厂模式中只提供一个工厂类,该工厂类处于对产品类进行实例化的中心位置,它需要知道每一个产品对象的创建细节,并决定何时实例化哪一个产品类.简单工厂模式最大的缺点是当有新产品要加入到系统中时, ...
- 23种设计模式之简单工厂模式,工厂方法模式,抽象工厂模式详解
工厂模式详解 1. 简单工厂模式 1.1 需求分析 1.2 使用传统方式实现 1.2.1 类图 1.2.2 代码实现 1.2.2.1 新建pizza抽象类 1.2.2.2 希腊披萨实现类 1.2.2. ...
- 设计模式——工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)
声明: 本博客参考C语言中文网:C语言中文网连接 主要记录学习以下内容: 一.简单工厂模式 二.工厂方法模式 三.抽象工厂模式 每个大要点都主要包括以下两部分内容: (1)基本概念和模型结构(包括一些 ...
- JAVA设计模式——工厂模式【简单工厂模式、工厂方法模式、抽象工厂模式】
目录 简单工厂模式 传统方式 简单工厂模式 静态工厂模式 工厂方法模式 抽象工厂模式 工厂模式JDK-Calendar源码分析 工厂模式小结 简单工厂模式 看一个具体的需求 看一个披萨的项目:要便 ...
- 深入理解设计模式-简单工厂模式vs工厂方法模式vs抽象工厂模式对比讲解
文章目录 前言 一.简单工厂模式 1.描述 2.特点 3.优缺点 4.类图说明 二.工厂方法模式 1.描述 2.特点 3.适用场景 4.类图说明 5.简单工厂模式与工厂方法模式区别: 三.抽象工厂模式 ...
- 设计模式之工厂类模式总结对比、简单工厂模式、工厂方法模式、抽象工厂模式、带反射的工厂模式、例子代码分析、最详细
1. 题目 假设某公司同时用SqlServer.MySql数据库,即会切换两数据库(不同数据库的sql语句有些许差异),同时,两数据库里均有对Users.Departments表的操作(sql代码不一 ...
- 设计模式系列——三个工厂模式(简单工厂模式,工厂方法模式,抽象工厂模式)...
转自:http://www.cnblogs.com/stonehat/archive/2012/04/16/2451891.html 设计模式系列--三个工厂模式(简单工厂模式,工厂方法模式,抽象工厂 ...
- 初识设计模式之简单工厂模式、工厂方法模式、抽象工厂模式
简单工厂模式 工厂方法模式 抽象工厂模式 工厂顾名思义就是生产产品的意思,根据产品是具体产品还是具体工厂可分为简单工厂模式和工厂方法模式,根据工厂的抽象程度可分为工厂方法模式和抽象工厂模式.该模式 ...
最新文章
- JAVA基础16-Java匿名内部类
- [分布式学习]消息队列之rocketmq笔记
- 如何在Kubernetes上运行Apache Flink
- python turtle 绘图_谈一下Pycharm中关联系统Python解释器的方法
- java 反射机制 视频_【视频笔记】Java反射机制笔记
- 错过SaaS,就是错过这个时代
- layui清空表单数据_layui表格怎么清空
- 计蒜客模拟赛D2T3 蒜头君救人:用bfs转移状压dp
- 【C语言数据结构】双向循环链表
- PPT转图片/PDF-实用干货
- sklearn 5.18.3 SGD - Maximum margin separating hyperplane
- 重写equals方法一定要重写hashcode方法吗
- 中国书法列入非物质文化遗产
- 使用MindStudio进行城市道路交通预测
- Python 学习笔记9 循环语句 For in
- 关于电脑使用墨墨背单词使用键盘操作问题
- 写一篇以名字叫御坂网络20002的魅魔为主角打败女勇者的小说
- 【图书资料】编译原理三大经典书籍(龙书 虎书 鲸书)
- 《General Virtual Sketching Framework for Vector Line Art》论文介绍
- 为c/c++程序设置默认头文件
热门文章
- java-通信-ip-1
- 按键扫描——74HC164驱动(一)
- 大江大河——通信设备商们的2019年
- android 函数式编程,思想交融,Android中的函数式编程(2):什么是函数式编程...
- UI设计师注意,网站头图的10个黄金法则
- 搭建Kubernetes多节点集群
- 软件工程考C语言的学校,软件考研学校排名,软件工程性价比较高的考研学校有哪些?...
- python提取cad坐标_教你一个CAD坐标提取的小技巧
- mysql时间格式化%Y与%y_MySQL日期格式化 DATE_FORMAT() 函数
- MongoDB学习笔记~对集合属性的操作