设计模式学习笔记-2 创建者模式-工厂方法模式

工厂模式介绍

工厂模式又称工厂方法模式,是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。

这种设计模式使Java开发中最常见的一种设计模式,他的主要意图是定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

简单说就是为了提供代码结构的扩展性,屏蔽每一个功能类中的具体实现逻辑。让外部可以更加简单的只是知道调用即可,同时,这也是去掉众多if else的方式,这种方式也存在一些缺点,比如需要实现的类非常多,如何去维护,怎样减低开发成本。但这些问题可以在后续的设计模式结合使用中,逐步降低。

模拟发奖多种商品

场景:用户使用积分兑换多种类型商品,假设现有以下三种类型的商品接口

序号 类型 接口
1 优惠券 CouponResult sendCoupon(String uId, String couponNumber, String uuid)
2 实物商品 Boolean deliverGoods(DeliverReq req)
3 第三方爱奇艺兑换卡 void grantToken(String bindMobileNumber, String cardId)

以上接口有如下信息:

  • 三个接口返回类型不同,有对象类型,布尔类型,还有一个空类型。
  • 入参不同,发放优惠券需要仿重,兑换卡需要卡ID,实物商品需要发货位置。
  • 另外可能会随着后续的业务的发展,会新增其他种商品类型。因为你所有的开发需求都是随着业务对市场的拓展而带来的。

使用if else实现

请求参数:

import lombok.Data;
import lombok.ToString;import java.util.Map;@Data
@ToString
public class AwardReq {private String uId;private Integer awardType;private String awardNumber;private String bizId;private Map<String, String> extMap;
}

响应参数:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class AwardRes {private String code;private String message;
}

优惠券接口响应结果:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class CouponResult {private String code;private String message;
}

优惠券发放接口:

public class CouponService {public CouponResult sendCoupon(String uId, String couponNumber, String uuid) {return new CouponResult("0000", "优惠券发送成功");}}

实物商品发放接口请求参数:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeliverReq {private String username;private String userPhone;private String sku;private String orderId;private String consigneeUsername;private String consigneeUserPhone;private String consigneeUserAddress;
}

实物商品发放接口:

public class GoodsService {public Boolean deliverGoods(DeliverReq req) {return true;}
}

第三方兑换(爱奇艺)接口:

public class IQiYiCardService {public void grantToken(String bindMobileNumber, String cardId) {}
}

兑换接口:

import lombok.extern.slf4j.Slf4j;@Slf4j
public class PrizeController {public AwardRes awardToUser(AwardReq req) {AwardRes awardRes = null;try {log.info("奖品发放开始{},req:{}", req.getUId(), req);if (req.getAwardType() == 1) {CouponService couponService = new CouponService();CouponResult couponResult = couponService.sendCoupon(req.getUId(), req.getAwardNumber(), req.getBizId());if ("0000".equals(couponResult.getCode())) {awardRes = new AwardRes("0000", "发放成功");} else {awardRes = new AwardRes("0001", couponResult.getMessage());}} else if (req.getAwardType() == 2) {GoodsService goodsService = new GoodsService();DeliverReq deliverReq = new DeliverReq();deliverReq.setUsername(queryUsername(req.getUId()));deliverReq.setUserPhone(queryUserPhoneNumber(req.getUId()));deliverReq.setSku(req.getAwardNumber());deliverReq.setOrderId(req.getBizId());deliverReq.setConsigneeUsername(req.getExtMap().get("consigneeUserName"));deliverReq.setConsigneeUserPhone(req.getExtMap().get("consigneeUserPhone"));deliverReq.setConsigneeUserAddress(req.getExtMap().get("consigneeUserAddress"));Boolean isSuccess = goodsService.deliverGoods(deliverReq);if (isSuccess) {awardRes = new AwardRes("0000", "发放成功");} else {awardRes = new AwardRes("0001", "发放失败");}} else if (req.getAwardType() == 3) {String bindMobileNumber = queryUserPhoneNumber(req.getUId());IQiYiCardService iQiYiCardService = new IQiYiCardService();iQiYiCardService.grantToken(bindMobileNumber,req.getAwardNumber());awardRes = new AwardRes("0000", "发放成功");}log.info("奖品发放完成{}", req.getUId());} catch (Exception e) {log.error("奖品发放失败{},req:{}", req.getUId(), req);awardRes = new AwardRes("0001", e.getMessage());}return awardRes;}private String queryUserPhoneNumber(String uId) {return "18800001111";}private String queryUsername(String uId) {return "yaroo";}
}

单元测试:

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;import java.util.HashMap;
import java.util.Map;@Slf4j
class PrizeControllerTest {@Testvoid awardToUser() {PrizeController prizeController = new PrizeController();// 模拟发放优惠券测试System.out.println("\r\n模拟发放优惠券测试\r\n");AwardReq req01 = new AwardReq();req01.setUId("1001");req01.setAwardType(1);req01.setAwardNumber("EGM1023938910232121323432");req01.setBizId("791098764902132");AwardRes awardRes01 = prizeController.awardToUser(req01);log.info("请求参数:{}", req01);log.info("测试结果:{}", awardRes01);// 模拟发放实物商品System.out.println("\r\n模拟发放实物商品\r\n");AwardReq req02 = new AwardReq();req02.setUId("10001");req02.setAwardType(2);req02.setAwardNumber("9820198721311");req02.setBizId("1023000020112221113");Map<String, String> extMap = new HashMap<>();extMap.put("consigneeUserName", "谢⻜机");extMap.put("consigneeUserPhone", "15200292123");extMap.put("consigneeUserAddress", "吉林省.⻓春市.双阳区.XX街道.檀溪苑⼩区.#18-2109");req02.setExtMap(extMap);AwardRes awardRes02 = prizeController.awardToUser(req02);log.info("请求参数:{}", req02);log.info("测试结果:{}", 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);log.info("请求参数:{}", req03);log.info("测试结果:{}", awardRes03);}
}

测试结果:


模拟发放优惠券测试15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeController - 奖品发放开始1001,req:AwardReq(uId=1001, awardType=1, awardNumber=EGM1023938910232121323432, bizId=791098764902132, extMap=null)
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeController - 奖品发放完成1001
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeControllerTest - 请求参数:AwardReq(uId=1001, awardType=1, awardNumber=EGM1023938910232121323432, bizId=791098764902132, extMap=null)
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeControllerTest - 测试结果:AwardRes(code=0000, message=发放成功)模拟发放实物商品15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeController - 奖品发放开始10001,req:AwardReq(uId=10001, awardType=2, awardNumber=9820198721311, bizId=1023000020112221113, extMap={consigneeUserName=谢⻜机, consigneeUserPhone=15200292123, consigneeUserAddress=吉林省.⻓春市.双阳区.XX街道.檀溪苑⼩区.#18-2109})
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeController - 奖品发放完成10001
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeControllerTest - 请求参数:AwardReq(uId=10001, awardType=2, awardNumber=9820198721311, bizId=1023000020112221113, extMap={consigneeUserName=谢⻜机, consigneeUserPhone=15200292123, consigneeUserAddress=吉林省.⻓春市.双阳区.XX街道.檀溪苑⼩区.#18-2109})
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeControllerTest - 测试结果:AwardRes(code=0000, message=发放成功)第三方兑换卡(爱奇艺)15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeController - 奖品发放开始10001,req:AwardReq(uId=10001, awardType=3, awardNumber=AQY1xjkUodl8LO975GdfrYUio, bizId=null, extMap=null)
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeController - 奖品发放完成10001
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeControllerTest - 请求参数:AwardReq(uId=10001, awardType=3, awardNumber=AQY1xjkUodl8LO975GdfrYUio, bizId=null, extMap=null)
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeControllerTest - 测试结果:AwardRes(code=0000, message=发放成功)

工厂模式优化代码

发奖接口:

import java.util.Map;public interface ICommodity {void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception;
}
  • 所有的奖品无论是实物,虚拟还是第三方,都需要通过我们的程序实现此接口进行处理,以保证最终入参与出参的统一性。
  • 接口的入参包括:用户ID,奖品ID,业务ID以及扩展字段用于处理发放实物商品时的收获地址。

奖品发放接口

优惠券
import com.yaroo.head_first_demo.design_pattern.demo1_01.CouponResult;
import com.yaroo.head_first_demo.design_pattern.demo1_01.CouponService;
import com.yaroo.head_first_demo.design_pattern.demo1_02.store.ICommodity;
import lombok.extern.slf4j.Slf4j;import java.util.Map;@Slf4j
public class CouponCommodityService implements ICommodity {private final CouponService couponService = new CouponService();@Overridepublic void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {CouponResult couponResult = couponService.sendCoupon(uId, commodityId, bizId);log.info("请求参数[优惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, extMap);log.info("测试结果[优惠券]:{}", couponResult);if (!"0000".equals(couponResult.getCode())) {throw new RuntimeException(couponResult.getMessage());}}
}
实物商品
import com.yaroo.head_first_demo.design_pattern.demo1_01.DeliverReq;
import com.yaroo.head_first_demo.design_pattern.demo1_01.GoodsService;
import com.yaroo.head_first_demo.design_pattern.demo1_02.store.ICommodity;
import lombok.extern.slf4j.Slf4j;import java.util.Map;@Slf4j
public class GoodsCommodityService implements ICommodity {private final GoodsService goodsService = new GoodsService();@Overridepublic 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);log.info("请求参数[实物商品] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, extMap);log.info("测试结果[实物商品]:{}", isSuccess);if (!isSuccess) {throw new RuntimeException("实物商品发放失败");}}private String queryUserName(String uId) {return "花花";}private String queryUserPhoneNumber(String uId) {return "15200101232";}
}
第三方兑换卡
import com.yaroo.head_first_demo.design_pattern.demo1_01.IQiYiCardService;
import com.yaroo.head_first_demo.design_pattern.demo1_02.store.ICommodity;
import lombok.extern.slf4j.Slf4j;import java.util.Map;@Slf4j
public class CardCommodityService implements ICommodity {private final IQiYiCardService iQiYiCardService = new IQiYiCardService();@Overridepublic void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {String mobile = queryUserMobile(uId);iQiYiCardService.grantToken(mobile, bizId);log.info("请求参数[爱奇艺兑换卡] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, extMap);log.info("测试结果[爱奇艺兑换卡]:success");}private String queryUserMobile(String uId) {return "15200101232";}
}
  • 上面所有的奖品的发放类可以看出,每一种的奖品的实现都包括在自己的类中,新增,修改或者删除都不会影响其他奖品功能的测试,降低回归测试的可能。
  • 后续在新增的奖品只需要按照此结构进行填充即可,非常易于维护和扩展。
  • 在统一处理了入参与出参后,调用方不需要关心奖品发放的内部逻辑,按照统一的方式即可处理。

商店工厂类

import com.yaroo.head_first_demo.design_pattern.demo1_02.store.ICommodity;
import com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.CardCommodityService;
import com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.CouponCommodityService;
import com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.GoodsCommodityService;public class StoreFactory {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("不存在的商品服务类型");}
}

统一定义了一个商品的工厂类,在里面按照类型实现各种商品的服务。

单元测试

测试类:

import com.yaroo.head_first_demo.design_pattern.demo1_02.store.ICommodity;
import org.junit.jupiter.api.Test;import java.util.HashMap;
import java.util.Map;class StoreFactoryTest {@Testvoid getCommodityService() 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);Map<String, String> extMap = new HashMap<>();extMap.put("consigneeUserName", "谢⻜机");extMap.put("consigneeUserPhone", "15200292123");extMap.put("consigneeUserAddress", "吉林省.⻓春市.双阳区.XX街道.檀溪苑⼩区.#18-2109");commodityService_2.sendCommodity("10001", "9820198721311", "102300002011222 1113", extMap);// 3. 第三⽅兑换卡(爱奇艺)ICommodity commodityService_3 = storeFactory.getCommodityService(3);commodityService_3.sendCommodity("10001", "AQY1xjkUodl8LO975GdfrYUio", null, null);}
}

测试结果:

16:02:32.867 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.CouponCommodityService - 请求参数[优惠券] => uId:10001 commodityId:EGM1023938910232121323432 bizId:791098764902132 extMap:null
16:02:32.883 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.CouponCommodityService - 测试结果[优惠券]:CouponResult(code=0000, message=优惠券发送成功)
16:02:32.883 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.GoodsCommodityService - 请求参数[实物商品] => uId:10001 commodityId:9820198721311 bizId:102300002011222 1113 extMap:{consigneeUserName=谢⻜机, consigneeUserPhone=15200292123, consigneeUserAddress=吉林省.⻓春市.双阳区.XX街道.檀溪苑⼩区.#18-2109}
16:02:32.883 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.GoodsCommodityService - 测试结果[实物商品]:true
16:02:32.883 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.CardCommodityService - 请求参数[爱奇艺兑换卡] => uId:10001 commodityId:AQY1xjkUodl8LO975GdfrYUio bizId:null extMap:null
16:02:32.883 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.CardCommodityService - 测试结果[爱奇艺兑换卡]:success

总结

  • 避免创建者与具体的产品逻辑耦合
  • 满足单一职责,每一个业务逻辑实现都在所属自己的类中完成
  • 满足开闭原则,无需更改使用调用方就可以在程序中引起新的产品类型

设计模式学习笔记-2 创建者模式-工厂方法模式相关推荐

  1. java/android 设计模式学习笔记(3)---工厂方法模式

    这篇来介绍一下工厂方法模式(Factory Method Pattern),在实际开发过程中我们都习惯于直接使用 new 关键字用来创建一个对象,可是有时候对象的创造需要一系列的步骤:你可能需要计算或 ...

  2. 大话设计模式读书 笔记(六) 工厂方法模式

    工厂方法模式(Factory Method) 书中通过一个学雷锋的例子,来引出工厂方法模式. 工厂方法模式:定义了一个用于创建对象的接口,让子类决定实例化哪一个类.工厂方法使一个类的实例化延迟到其子类 ...

  3. 重学Java设计模式-创建者模式-工厂方法模式

    重学Java设计模式-创建者模式-工厂方法模式 内容摘自:重学 Java 设计模式:实战工厂方法模式「多种类型商品不同接口,统一发奖服务搭建场景」 | bugstack 虫洞栈 工厂方法模式介绍 图片 ...

  4. 【设计模式】 - 创建者模式 - 工厂方法模式、抽象工程模式

    工厂方法模式.抽象工程模式 前言 工厂方法模式. 抽象工程模式 1. 简单工厂模式(不属于GOF的23种经典设计模式) 1.1 结构 1.2 实现 缺点 2. 工厂方法模式 2.1 结构 2.2 实现 ...

  5. 设计模式学习笔记(十七)——Command命令模式

    设计模式学习笔记(十七)--Command命令模式 Command命令模式介绍: Command命令模式是一种对象行为型模式,它主要解决的问题是:在软件构建过程中,"行为请求者"与 ...

  6. 设计模式学习笔记——责任链(Chain of Responsibility)模式

    设计模式学习笔记--责任链(Chain of Responsibility)模式 @(设计模式)[设计模式, 责任链模式, chain of responsibility] 设计模式学习笔记责任链Ch ...

  7. 【设计模式】简单工厂模式+工厂方法模式+抽象工厂模式

    前提导论 故事 不采用工厂模式 简单工厂模式 工厂方法模式 故事 抽象工厂模式 故事结局 前提导论 为了学习设计模式时便于理解,我将用基于农夫和他的村子与森林为背景环境,讲一个故事时,阐述一个设计模式 ...

  8. 策略模式和工厂模式的区别_设计模式之工厂模式-工厂方法模式

    设计模式之工厂模式-工厂方法模式 大家好,欢迎来到污污弹公司,今天司小司又接到了一个新活-披萨项目. 来源:凯哥Java(kaigejava) 需求: 披萨项目: 要方便披萨品种的扩展.要便于维护.要 ...

  9. JAVA设计模式之工厂模式(简单工厂模式+工厂方法模式)

    在面向对象编程中, 最通常的方法是一个new操作符产生一个对象实例,new操作符就是用来构造对象实例的.但是在一些情况下, new操作符直接生成对象会带来一些问题.举例来说, 许多类型对象的创造需要一 ...

最新文章

  1. AD rodc扩展报错
  2. 关于web项目的 ajax 处理方式
  3. android切换字体颜色,Android开发实现按钮点击切换背景并修改文字颜色的方法
  4. Maven无法上传到到私服
  5. 数字化转型,赋能新零售解决方案介绍
  6. 「博客之星」评选,互投5星,留链必投
  7. 图像目标分割_3 SegNet + U-Net
  8. C++ strlen和size的等价性
  9. vs2012 MSDN帮助文档离线包下载安装方法
  10. 华擎 j3455 时钟 linux,经验 篇一:华擎J3455 硬改MAC地址
  11. java适配器模式_java设计模式之适配器模式
  12. css让div背景变成半透明
  13. 浅谈(零火)智能开关和(单火)智能开关的工作原理和优势区别
  14. 功能升级 | Choerodon猪齿鱼“新”知识管理介绍
  15. P34-c++中的代码重用-03多重继承详细介绍
  16. Kotlin 元编程(注解,反射)
  17. 西电雨课堂等网课测试大合集【2023.5.22更新】
  18. 怎么判断MES系统好不好?MES又是如何帮企业省钱的?
  19. 计算机vb中的缺省是什么意思,请问缺省.既然缺省的意思为默认可是为什么不 – 手机爱问...
  20. 用最速下降法求最优解

热门文章

  1. 学大伟业:在数学竞赛学习中,你属于哪种类型?
  2. vb.net 教程 3-4 窗体编程 公共控件2 radiobutton ComboBox
  3. 如何快速掌握一门新的技术
  4. Ae试水~(待填坑)
  5. Python学习---根据excel的内容自动批量修改excel名称
  6. 网页制作的形式美的规则
  7. 常用Windows系统进程列表
  8. 计算机二级买那几本书,计算机二级公共基础知识买哪本书复习好?重点在哪..._公共英语考试_帮考网...
  9. eps提取高程点在哪里_只需一个命令,就能提取CAD图纸所有高程点坐标,感觉学费白交了...
  10. GPS坐标间距离计算