一、前言
难看的代码千篇一律,恶心的程序升职加薪。
该说不说简直是程序员就都晓得或者理解设计模式,但大部分小伙伴写代码总是习惯于一把梭。无论多少业务逻辑就一个类几千行,这样的开发也能够演绎为三步;定义属性、创立办法、调用展现,Done!只不过开发一时爽,重构火葬场。
好的代码不只为了实现现有性能,也会思考后续扩大。在结构设计上松耦合易读易扩大,在畛域实现上高内聚不对外暴漏实现细节不被内部烦扰。而这就有点像家里三居 (MVC) 室、四居 (DDD) 室的装修,你不会容许几十万的房子把走线水管裸漏在里面,也不会容许把马桶放到厨房,炉灶装置到卫生间。
谁创造了设计模式? 设计模式的概念最早是由 克里斯托佛·亚历山大 在其著述《修建模式语言》中首次提出的。本书介绍了城市设计的“语言”,提供了 253 个形容城镇、邻里、住宅、花园、房间及西部结构的模式,而此类“语言”的根本单元就是模式。起初,埃里希·伽玛、约翰·弗利赛德斯、拉尔夫·约翰逊 和 理查德·赫尔姆 这四位作者承受了模式的概念。1994 年,他们出版了《设计模式:可复用面向对象软件的根底》一书,将设计模式的概念利用到程序开发畛域中。
其实有一部分人并没有仔细阅读过设计模式的相干书籍和材料,但仍旧能够编写出优良的代码。这次要是因为在通过泛滥我的项目的锻炼和对程序设计的一直谋求,从而在多年编程历程上提炼进去的心得体会。而这份教训最终会与设计模式提到的内容简直统一,同样会要求高内聚、低耦合、可扩大、可复用。你可能也遇到相似的经验,在学习一些框架的源码时,发现它里的某些设计和你在做开发时一样。
我怎么学不会设计模式? 钱也花了,书也买了。代码还是一坨一坨的!设计模式是由多年的教训提炼进去开发指导思想。就像我通知你自行车怎么骑、汽车怎么开,但只有你没跑过几千公里,你能记住的只是实践,想上道仍旧很慌!
所以,本设计模式专题系列开始,会带着你应用设计模式的思维去优化代码。从而学习设计模式的心得并融入给本人。当然这里还须要多加练习,肯定是人车合一,能力站在设计模式的根底上构建出更加正当的代码。
二、开发环境
1、JDK 1.8
2、Idea + Maven
3、波及工程三个,能够通过关注公众号:bugstack 虫洞栈,回复源码下载获取。
你会取得一个连贯关上后的列表中编号 18:itstack-demo-design
itstack-demo-design-1-00 | 场景模仿工程,用于提供三组不同奖品的发放接口
itstack-demo-design-1-01 | 应用一坨代码实现业务需要,也是对 ifelse 的应用
itstack-demo-design-1-02 | 通过设计模式优化革新代码,产生对比性从而学习
- 1-00,1 代表着第一个设计模式,工厂办法模式
- 1-00,00 代表模仿的场景
- 1-01,01 代表第一种实现计划,后续 02 03 以此类推
三、工厂办法模式介绍
- 图片来自 refactoringguru.cn
工厂模式又称工厂办法模式,是一种创立型设计模式,其在父类中提供一个创建对象的办法,容许子类决定实例化对象的类型。
这种设计模式也是 Java 开发中最常见的一种模式,它的次要用意是定义一个创建对象的接口,让其子类本人决定实例化哪一个工厂类,工厂模式使其创立过程提早到子类进行。
简略说就是为了提供代码构造的扩展性,屏蔽每一个性能类中的具体实现逻辑。让内部能够更加简略的只是晓得调用即可,同时,这也是去掉泛滥 ifelse 的形式。当然这可能也有一些毛病,比方须要实现的类十分多,如何去保护,怎么减低开发成本。但这些问题都能够在后续的设计模式联合应用中,逐渐升高。
四、模仿发奖多种商品
为了能够让整个学习的案例更加贴近理论开发,这里模仿互联网中在营销场景下的业务。因为营销场景的简单、多变、长期的个性,它所须要的设计须要更加深刻,否则会常常面临各种紧急 CRUD 操作,从而让代码构造凌乱不堪,难以保护。
在营销场景中常常会有某个用户做了一些操作;打卡、分享、留言、邀请注册等等,进行返利积分,最初通过积分在兑换商品,从而促活和拉新。
那么在这里咱们模仿积分兑换中的发放多种类型商品,如果当初咱们有如下三种类型的商品接口;
1、优惠券:CouponResult sendCoupon(String uId, String couponNumber, String uuid)
2、实物商品:Boolean deliverGoods(DeliverReq req)
3、第三方爱奇艺兑换卡:void grantToken(String bindMobileNumber, String cardId)
从以上接口来看有如下信息:
- 三个接口返回类型不同,有对象类型、布尔类型、还有一个空类型。
- 入参不同,发放优惠券须要仿重、兑换卡须要卡 ID、实物商品须要发货地位(对象中含有)。
- 另外可能会随着后续的业务的倒退,会新增其余种商品类型。因为你所有的开发需要都是随着业务对市场的拓展而带来的。
五、用一坨坨代码实现
如果不思考任何扩展性,只为了尽快满足需要,那么对这么几种处分发放只需应用 ifelse 语句判断,调用不同的接口即可满足需要。可能这也是一些刚入门编程的小伙伴,罕用的形式。接下来咱们就先依照这样的形式来实现业务的需要。
1. 工程构造
itstack-demo-design-1-01
└── src
├── main
│ └── java
│ └── org.itstack.demo.design
│ ├── AwardReq.java
│ ├── AwardRes.java
│ └── PrizeController.java
└── test
└── java
└── org.itstack.demo.design.test
└── ApiTest.java
- 工程构造上非常简单,一个入参对象 AwardReq、一个出参对象 AwardRes,以及一个接口类 PrizeController
2. ifelse 实现需求
public class PrizeController {private Logger logger = LoggerFactory.getLogger(PrizeController.class);
public AwardRes awardToUser(AwardReq req) {String reqJson = JSON.toJSONString(req);
AwardRes awardRes = null;
try {logger.info("奖品发放开始{}。req:{}", req.getuId(), reqJson);
// 依照不同类型办法商品[1 优惠券、2 实物商品、3 第三方兑换卡(爱奇艺)]
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.getInfo());
}
} 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", "发放胜利");
}
logger.info("奖品发放实现{}。", req.getuId());
} catch (Exception e) {logger.error("奖品发放失败{}。req:{}", req.getuId(), reqJson, e);
awardRes = new AwardRes("0001", e.getMessage());
}
return awardRes;
}
private String queryUserName(String uId) {return "花花";}
private String queryUserPhoneNumber(String uId) {return "15200101232";}
}
- 如上就是应用 ifelse 十分间接的实现进去业务需要的一坨代码,如果仅从业务角度看,研发如期甚至提前实现了性能。
- 那这样的代码目前来看并不会有什么问题,但如果在通过几次的迭代和拓展,接手这段代码的研发将非常苦楚。重构老本高须要理清之前每一个接口的应用,测试回归验证工夫长,须要全副验证一次。这也就是很多人并不违心接手他人的代码,如果接手了又被压迫开发工夫。那么可想而知这样的 ifelse 还会持续减少。
3. 测试验证
写一个单元测试来验证下面编写的接口方式,养成单元测试的好习惯会为你加强代码品质。
编写测试类:
@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");
Map<String,String> extMap = new HashMap<String,String>();
extMap.put("consigneeUserName", "谢飞机");
extMap.put("consigneeUserPhone", "15200292123");
extMap.put("consigneeUserAddress", "吉林省. 长春市. 双阳区.XX 街道. 檀溪苑小区.#18-2109");
req02.setExtMap(extMap);
commodityService_2.sendCommodity("10001","9820198721311","1023000020112221113", extMap);
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));
}
后果:
模仿发放优惠券测试
22:17:55.668 [main] INFO o.i.demo.design.PrizeController - 奖品发放开始 10001。req:{"awardNumber":"EGM1023938910232121323432","awardType":1,"bizId":"791098764902132","uId":"10001"}
模仿发放优惠券一张:10001,EGM1023938910232121323432,791098764902132
22:17:55.671 [main] INFO o.i.demo.design.PrizeController - 奖品发放实现 10001。22:17:55.673 [main] INFO org.itstack.demo.test.ApiTest - 申请参数:{"uId":"10001","bizId":"791098764902132","awardNumber":"EGM1023938910232121323432","awardType":1}
22:17:55.674 [main] INFO org.itstack.demo.test.ApiTest - 测试后果:{"code":"0000","info":"发放胜利"}
模仿办法实物商品
22:17:55.675 [main] INFO o.i.demo.design.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"}
22:17:55.677 [main] INFO o.i.demo.design.PrizeController - 奖品发放实现 10001。22:17:55.677 [main] INFO org.itstack.demo.test.ApiTest - 申请参数:{"extMap":{"consigneeUserName":"谢飞机","consigneeUserAddress":"吉林省. 长春市. 双阳区.XX 街道. 檀溪苑小区.#18-2109","consigneeUserPhone":"15200292123"},"uId":"10001","bizId":"1023000020112221113","awardNumber":"9820198721311","awardType":2}
22:17:55.677 [main] INFO org.itstack.demo.test.ApiTest - 测试后果:{"code":"0000","info":"发放胜利"}
第三方兑换卡(爱奇艺)
22:17:55.678 [main] INFO o.i.demo.design.PrizeController - 奖品发放开始 10001。req:{"awardNumber":"AQY1xjkUodl8LO975GdfrYUio","awardType":3,"uId":"10001"}
模仿发放爱奇艺会员卡一张:15200101232,AQY1xjkUodl8LO975GdfrYUio
22:17:55.678 [main] INFO o.i.demo.design.PrizeController - 奖品发放实现 10001。22:17:55.678 [main] INFO org.itstack.demo.test.ApiTest - 申请参数:{"uId":"10001","awardNumber":"AQY1xjkUodl8LO975GdfrYUio","awardType":3}
22:17:55.678 [main] INFO org.itstack.demo.test.ApiTest - 测试后果:{"code":"0000","info":"发放胜利"}
Process finished with exit code 0
- 运行后果失常,满足以后所有业务产品需要,写的还很快。但!切实难以为保护!
六、工厂模式优化代码
接下来应用工厂办法模式来进行代码优化,也算是一次很小的重构。整顿重构会你会发现代码构造清晰了、也具备了下次新增业务需要的扩展性。但在理论应用中还会对此进行欠缺,目前的只是抽离出最外围的局部体现到你背后,不便学习。
1. 工程构造
itstack-demo-design-1-02
└── src
├── main
│ └── java
│ └── org.itstack.demo.design
│ ├── store
│ │ ├── impl
│ │ │ ├── CardCommodityService.java
│ │ │ ├── CouponCommodityService.java
│ │ │ └── GoodsCommodityService.java
│ │ └── ICommodity.java
│ └── StoreFactory.java
└── test
└── java
└── org.itstack.demo.design.test
└── ApiTest.java
- 首先,从下面的工程构造中你是否一些感觉,比方;它看上去清晰了、这样分层能够更好扩大了、仿佛能够设想到每一个类做了什么。
- 如果还不能了解为什么这样批改,也没有关系。因为你是在通过这样的文章,来学习设计模式的魅力。并且再获取源码后,进行实际操作几次也就缓缓把握了工厂模式的技巧。
2. 代码实现
2.1 定义发奖接口
public interface ICommodity {void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception;
}
- 所有的奖品无论是实物、虚构还是第三方,都须要通过咱们的程序实现此接口进行解决,以保障最终入参出参的统一性。
- 接口的入参包含;用户 ID、奖品 ID、业务 ID 以及扩大字段用于解决发放实物商品时的播种地址。
2.2 实现奖品发放接口
优惠券
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";}
}
- 从下面能够看到每一种奖品的实现都包含在本人的类中,新增、批改或者删除都不会影响其余奖品性能的测试,升高回归测试的可能。
- 后续在新增的奖品只须要依照此构造进行填充即可,十分易于保护和扩大。
在对立了入参以及出参后,调用方不在须要关怀奖品发放的外部逻辑,依照对立的形式即可解决。
2.3 创立商店工厂
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("不存在的商品服务类型");
}
}
- 这里咱们定义了一个商店的工厂类,在外面依照类型实现各种商品的服务。能够十分洁净整洁的解决你的代码,后续新增的商品在这里扩大即可。如果你不喜爱 if 判断,也能够应用 switch 或者 map 配置构造,会让代码更加洁净。
- 另外很多代码查看软件和编码要求,不喜爱 if 语句前面不写扩大,这里是为了更加洁净的向你体现逻辑。在理论的业务编码中能够增加括号。
3. 测试验证
编写测试类:
@Test
public void test_commodity() 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<String,String>();
extMap.put("consigneeUserName", "谢飞机");
extMap.put("consigneeUserPhone", "15200292123");
extMap.put("consigneeUserAddress", "吉林省. 长春市. 双阳区.XX 街道. 檀溪苑小区.#18-2109");
commodityService_2.sendCommodity("10001","9820198721311","1023000020112221113", extMap);
// 3. 第三方兑换卡(爱奇艺)
ICommodity commodityService_3 = storeFactory.getCommodityService(3);
commodityService_3.sendCommodity("10001","AQY1xjkUodl8LO975GdfrYUio",null,null);
}
后果:
模仿发放优惠券一张:10001,EGM1023938910232121323432,791098764902132
22:48:10.922 [main] INFO o.i.d.d.s.i.CouponCommodityService - 申请参数[优惠券] => uId:10001 commodityId:EGM1023938910232121323432 bizId:791098764902132 extMap:null
22:48:10.957 [main] INFO o.i.d.d.s.i.CouponCommodityService - 测试后果[优惠券]:{"code":"0000","info":"发放胜利"}
模仿发货实物商品一个:{"consigneeUserAddress":"吉林省. 长春市. 双阳区.XX 街道. 檀溪苑小区.#18-2109","consigneeUserName":"谢飞机","consigneeUserPhone":"15200292123","orderId":"1023000020112221113","sku":"9820198721311","userName":"花花","userPhone":"15200101232"}
22:48:10.962 [main] INFO o.i.d.d.s.impl.GoodsCommodityService - 申请参数[优惠券] => uId:10001 commodityId:9820198721311 bizId:1023000020112221113 extMap:{"consigneeUserName":"谢飞机","consigneeUserAddress":"吉林省. 长春市. 双阳区.XX 街道. 檀溪苑小区.#18-2109","consigneeUserPhone":"15200292123"}
22:48:10.962 [main] INFO o.i.d.d.s.impl.GoodsCommodityService - 测试后果[优惠券]:true
模仿发放爱奇艺会员卡一张:15200101232,null
22:48:10.963 [main] INFO o.i.d.d.s.impl.CardCommodityService - 申请参数[爱奇艺兑换卡] => uId:10001 commodityId:AQY1xjkUodl8LO975GdfrYUio bizId:null extMap:null
22:48:10.963 [main] INFO o.i.d.d.s.impl.CardCommodityService - 测试后果[爱奇艺兑换卡]:success
Process finished with exit code 0
- 运行后果失常,既满足了业务产品需要,也满足了本人对代码的谋求。这样的代码部署上线运行,心田不会恐慌,不会感觉中午会有电话。
- 另外从运行测试后果上也能够看进去,在进行封装后能够十分清晰的看到一整套发放奖品服务的完整性,对立了入参、对立了后果。
七、总结
- 从上到下的优化来看,工厂办法模式并不简单,甚至这样的开发构造在你有所了解后,会发现更加简略了。
- 那么这样的开发的益处晓得后,也能够总结进去它的长处;防止创建者与具体的产品逻辑耦合、满足繁多职责,每一个业务逻辑实现都在所属本人的类中实现、满足开闭准则,无需更改应用调用方就能够在程序中引入新的产品类型。但这样也会带来一些问题,比方有十分多的奖品类型,那么实现的子类会极速扩张。因而也须要应用其余的模式进行优化,这些在后续的设计模式中会逐渐波及到。
- 从案例动手看设计模式往往要比看实践学的更加容易,因为案例是缩短实践到上手的最佳形式,如果你曾经有所播种,肯定要去尝试实操。
受权转载作者:小傅哥
作者博客:https://bugstack.cn/
→ 退出群一起学习交换
→ 一起拓展行业人脉资源
→ 取得学习材料(不卖课、不关注、纯分享、进群群布告自取)
→ 学习材料戳蓝字:https://shimo.im/sheets/CtcH8…
→ 进群请扫下方二维码: