设计模式学习笔记-2 创建者模式-工厂方法模式

设计模式学习笔记-2 创建者模式-工厂方法模式

工厂模式介绍

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

这种设计模式使Java开发中最常见的一种设计模式,他的主要意图是定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

简单说就是为了提供代码结构的扩展性,屏蔽每一个功能类中的具体实现逻辑。让外部可以更加简单的只是知道调用即可,同时,这也是去掉众多if else的方式,这种方式也存在一些缺点,比如需要实现的类非常多,如何去维护,怎样减低开发成本。但这些问题可以在后续的设计模式结合使用中,逐步降低。

模拟发奖多种商品

场景:用户使用积分兑换多种类型商品,假设现有以下三种类型的商品接口

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

以上接口有如下信息:

  • 三个接口返回类型不同,有对象类型,布尔类型,还有一个空类型。
  • 入参不同,发放优惠券需要仿重,兑换卡需要卡ID,实物商品需要发货位置。
  • 另外可能会随着后续的业务的发展,会新增其他种商品类型。因为你所有的开发需求都是随着业务对市场的拓展而带来的。

使用if else实现

请求参数:
import lombok.Data;
import lombok.ToString;

import java.util.Map;

@Data
@ToString
public class AwardReq {
    private String uId;
    private Integer awardType;
    private String awardNumber;
    private String bizId;
    private Map<String, String> extMap;
}
响应参数:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class AwardRes {

    private String code;
    private String message;
}
优惠券接口响应结果:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class CouponResult {
    private String code;
    private String message;
}
优惠券发放接口:
public class CouponService {

    public CouponResult sendCoupon(String uId, String couponNumber, String uuid) {
        return new CouponResult("0000", "优惠券发送成功");
    }

}
实物商品发放接口请求参数:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeliverReq {
    private String username;
    private String userPhone;
    private String sku;
    private String orderId;
    private String consigneeUsername;
    private String consigneeUserPhone;
    private String consigneeUserAddress;
}
实物商品发放接口:
public class GoodsService {

    public Boolean deliverGoods(DeliverReq req) {
        return true;
    }
}
第三方兑换(爱奇艺)接口:
public class IQiYiCardService {

    public void grantToken(String bindMobileNumber, String cardId) {

    }
}
兑换接口:
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class PrizeController {

    public AwardRes awardToUser(AwardReq req) {
        AwardRes awardRes = null;
        try {
            log.info("奖品发放开始{},req:{}", req.getUId(), req);
            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.getMessage());
                }
            } 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", "发放成功");
            }
            log.info("奖品发放完成{}", req.getUId());
        } catch (Exception e) {
            log.error("奖品发放失败{},req:{}", req.getUId(), req);
            awardRes = new AwardRes("0001", e.getMessage());
        }
        return awardRes;
    }

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

    private String queryUsername(String uId) {
        return "yaroo";
    }
}
单元测试:
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;

@Slf4j
class PrizeControllerTest {

    @Test
    void awardToUser() {
        PrizeController prizeController = new PrizeController();

        // 模拟发放优惠券测试
        System.out.println("\r\n模拟发放优惠券测试\r\n");
        AwardReq req01 = new AwardReq();
        req01.setUId("1001");
        req01.setAwardType(1);
        req01.setAwardNumber("EGM1023938910232121323432");
        req01.setBizId("791098764902132");
        AwardRes awardRes01 = prizeController.awardToUser(req01);
        log.info("请求参数:{}", req01);
        log.info("测试结果:{}", awardRes01);

        // 模拟发放实物商品
        System.out.println("\r\n模拟发放实物商品\r\n");
        AwardReq req02 = new AwardReq();
        req02.setUId("10001");
        req02.setAwardType(2);
        req02.setAwardNumber("9820198721311");
        req02.setBizId("1023000020112221113");
        Map<String, String> extMap = new HashMap<>();
        extMap.put("consigneeUserName", "谢⻜机");
        extMap.put("consigneeUserPhone", "15200292123");
        extMap.put("consigneeUserAddress", "吉林省.⻓春市.双阳区.XX街道.檀溪苑⼩区.#18-2109");
        req02.setExtMap(extMap);
        AwardRes awardRes02 = prizeController.awardToUser(req02);
        log.info("请求参数:{}", req02);
        log.info("测试结果:{}", 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);
        log.info("请求参数:{}", req03);
        log.info("测试结果:{}", awardRes03);
    }
}
测试结果:

模拟发放优惠券测试

15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeController - 奖品发放开始1001,req:AwardReq(uId=1001, awardType=1, awardNumber=EGM1023938910232121323432, bizId=791098764902132, extMap=null)
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeController - 奖品发放完成1001
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeControllerTest - 请求参数:AwardReq(uId=1001, awardType=1, awardNumber=EGM1023938910232121323432, bizId=791098764902132, extMap=null)
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeControllerTest - 测试结果:AwardRes(code=0000, message=发放成功)

模拟发放实物商品

15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeController - 奖品发放开始10001,req:AwardReq(uId=10001, awardType=2, awardNumber=9820198721311, bizId=1023000020112221113, extMap={consigneeUserName=谢⻜机, consigneeUserPhone=15200292123, consigneeUserAddress=吉林省.⻓春市.双阳区.XX街道.檀溪苑⼩区.#18-2109})
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeController - 奖品发放完成10001
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeControllerTest - 请求参数:AwardReq(uId=10001, awardType=2, awardNumber=9820198721311, bizId=1023000020112221113, extMap={consigneeUserName=谢⻜机, consigneeUserPhone=15200292123, consigneeUserAddress=吉林省.⻓春市.双阳区.XX街道.檀溪苑⼩区.#18-2109})
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeControllerTest - 测试结果:AwardRes(code=0000, message=发放成功)

第三方兑换卡(爱奇艺)

15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeController - 奖品发放开始10001,req:AwardReq(uId=10001, awardType=3, awardNumber=AQY1xjkUodl8LO975GdfrYUio, bizId=null, extMap=null)
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeController - 奖品发放完成10001
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeControllerTest - 请求参数:AwardReq(uId=10001, awardType=3, awardNumber=AQY1xjkUodl8LO975GdfrYUio, bizId=null, extMap=null)
15:33:40.695 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_01.PrizeControllerTest - 测试结果:AwardRes(code=0000, message=发放成功)

工厂模式优化代码

发奖接口:
import java.util.Map;

public interface ICommodity {
    void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception;
}
  • 所有的奖品无论是实物,虚拟还是第三方,都需要通过我们的程序实现此接口进行处理,以保证最终入参与出参的统一性。
  • 接口的入参包括:用户ID,奖品ID,业务ID以及扩展字段用于处理发放实物商品时的收获地址。
奖品发放接口
优惠券
import com.yaroo.head_first_demo.design_pattern.demo1_01.CouponResult;
import com.yaroo.head_first_demo.design_pattern.demo1_01.CouponService;
import com.yaroo.head_first_demo.design_pattern.demo1_02.store.ICommodity;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;

@Slf4j
public class CouponCommodityService implements ICommodity {

    private final 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);
        log.info("请求参数[优惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, extMap);
        log.info("测试结果[优惠券]:{}", couponResult);
        if (!"0000".equals(couponResult.getCode())) {
            throw new RuntimeException(couponResult.getMessage());
        }
    }
}
实物商品
import com.yaroo.head_first_demo.design_pattern.demo1_01.DeliverReq;
import com.yaroo.head_first_demo.design_pattern.demo1_01.GoodsService;
import com.yaroo.head_first_demo.design_pattern.demo1_02.store.ICommodity;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;

@Slf4j
public class GoodsCommodityService implements ICommodity {

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

    private String queryUserName(String uId) {
        return "花花";
    }
    private String queryUserPhoneNumber(String uId) {
        return "15200101232";
    }
}
第三方兑换卡
import com.yaroo.head_first_demo.design_pattern.demo1_01.IQiYiCardService;
import com.yaroo.head_first_demo.design_pattern.demo1_02.store.ICommodity;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;

@Slf4j
public class CardCommodityService implements ICommodity {

    private final IQiYiCardService iQiYiCardService = new IQiYiCardService();

    @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, extMap);
        log.info("测试结果[爱奇艺兑换卡]:success");
    }

    private String queryUserMobile(String uId) {
        return "15200101232";
    }
}
  • 上面所有的奖品的发放类可以看出,每一种的奖品的实现都包括在自己的类中,新增,修改或者删除都不会影响其他奖品功能的测试,降低回归测试的可能。
  • 后续在新增的奖品只需要按照此结构进行填充即可,非常易于维护和扩展。
  • 在统一处理了入参与出参后,调用方不需要关心奖品发放的内部逻辑,按照统一的方式即可处理。
商店工厂类
import com.yaroo.head_first_demo.design_pattern.demo1_02.store.ICommodity;
import com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.CardCommodityService;
import com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.CouponCommodityService;
import com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.GoodsCommodityService;

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

统一定义了一个商品的工厂类,在里面按照类型实现各种商品的服务。

单元测试

测试类:

import com.yaroo.head_first_demo.design_pattern.demo1_02.store.ICommodity;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;

class StoreFactoryTest {

    @Test
    void getCommodityService() 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<>();
        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);
    }
}

测试结果:

16:02:32.867 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.CouponCommodityService - 请求参数[优惠券] => uId:10001 commodityId:EGM1023938910232121323432 bizId:791098764902132 extMap:null
16:02:32.883 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.CouponCommodityService - 测试结果[优惠券]:CouponResult(code=0000, message=优惠券发送成功)
16:02:32.883 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.GoodsCommodityService - 请求参数[实物商品] => uId:10001 commodityId:9820198721311 bizId:102300002011222 1113 extMap:{consigneeUserName=谢⻜机, consigneeUserPhone=15200292123, consigneeUserAddress=吉林省.⻓春市.双阳区.XX街道.檀溪苑⼩区.#18-2109}
16:02:32.883 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.GoodsCommodityService - 测试结果[实物商品]:true
16:02:32.883 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.CardCommodityService - 请求参数[爱奇艺兑换卡] => uId:10001 commodityId:AQY1xjkUodl8LO975GdfrYUio bizId:null extMap:null
16:02:32.883 [main] INFO com.yaroo.head_first_demo.design_pattern.demo1_02.store.impl.CardCommodityService - 测试结果[爱奇艺兑换卡]:success

总结

  • 避免创建者与具体的产品逻辑耦合
  • 满足单一职责,每一个业务逻辑实现都在所属自己的类中完成
  • 满足开闭原则,无需更改使用调用方就可以在程序中引起新的产品类型
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值