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

news2025/7/19 8:40:41

引言

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

我们将以一个物流系统为例,展示如何从大量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 {
    @Override
    public boolean appliesTo(Package pkg) {
        return "国际".equals(pkg.getDestination());
    }

    @Override
    public void processPackage(Package pkg) {
        // 实现国际物流处理逻辑
    }
}

@Component
public class PkgSizeLogisticsStrategy implements LogisticsStrategy {

 	@Override
    public boolean appliesTo(Package pkg) {
        return pkg.size() > 30;
    }
    
    @Override
    public void processPackage(Package pkg) {
        // 实现大件物流处理逻辑
    }
}

@Component
public class DefaultLogisticsStrategy implements LogisticsStrategy {

 	@Override
    public boolean appliesTo(Package pkg) {
        //....
    }
    
    @Override
    public 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;

    @Override
    public 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 {
    @Override
    public LogisticsStrategy createStrategy(Package pkg) {
        return new InternationalLogisticsStrategy();
    }
}

public class PkgSizeLogisticsStrategyFactory implements LogisticsStrategyFactory {
    @Override
    public 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.coloradmin.cn/o/1344723.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mybatis底层原理分析以及源码阅读

费话不多少先上图&#xff0c;我只喜欢画图分析&#xff0c;看图片&#xff1a; 有两个问题&#xff1a; 问题1&#xff1a; 我们一直在写Mapper/DAO只写了接口&#xff0c;没有写具体的实现吧&#xff1f; 【是的】 问题2&#xff1a; 没有写实现类就没办法实例化执行后续的操…

Windows系统清理优化方法总结

一、禁用不必要的系统服务 1、家庭组&#xff1a;家庭组是Win10磁盘&#xff0c;CPU需要高服务&#xff0c;多数用户都没有使用&#xff0c;因此建议关闭&#xff0c;不会造成其他负面影响。 快捷键 winr 调出服务输入框键入services.msc点击确定 找到家庭组相关的两个服务双…

【已解决】 ubuntu apt-get update连不上dl.google.com

在终端使用apt-get update时&#xff0c;连接dl.google.com超时&#xff0c;一直卡在0%&#xff0c;原因是当前ip无法ping到google&#xff08;墙&#xff09;。 解决方法&#xff1a; dl.google.com国内可用IP 选一个&#xff0c;然后按以下命令操作&#xff1a; cd ~ vim …

css实现一个斑马条纹动画,实现一个理发店门口的小转转,进度条动画同理!

css实现一个斑马条纹动画&#xff0c;实现一个理发店门口的小转转 前置基础知识 css背景background的重复渐变属性repeating-linear-gradient() 该属性类似于linear-gradient(),但他会在整个方向上重复渐变以覆盖整个容器 一、先写一个普通渐变例子linear-gradient() &…

【STM32】STM32学习笔记-TIM输出比较(15)

00. 目录 文章目录 00. 目录01. 输出比较简介02. PWM简介03. 输出比较通道(高级)04. 输出比较通道(通用)05. 输出比较模式06. PWM基本结构07. PWM参数计算08. 舵机简介09. 舵机硬件电路10. 直流电机及驱动简介11. 直流电机硬件电路12. 附录 01. 输出比较简介 OC&#xff08;Ou…

如何解决“电脑缺失msvcp110.dll”错误,msvcp110.dll文件解决方法

“msvcr110.dll丢失”。那么&#xff0c;msvcr110.dlll丢失到底是什么意思呢&#xff1f;它对我们的电脑有什么影响&#xff1f;本文将详细介绍msvcr110.dll的作用以及msvcr110.dll丢失对电脑的影响&#xff0c;并提供5个解决方案来解决这个问题。 一、msvcr110.dll的作用 ms…

成为一名FPGA工程师:面试题与经验分享

在现代科技领域&#xff0c;随着数字电子技术的迅猛发展&#xff0c;FPGA&#xff08;可编程逻辑器件&#xff09;工程师成为了备受瞩目的职业之一。FPGA工程师不仅需要掌握硬件设计的基本原理&#xff0c;还需要具备良好的编程能力和解决问题的实践经验。面对如此竞争激烈的行…

超简单一行命令在电脑上部署安装短视频直播、引流推流人气的工具,7个开源免费流媒体直播平台和3款开源免费直播推流工具

超简单一行命令在电脑上部署安装短视频直播、引流推流人气的工具,7个开源免费流媒体直播平台和3款开源免费直播推流工具。爆款引流短视频关注的指标、分类和引流技巧,引流短视频应用技巧,直播短视频引流五维方法论,实操干货整理分享。 如今上到太空站、下到在家养猪,各行…

编程羔手解决Maven引入多个版本的依赖包,导致包冲突了

最近升级了些依赖发现有个hutool的方法老报错&#xff0c;java.lang.NoSuchMethodError: cn.hutool.core.util.ObjectUtil.defaultIfNull(Ljava/lang/Object;Ljava/util/function/Supplier;) 在 Maven 项目中&#xff0c;当不同的依赖模块引入 Hutool 的不同版本时&#xff0c…

ubuntu20部署Bringing-Old-Photos-Back-to-Life

环境准备&#xff1a; ubuntu20.04 Python 3.8.10 首先将微软的「Bringing-Old-Photos-Back-to-Life」库 clone 到本地&#xff1a; git clone https://github.com/microsoft/Bringing-Old-Photos-Back-to-Life.git cd Face_Enhancement/models/networks/ git clone https:/…

迷宫问题的对比实验研究(代码注释详细、迷宫及路径可视化)

题目描述 对不同的迷宫进行算法问题&#xff0c;广度优先、深度优先、以及人工智能上介绍的一些算法&#xff1a;例如A*算法&#xff0c;蚁群算法等。 基本要求&#xff1a; &#xff08;1&#xff09;从文件读入9*9的迷宫&#xff0c;设置入口和出口&#xff0c;分别采用以上方…

Vscode —— 解决Vscode终端无法使用npm的命令的问题

在cmd中可以正常执行npm -v等指令,但是在vs code终端中,无法执行npm -v,node -v等指令 出现报错 解决办法&#x1f447; 方法一&#xff1a;【右键单击Vscode】以【管理员身份运行】&#xff0c;【重启Vscode】 方法二&#xff1a;①【用户变量】的【path】添加npm所在路径的…

C语言实验3:函数的定义

目录 一、实验要求 二、实验原理 1.函数头 2.函数体 3.函数的定义及使用 三、实验内容 1. sum函数 代码 截图 分析 2. sum函数 代码 截图 分析 3. rank_grade函数 代码 截图 分析 4. rank_grade函数 代码 截图 分析 5. 函数的嵌套使用 代码 截图 分析…

实现二叉树的基本操作与OJ练习

目录 1.二叉树的基本操作 1.1二叉树基本操作完整代码 1.2检测value值是否存在 1.3层序遍历 1.4判断一棵树是不是完全二叉树 2.OJ练习 2.1平衡二叉树 2.2对称二叉树 2.3二叉树遍历 1.二叉树的基本操作 1.1二叉树基本操作完整代码 public class BinaryTree {static…

推荐系统/电商中的 业务指标GMV

GMV&#xff08;Gross Merchandise Volume&#xff09;是指在一定时间内&#xff0c;一个电商平台上所有商品的总销售价值&#xff0c;通常以货币单位&#xff08;例如美元、人民币等&#xff09;表示。GMV是一个关键的电商业务指标&#xff0c;用于衡量平台的交易规模和业务增…

Flink(十一)【状态管理】

Flink 状态管理 我们一直称 Flink 为运行在数据流上的有状态计算框架和处理引擎。在之前的章节中也已经多次提到了“状态”&#xff08;state&#xff09;&#xff0c;不论是简单聚合、窗口聚合&#xff0c;还是处理函数的应用&#xff0c;都会有状态的身影出现。状态就如同事务…

【网络安全】upload靶场pass1-10思路

目录 Pass-1 Pass-2 Pass-3 Pass-4 Pass-5 Pass-6 Pass-7 Pass-8 Pass-9 Pass-10 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1…

“从零到一“基于Freeswitch二次开发: 应用架构设计(二)

一、架构分享 上一篇文章“从零到一“基于Freeswitch二次开发:Freeswitch入门与网络架构 (一) 对Freeswitch二次开发做了一个介绍&#xff0c;距离这篇文章的发布时间有点久了&#xff0c;之前一直没时间把下文补上来。正好到了年末想起来&#xff0c;就把我们的一个实现架构进…

前端性能优化 将资源放到 linux 服务器上 提升访问效率

我们先远端连接服务器 然后服务器终端输入 mkdir 目录路径建出一个新的文件路径 回到我们自己的电脑 然后 在要缓存到服务器的文件目录下打开终端 输入 scp -r ./xidis.hdr 用户名 如果没设置用户名就是root服务器公网IP:/root/xhdr例如 scp -r ./xidis.hdr root1.113.266…

JavaScript基础知识点总结:从零开始学习JavaScript(六)

本章内容主要让小伙伴们自主练习 &#xff0c;建议大家先自己写出来答案&#xff0c;然后对照我的&#xff01;&#xff08;题不难主要培养自己的编程思维&#xff01;&#xff01;&#xff01;&#xff09; 如果大家感感兴趣也可以去看&#xff1a; &#x1f389;博客主页&…