创造者模式:工厂方法、抽象工厂、生成器、原型、单例

这类模式提供创建对象的机制,能够提升已有代码的灵活性和可复用性

第一节:工厂方法模式

一、工厂方法模式介绍

工厂模式又称为工厂方法模式,是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。

它的主要意图是定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

简单来说就是提供了代码结构的扩展性,屏蔽每一个功能类中的具体实现逻辑。

二、实例:模拟发奖多种商品

在营销场景中经常会有某个⽤户做了⼀些操作;打卡、分享、留⾔、邀请注册等等,进⾏返利积分,最后通过积分在兑换商品,从⽽促活和拉新。

那么在这⾥我们模拟积分兑换中的发放多种类型商品,假如现在我们有如下三种类型的商品接⼝;

序号 类型 接口
1 优惠券 CouponResult sendCoupon(String uId, String couponNumber, String uuid)
2 实物商品 Boolean deliverGoods(DeliveReq req)
3 第三方爱奇艺兑换卡 void grantToken(String bindMobileNumber, String cardId)

从以上三个接口可得到以下信息

  1. 三个接口返回类型不同:对象、布尔、空
  2. 传入参数不同:优惠券需要仿重、兑换卡需要卡ID、实体商品需要发货位置(对象中)
  3. 另外可能会随着后续的业务的发展,会新增其他种商品类型。开发需求是随着业务对市场的拓展而带来的

三、使用ifelse

1.工程结构

工程结构简单:一个入参对象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);
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 "levtio";
}

private String queryUserPhoneNumber(String uId) {
return "18708422873";
}
}

代码功能实现简单,但是重构成本高。

3.测试类

通过编写测试类来验证上方的接口。

public class ApiTest {
private Logger logger = LoggerFactory.getLogger(ApiTest.class);

@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", "levtio");
put("consigneeUserPhone", "18708422873");
put("consigneeUserAddress", "四川省成都市新都区");
}});

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));
}
}

结果:

模拟发放优惠券测试

15:41:00.686 [main] INFO o.levtio.demo.design.PrizeController - 奖品开始发放10001。req:{"awardNumber":"EGM1023938910232121323432","awardType":1,"bizId":"791098764902132","uId":"10001"}
模拟发放优惠券一张:10001,EGM1023938910232121323432,791098764902132
15:41:00.690 [main] INFO o.levtio.demo.design.PrizeController - 奖品发放完成10001。
15:41:00.692 [main] INFO org.levtio.demo.test.ApiTest - 请求参数:{"uId":"10001","bizId":"791098764902132","awardNumber":"EGM1023938910232121323432","awardType":1}
15:41:00.693 [main] INFO org.levtio.demo.test.ApiTest - 测试结果:{"code":"0000","info":"发送成功"}

模拟发放实物商品

15:41:00.693 [main] INFO o.levtio.demo.design.PrizeController - 奖品开始发放10001。req:{"awardNumber":"9820198721311","awardType":2,"bizId":"1023000020112221113","extMap":{"consigneeUserName":"levtio","consigneeUserPhone":"18708422873","consigneeUserAddress":"四川省成都市新都区"},"uId":"10001"}
模拟发货实物商品一个:{"consigneeUserAddress":"四川省成都市新都区","consigneeUserName":"levtio","consigneeUserPhone":"18708422873","orderId":"1023000020112221113","sku":"9820198721311","userName":"levtio","userPhone":"18708422873"}
15:41:00.695 [main] INFO o.levtio.demo.design.PrizeController - 奖品发放完成10001。
15:41:00.695 [main] INFO org.levtio.demo.test.ApiTest - 请求参数:{"extMap":{"consigneeUserName":"levtio","consigneeUserAddress":"四川省成都市新都区","consigneeUserPhone":"18708422873"},"uId":"10001","bizId":"1023000020112221113","awardNumber":"9820198721311","awardType":2}
15:41:00.696 [main] INFO org.levtio.demo.test.ApiTest - 测试结果:{"code":"0000","info":"发送成功"}

第三方兑换卡(爱奇艺)

15:41:00.696 [main] INFO o.levtio.demo.design.PrizeController - 奖品开始发放10001。req:{"awardNumber":"AQY1xjkUodl8LO975GdfrYUio","awardType":3,"uId":"10001"}
模拟发放爱奇艺会员卡一张:18708422873,AQY1xjkUodl8LO975GdfrYUio
15:41:00.696 [main] INFO o.levtio.demo.design.PrizeController - 奖品发放完成10001。
15:41:00.696 [main] INFO org.levtio.demo.test.ApiTest - 请求参数:{"uId":"10001","awardNumber":"AQY1xjkUodl8LO975GdfrYUio","awardType":3}
15:41:00.696 [main] INFO org.levtio.demo.test.ApiTest - 测试结果:{"code":"0000","info":"发放成功"}

进程已结束,退出代码为 0

运行结果无误,缺点是难以维护

四、使用工厂模式优化代码

1. 项目结构

可以看为工厂的不同的service都需要通过ICommodity。

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();

@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 "levtio";
}

private String queryUserPhoneNumber(String uId) {
return "18708422873";
}

}

第三方礼品卡

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 "18708422873";
}

}
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("不存在的商品服务类型");
}
}

3. 测试验证

编写测试类

public class ApiTest {

@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", "levtio");
extMap.put("consigneeUserPhone", "18708422873");
extMap.put("consigneeUserAddress", "四川省成都市新都区");

commodityService_2.sendCommodity("10001","9820198721311","1023000020112221113",new HashMap<String, String>() {{
put("consigneeUserName", "levtio");
put("consigneeUserPhone", "18708422873");
put("consigneeUserAddress", "四川省成都市新都区");
}});

// 3. 第三方兑换卡(爱奇艺)
ICommodity commodityService_3 = storeFactory.getCommodityService(3);
commodityService_3.sendCommodity("10001","AQY1xjkUodl8LO975GdfrYUio",null,null);

}

}

五、总结

工厂模式优点: 避免创建者与具体的产品逻辑耦合、满足单一职责,每一个业务逻辑实现都在所属自己的类中完成、满足开闭原则,无需更改使用调节方就可以在程序中引入新的产品类型。

缺点:如果产品类型过多,则需要实现的子类会极速扩张。后续需要优化