设计模式---工厂模式

        工厂设计模式是一种创建型设计模式,用于处理对象创建的问题,使得对象的创建与使用分离。它提供了一种封装机制,使得客户端不需要知道具体的类,只需要知道接口即可。工厂模式允许系统在不指定具体类的情况下创建对象,使得系统更加灵活和易于扩展。

1. 适用场景  

  • 创建复杂对象:当对象的创建过程复杂或者依赖多个其他对象时,使用工厂模式可以简化对象的创建过程。

  • 需要解耦:当客户端不应该知道具体的产品类时,工厂模式可以将对象的创建和使用分离,提高系统的灵活性。

  • 支持多种产品:当需要支持多种产品,并且这些产品之间存在共同的接口时,工厂模式可以提供统一的创建接口。

  • 需要扩展系统:当系统需要在未来扩展以支持更多产品时,工厂模式可以方便地添加新的产品类而不影响现有代码。

2. 逻辑流程图

 

3. 案例

3.1 业务场景

        多种类型商品不同接口,统一发奖服务搭建场景。

3.2 要点

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

3.3 实现

3.3.1 工厂结构
com.xiakai
│
├── model
│   ├── VO
│   │   ├── DeliverReq.java
│   │── CouponResult.java
│   │
│
├── service
│   ├── impl
│   │   ├── CouponService.java
│   │   ├── GoodsService.java
│   │   └── QiYiCardService.java
│   │
│   ├── ICouponService.java
│   ├── IGoodsService.java
│   └── IQiYiCardService.java
│
└── store
    ├── factory
    │   └── cStoreFactory.java
    │
    ├── impl
    │   ├── CardCommodityService.java
    │   ├── CouponCommodityService.java
    │   └── GoodsCommodityService.java
    └── ICommodity.java
3.3.2 模型
/**
 * Author:yang
 * Date:2024-09-21 10:26
 */
@Data
public class DeliverReq {
    private String userName;
    private String userPhone;
    private String sku;
    private String orderId;
    private String consigneeUserName;
    private String consigneeUserPhone;
    private String consigneeUserAddress;
}


/**
 * Author:yang
 * Date:2024-09-21 10:21
 */
@Data
@Builder
public class CouponResult {
    private String code;
    private String info;
}
3.3.3 定义发放接口
/**
 * Author:yang
 * Date:2024-09-21 10:12
 * Description:发奖接口
 */
public interface ICommodity {
    void sendCommodity(String uId, String commodityId,
 String bizId, Map<String, String> extMap) throws Exception;
}
  • 所有的奖品无论是实物、虚拟还是第三方,都需要通过程序实现此接口进行处理,以保证最终入参出参的统一性。

  • 接口的入参包括;用户ID、 奖品ID、 业务ID以及扩展字段用于处理发放实物商品时的收获地址。

3.3.4 实现奖品发放接口

        优惠券

/**
 * Author:yang
 * Date:2024-09-21 10:14
 * Description:优惠券商品服务
 */
@Slf4j
public class CouponCommodityService implements ICommodity {


    private ICouponService couponService = new CouponService();

    @Override
    public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
        log.info("请求参数[优惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));

        CouponResult couponResult = couponService.sendCoupon(uId, commodityId, bizId, extMap);

        log.info("测试结果[优惠券]:{}", JSON.toJSON(couponResult));
        if (!"0000".equals(couponResult.getCode())) throw new RuntimeException(couponResult.getInfo());

    }
}

        实物商品

/**
 * Author:yang
 * Date:2024-09-21 10:23
 * Description:实物商品服务
 */
@Slf4j
public class GoodsCommodityService implements ICommodity {


    private IGoodsService 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);
        log.info("请求参数[实物商品] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
        log.info("测试结果[实物商品]:{}", isSuccess);
        if (!isSuccess) throw new RuntimeException("实物商品发放失败");
    }

    private String queryUserName(String uId) {
        return "花花";
    }
    private String queryUserPhoneNumber(String uId) {
        return "15200101232";

    }
}

        第三方服务

/**
 * Author:yang
 * Date:2024-09-21 10:26
 * Description:第三方兑换卡服务
 */
@Slf4j
public class CardCommodityService implements ICommodity {

    private IQiYiCardService iQiYiCardService = new QiYiCardService();

    @Override
    public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
        String mobile = queryUserMobile(uId);
        iQiYiCardService.grantToken(mobile, bizId);
        log.info("请求参数[爱奇艺兑换卡] => uId:{} commodityId:{} bizId: {} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
        log.info("测试结果[爱奇艺兑换卡]:success");
    }

    private String queryUserMobile(String uId) {
        return "15200101232";
    }
}
  • 从上面可以看到每一种奖品的实现都包括在自己的类中,新增、修改或者删除都不会影响其他奖品功能的测试,降低回归测试的可能。

  • 后续在新增的奖品只需要按照此结构进行填充即可,非常易易于维护和扩展。 在统一了入参以及出参后,调用方不再需要关心奖品发放的内部逻辑,按照统一的方式即可处理。

3.3.5 创建商品工厂
/**
 * Author:yang
 * Date:2024-09-21 10:29
 * Description:生产工厂
 */
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("不不存在的商品服务类型");

    }
}
  • 这里定义了一个商店的工厂类,在里面按照类型实现各种商品的服务。可以非常干净整洁的处理代码,后续新增的商品在这里扩展即可。如果你不喜欢if判断,也可以使用switch或者map配置结构,会让代码更加干净。

  • 另外很多代码检查软件和编码要求,不喜欢if语句后面不写扩展,这里是为了更加干净的体现逻辑。在实际的业务编码中可以添加括号。

3.3.6 测试
public class Main {

    public static void main(String[] args) 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", "谢⻜飞机");
        extMap.put("consigneeUserPhone", "15200292123");
        extMap.put("consigneeUserAddress", "吉林林省.⻓长春市.双阳区.XX街道.檀溪苑⼩小区.#18-2109");
        commodityService_2.sendCommodity("10001", "9820198721311", "102300002011222 1113", extMap);

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

}

        结果:

10:52:16.548 [main] INFO com.xiakai.store.impl.CouponCommodityService - 请求参数[优惠券] => uId:10001 commodityId:EGM1023938910232121323432 bizId:791098764902132 extMap:null
10:52:16.560 [main] INFO com.xiakai.store.impl.CouponCommodityService - 测试结果[优惠券]:{"code":"0000","info":"success"}
10:52:16.576 [main] INFO com.xiakai.store.impl.GoodsCommodityService - 请求参数[实物商品] => uId:10001 commodityId:9820198721311 bizId:102300002011222 1113 extMap:{"consigneeUserName":"谢⻜飞机","consigneeUserPhone":"15200292123","consigneeUserAddress":"吉林林省.⻓长春市.双阳区.XX街道.檀溪苑⼩小区.#18-2109"}
10:52:16.576 [main] INFO com.xiakai.store.impl.GoodsCommodityService - 测试结果[实物商品]:true
10:52:16.577 [main] INFO com.xiakai.store.impl.CardCommodityService - 请求参数[爱奇艺兑换卡] => uId:10001 commodityId:AQY1xjkUodl8LO975GdfrYUio bizId: null extMap:null
10:52:16.577 [main] INFO com.xiakai.store.impl.CardCommodityService - 测试结果[爱奇艺兑换卡]:success
3.3.7 优化

        引入SpringBoot,优化代码逻辑

  • 定义商品枚举

/**
 * Author:yang
 * Date:2024-09-21 13:18
 * Description:商品类型枚举
 */
public enum ProductTypeEnum {
    COUPON(1, "coupon"),
    GOODS(2, "goods"),
    CARD(3, "card");

    private int code;
    private String serviceName;

    ProductTypeEnum(int code, String serviceName) {
        this.code = code;
        this.serviceName = serviceName;
    }

    /*
     * param code 商品类型编码
     * return serviceName 商品类型名称
     */
    public static String getServiceName(int code) {
        for (ProductTypeEnum productTypeEnum : ProductTypeEnum.values()) {
            if (productTypeEnum.getCode() == code) {
                return productTypeEnum.getServiceName();
            }
        }
        return null;
    }

    public int getCode() {
        return code;
    }

    public String getServiceName() {
        return serviceName;
    }
}
  • 工厂方法

package com.xiakai.store.factory;

import com.xiakai.model.enums.ProductTypeEnum;
import com.xiakai.store.ICommodity;
import org.springframework.stereotype.Service;

import java.util.Map;

/**
 * Author:yang
 * Date:2024-09-21 10:29
 * Description:生产工厂
 */
@Service
public class StoreFactory {

    private final Map<String,ICommodity> commodityGroup;

    public StoreFactory(Map<String, ICommodity> commodityServiceMap) {
        this.commodityGroup = commodityServiceMap;
    }


    public ICommodity getCommodityService(Integer commodityType) {
        String serviceName = ProductTypeEnum.getServiceName(commodityType);
        ICommodity commodity = commodityGroup.get(serviceName);
        return commodity;
    }
}

        使用map注入,同时根据传入的type去寻找对于bean的name,最后返回bean。

@Service("coupon")
public class CouponCommodityService implements ICommodity
  • 测试
package com.xiaokai;

import com.xiakai.FactoryApplication;
import com.xiakai.store.ICommodity;
import com.xiakai.store.factory.StoreFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * Author:yang
 * Date:2024-09-21 13:02
 */
@SpringBootTest(classes = FactoryApplication.class)
@RunWith(SpringRunner.class)
public class FactoryTest {

    @Autowired
    private StoreFactory storeFactory;

    @Test
    public void testFactory() {
        ICommodity commodityService = storeFactory.getCommodityService(1);
        try {
            commodityService.sendCommodity("10001", "EGM1023938910232121323432", "791098764902132", null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
3.3.8 总结
  • 从上到下的优化来看,工厂方法模式并不不复杂,甚至这样的开发结构在你有所理解后,会发现更加简单了。 那么这样的开发的好处知道后,也可以总结出来它的优点;避免创建者与具体的产品逻辑耦合、满足单一职责,每一个业务逻辑实现都在所属自己的类中完成、 满足开闭原则,无需更改使用调用方就可以在程序中引入新的产品类型。但这样也会带来一些问题,比如有非常多的奖品类型,那么实现的子类会极速扩张。因此也需要使用其他的模式进行优化,这些在后续的设计模式中会逐步及到。

  • 从案例入手看设计模式往往要比看理论学的更加容易,因为案例是缩短理论到上手的最佳方式。

4. 其他案例

4.1 简单工厂模式

场景:一个软件系统需要根据不同的配置文件生成不同类型的报告。

实现

  • 定义一个报告接口 Report

  • 创建具体的报告类,如 PDFReportWordReport,它们都实现 Report 接口。

  • 实现一个工厂类 ReportFactory,它根据传入的配置文件类型(如 "PDF" 或 "Word")来创建相应的报告对象。

4.2 工厂方法模式

场景:一个图形编辑器需要能够加载不同类型的图形文件。

实现

  • 定义一个抽象类 GraphicEditor,它包含一个抽象方法 loadGraphicFile()

  • 创建具体的编辑器类,如 JPEGEditorPNGEditor,它们继承自 GraphicEditor 并实现 loadGraphicFile() 方法。

  • 每个编辑器类都有自己的工厂类,如 JPEGEditorFactoryPNGEditorFactory,它们实现了一个工厂接口 GraphicEditorFactory,该接口包含一个方法 createEditor() 用于创建编辑器实例。

4.3 抽象工厂模式

场景:一个应用程序需要支持多种操作系统的界面元素。

实现

  • 定义一组抽象产品类,如 ButtonTextBox 等。

  • 为每种操作系统定义具体的产品类,如 WindowsButtonMacButton 等。

  • 创建一个抽象工厂接口 GUIFactory,它包含创建所有抽象产品的方法。

  • 为每种操作系统实现一个具体的工厂类,如 WindowsFactoryMacFactory,它们都实现了 GUIFactory 接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值