运用设计模式告别项目中大量臃肿的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实现 蓝桥杯VIP 算法训练 数位分离
** 算法训练 数位分离** 问题描述 编写一个程序,输入一个1000 以内的正整数,然后把这个整数的每一位数字都分离出来,并逐一地显示. 输入格式:输入只有一行,即一个1000以内的正整数. 输出格 ...
- java代码(4)---guava之Immutable(不可变)集合
Immutable(不可变)集合 一,概述 guava是google的一个库,弥补了java语音的很多方面的不足,很多在java8中已有实现,暂时不展开,Collections是jdk提供的一个工 ...
- Dockerfile 解析
Dockerfile Dockerfile是用来构建Docker镜像的构建文件,是由一系列参数和命令构成的脚本. 构建的三个步骤:1.编写Dockerfile文件 2.docker build 3 ...
- 三分钟搭建websocket实时在线聊天,项目经理也不敢这么写
我们先看一下下面这张图: 可以看到这是一个简易的聊天室,两个窗口的消息是实时发送与接收的,这个主要就是用我们今天要讲的websocket实现的. websocket是什么? websocket是一种网 ...
- kka-typed(5) - cluster:集群节点状态监视
akka-cluster对每个节点的每种状态变化都会在系统消息队列里发布相关的事件.通过订阅有关节点状态变化的消息就可以获取每个节点的状态.这部分已经在之前关于akka-cluster的讨论里介绍过了 ...
- 是时候拥抱.NET CORE了
微软和社区已经做了大量艰苦的工作,使.net core成为市场上具有竞争力的框架,帮助开发人员快速开发具有最佳性能和可扩展性的强大应用程序.做的最棒的事情使.net framework开发人员不需要任 ...
- SpringBoot任务
异步任务: 在方法上添加@Async注解 表明这个方法是一个异步的方法 package com.king.service; import org.springframework.scheduling. ...
- dbca oracle 12 c 遇到ora27125
网上大部分方法是把dba组放在内核的,没有效果,可以尝试 google找到一位大神的方案,成功解决 https://oracle-admin.com/2014/01/22/ora-27125-unab ...
- <WP8开发学习笔记>获取手机的常用型号(如Lumia920,而非RM-822)
之前WP7时代可以用API获得WP手机的型号如lumia510,但是到了WP8后用APi只能获得硬件版本号了如RM-822,这种型号可以让我们更详细的了解具体的硬件版本,比如国行和港行,设备版本号不一 ...
- @loj - 2004@ 「SDOI2017」硬币游戏
目录 @description@ @solution@ @accepted code@ @details@ @description@ 周末同学们非常无聊,有人提议,咱们扔硬币玩吧,谁扔的硬币正面次数 ...