目录
1.概述
工厂模式也称简单工厂模式,是创建型设计模式的一种,这种设计模式提供了按需创建对象的最佳方式。同时,这种创建方式不会对外暴露创建细节,并且会通过一个统一的接口创建所需对象。这种设计模式也是Java开发中常见的一种模式,它的主要意图是定义一个创建对象的接口,让其子类自己决定将哪一个工厂类实例化,工厂模式使创建过程延迟到子类中进行。
简单地说,就是为了给代码结构提供扩展性,屏蔽每一个功能类中的具体实现逻辑。这种方式便于外部更加简单地调用,同时也是去掉众多if…else的最佳手段。当然,这种设计模式也有一些缺点,需要治理。例如需要实现的类比较多、难以维护、开发成本高等,但这些问题都可以通过结合不同的设计模式逐步优化。
2.业务场景
在营销场景中,经常会约定在用户完成打卡、分享、留言、邀请注册等一系列行为操作后进行返利积分操作。用户再通过这些返利积分兑换商品,从而让整个系统构成一个生态闭环,达到促活和拉新的目的。此处有优惠券,实物商品和第三方兑换卡三种营销方式。
3.运用设计模式前代码实现
3.1.代码实现
PrizeController.java
package chapter04;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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";
}
}
测试用例
@Test
public void test_awardToUser(){
PrizeController prizeController = new PrizeController();
System.out.println("\r\n模拟发放优惠券测试\r\n");
//模拟发放优惠券测试
AwardReq req01 = new AwardReq();
req01.setuId("1001");
req01.setAwardType(1);
req01.setAwardNumber("EGM102393891292992");
req01.setBizId("7910987");
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("1001");
req02.setAwardType(2);
req02.setAwardNumber("98201989");
req02.setBizId("102300002011");
Map<String,String> extMap = new HashMap<String,String>();
extMap.put("consigneeUserName","谢先生");
extMap.put("consigneeUserPhone","15200291234");
extMap.put("consigneeUserAddress","吉林省,长春市,xxx");
req02.setExtMap(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("1001");
req03.setAwardType(3);
req03.setAwardNumber("98201989");
AwardRes awardRes03 = prizeController.awardToUser(req03);
logger.info("请求参数:{}", JSON.toJSON(req03));
logger.info("测试结果:{}",JSON.toJSON(awardRes03));
}
3.2.总结
上述代码使用了if…else语句,用非常直接的方式实现了业务需求。如果仅从产品需求角度来说,确实实现了相应的功能逻辑。甚至靠这样简单粗暴的开发方式,也许能让需求提前上线。既然这样的代码可以实现快速交付,又存在什么问题呢?在互联网业务快速迭代的情况下,这段代码会在源源不断的需求中迭代和拓展。如果这些逻辑都用 if…else填充到一个类里,则非常难以维护。这样的代码使用的时间越久,其重构成本就越高。重构前需要清理所有的使用方,测试回归验证时间加长,带来的风险也会非常高。所以,很多研发人员并不愿意接手这样的代码,如果接手后需求开发又非常紧急,可能根本来不及重构,导致这样的if…else语句还会继续增加。
4.运用设计模式后代码实现
接下来使用工厂模式优化代码,也算是一次代码重构。当整理代码流程并重构后,会发现代码结构更清晰了,也具备了应对下次新增业务需求的扩展性。
4.1.代码实现
ICommodity.java
package chapter04.design.store;
import java.util.Map;
public interface ICommodity {
void sendCommodity(String uId, String commodityId, String bizId, Map<String,String> extMap);
}
CardCommodityService.java
package chapter04.design.store.impl;
import chapter04.AwardRes;
import chapter04.IQiYiCardService;
import chapter04.design.store.ICommodity;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
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) {
String bindMobileNumber = queryUserPhoneNumber(uId);
iQiYiCardService.grantToken(bindMobileNumber,bizId);
logger.info("请求参数[兑换卡]=> uId:{} commodityId:{} bizId:{} extMap:{}",uId,commodityId,bizId, JSON.toJSON(extMap));
logger.info("测试结果[兑换卡]:success");
}
private String queryUserPhoneNumber(String uId){
return "15200101232";
}
}
CouponCommodityService.java
package chapter04.design.store.impl;
import chapter04.CouponResult;
import chapter04.CouponService;
import chapter04.design.store.ICommodity;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
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) {
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());
}
}
}
GoodsCommodityService.java
package chapter04.design.store.impl;
import chapter04.AwardRes;
import chapter04.DeliverReq;
import chapter04.GoodsService;
import chapter04.design.store.ICommodity;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
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) {
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";
}
}
StoreFactory.java
package chapter04.design.store;
import chapter04.design.store.impl.CardCommodityService;
import chapter04.design.store.impl.CouponCommodityService;
import chapter04.design.store.impl.GoodsCommodityService;
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 实例化对象
* @throws InstantiationException
* @throws IllegalAccessException
*/
public ICommodity getCommodityService(Class<? extends ICommodity> clazz) throws InstantiationException, IllegalAccessException {
if (null == clazz) return null;
return clazz.newInstance();
}
}
测试用例
@Test
public void test_StoreFactory_01(){
StoreFactory storeFactory = new StoreFactory();
//1.优惠券
ICommodity commodityService_1 = storeFactory.getCommodityService(1);
commodityService_1.sendCommodity("1001","EGM10239389","79109864",null);
//2.实物商品
ICommodity commodityService_2 = storeFactory.getCommodityService(2);
commodityService_2.sendCommodity("1001","98201989","1023002011",new HashMap<String, String>(){{
put("consigneeUserName","谢先生");
put("consigneeUserPhone","152002929202");
put("consigneeUserAddrss","吉林省长春市xxxx");
}});
//3.第三方兑换卡
ICommodity commodityService_3 = storeFactory.getCommodityService(3);
commodityService_3.sendCommodity("1001","11343433",null,null);
}
@Test
public void test_ScoreFactory_02() throws Exception{
StoreFactory storeFactory = new StoreFactory();
//1.优惠券
ICommodity commodityService = storeFactory.getCommodityService(CouponCommodityService.class);
commodityService.sendCommodity("1001","EGM10239389","79109864",null);
}
4.2.总结
从运行结果可以看到,这两种获取工厂实现的接口都可以满足业务需求。在实际使用中按需选择即可。这段重构后的代码既满足了业务方和产品经理的需求,也满足了研发人员对代码质量的追求。另外,从运行的测试结果也可以看出来,在进行封装后,这样一整套发放奖品服务有统一的入参、统一的结果。既提高了代码的结构性,也让工程易于维护和扩展。