《设计模式面试小炒》策略和工厂模式替代业务场景中复杂的ifelse

我是肥哥,一名不专业的面试官!

我是囧囧,一名积极找工作的小菜鸟!

囧囧表示:小白面试最怕的就是面试官问的知识点太笼统,自己无法快速定位到关键问题点!!!


本期主要面试考点

面试官考点之如何用设计模式替换业务场景中复杂的ifelse?

VIP类型

import java.util.Objects;

/**
* @author: 欢迎关注喂信公猪号:囧么肥事
* @date: 2021/12/16
* @email: jiongmefeishi@163.com
*
* 会员类型
*/
public enum VIPEnums { GOLD(1, "黄金会员"),
STAR(2, "星钻会员"),
SPORTS(3, "体育会员"),
FUN_VIP(4, "FUN会员"); private final int code;
private final String desc; VIPEnums(int code, String desc) {
this.code = code;
this.desc = desc;
} public int getCode() {
return code;
} public String getDesc() {
return desc;
} public static VIPEnums getByCode(Integer code) {
for (VIPEnums s : VIPEnums.values()) {
if (Objects.equals(s.getCode(), code)) {
return s;
}
}
return null;
}
}

VIP实体

/**
* @author: 欢迎关注喂信公猪号:囧么肥事
* @date: 2021/12/16
* @email: jiongmeifeishi@163.com
*
* vip
*/
public class VIP { private VIPEnums vipType; // TODO VIP 其他属性 id, name ... public VIP() {
} public VIP(VIPEnums vipType) {
this.vipType = vipType;
} public VIPEnums getVipType() {
return vipType;
} public void setVipType(VIPEnums vipType) {
this.vipType = vipType;
}
}

if-else 模式

// if-else 模式
public class App {
public static void main( String[] args ) { // 黄金会员
VIP vip = new VIP(VIPEnums.GOLD); if (vip.getVipType().getCode() == VIPEnums.GOLD.getCode()) {
// TODO 黄金会员权益
} else if (vip.getVipType().getCode() == VIPEnums.STAR.getCode()) {
// TODO 星钻会员权益
} else if (vip.getVipType().getCode() == VIPEnums.SPORTS.getCode()) {
// TODO 体育会员权益
} else if (vip.getVipType().getCode() == VIPEnums.FUN_VIP.getCode()) {
// TODO FUN会员权益
} else {
// TODO 其他会员...
} }
}

策略模式

VIP策略接口

/**
* @author: 欢迎关注喂信公猪号:囧么肥事
* @date: 2021/12/16
* @email: jiongmefeishi@163.com
* VIP 策略接口
*/
public interface VIPStrategy { // VIP 具备的权益
void equity();
}

策略接口具体实现类-黄金会员

/**
* @author: 欢迎关注喂信公猪号:囧么肥事
* @date: 2021/12/16
* @email: jiongmefeishi@163.com
*
* 策略接口具体实现类-黄金会员
*/
public class GoldVIPStrategyImpl implements VIPStrategy { @Override
public void equity() {
// TODO 黄金会员具备的具体权益
}
}

策略接口具体实现类-星钻会员

/**
* @author: 欢迎关注喂信公猪号:囧么肥事
* @date: 2021/12/16
* @email: jiongmefeishi@163.com
*
* 策略接口具体实现类-星钻会员
*/
public class StarVIPStrategyImpl implements VIPStrategy { @Override
public void equity() {
// TODO 星钻会员具备的具体权益
}
}

策略接口具体实现类-体育会员

/**
* @author: 欢迎关注喂信公猪号:囧么肥事
* @date: 2021/12/16
* @email: jiongmefeishi@163.com
*
* 策略接口具体实现类-体育会员
*/
public class SportsVIPStrategyImpl implements VIPStrategy { @Override
public void equity() {
// TODO 体育会员具备的具体权益
}
}

策略接口具体实现类-FUN会员

/**
* @author: 欢迎关注喂信公猪号:囧么肥事
* @date: 2021/12/16
* @email: jiongmefeishi@163.com
*
* 策略接口具体实现类-FUN会员
*/
public class FunVIPStrategyImpl implements VIPStrategy { @Override
public void equity() {
// TODO FUN会员具备的具体权益
}
}

策略上下文类

/**
* @author: 欢迎关注喂信公猪号:囧么肥事
* @date: 2021/12/16
* @email: jiongmefeishi@163.com
*
* 策略上下文类( vip 策略接口的持有者)
*/
public class VIPStrategyContext { private VIPStrategy vipStrategy; // 设置VIP策略
public void setVipStrategy(VIPStrategy vipStrategy) {
this.vipStrategy = vipStrategy;
} // 执行 VIP 权益
public void handle() {
if (vipStrategy != null) {
vipStrategy.equity();
}
}
}

策略工厂

/**
* @author: 欢迎关注喂信公猪号:囧么肥事
* @date: 2021/12/16
* @email: jiongmefeishi@163.com
*
* VIP策略工厂
*/
public class VIPStrategyFactory { private VIPStrategyFactory() {
} public static VIPStrategy getVipStrategy(VIP vip) {
VIPStrategy vipStrategy = null; if (vip.getVipType().getCode() == VIPEnums.GOLD.getCode()) {
// 黄金会员策略实现类
vipStrategy = new GoldVIPStrategyImpl();
} else if (vip.getVipType().getCode() == VIPEnums.STAR.getCode()) {
// 星钻会员策略实现类
vipStrategy = new StarVIPStrategyImpl();
} else if (vip.getVipType().getCode() == VIPEnums.SPORTS.getCode()) {
// 体育会员策略实现类
vipStrategy = new SportsVIPStrategyImpl();
} else if (vip.getVipType().getCode() == VIPEnums.FUN_VIP.getCode()) {
// FUN会员策略实现类
vipStrategy = new FunVIPStrategyImpl();
} else {
// 其他会员...
} return vipStrategy;
}
}

模拟会员登录获取权益

/**
* @author: 欢迎关注喂信公猪号:囧么肥事
* @date: 2021/12/16
* @email: jiongmefeishi@163.com
*
* 模拟会员登录获取权益
*/
public class TestStrategy { public static void main(String[] args) { // 黄金会员
VIP vip = new VIP(VIPEnums.GOLD); // 策略上下文,执行者
VIPStrategyContext context = new VIPStrategyContext(); // 根据会员类型,获取会员具体策略,获取黄金会员策略
VIPStrategy strategy = VIPStrategyFactory.getVipStrategy(vip); // 绑定给执行者
context.setVipStrategy(strategy); // 执行黄金会员的策略,黄金权益
context.handle();
}
}

我们知道, 策略模式的本身设计出来的目的是封装一系列的算法,这些算法都具有共性,可以相互替换,算法独立于使用它的客户端独立变化,客户端不需要了解关注算法的具体实现,客户端仅仅依赖于策略接口 。

通过使用策略模式和工厂模式结合,是不是感觉变得高大上起来了呢?

当然了,最主要的是程序的扩展来说更方便了一些,更符合开闭原则,开放扩展,关闭修改。无论新增多少种新类型的会员,每个人只需要去继承策略接口,实现新会员应有的权益即可。

注意,虽然利于扩展,但是策略模式的缺点也很明显,策略工厂在创建具体的策略实现类的时候,还是书写大量的 if-else 去进行判断,如图

有小伙伴就说了这和不使用策略模式和工厂模式似乎差不多???

抽出一个方法或者封装成一个对象去调用岂不是更简单???

接下来,我们就说说如何优化策略工厂

首先,我们的工厂,是根据当前传入的用户的会员类型,判断后,返回相应的策略实现类,那么可以借助集合来存储实现类,会员类型作为 key,将所有的会员策略都注册到 map 中。需要注意的是,日常开发基于Spring进行bean管理,上面需要创建的策略类,当然都是希望被 Spring 动态托管,而不是我们自己去一个个的new 出实例。

问题是,如何去实现策略类通过spring进行托管注册?

Spring种提供的InitializingBean接口,这个接口为Bean提供了属性初始化后的处理方法,它只包括afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。我们利用此方法把Spring通过IOC创建出来的Bean注册Map 中。

改造策略工厂


import org.example.model.VIP;
import org.example.strategy.VIPStrategy; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; /**
* @author: 欢迎关注喂信公猪号:囧么肥事
* @date: 2021/12/16
* @email: jiongmefeishi@163.com
* <p>
* VIP策略工厂
*/
public class VIPStrategyFactory { // 存储策略类实例
public static Map<Integer, VIPStrategy> strategyMap = new ConcurrentHashMap<>(); private VIPStrategyFactory() {
} public static VIPStrategy getVipStrategy(VIP vip) {
if (vip == null) {
return null;
}
return strategyMap.get(vip.getVipType().getCode());
}
}

改造策略类,在bean属性初始化后,将实例对象注册到工厂类中的 map

以黄金会员为例:

import org.example.factory.VIPStrategyFactory;
import org.example.model.VIPEnums;
import org.example.strategy.VIPStrategy;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service; /**
* @author: 欢迎关注喂信公猪号:囧么肥事
* @date: 2021/12/16
* @email: jiongmefeishi@163.com
*
* 策略接口具体实现类-黄金会员
*/
@Service
public class GoldVIPStrategyImpl implements VIPStrategy, InitializingBean { @Override
public void equity() {
// TODO 黄金会员具备的具体权益
System.out.println("黄金会员具备的具体权益");
} @Override
public void afterPropertiesSet() throws Exception {
VIPStrategyFactory.strategyMap.put(VIPEnums.GOLD.getCode(), new GoldVIPStrategyImpl());
}
}

通过策略模式、工厂模式以及Spring的InitializingBean接口,算是解决了大量的if else,后续新VIP出现也更容易扩展,当然了,这里只是对于设计模式思想的一个简单的示例,实际应用开发中,还是要根据具体的业务场景灵活变通。有需要的小伙伴也可以自己手动模拟一些场景,比如奶茶店各种奶茶新品等等。如果想用囧囧的示例,可公猪号上回复220110 自行导入示例运行即可。

注意:学习软件设计原则,千万不能形成强迫症。当碰到业务复杂的场景时,需要随机应变。

学习设计原则是学习设计模式的基础。在实际开发过程中,并不是一定要求所有代码都遵循设计原则,而是要综合考虑人力、时间、成本、质量,不刻意追求完美,要在适当的场景遵循设计原则。这体现的是一种平衡取舍,可以帮助我们设计出更加优雅的代码结构。

设计模式其实也是一门艺术。设计模式源于生活,不要为了套用设计模式而使用设计模式。

喜欢的小伙伴,欢迎点赞收藏关注

《设计模式面试小炒》策略和工厂模式替代业务场景中复杂的ifelse的更多相关文章

  1. Java设计模式(三) 抽象工厂模式

    原创文章,同步发自作者个人博客,转载请注明出处 http://www.jasongj.com/design_pattern/abstract_factory/ 抽象工厂模式解决的问题 上文<工厂 ...

  2. Java设计模式(一) 简单工厂模式不简单

    摘要:本文介绍了简单工厂模式的概念,优缺点,实现方式,以及结合Annotation和反射的改良方案(让简单工厂模式不简单).同时介绍了简单工厂模式(未)遵循的OOP原则.最后给出了简单工厂模式在JDB ...

  3. 设计模式(3)抽象工厂模式(Abstract Factory)

    设计模式(0)简单工厂模式 设计模式(1)单例模式(Singleton) 设计模式(2)工厂方法模式(Factory Method) 源码地址 0 抽象工厂模式简介 0.0 抽象工厂模式定义 抽象工厂 ...

  4. 设计模式(二 & 三)工厂模式:概述

    工厂 从 coding 的角度来说,在需要创建对象的时候,直接在方法内部使用 new 关键字来创建,是非常方便的. 然而从全局的角度考虑,这样会使对象变得难以管理和控制,代码会变得非常脆弱,缺乏弹性. ...

  5. PHP设计模式(三)抽象工厂模式(Abstract Factory)

    一.什么是抽象工厂模式 抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象 ,而且使用抽象工厂模式还要满足以下条件: 系统中有多个产品族,而系统一次只可能消费其中一族产品. 同 ...

  6. Java设计模式学习笔记(二) 简单工厂模式

    前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 正文开始... 1. 简介 简单工厂模式不属于GoF23中设计模式之一,但在软件开发中应用也较为 ...

  7. Java设计模式学习笔记(四) 抽象工厂模式

    前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 1. 抽象工厂模式概述 工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问 ...

  8. C#设计模式学习笔记:简单工厂模式(工厂方法模式前奏篇)

    本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/7551373.html,记录一下学习过程以备后续查用. 一.引言 简单工厂模式并不属于GoF23里面的设计模式 ...

  9. Java设计模式之(二)——工厂模式

    1.什么是工厂模式 Define an interface for creating an object,but let subclasses decide which class toinstant ...

随机推荐

  1. CF908A New Year and Counting Cards 题解

    Content 有 \(n\) 张卡牌,每张卡牌上只会有大小写字母和 \(0\sim 9\) 的阿拉伯数字.有这样一个描述:"如果卡牌正面写有元音字母(\(\texttt{A,E,I,O,U ...

  2. 当在myeclipse里发送邮件有错误时,不妨把环境都改成jdk的

    当在myeclipse里发送邮件有错误时,不妨把环境都改成jdk的, 如果是jre的环境会出错,改成jdk的就行了.

  3. Linux(centos7)安装redis并设置redis开机自启动

    1.下载redis安装包 wget http://download.redis.io/releases/redis-4.0.6.tar.gz 2.解压安装包 tar -zxvf redis-4.0.6 ...

  4. Dapr项目应用探索

    背景介绍 前面文章对Dapr的基本信息进行了学习,接下来尝试将Dapr应用相关应用中. 接下来一步步实现应用dapr功能. 一.预期效果 如上图应用Dapr点包含: a) 报表服务绑定统一数据源服务: ...

  5. javascript原始值和引用值类型及区别

    原始值和引用值类型及区别 首先,原始值和引用值类型都是js中的数据类型,为了充分利用存储空间,定义了不同的数据类型,而且js是弱类型,动态语言,数据类型可变. 原始值(简单数据类型) 存储在栈中的简单 ...

  6. Chapter 10 Random Variability

    目录 10.1 Identification versus estimation 10.2 Estimation of causal effects 10.3 The myth of the supe ...

  7. matplotlib 高阶之Transformations Tutorial

    目录 Data coordinates Axes coordinates Blended transformations 混合坐标系统 plotting in physical units 使用off ...

  8. 当MySQL执行XA事务时遭遇崩溃,且看华为云如何保障数据一致性

    摘要:当前MySQL所有版本不支持分布式事务的崩溃恢复安全,这严重影响了分布式事务的高可用保障. 华为云数据库内核高级技术专家,拥有十多年MySQL内核研发经验,目前在华为云数据库团队研发华为云数据库 ...

  9. CS5211完全替代兼容LT7211|PS8625|CH7511方案|EDP转LVDS|Capstone CS5211

    CH7511|LT7211|PS8625替代方案--Capstone CS5211AN 设计EDP转LVDS优势方案原理图+PCB板设计 CH7511|LT7211|PS8625这三款都是专门用于设计 ...

  10. BUUCTF [极客大挑战 2019]Not Bad

    总的来说这是一个64位orw的题 开头先在主函数里分配了一个很大的空间 1 __int64 __fastcall main(int a1, char **a2, char **a3) 2 { 3 mm ...