Factory Method:再工厂方法设计模式中:父类决定实例的生成方式,但不决定所要生成的具体的类,具体的处理让子类负责
UML
![image-20210210145007493](https://kay-rick.oss-cn-beijing.aliyuncs.com/img/image-20210210145007493.png)
-
创建者
Factory
负责具体生成
Product
角色的抽象类,Factory
角色对于实际负责生成实例的ConcreteFactorty
角色一无所知,它唯一知道的是要调用Product
角色和生成实例的方法(factoryMethod
)就可以生成Product
实例 -
产品
Product
是一个抽象类,它定义了在Factory Method模式中生成哪些实例所持有的接口(API),具体处理由子类ConcreteProduct
决定 -
具体的产品
ConcreteProduct
ConcreteProduct
角色属于加工这一方,他决定了具体的产品 -
具体的创建者
ConcreteFactory
ConcreteFactory
角色属于加工这一方,它负责生成具体的产品
Sample
我们以生成身份ID卡为例,一共有四个类:
Factory
public abstract class Factory {
/**
* 使用了一系列抽象工厂方法来创建产品,方法实现由具体的工厂来完成
*/
public final Product create(String owner) {
Product product = createProduct(owner);
registerProduct(product);
return product;
}
/**
* 制造产品
* @param owner
* @return
*/
protected abstract Product createProduct(String owner);
/**
* 注册产品
* @param product
* @return
*/
protected abstract void registerProduct(Product product);
}
Product
public abstract class Product {
public abstract void use();
}
ConcreteFactory
public class IDCardFactory extends Factory {
private List<String> owners = new ArrayList<>();
@Override
protected Product createProduct(String owner) {
return new IDCard(owner);
}
@Override
protected void registerProduct(Product product) {
owners.add(((IDCard)product).getOwner());
}
public List<String> getOwners() {
return owners;
}
}
ConcreteProduct
public class IDCard extends Product {
private String owner;
public IDCard(String owner) {
System.out.println("制作" + owner + "的ID卡");
this.owner = owner;
}
@Override
public void use() {
System.out.println("使用" + owner + "的ID卡");
}
public String getOwner() {
return owner;
}
}
Test
public class Test {
public static void main(String[] args) {
Factory factory = new IDCardFactory();
Product card1 = factory.create("Rick");
Product card2 = factory.create("Kay");
card1.use();
card2.use();
}
}
System Suitability
工厂方法非常适用以下情况:
- 当一个类不知道它所必须创建的对象的类的时候
- 当一个类希望由它的子类来指定它所创建的对象的时候
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且希望帮助子类是代理者这一信息局部化的时候
在实际业务开发中可以对业务进行优化:避免创建者与具体的产品逻辑耦合,满足单一职责、每一个业务逻辑实现都在所属自己的类中完成,满足开闭原则,无需更改使用调用方法就可以在程序中引入新的产品类型。
我们来看一个具体的业务场景:在互联网营销场景下我们经常会给客户返利积分,最后通过积分在兑换商品,那么我们模拟积分兑换中的发放多种类型的商品,假设我们由下面两种类型的商品接口
- 优惠券:CardService中
CouponResult sendCoupon(String uId, String couponNumber, String uuid)
- 实物商品:GoodsService中
Boolean deliverGoods(DeliverReq req)
我们发现:接口返回类型不同,入参不同,另外可能会随着后续的业务的发展,会新增其他种商品类型。如果我们不考虑任何扩展性,那么对这几种奖励发放只需使用if-else语句判断,调用不同接口即可满足需求。
但是在经过多次迭代和拓展之后,代码的维护和重构成本将变得非常高,测试回归验证时间长。这种场景我们考虑使用工厂方法对代码进行进一步优化
![image-20210210212315933](https://kay-rick.oss-cn-beijing.aliyuncs.com/img/image-20210210212315933.png)
StoreFactory
:定义一个工厂类,在工厂类里面按照类型实现各种商品的服务:当id是1时表示是优惠劵发放,id是2时表示是实物商品发放
public class StoreFactory {
public StoreInterface getService(Integer id) {
if (null == id)
return null;
if (1 == id)
return new CardService();
if (2 == id)
return new GoodsService();
throw new RuntimeException("不存在商品服务信息");
}
}
StoreInterface
:统一发奖接口,所有奖品无论是实物、虚拟还是第三方,都需要我们的程序实现此接口进行处理,以保证最终入参出参的统一性。接口入参包括:用户ID、奖品ID、业务ID以及扩展字段用于处理发放实物商品时的收货地址
public interface StoreInterface {
void sendGift(String uid, String giftId, String bizId, Map<String, String> extmap) throws Exception;
}
CardCommonService
和GoodsCommonService
实现奖品发放接口:实现接口中的方法,在方法中调用原有接口的方法,并对出参和入参进行封装适配现在的接口(代码中没有展现具体CardService和GoodsService)- 在统一了入参及出参后,调用方不在需要关心奖品发放的内部逻辑,按照统一的方式处理即可
public class GoodsCommonService implements StoreInterface {
private Logger logger = LoggerFactory.getLogger(this.getClass());
// 模拟注入
private GoodsService goodsService = new GoodsService();
@Override
public void sendGift(String uid, String giftId, 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 "Rick";
}
private String queryUserPhoneNumber(String uId) {
return "15200101232";
}
}
public class CardCommonService implements StoreInterface {
private Logger logger = LoggerFactory.getLogger(this.getClass());
// 模拟注入
private CardService CardService = new CardService();
@Override
public void sendGift(String uid, String giftId, String bizId, Map<String, String> extmap) throws Exception {
String mobile = queryUserMobile(uId);
// 正在调用原来接口
CardService.grantToken(mobile, bizId);
logger.info("请求参数[优惠劵] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
logger.info("测试结果[优惠劵]:success");
}
}
测试:该工厂方法完美适应了需求,如果再加入新的奖品类型也会很轻松适配,代码可维护性大大提高
@Test
public void test1() throws Exception {
StoreFactory storeFactory = new StoreFactory();
// 1. 优惠劵发放
StoreInterface cardService = storeFactory.getService(1);
cardService.sendGift("10001", "EGM1023938910232121323432", "791098764902132", null);
// 2. 实物商品发放
StoreInterface goodsService = storeFactory.getService(2);
Map<String,String> extMap = new HashMap<String,String>();
extMap.put("consigneeUserName", "Rick");
extMap.put("consigneeUserPhone", "15200292123");
extMap.put("consigneeUserAddress", "北京市朝阳区");
goodsService.sendGift("10001", "9820198721311", "1023000020112221113", extMap);
}