前言

以前写过的一个老项目中,有这样一个业务场景,比喻:一个外卖系统需要接入多家餐馆,在外卖系统中返回每个餐馆的菜单列表 ,每个餐馆的菜单价格都需要不同的算法计算。

代码中使用了大量的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的更多相关文章

  1. 几种常见设计模式在项目中的应用<Singleton、Factory、Strategy>

    一.前言 前几天阅读一框架文档,里面有一段这样的描述 “从对象工厂中………” ,促使写下本文.尽管一些模式简单和简单,但是常用.有用. 结合最近一个项目场景回顾一下里面应用到的一些模式<Sing ...

  2. C#项目中常用到的设计模式

    1. 引言 一个项目的通常都是从Demo开始,不断为项目添加新的功能以及重构,也许刚开始的时候代码显得非常凌乱,毫无设计可言.但是随着项目的迭代,往往需要将很多相同功能的代码抽取出来,这也是设计模式的 ...

  3. 设计模式(一)单例模式:创建模式 ASPNET CORE WEB 应用程序的启动 当项目中 没有STARTUP.CS 类如何设置启动 配置等等

    设计模式(一)单例模式:创建模式 先聊一下关于设计的几个原则(1)单一原则(SRP):一个类应该仅有一个引起它变化的原因 :意思就是 (一个类,最好只负责一件事情,并且只有一个引起它变化的原因(2)开 ...

  4. 【一起学设计模式】观察者模式实战:真实项目中屡试不爽的瓜娃EventBus到底如何实现观察者模式的?

    申明 本文章首发自本人公众号:壹枝花算不算浪漫,如若转载请标明来源! 感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫 22.jpg 前言 之前出过一个设计模式的系列文章,这些文章和其他讲设计模式的文 ...

  5. 剑指Offer——企业级项目中分层的含义与依据及多态的优势

    剑指Offer--企业级项目中分层的含义与依据及多态的优势   关于以上两点,由于项目经验较少,自己不是很明白,特整理如下. 常见分层架构模式 三层架构 3-tier architecture   微 ...

  6. [Head First设计模式]山西面馆中的设计模式——装饰者模式

    引言 在山西面馆吃鸡蛋面的时候突然想起装饰者这个模式,觉得面馆这个场景跟书中的星巴兹咖啡的场景很像,边吃边思考装饰者模式.这里也就依葫芦画瓢,换汤不换药的用装饰者模式来模拟一碗鸡蛋面是怎么出来的吧.吃 ...

  7. java 项目中几种O实体类的概念

    经常会接触到vo,do,dto的概念,本文从领域建模中的实体划分和项目中的实际应用情况两个角度,对这几个概念进行简析. 得出的主要结论是:在项目应用中,vo对应于页面上需要显示的数据(表单),do对应 ...

  8. 项目中应用eventbus解决的问题

    在项目开发过程中,往往有些功能表面看起来简单,但实际开发的结果非常复杂,仔细分析下原因发现很多都是因为附加了许多的额外功能. 真的简单吗? 比如我们对一个电商平台的商品数据做修改的功能来讲,其实非常简 ...

  9. 简述MVC框架模式以及在你(Android)项目中的应用

    标题是阿里电话面试的问题,一直以为自己很清楚MVC模式,结果被问到时,居然没法将MVC和Android中各个组件对应起来,所以,面试肯定挂了,不过面试也是学习的一种方式,可以知道大公司看中什么,以及自 ...

随机推荐

  1. Java实现 蓝桥杯VIP 算法训练 FBI树

    问题描述 我们可以把由"0"和"1"组成的字符串分为三类:全"0"串称为B串,全"1"串称为I串,既含"0&q ...

  2. Docker安装及阿里云镜像加速器配置

    Docker安装 Windows系统安装就不用说了,因为Docker是开源的,所以,直接去官网:https://www.docker.com/下载安装包安装就行了 其实,Linux系统安装也很简单,照 ...

  3. Linux 文件特殊权限-Sticky BIT

    SBIT粘着位作用 只对目录有效 普通用户对该目录拥有w和x权限,即普通用户可以在此目录有写权限 如果没有粘着位,普通拥有写权限,就可以删除目录下所有文件,包括其他用户创建的文件,一旦有粘着位,只有r ...

  4. Linux目录处理命令mkdir详解

    mkdir(英文原意:make directories),基本作用是创建新的目录,命令的路径及权限: 可以看到,这个命令的路径是/usr/bin/mkdir,所以它的执行权限是所有用户 mkdir 创 ...

  5. (十)DVWA之SQL Injection--测试分析(Impossible)

    DVWA之SQL Injection--测试分析(Impossible) 防御级别为Impossible的后端代码:impossible.php <?php if( isset( $_GET[ ...

  6. zabbix 大流量断图

    一. 环境介绍 系统版本:Centos7.4 zabbix-agent 版本:zabbix-agent 3.4.7   二. 问题现象 在使用zabbix的snmp方式的监控端口流量时,某一个图总是断 ...

  7. [Web][学习随笔]Session&cookie

    Session 从登录建立连接到退出就是一次会话.Session数据就会在会话期间用户存在服务器端的数据.这样,当用户在Web页之间跳转时,存储在Session对象中的变量将不会丢失,而是在整个用户会 ...

  8. ODEINT 求解常微分方程(4)

    import numpy as np from scipy.integrate import odeint import matplotlib.pyplot as plt # function tha ...

  9. Shell编译安装nginx

    环境及规划 [root@nginx-node01 ~]# cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core) ID 主机名 ip ...

  10. flink实时数仓从入门到实战

    第一章.flink实时数仓入门 一.依赖 <!--Licensed to the Apache Software Foundation (ASF) under oneor more contri ...