搞定设计模式之工厂模式一篇文章就够了!!!

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是具体的逻辑实现)。

通过商店工厂类获取各种奖品服务,可以非常干净、整洁地处理业务逻辑代码。

后续新增的奖品按照这样的结构扩展即可。

五、总结

从优化过程来看,工厂模式并不复杂。

一旦理解和掌握,会发现它更加简单,同时也可以借助它提升开发效率。

同时,不难总结出它的优点:避免创建者与具体的产品逻辑耦合;满足单一职责,每一个业务逻辑实现都在自己所属的类中完成;满足开闭原则,无须更改使用调用方就可以在程序中引入新的产品类型。

当然,这也会带来一些问题,例如有非常多的奖品类型,实现的子类会极速扩张,因此需要使用其他的模式进行优化,这些在后续的设计模式中会逐步介绍

以上是关于搞定设计模式之工厂模式一篇文章就够了!!!的主要内容,如果未能解决你的问题,请参考以下文章

搞定设计模式之接口隔离原则一篇文章就够了!!!

搞定设计模式之迪米特法则一篇文章就够了!!!

搞定里式替换原则的设计模式一篇文章就够了!!!

搞定单一职责的设计模式一篇文章就够了!!!

搞定开闭原则的设计模式一篇文章就够了!!!

100JAVA设计模式看着一篇文章就够了!