工厂+策略模式的妙用~

只做分享,不涉及利益,大家一起进步!!!

Hello,大家好。大家工作中有没有在一个业务使用过大量的if else啊,如果有,你看到这篇文章就能去优化自己的代码了,兄弟们赶紧冲啊!!!

在这篇文章中,我将使用会员制度打折促销为案例,为大家循序渐进的讲述使用工厂+策略模式之优雅。

Java版本: 21

SpringBoot版本: 3.3.0

案例前置:

Demo 中没有使用数据库相关内容,而是使用缓存热启动提前在 Map 中加载了些参数,存在用户(id, name, gender, age, type)与商品(id, name, price)实体,之前我写过关于缓存热启动的文章,大家有兴趣可以去看看,这里放个具体的缓存代码,在这个 Demo 中,缓存热启动也做了优化,我会把源码 github 链接放在下边,大家也可以进行参阅:

用户缓存

java 复制代码
@Slf4j
@Component
public class UserCache extends AbstractCache<Long, User> {

    private final Map<Long, User> userMap = new HashMap<>();

    /**
     * 初始化缓存
     */
    @Override
    public void init() {
        this.userMap.put(1L, User.builder().id(1L).name("Lucy").gender("女").age(18).type(0).build());
        this.userMap.put(2L, User.builder().id(2L).name("Jack").gender("男").age(20).type(1).build());
        this.userMap.put(3L, User.builder().id(3L).name("Mick").gender("男").age(22).type(2).build());
        this.userMap.put(4L, User.builder().id(4L).name("Andy").gender("女").age(23).type(3).build());
        this.userMap.put(5L, User.builder().id(5L).name("Pelin").gender("女").age(23).type(4).build());
        log.info("user cache init success: {}", JSON.toJSONString(this.userMap));
    }

    /**
     * 获取缓存
     *
     * @param key 键
     * @return 值
     */
    @Override
    public User get(Long key) {
        return this.userMap.get(key);
    }

    /**
     * 清空缓存
     */
    @Override
    public void clear() {
        this.userMap.clear();
    }
}

商品缓存

java 复制代码
@Slf4j
@Component
public class MerchandiseCache extends AbstractCache<Long, Merchandise> {

    private final Map<Long, Merchandise> merchandiseMap = new HashMap<>();

    @Override
    public void init() {
        this.merchandiseMap.put(1L, Merchandise.builder().id(1L).name("小米14").price(BigDecimal.valueOf(3999.00)).build());
        this.merchandiseMap.put(2L, Merchandise.builder().id(2L).name("小米路由器").price(BigDecimal.valueOf(399.00)).build());
        this.merchandiseMap.put(3L, Merchandise.builder().id(3L).name("小米扫地机器人").price(BigDecimal.valueOf(2299.00)).build());
        this.merchandiseMap.put(4L, Merchandise.builder().id(4L).name("小米电视S75英寸4K").price(BigDecimal.valueOf(3899.00)).build());
        this.merchandiseMap.put(5L, Merchandise.builder().id(5L).name("小米SoundPro").price(BigDecimal.valueOf(899)).build());
        log.info("merchandise cache init success: {}", JSON.toJSONString(this.merchandiseMap));
    }

    @Override
    public Merchandise get(Long key) {
        return this.merchandiseMap.get(key);
    }

    @Override
    public void clear() {
        this.merchandiseMap.clear();
    }
}

折扣枚举

java 复制代码
@Getter
public enum DiscountEnum {

    NORMAL(0, "普通用户", BigDecimal.valueOf(1.00)),
    VIP(1, "普通VIP", BigDecimal.valueOf(0.95)),
    PVIP(2, "高级VIP", BigDecimal.valueOf(0.90)),
    EVIP(4, "至尊VIP", BigDecimal.valueOf(0.85)),
    SVIP(3, "超级VIP", BigDecimal.valueOf(0.80));

    /**
     * 用户类型code
     */
    private final int code;
    /**
     * 用户类型文字
     */
    private final String type;
    /**
     * 折扣
     */
    private final BigDecimal discount;

    DiscountEnum(int code, String type, BigDecimal discount) {
        this.code = code;
        this.type = type;
        this.discount = discount;
    }

    /**
     * 根据code获取枚举
     *
     * @param code code
     * @return DiscountEnum
     */
    public static DiscountEnum valueOf(int code) {
        for (DiscountEnum value : DiscountEnum.values()) {
            if (value.getCode() == code) {
                return value;
            }
        }
        return null;
    }

}

大量 if else 的弊端

大家看,下边这段根据用户类型(type)进行打折促销的代码:

java 复制代码
@Slf4j
@Service("shoppingService1")
public class ShoppingService1Impl implements ShoppingService {

    @Resource
    private UserCache userCache;

    @Resource
    private MerchandiseCache merchandiseCache;

    /**
     * 购买商品
     *
     * @param userId        用户ID
     * @param merchandiseId 商品ID
     * @return 购买结果
     */
    @Override
    public Map<String, Object> buy(Long userId, Long merchandiseId) {
        User user = userCache.get(userId);
        Merchandise merchandise = merchandiseCache.get(merchandiseId);
        log.info("current user: {}", JSON.toJSONString(user));
        log.info("current merchandise: {}", JSON.toJSONString(merchandise));
        DecimalFormat df = new DecimalFormat("0.00");
        DiscountEnum discountEnum = DiscountEnum.valueOf(user.getType());
        log.info("user type: {}, 享受折扣: {}", discountEnum.getType(), df.format(discountEnum.getDiscount()));
        // 普通用户
        if (user.getType() == 0) {
            return Map.of("用户姓名", user.getName(),
                    "商品名称", merchandise.getName(),
                    "商品价格", merchandise.getPrice(),
                    "折后价格", merchandise.getPrice().multiply(discountEnum.getDiscount()));

        } else
            // 普通VIP
            if (user.getType() == 1) {
                return Map.of("用户姓名", user.getName(),
                        "用户性别", user.getGender(),
                        "用户等级", discountEnum.getType(),
                        "商品名称", merchandise.getName(),
                        "商品价格", merchandise.getPrice(),
                        "应享折扣", discountEnum.getDiscount(),
                        "折后价格", merchandise.getPrice().multiply(discountEnum.getDiscount()));
            } else
                // 高级VIP
                if (user.getType() == 2) {
                    return Map.of(
                            "用户姓名", user.getName(),
                            "用户年龄", user.getAge(),
                            "用户等级", discountEnum.getType(),
                            "商品名称", merchandise.getName(),
                            "商品价格", merchandise.getPrice(),
                            "应享折扣", discountEnum.getDiscount(),
                            "折后价格", merchandise.getPrice().multiply(discountEnum.getDiscount()));
                } else
                    // 至尊VIP
                    if (user.getType() == 3) {
                        return Map.of("用户姓名", user.getName(),
                                "用户性别", user.getGender(),
                                "用户年龄", user.getAge(),
                                "用户等级", discountEnum.getType(),
                                "商品名称", merchandise.getName(),
                                "商品价格", merchandise.getPrice(),
                                "应享折扣", discountEnum.getDiscount(),
                                "折后价格", merchandise.getPrice().multiply(discountEnum.getDiscount()));
                    } else
                        // 超级VIP
                        if (user.getType() == 4) {
                            return Map.of("用户ID", user.getId(),
                                    "用户姓名", user.getName(),
                                    "用户性别", user.getGender(),
                                    "用户年龄", user.getAge(),
                                    "用户等级", discountEnum.getType(),
                                    "商品名称", merchandise.getName(),
                                    "商品价格", merchandise.getPrice(),
                                    "应享折扣", discountEnum.getDiscount(),
                                    "折后价格", merchandise.getPrice().multiply(discountEnum.getDiscount()));
                        }
        return Map.of();
    }

}

其实问题很容易看出来,就是这样的代码太臃肿了,也违反了开闭原则(对扩展开放,对修改关闭),将来又加入新的用户类型,还需要在这基础上继续添加逻辑代码,实在是太不优雅了。

为了解决这个问题呢,先是引入了策略模式,其实单纯的策略模式还是不能够解决全部问题,大家继续往下看。

策略模式

先定义一个策略接口:

java 复制代码
public interface ShoppingStrategyAware {
    /**
     * 购买
     *
     * @param user        用户
     * @param merchandise 商品
     * @return 结果
     */
    Map<String, Object> buy(User user, Merchandise merchandise);
}

再将不同的用户类型当做不同的策略分别创建一个类来继承这个策略接口,案例中一共有五个用户类型,全部代码放上来就太多啦(普通用户、普通VIP、高级VIP、至尊VIP、超级VIP),这里放两个进行举例,完整的请查看下边的 github 源码链接。

普通用户

java 复制代码
@Slf4j
@Component
public class NormalStrategy implements ShoppingStrategyAware {

    @Override
    public Map<String, Object> buy(User user, Merchandise merchandise) {
        log.info("current strategy: {}", getStrategy().getType());
        DecimalFormat df = new DecimalFormat("0.00");
        Map<String, Object> result = new HashMap<>();
        result.put("用户姓名", user.getName());
        result.put("商品名称", merchandise.getName());
        result.put("商品原价", df.format(merchandise.getPrice()));
        result.put("折后价格", df.format(merchandise.getPrice().multiply(getStrategy().getDiscount())));
        return result;
    }
    
}

普通VIP

java 复制代码
@Slf4j
@Component
public class VIPStrategy implements ShoppingStrategyAware {

    @Override
    public Map<String, Object> buy(User user, Merchandise merchandise) {
        log.info("current strategy: {}", getStrategy().getType());
        DecimalFormat df = new DecimalFormat("0.00");
        Map<String, Object> result = new HashMap<>();
        result.put("用户姓名", user.getName());
        result.put("用户性别", user.getGender());
        result.put("用户等级", getStrategy().getType());
        result.put("商品名称", merchandise.getName());
        result.put("商品原价", df.format(merchandise.getPrice()));
        result.put("应享折扣", df.format(getStrategy().getDiscount()));
        result.put("折后价格", df.format(merchandise.getPrice().multiply(getStrategy().getDiscount())));
        return result;
    }
    
}

将上边大量 if else 进行改造,将不同的策略在业务中进行注入:

java 复制代码
@Slf4j
@Service("shoppingService2")
public class ShoppingService2Impl implements ShoppingService {

    @Resource
    private UserCache userCache;

    @Resource
    private MerchandiseCache merchandiseCache;

    @Resource(name = "normalStrategy")
    private ShoppingStrategyAware normalStrategy;

    @Resource(name = "VIPStrategy")
    private ShoppingStrategyAware vipStrategy;

    @Resource(name = "PVIPStrategy")
    private ShoppingStrategyAware pvipStrategy;

    @Resource(name = "EVIPStrategy")
    private ShoppingStrategyAware evipStrategy;

    @Resource(name = "SVIPStrategy")
    private ShoppingStrategyAware svipStrategy;

    /**
     * 购买商品
     *
     * @param userId        用户ID
     * @param merchandiseId 商品ID
     * @return 购买结果
     */
    @Override
    public Map<String, Object> buy(Long userId, Long merchandiseId) {
        User user = userCache.get(userId);
        Merchandise merchandise = merchandiseCache.get(merchandiseId);
        log.info("current user: {}", JSON.toJSONString(user));
        log.info("current merchandise: {}", JSON.toJSONString(merchandise));
        // 普通用户
        if (user.getType() == 0) {
            return normalStrategy.buy(user, merchandise);
        } else
            // 普通VIP
            if (user.getType() == 1) {
                return vipStrategy.buy(user, merchandise);
            } else
                // 高级VIP
                if (user.getType() == 2) {
                    return pvipStrategy.buy(user, merchandise);
                } else
                    // 至尊VIP
                    if (user.getType() == 3) {
                        return evipStrategy.buy(user, merchandise);
                    } else
                        // 超级VIP
                        if (user.getType() == 4) {
                            return svipStrategy.buy(user, merchandise);
                        }
        return Map.of();
    }
}

我相信聪明的小伙伴们,一眼就能发现弊端,虽然业务逻辑进行了封装,但是,这也注入太多策略了,而且,还是不能避免使用大量的 if else,那么这样还需要如何进行优化呢?其实,再加一个工厂模式就能完美解决。

引入工厂模式

我们现将之前创建的策略接口加个方法:

java 复制代码
/**
 * 返回应享折扣枚举
 *
 * @return 折扣
 */
DiscountEnum getStrategy();

再将具体的策略进行方法重写,将策略的枚举进行返回即可:

普通用户

java 复制代码
@Override
public DiscountEnum getStrategy() {
    log.info("load strategy: {}", JSON.toJSONString(DiscountEnum.NORMAL));
    return DiscountEnum.NORMAL;
}

普通VIP

java 复制代码
@Override
public DiscountEnum getStrategy() {
    log.info("load strategy: {}", JSON.toJSONString(DiscountEnum.VIP));
    return DiscountEnum.VIP;
}

然后我们定义一个工厂类:

java 复制代码
@Slf4j
@Component
public class ShoppingFactory {

    @Resource
    private List<ShoppingStrategyAware> shoppingStrategies;

    private final Map<DiscountEnum, ShoppingStrategyAware> shoppingMap = new HashMap<>();

    /**
     * 根据code获取折扣策略
     *
     * @param code code
     * @return DiscountEnum
     */
    public ShoppingStrategyAware getStrategy(int code) {
        DiscountEnum discountEnum = DiscountEnum.valueOf(code);
        return shoppingMap.get(discountEnum);
    }

    /**
     * 注册策略枚举
     */
    @PostConstruct
    public void register() {
        shoppingStrategies.forEach(shoppingStrategy -> {
            DiscountEnum discount = shoppingStrategy.getStrategy();
            shoppingMap.put(discount, shoppingStrategy);
        });
    }
}

在上边的工厂类里,我注入了一个List类型的策略接口和一个Map缓存,用于存放折扣枚举对应的策略,然后在下边register方法上加了一个@PostConstruct注解,目的是将所有的策略在项目启动时就加载好。

然后再提供一个根据用户类型获取策略的方法getStrategy,这样之后,我们就又可以对代码进行改造了,在业务层,我们只需要注入一个策略工厂就好了:

java 复制代码
@Service("shoppingService3")
public class ShoppingService3Impl implements ShoppingService {

    @Resource
    private ShoppingFactory shoppingFactory;

    @Resource
    private UserCache userCache;

    @Resource
    private MerchandiseCache merchandiseCache;

    /**
     * 购买
     *
     * @param userId        用户ID
     * @param merchandiseId 商品ID
     * @return 购买结果
     */
    @Override
    public Map<String, Object> buy(Long userId, Long merchandiseId) {
        User user = userCache.get(userId);
        Merchandise merchandise = merchandiseCache.get(merchandiseId);
        log.info("current user: {}", JSON.toJSONString(user));
        log.info("current merchandise: {}", JSON.toJSONString(merchandise));
        ShoppingStrategyAware strategy = shoppingFactory.getStrategy(user.getType());
        return strategy.buy(user, merchandise);
    }
}

可以看到,业务逻辑那里,通过调用工厂的getStrategy方法传入用户类型来获取具体策略,然后调用策略的buy方法就好了,这样的好处就是,将来不管怎么扩展,我们都不需要更改这里的代码,只需要,继续实现策略接口,然后实现具体的业务逻辑就好啦,是不是很优雅呢~

看到这里的小伙伴,为努力的自己点个赞吧!欢迎留言,文章中如有错误,请指出,我会进行修改,感谢大家的阅读,我们下期再见啦~~~

Demo 源码地址

github.com/hanjx369/sh...

相关推荐
哪 吒3 小时前
最简单的设计模式,抽象工厂模式,是否属于过度设计?
设计模式·抽象工厂模式
Theodore_10223 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
冰帝海岸4 小时前
01-spring security认证笔记
java·笔记·spring
世间万物皆对象4 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
没书读了5 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
小二·5 小时前
java基础面试题笔记(基础篇)
java·笔记·python
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
懒洋洋大魔王5 小时前
RocketMQ的使⽤
java·rocketmq·java-rocketmq
武子康6 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
qq_17448285756 小时前
springboot基于微信小程序的旧衣回收系统的设计与实现
spring boot·后端·微信小程序