设计模式-工厂方法

Factory Method:再工厂方法设计模式中:父类决定实例的生成方式,但不决定所要生成的具体的类,具体的处理让子类负责


UML

image-20210210145007493
  • 创建者Factory

    负责具体生成Product角色的抽象类,Factory角色对于实际负责生成实例的ConcreteFactorty角色一无所知,它唯一知道的是要调用Product角色和生成实例的方法(factoryMethod)就可以生成Product实例

  • 产品Product
    是一个抽象类,它定义了在Factory Method模式中生成哪些实例所持有的接口(API),具体处理由子类ConcreteProduct决定

  • 具体的产品ConcreteProduct

    ConcreteProduct角色属于加工这一方,他决定了具体的产品

  • 具体的创建者ConcreteFactory

    ConcreteFactory角色属于加工这一方,它负责生成具体的产品


Sample


我们以生成身份ID卡为例,一共有四个类:

image-20210210152516916

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
  • 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;

}
  • CardCommonServiceGoodsCommonService实现奖品发放接口:实现接口中的方法,在方法中调用原有接口的方法,并对出参和入参进行封装适配现在的接口(代码中没有展现具体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);
    
}
赞赏