前言

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

代码中使用了大量的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实现 LeetCode 150 逆波兰表达式求值

    150. 逆波兰表达式求值 根据逆波兰表示法,求表达式的值. 有效的运算符包括 +, -, *, / .每个运算对象可以是整数,也可以是另一个逆波兰表达式. 说明: 整数除法只保留整数部分. 给定逆波 ...

  2. Java中map.getOrDefault()方法的使用

    Map.getOrDefault(Object key, V defaultValue)方法的作用是:   当Map集合中有这个key时,就使用这个key值:   如果没有就使用默认值defaultV ...

  3. Java 实现 蓝桥杯 历届试题 分糖果

    问题描述 有n个小朋友围坐成一圈.老师给每个小朋友随机发偶数个糖果,然后进行下面的游戏: 每个小朋友都把自己的糖果分一半给左手边的孩子. 一轮分糖后,拥有奇数颗糖的孩子由老师补给1个糖果,从而变成偶数 ...

  4. Java多线程之深入解析ThreadLocal和ThreadLocalMap

    ThreadLocal概述 ThreadLocal是线程变量,ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的.ThreadLocal为变量在每个线程中都创建了一个副本,那 ...

  5. keras搭建神经网络快速入门笔记

    之前学习了tensorflow2.0的小伙伴可能会遇到一些问题,就是在读论文中的代码和一些实战项目往往使用keras+tensorflow1.0搭建, 所以本次和大家一起分享keras如何搭建神经网络 ...

  6. iOS — 内存分配与分区

    1  RAM ROM RAM:运行内存,不能掉电存储.ROM:存储性内存,可以掉电存储,例如内存卡.Flash.      由于RAM类型不具备掉电存储能力(即一掉电数据消失),所以app程序一般存放 ...

  7. 第一章01-正常情况下Activity的生命周期

    一.Android下能见到的界面 Window Dialog Toast Activity 二.Activity的生命周期分析 典型情况下的生命周期 ​是指在有用户参与的情况下,Activity所经过 ...

  8. Dubbo+Zookeeper集群案例

    一.开源分布式服务框架 1.Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以Spring框架无缝集成.    Dubbo是一款高性 ...

  9. 小程序-图片/文件本地缓存,减少CDN流量消耗

    写在前面 小程序网络图片读取: 在读取OSS图片CDN分发时流量大量消耗,导致资金费用增加. 网络图片比较大时,图片加载缓慢. 为了尽量减少上面两个问题,所以对已读的图片进行缓存处理,减少多次访问不必 ...

  10. Cookie 与 SessionID 的本质

    当用户首次访问服务器的时候,服务器为每个用户单独创建一个 Session 对象,并分配一个新的 SessionID,此时 SessionID 通过 Cookie 保存在用户端. 当用户再次访问服务器的 ...