写在前面:
初为职场新手,难免会把代码写的一把梭。遇到业务的时候直接if-else干。但是本着不想被开除的原则,还是学习一下设计模式,今天来看看设计模式之工厂模式。
用需求引出代码:
抽奖系统当中有个业务是发奖业务,奖品有优惠券,实物商品,第三方优惠券。当用户抽中对应的奖品之后,我们需要有对应的方法来发奖。针对这样的业务我们先来看一下代码一把梭的情况。
工程结构:
⼯程结构上⼀个⼊参对象 AwardReq 、⼀个出参对象 AwardRes ,以及⼀个接⼝类
PrizeController
先看看ifelse实现需求
public class PrizeController {
private Logger logger = LoggerFactory.getLogger(PrizeController.class);
/**
* 给用户发奖
* @param req
* @return
*/
public AwardRes awardToUser(AwardReq req) {
// 把req转为json格式好处理
String reqJson = JSON.toJSONString(req);
// 初始化返回结果为空
AwardRes awardRes = null;
try {
logger.info("奖品发放开始{}。req:{}", req.getuId(), reqJson);
// 按照不同类型方法商品[1优惠券、2实物商品、3第三方兑换卡(爱奇艺)]
if (req.getAwardType() == 1) {
// 这是1-00当中的类
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"));
// 调用方法来发实物商品,如果发成功返回true
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";
}
}
如上的代码有什么问题?
- 业务不变还好(基本不可能),业务如果变更,那就得去修改这个类中的代码,违背开闭原则(对修改关闭,对扩展开放),违背最小知识原则(一个类只干一件事)。
- 被组长知道了,绩效3.0跑不了,假如离职下一个同事接手了估计直骂娘。
接下来我们使用工厂模式重构代码,设计模式没什么可怕的,就是听起来高大上而已,要从战术上藐视他,多练练就学会了。
工程结构:
为什么叫工厂模式呢?
- 工厂是生产东西的,买家可以直接去工厂拿东西 ,并且工厂能生产的东西是多样性的。比如食品工厂,可以生产旺旺雪饼,雪糕,华夫饼。那我们需要去进货的时候,只需要带着需求清单去找工厂就行。
调用关系图:
具体代码实现:
发送商品接口:
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();
@Override
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();
@Override
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(); @Override 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 { // 工厂对外交接部门,参数为接口类型。多态的体现 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 配置结构,会让代码更加⼲净
总结:
- 从上到下的优化来看,⼯⼚⽅法模式并不复杂,使用工厂模式之后代码结构更加清晰了,但是还是要根据业务和开发工期来动态决策具体该怎么写。