运用设计模式告别项目中大量臃肿的if else
前言
以前写过的一个老项目中,有这样一个业务场景,比喻:一个外卖系统需要接入多家餐馆,在外卖系统中返回每个餐馆的菜单列表 ,每个餐馆的菜单价格都需要不同的算法计算。
代码中使用了大量的if else嵌套连接,一个类中数千行代码(眼睛快看瞎...),而且随着业务的扩展,接入的餐馆会越来越多,每接入一个餐馆都要增加一个 if else,满屏幕密密麻麻的逻辑代码,毫无可读性。然后前段时间进行了代码重构,使用了策略模式+工厂模式+反射代替了这整片的臃肿代码,瞬间神清气爽。
模拟原业务代码
原代码的简单模拟实现,根据传入的不同餐馆编码获取对应的餐馆类集合,每个餐馆菜单价格的算法都不同。每当需要新接入一家餐馆时,都需要在此增加一个if else,中间加入一大长串的处理逻辑,当餐馆越来越多的时候,代码就变得越来越沉重,维护成本高。
public List server(String hotelCode) {
if ("HotelA".equals(hotelCode)) {
//获取数据
List<HotelA> hotelList = new ArrayList<HotelA>() {
{
add(new HotelA("爆炒腰子", 100d, 0.8, null));
add(new HotelA("红烧腰子", 200d, 0.8, null));
add(new HotelA("腰子刺身", 300d, 0.8, null));
}
};
//逻辑计算 最终价格 = 原价 * 折扣
hotelList.parallelStream().forEach(v -> v.setFinalPrice(v.getPrice() * v.getDiscount()));
return hotelList;
} else if ("HotelB".equals(hotelCode)) {
//获取数据
List<HotelB> hotelList = new ArrayList<HotelB>() {
{
add(new HotelB("兰州拉面", 100d, 10d, null));
add(new HotelB("落魄后端在线炒粉", 200d, 20d, null));
}
};
//逻辑计算 最终价格 = 原价 - 优惠
hotelList.parallelStream().forEach(v -> v.setFinalPrice(v.getPrice() - v.getPreferential()));
return hotelList;
} else if ("HotelC".equals(hotelCode)) {
//获取数据
List<HotelC> hotelList = new ArrayList<HotelC>() {
{
add(new HotelC("秘制奥利给", 1000d, 0.6, 20d, null));
add(new HotelC("老八辣酱", 2000d, 0.6, 10d, null));
}
};
//逻辑计算 最终价格 = 原价 * 折扣 - 服务费
hotelList.parallelStream().forEach(v -> v.setFinalPrice(v.getPrice() * v.getDiscount() - v.getTip()));
return hotelList;
}
return new ArrayList();
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HotelA { //菜品名
private String menu; //原价
private Double price; //折扣
private Double discount; //最终价格 = 原价 * 折扣
private Double finalPrice;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HotelB { //菜品名
private String menu; //原价
private Double price; //优惠
private Double preferential; //最终价格 = 原价 - 优惠
private Double finalPrice;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HotelC { //菜品名
private String menu; //原价
private Double price; //折扣
private Double discount; //服务费
private Double tip; //最终价格 = 原价 * 折扣 - 服务费
private Double finalPrice;
}
策略模式+工厂模式+反射
由上述代码首先抽离出一个接口,if else中的业务逻辑最终都是返回一个列表
/**
* 餐馆服务接口
*/
public interface HotelService { /**
* 获取餐馆菜单列表
* @return
*/
List getMenuList();
}
把每个分支的业务逻辑封装成实现类,实现HotelService接口
public class HotelAServiceImpl implements HotelService {
/**
* 逻辑计算 返回集合
* @return
*/
@Override
public List getMenuList() {
return initList().parallelStream()
.peek(v -> v.setFinalPrice(v.getPrice() * v.getDiscount()))
.collect(Collectors.toList());
}
/**
* 获取数据
* @return
*/
public List<HotelA> initList() {
return new ArrayList<HotelA>() {
{
add(new HotelA("爆炒腰子", 100d, 0.8, null));
add(new HotelA("红烧腰子", 200d, 0.8, null));
add(new HotelA("腰子刺身", 300d, 0.8, null));
}
};
}
}
public class HotelBServiceImpl implements HotelService {
/**
* 逻辑计算 返回集合
* @return
*/
@Override
public List getMenuList() {
return initList().parallelStream()
.peek(v -> v.setFinalPrice(v.getPrice() - v.getPreferential()))
.collect(Collectors.toList());
}
/**
* 获取数据
* @return
*/
public List<HotelB> initList() {
return new ArrayList<HotelB>() {
{
add(new HotelB("兰州拉面", 100d, 10d, null));
add(new HotelB("落魄后端在线炒粉", 200d, 20d, null));
}
};
}
}
public class HotelCServiceImpl implements HotelService {
/**
* 逻辑计算 返回集合
* @return
*/
@Override
public List getMenuList() {
return initList().parallelStream()
.peek(v -> v.setFinalPrice(v.getPrice() * v.getDiscount() - v.getTip()))
.collect(Collectors.toList());
}
/**
* 获取数据
* @return
*/
public List<HotelC> initList() {
return new ArrayList<HotelC>() {
{
add(new HotelC("秘制奥利给", 1000d, 0.6, 20d, null));
add(new HotelC("老八辣酱", 2000d, 0.6, 10d, null));
}
};
}
}
这样就是一个简单的策略模式了,但是现在要调用不同的实现类中的getMenuList方法,好像还是离不开if else,那么现在就需要用工厂模式把所有实现类包装起来。
先定义一个枚举类,里面是各餐馆的code
public enum HotelEnum {
HOTEL_A("HotelA"),
HOTEL_B("HotelB"),
HOTEL_C("HotelC"),;
private String hotelCode;
/**
* 返回所有餐馆编码的集合
* @return
*/
public static List<String> getList() {
return Arrays.asList(HotelEnum.values())
.stream()
.map(HotelEnum::getHotelCode)
.collect(Collectors.toList());
}
HotelEnum(String hotelCode) {
this.hotelCode = hotelCode;
}
public String getHotelCode() {
return hotelCode;
}
}
接下来定义一个服务工厂,在静态块中利用反射机制把所有服务实现类动态加载到HOTEL_SERVER_MAP中,然后实现一个对外的获取对应服务的方法
这里有几个需要注意的地方:
1.由于包名是写死的,那么所有实现HotelService的实现类都需要放在固定的包下
2.类名的格式也是固定的,即枚举类中的hotelCode + "ServiceImpl"
/**
* 服务工厂类
*/
public class HotelServerFactory {
/**
* 类路径目录
*/
private static final String CLASS_PATH = "com.tactics.service.impl."; /**
* 服务实现后缀
*/
private static final String GAME_SERVICE_SUFFIX = "ServiceImpl"; private static final Map<String, HotelService> HOTEL_SERVER_MAP = new ConcurrentHashMap<>(); /**
* 初始化实现类到COMPANY_SERVER_MAP中
*/
static {
HotelEnum.getList().forEach(v -> {
String className = CLASS_PATH + v + GAME_SERVICE_SUFFIX;
try {
HOTEL_SERVER_MAP.put(v, (HotelService) Class.forName(className).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
});
} /**
* 获取餐馆服务实现
*
* @param hotelCode
* @return
*/
public static HotelService getHotelServerImpl(String hotelCode) {
return HOTEL_SERVER_MAP.get(hotelCode);
}
}
这里有一个问题,如果你的服务实现类是交给Spring容器管理的,里面有注入Mapper等等,使用反射的方式new出来的话,其中的属性是没有值的。
Spring容器就相当于是一个工厂了,可以直接从Spring上下文中获取。
/**
* 服务工厂类
*/
public class HotelServerFactory {
/**
* 类路径目录
*/
private static final String CLASS_PATH = "com.tactics.service.impl."; /**
* 服务实现后缀
*/
private static final String GAME_SERVICE_SUFFIX = "ServiceImpl"; /**
* 获取餐馆服务实现
*
* @param hotelCode
* @return
*/
public static HotelService getHotelServerImpl(String hotelCode) {
Class clazz = Class.forName(CLASS_PATH + hotelCode + GAME_SERVICE_SUFFIX);
String className = hotelCode + GAME_SERVICE_SUFFIX;
return (HotelService) ApplicationConfig.getBean(className, clazz);
}
}
最终的调用
public List server(String hotelCode) {
//获取对应的服务
HotelService hotelService = HotelServerFactory.getCompanyServerImpl(hotelCode);
//获取经过逻辑计算后返回的集合列表
return hotelService.getMenuList();
}
怎么样,是不是觉得可读性,复用性和扩展性都大大提高了,业务扩展需要新加一个餐馆的时候,只需要在枚举类中加一个hotelCode,然后定义一个实现类实现HotelService接口就好了,这个Demo也让我们知道了策略模式和工厂模式在实际项目中的应用场景。
运用设计模式告别项目中大量臃肿的if else的更多相关文章
- 几种常见设计模式在项目中的应用<Singleton、Factory、Strategy>
一.前言 前几天阅读一框架文档,里面有一段这样的描述 “从对象工厂中………” ,促使写下本文.尽管一些模式简单和简单,但是常用.有用. 结合最近一个项目场景回顾一下里面应用到的一些模式<Sing ...
- C#项目中常用到的设计模式
1. 引言 一个项目的通常都是从Demo开始,不断为项目添加新的功能以及重构,也许刚开始的时候代码显得非常凌乱,毫无设计可言.但是随着项目的迭代,往往需要将很多相同功能的代码抽取出来,这也是设计模式的 ...
- 设计模式(一)单例模式:创建模式 ASPNET CORE WEB 应用程序的启动 当项目中 没有STARTUP.CS 类如何设置启动 配置等等
设计模式(一)单例模式:创建模式 先聊一下关于设计的几个原则(1)单一原则(SRP):一个类应该仅有一个引起它变化的原因 :意思就是 (一个类,最好只负责一件事情,并且只有一个引起它变化的原因(2)开 ...
- 【一起学设计模式】观察者模式实战:真实项目中屡试不爽的瓜娃EventBus到底如何实现观察者模式的?
申明 本文章首发自本人公众号:壹枝花算不算浪漫,如若转载请标明来源! 感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫 22.jpg 前言 之前出过一个设计模式的系列文章,这些文章和其他讲设计模式的文 ...
- 剑指Offer——企业级项目中分层的含义与依据及多态的优势
剑指Offer--企业级项目中分层的含义与依据及多态的优势 关于以上两点,由于项目经验较少,自己不是很明白,特整理如下. 常见分层架构模式 三层架构 3-tier architecture 微 ...
- [Head First设计模式]山西面馆中的设计模式——装饰者模式
引言 在山西面馆吃鸡蛋面的时候突然想起装饰者这个模式,觉得面馆这个场景跟书中的星巴兹咖啡的场景很像,边吃边思考装饰者模式.这里也就依葫芦画瓢,换汤不换药的用装饰者模式来模拟一碗鸡蛋面是怎么出来的吧.吃 ...
- java 项目中几种O实体类的概念
经常会接触到vo,do,dto的概念,本文从领域建模中的实体划分和项目中的实际应用情况两个角度,对这几个概念进行简析. 得出的主要结论是:在项目应用中,vo对应于页面上需要显示的数据(表单),do对应 ...
- 项目中应用eventbus解决的问题
在项目开发过程中,往往有些功能表面看起来简单,但实际开发的结果非常复杂,仔细分析下原因发现很多都是因为附加了许多的额外功能. 真的简单吗? 比如我们对一个电商平台的商品数据做修改的功能来讲,其实非常简 ...
- 简述MVC框架模式以及在你(Android)项目中的应用
标题是阿里电话面试的问题,一直以为自己很清楚MVC模式,结果被问到时,居然没法将MVC和Android中各个组件对应起来,所以,面试肯定挂了,不过面试也是学习的一种方式,可以知道大公司看中什么,以及自 ...
随机推荐
- java实现 洛谷 P1425 小鱼的游泳时间
题目描述 伦敦奥运会要到了,小鱼在拼命练习游泳准备参加游泳比赛,可怜的小鱼并不知道鱼类是不能参加人类的奥运会的. 这一天,小鱼给自己的游泳时间做了精确的计时(本题中的计时都按24小时制计算),它发现自 ...
- Linux文本编辑vi基本操作
vi是Linux/Unix最常用的全屏幕文本编辑器,他的作用是显示.编辑.建立文本文件.它没有菜单,只有命令. vi工作模式图: 编辑模式进入插入模式命令:A:在光标所在行尾插入 a:在光标所在字符后 ...
- MAC抓包工具Charles安装及破解
参考资料:https://juejin.im/post/5c0a430f51882516207d205d 下载 Charles官网下载安装包,下载成功后根据指示安装即可 官网地址:http://www ...
- mysql基础之-mysql锁和事务(七)
0x01 MySQL锁: 执行操作时施加锁的模式 读锁:用户在读的时候施加的锁,为防止别人修改,但是用户可以读,还被称为共享锁 不会对其他用户进行阻塞 理解: ----->(这里的不阻塞,是可以 ...
- 2020/06/06 JavaScript高级程序设计 面向对象的程序设计
ECMAScript虽然是一种面向对象的语言,但是他没有类的概念.所以他的对象也与其他语言中的对象有所不同. ECMA-262定义对象:一组没有特定顺序的值. 6.1 理解对象 创建对象的方法: 1. ...
- RabbitMQ系列之【CentOS6.5安装RabbitMQ】
环境准备 操作系统:CentOS 6.5 Final RabbitMQ: 3.1.5 Python: 2.7.11 ErLang: R16B02 安装预环境(少什么安装什么) yum -y insta ...
- akka-typed(7) - cluster:sharding, 集群分片
在使用akka-typed的过程中发现有很多地方都简化了不少,变得更方便了,包括:Supervision,只要用Behaviors.supervise()把Behavior包住,很容易就可以实现这个a ...
- FTP 常用命令
1. 准备 1.1 ftp 信息: ftp 服务器地址:192.168.168.10 用户名:will 密码:123 1.2 ftp 工具 使用 Windows 命令行: “开始” 按钮-> 搜 ...
- 动手造轮子:实现一个简单的 AOP 框架
动手造轮子:实现一个简单的 AOP 框架 Intro 最近实现了一个 AOP 框架 -- FluentAspects,API 基本稳定了,写篇文章分享一下这个 AOP 框架的设计. 整体设计 概览 I ...
- 在MS SQL(SSMS中)_Format_SQL_更改设置_增加命令
在MS SQL(SSMS中)_Format_SQL_更改设置_增加命令 目的:要格式化这么一段SQL语句(这是随便从网上Copy的一段),没细看内容,反正看到头疼,乱七八糟的不想看. select b ...