搞定设计模式之工厂模式一篇文章就够了!!!
Posted 南淮北安
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了搞定设计模式之工厂模式一篇文章就够了!!!相关的知识,希望对你有一定的参考价值。
一、定义
工厂模式也称简单工厂模式,是创建型设计模式的一种,这种设计模式提供了按需创建对象的最佳方式。同时,这种创建方式不会对外暴露创建细节,并且会通过一个统一的接口创建所需对象
柳州动力机械厂可以生产织布机和缝纫机。
它的主要意图是定义一个创建对象的接口,让其子类自己决定将哪一个工厂类实例化,工厂模式使创建过程延迟到子类中进行。
简单地说,就是为了给代码结构提供扩展性,屏蔽每一个功能类中的具体实现逻辑。这种方式便于外部更加简单地调用,同时也是去掉众多 if…else 的最佳手段。
当然,这种设计模式也有一些缺点,需要治理。
例如需要实现的类比较多、难以维护、开发成本高等,但这些问题都可以通过结合不同的设计模式逐步优化。
二、问题背景
在营销场景中,经常会约定在用户完成打卡、分享、留言、邀请注册等一系列行为操作后进行返利积分操作。
用户再通过这些返利积分兑换商品,从而让整个系统构成一个生态闭环,达到促活和拉新的目的。
假设现在有如表4-1所示的三种类型的商品接口。
从以上接口来看,有如下信息:
- 三种接口返回类型不同,有对象类型、布尔类型和空类型。
- 入参不同,发放优惠券需要仿重,兑换卡需要卡ID,实物商品需要发货位置(对象中含有)。
- 可能会随着后续业务的发展,新增其他的商品类型。因为所有的开发需求都是由业务对市场的拓展带来的。
三、违背设计模式的设计实现
如果不考虑程序的任何扩展性,只为了尽快满足需求,那么对这三种奖励的发放只需使用if…else语句判断,调用不同的接口即可。
我们先按照这样的方式实现业务需求,最后再使用设计模式重构这段代码,方便对照理解。
上述代码使用了if…else
语句,用非常直接的方式实现了业务需求。
如果仅从产品需求角度来说,确实实现了相应的功能逻辑。
甚至靠这样简单粗暴的开发方式,也许能让需求提前上线。既然这样的代码可以实现快速交付,又存在什么问题呢?在互联网业务快速迭代的情况下,这段代码会在源源不断的需求中迭代和拓展。
如果这些逻辑都用if…else
填充到一个类里,则非常难以维护。
这样的代码使用的时间越久,其重构成本就越高
重构前需要清理所有的使用方,测试回归验证时间加长,带来的风险也会非常高。
所以,很多研发人员并不愿意接手这样的代码,如果接手后需求开发又非常紧急,可能根本来不及重构,导致这样的if…else
语句还会继续增加。
四、问题改进
接下来使用工厂模式优化代码,也算是一次代码重构。
当整理代码流程并重构后,会发现代码结构更清晰了,也具备了应对下次新增业务需求的扩展性。
这样的工程看上去更清晰,类的职责更明确,分层可以更好地扩展,可以通过类名就能大概知道每个类的功能。
定义发奖接口:
public interface ICommodity {
void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception;
}
对于所有的奖品,无论是实物商品、优惠券还是第三方兑换卡,都需要通过程序实现此接口并处理。这样的方式可以保证入参和出参的统一性。
接口的入参包括:用户 ID(uId)、奖品 ID(commodityId)、业务 ID(bizId)及扩展字段(extMap),用于处理发放实物商品时的收货地址。
实现三种发奖接口:
1 - 优惠券
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());
}
}
2 - 实物商品
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";
}
}
3 - 第三方兑换卡
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是具体的逻辑实现)。
通过商店工厂类获取各种奖品服务,可以非常干净、整洁地处理业务逻辑代码。
后续新增的奖品按照这样的结构扩展即可。
五、总结
从优化过程来看,工厂模式并不复杂。
一旦理解和掌握,会发现它更加简单,同时也可以借助它提升开发效率。
同时,不难总结出它的优点:避免创建者与具体的产品逻辑耦合;满足单一职责,每一个业务逻辑实现都在自己所属的类中完成;满足开闭原则,无须更改使用调用方就可以在程序中引入新的产品类型。
当然,这也会带来一些问题,例如有非常多的奖品类型,实现的子类会极速扩张,因此需要使用其他的模式进行优化,这些在后续的设计模式中会逐步介绍
以上是关于搞定设计模式之工厂模式一篇文章就够了!!!的主要内容,如果未能解决你的问题,请参考以下文章