文章目录
- 创建型模式
- 概述
- 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
还会继续增加。
【测试验证】
写⼀个单元测试来验证上⾯编写的接⼝⽅式
@Test
public 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":"发放成功"}
运⾏结果正常,满⾜当前所有业务产品需求,写的还很快。但实在难以为维护!