Java设计模式实战:从If-Else到策略+工厂方法的演变

引言

可能很多开发者,虽然理解了设计模式的概念,但在实际工作中应用却是另一回事。本篇文章旨在用一个具体的案例来展示如何将设计模式应用到工作的编程问题中。正所谓:“纸上得来终觉浅,绝知此事要躬行。”理论的学习固然重要,但只有通过实战,我们才能真正掌握并灵活运用。

我们将以一个物流系统为例,展示如何从大量if-else语句的初始代码,一步步演进到应用工厂模式、策略模式的优雅设计。通过这个过程,你将看到设计模式如何帮助我们改进代码结构,提高其可维护性和可扩展性。

如果你觉得这篇文章对你有帮助,请记得点赞和关注,支持一下!

目录

  1. 问题场景分析
    • 初始代码实现
    • 存在的问题点分析
  2. 引入策略模式
  3. 引入简单工厂模式
  4. 引入工厂方法模式
  5. 总结

问题场景分析

在这个物流系统中,根据包裹的不同特性(如目的地、大小、重量或优先级)来选择合适的物流供应商是一项常见任务。这个过程涉及到多个决策点,每个决策点都可能依赖于包裹的不同属性。例如,国际快递和本地配送就需要不同的处理逻辑。

随着业务的发展,新的供应商加入,特殊的配送需求增加,原本简单的决策逻辑迅速膨胀,变得越来越复杂。这不仅使得代码难以维护,也增加了引入错误的风险。

初始代码实现

初始的实现可能直接使用if-else语句来处理这些逻辑。虽然这种方法直观,但随着逻辑的增加,代码会变得越来越长,越来越难以理解和维护

public class LogisticsService {public void processPackage(Package pkg) {if (pkg.getDestination().equals("国际")) {// 处理国际物流processInternalPackage(pkg);} else if (pkg.getSize() > 30) {// 处理大件物流processBigSizePackage(pkg);} else if (pkg.isExpress()) {// 处理快递服务processExpressPackage(pkg);} else {// ....}// 可能还有更多的if-else逻辑...}
}
存在的问题点分析

这种基于if-else的实现方式有几个主要问题:

违反开闭原则:对于新的供应商或规则的添加,需要修改现有代码,而不是扩展
低可维护性:随着条件逻辑的增加,代码变得越来越复杂,越来越难以维护
低可测试性:复杂的条件逻辑使得编写和维护测试变得困难

引入策略模式

策略模式概述

策略模式是一种行为设计模式,它定义了算法族,分别封装起来,让它们之间可以互相替换。这种模式让算法的变化独立于使用算法的客户端。在我们的物流系统案例中,策略模式允许我们根据包裹的不同特性动态选择合适的物流处理策略
策略模式的类图
策略模式类图

应用策略模式重构

由于我们的物流系统需要根据包裹的目的地、大小和是否快递等因素选择不同的物流供应商。我们可以为每种情况定义一个策略,并结合使用Spring的依赖注入来管理这些策略

①定义策略接口

首先,我们定义一个策略接口,表示一个物流处理策略:

public interface LogisticsStrategy {// 根据包裹重量,匹配策略boolean appliesTo(Package pkg);// 处理包裹void processPackage(Package pkg);
}
②实现具体策略

为每种物流情况定义具体的策略类:

@Service
public class InternationalLogisticsStrategy implements LogisticsStrategy {@Overridepublic boolean appliesTo(Package pkg) {return "国际".equals(pkg.getDestination());}@Overridepublic void processPackage(Package pkg) {// 实现国际物流处理逻辑}
}@Component
public class PkgSizeLogisticsStrategy implements LogisticsStrategy {@Overridepublic boolean appliesTo(Package pkg) {return pkg.size() > 30;}@Overridepublic void processPackage(Package pkg) {// 实现大件物流处理逻辑}
}@Component
public class DefaultLogisticsStrategy implements LogisticsStrategy {@Overridepublic boolean appliesTo(Package pkg) {//....}@Overridepublic void processPackage(Package pkg) {// ...}
}
// 其他策略类...
③使用策略
@Service
public class LogisticsService {private final List<LogisticsStrategy> strategies;public void processPackage(Package pkg) {LogisticsStrategy strategy = strategies.stream().filter(s -> s.appliesTo(pkg)).findFirst().orElse(new DefaultLogisticsStrategy());strategy.processPackage(pkg);}
}

在这个重构后的版本中,LogisticsService通过构造函数注入的方式获取所有策略实现,从而避免了硬编码的if-else逻辑
现在我们来看下整体的类图
引入策略后的类图
在这个类图中:

  • LogisticsService类包含一个 List 类型的字段,用于存储不同的物流策略
  • LogisticsStrategy是一个接口,定义了appliesToprocessPackage方法,用于判断策略是否适用于特定的包裹,并处理包裹。
  • InternationalLogisticsStrategyDefaultLogisticsStrategy是实现了LogisticsStrategy接口的具体策略类

存在的缺陷分析

  • LogisticsService除了负责处理包裹,还要负责选择策略,显然违背了单一职责
  • LogisticsService依赖于具体的策略实现,没有依赖于抽象,显然违背了依赖倒置原则

因此考虑是否可以引入工厂模式?让选择策略的逻辑由工厂实现,解决单一职责问题。解耦LogisticsService与具体的策略实现,让LogisticsService只依赖于抽象的工厂,解决依赖倒置原则问题,显然是可行的,接下来我们走进引入工厂模式的篇章

引入简单工厂模式

简单工厂模式概述

工厂模式是一种创建型设计模式,用于提供一个创建对象的接口,从而将对象的实例化逻辑从使用对象的代码中分离出来。在我们的物流系统案例中,工厂模式可以用来灵活地创建和管理不同的物流策略对象,看下简单工厂的类图
简单工厂uml类图
在这个类图中:

  • SimpleFactory 是一个类,提供了一个 createProduct 方法,用于根据类型创建并返回 Product 类的对象
  • Product 是一个接口或抽象类,定义了产品的接口
  • ConcreteProductAConcreteProductBProduct 的具体实现

应用简单工厂模式重构

为了优化策略模式的实现,我们引入工厂模式来负责策略对象的创建和管理,从而简化LogisticsService类的职责

定义简单策略工厂

该类在Spring容器启动时自动注册所有策略实例,用与生产具体的物流策略

@Component
public class SimpleLogisticsStrategyFactory {private final List<LogisticsStrategy> strategies;@Overridepublic LogisticsStrategy createStrategy(Package pkg) {return strategies.stream().filter(strategy -> strategy.appliesTo(pkg)).findFirst().orElse(new DefaultLogisticsStrategy());}
}
修改LogisticsService

最后,LogisticsService类通过工厂类获取策略对象,而不是直接与具体策略类交互:

@Service
public class LogisticsService {private final SimpleLogisticsStrategyFactory strategyFactory;public void processPackage(Package pkg) {LogisticsStrategy strategy = strategyFactory.createStrategy(pkg);strategy.processPackage(pkg);}
}

现在似乎解决了所有问题,引入了SimpleLogisticsStrategyFactory来负责创建具体的物流策略,解偶LogisticsService与策略对象,看起来很完美,是这样吗?
问题点分析:

  • LogisticsService依赖了具体的工厂实现类,没有依赖于抽象,显然这违反了依赖倒置原则
  • 假如随着业务的发展,需要为特定类型的客户提供定制化的物流策略,按照当前的设计,只能再新建一个CustomLogisticsStrategyFactory,根据客户来选择不同的物流策略,但是这样的话LogisticsService这个类的代码就得修改,显然这违反了开闭原则

引入工厂方法模式

工厂方法模式概述

工厂方法模式是一种创建型设计模式,它通过定义一个创建对象的接口并让子类决定具体要实例化的类,从而在不指定具体类的情况下创建对象,增加了代码的灵活性和扩展性
工厂方法uml类图
在这个类图中:

  • Creator 是一个抽象类或接口,定义了一个工厂方法factoryMethod(),用于创建Product类的对象
  • ConcreteCreator是Creator的一个具体实现,实现了factoryMethod() 方法,返回ConcreteProduct的实例
  • Product是一个接口或抽象类,定义了产品的接口。ConcreteProduct是Product的具体实现
实现工厂方法模式

为了应用工厂方法模式,我们定义一个工厂接口,负责根据包裹的特性创建合适的物流策略。

public interface LogisticsStrategyFactory {LogisticsStrategy createStrategy(Package pkg);
}public class InternationalLogisticsFactory implements LogisticsStrategyFactory {@Overridepublic LogisticsStrategy createStrategy(Package pkg) {return new InternationalLogisticsStrategy();}
}public class PkgSizeLogisticsStrategyFactory implements LogisticsStrategyFactory {@Overridepublic PkgSizeLogisticsStrategyFactory createStrategy(Package pkg) {return new PkgSizeLogisticsStrategy();}
}public class LogisticsService {// 	依赖于接口private LogisticsStrategyFactory factory;public void processPackage(Package pkg) {// 根据工厂创建策略LogisticsStrategy strategy = factory.createStrategy(pkg);strategy.processPackage(pkg);}
}

通过工厂方法模式进一步提高了系统的灵活性和可扩展性。每个工厂类负责创建特定类型的物流策略,而服务类则通过工厂接口与具体的策略创建逻辑解耦。工厂负责创建具体的策略,LogisticsService只关注于处理包裹的逻辑,与策略实现类解耦

总结

通过引入策略模式、工厂模式(包括简单工厂和工厂方法模式),我们成功地将一个复杂且难以维护的物流系统重构成了一个灵活、可扩展且易于维护的系统。这个过程展示了设计模式在实际编程中的强大作用,特别是在面对复杂系统时,它们能够帮助我们更好地组织和优化代码

希望这篇文章能够帮助你更好地理解和应用设计模式。切记,理论知识的学习是基础,但只有通过实际的应用和实践,才能真正掌握。希望每个开发者都能在日常的编程工作中尝试和应用这些设计模式。如果你觉得这篇文章对你有帮助,请给我点赞和关注。感谢阅读!

  • 程序员三毛

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/309180.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ stack使用、模拟实现、OJ题

目录 一、介绍 二、常用函数 三、模拟实现 四、OJ练习题 1、最小栈 2、栈的压入、弹出序列 3、逆波兰表达式(后缀转中缀) 4、中缀转后缀思路 5、用栈实现队列 一、介绍 stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除…

HTML5+CSS3+JS小实例:过年3D烟花秀

实例:过年3D烟花秀 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><…

C++ Primer Plus----第十二章--类和动态内存分布

本章内容包括&#xff1a;对类成员使用动态内存分配&#xff1b;隐式和显式复制构造函数&#xff1b;隐式和显式重载赋值运算符&#xff1b;在构造函数中使用new所必须完成的工作&#xff1b;使用静态类成员&#xff1b;将定位new运算符用于对象&#xff1b;使用指向对象的指针…

Java中的反射原理,为什么要使用反射以及反射使用场景

什么是反射 反射是框架的灵魂 JAVA反射机制是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象&#xff0c;都能够调用它的任意一个方法和属性&#xff1b;这种动态获取的信息以及动态调用对象的方法的功能称…

用C语言函数求x^y-------(C每日一编程)

编写函数,计算x^y&#xff08;x,y都是整数&#xff09;。 参考代码&#xff1a; int fun(int x, int y) {int k 1, i;for (i 1; i < y; i)k k * x;return k; } int main() {int x, y;scanf("%d%d", &x, &y);printf("%d", fun(x, y));retur…

Leetcode算法系列| 10. 正则表达式匹配

目录 1.题目2.题解C# 解法一&#xff1a;分段匹配法C# 解法二&#xff1a;回溯法C# 解法三&#xff1a;动态规划 1.题目 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。 1.‘.’ 匹配任意单个字符 2.‘.’ 匹配任意单个字…

JavaSE语法之十二:Object类

文章目录 一、概念二、获取对象信息三、对象比较equals方法四、hashcode方法 一、概念 Object是Java默认提供的一个类。Java里面除了Object类&#xff0c;所有的类都是存在继承关系的&#xff0c;默认会继承Object父类&#xff0c;即所有的类的对象都可以使用Object的引用进行…

算法分析与设计基础

一、绪论 1.算法的概念及特征 1.1 定义&#xff1a; 算法是指求解某个问题或是某类问题的一系列无歧义的指令&#xff0c;也就是说&#xff0c;对于符合一定规范的输入&#xff0c;能够在有限时间内获得所要求的输出。 1.2 特征&#xff1a; 输入&#xff1a;算法中的各种运…

最新版 BaseRecyclerViewAdapterHelper4:4.1.2 最简单的QuickViewHolder用法,最简洁的代码,复制可用

为了照顾新手&#xff0c;尽量详细&#xff0c;高手勿喷&#xff01;&#xff01;&#xff01; 怕麻烦的话可以直接下载源码&#xff1a;https://download.csdn.net/download/ERP_LXKUN_JAK/88678044?spm1001.2014.3001.5503 先看文件结构&#xff0c;是不是很简单 AndroidSt…

用anaconda下载安装pytorch1.8.2+cudatoolkit11.1

用anaconda下载安装pytorch1.8.1cudatoolkit11.1 设置清华镜像下载&#xff1a; conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ conda con…

小程序面试题 | 17.精选小程序面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

【2023】通过docker安装hadoop以及常见报错

&#x1f4bb;目录 1、准备2、安装镜像2.1、创建centos-ssh的镜像2.2、创建hadoop的镜像 3、配置ssh网络3.1、搭建同一网段的网络3.2、配置host实现互相之间可以免密登陆3.3、查看是否成功 4、安装配置Hadoop4.1、添加存储文件夹4.2、添加指定配置4.3、同步数据 5、测试启动5.1…