工厂+策略模式的妙用~

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

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...

相关推荐
晨曦_子画1 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
假装我不帅21 分钟前
asp.net framework从webform开始创建mvc项目
后端·asp.net·mvc
南宫生24 分钟前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
神仙别闹24 分钟前
基于ASP.NET+SQL Server实现简单小说网站(包括PC版本和移动版本)
后端·asp.net
Heavydrink37 分钟前
HTTP动词与状态码
java
ktkiko1140 分钟前
Java中的远程方法调用——RPC详解
java·开发语言·rpc
计算机-秋大田1 小时前
基于Spring Boot的船舶监造系统的设计与实现,LW+源码+讲解
java·论文阅读·spring boot·后端·vue
神里大人1 小时前
idea、pycharm等软件的文件名红色怎么变绿色
java·pycharm·intellij-idea
货拉拉技术1 小时前
货拉拉-实时对账系统(算盘平台)
后端
小冉在学习1 小时前
day53 图论章节刷题Part05(并查集理论基础、寻找存在的路径)
java·算法·图论