可能很多开发者,虽然理解了设计模式的概念,但在实际工作中应用却是另一回事。本篇文章旨在用一个具体的案例来展示如何将设计模式应用到工作的编程问题中。正所谓:“纸上得来终觉浅,绝知此事要躬行。”理论的学习固然重要,但只有通过实战,我们才能真正掌握并灵活运用。
我们将以一个物流系统为例,展示如何从大量if-else
语句的初始代码,一步步演进到应用工厂模式、策略模式的优雅设计。通过这个过程,你将看到设计模式如何帮助我们改进代码结构,提高其可维护性和可扩展性。
如果你觉得这篇文章对你有帮助,请记得点赞和关注,支持一下!
在这个物流系统中,根据包裹的不同特性(如目的地、大小、重量或优先级)来选择合适的物流供应商是一项常见任务。这个过程涉及到多个决策点,每个决策点都可能依赖于包裹的不同属性。例如,国际快递和本地配送就需要不同的处理逻辑。
随着业务的发展,新的供应商加入,特殊的配送需求增加,原本简单的决策逻辑迅速膨胀,变得越来越复杂。这不仅使得代码难以维护,也增加了引入错误的风险。
初始的实现可能直接使用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
是一个接口,定义了appliesTo
和processPackage
方法,用于判断策略是否适用于特定的包裹,并处理包裹。InternationalLogisticsStrategy
和DefaultLogisticsStrategy
是实现了LogisticsStrategy
接口的具体策略类存在的缺陷分析
LogisticsService
除了负责处理包裹,还要负责选择策略,显然违背了单一职责
LogisticsService
依赖于具体的策略实现,没有依赖于抽象,显然违背了依赖倒置原则
因此考虑是否可以引入工厂模式?让选择策略的逻辑由工厂实现,解决单一职责
问题。解耦LogisticsService
与具体的策略实现,让LogisticsService
只依赖于抽象的工厂,解决依赖倒置原则
问题,显然是可行的,接下来我们走进引入工厂模式的篇章
工厂模式是一种创建型设计模式,用于提供一个创建对象的接口,从而将对象的实例化逻辑从使用对象的代码中分离出来。在我们的物流系统案例中,工厂模式可以用来灵活地创建和管理不同的物流策略对象,看下简单工厂的类图
在这个类图中:
SimpleFactory
是一个类,提供了一个 createProduct
方法,用于根据类型创建并返回 Product
类的对象Product
是一个接口或抽象类,定义了产品的接口ConcreteProductA
和 ConcreteProductB
是 Product
的具体实现为了优化策略模式的实现,我们引入工厂模式来负责策略对象的创建和管理,从而简化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类通过工厂类获取策略对象,而不是直接与具体策略类交互:
@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
这个类的代码就得修改,显然这违反了开闭原则
工厂方法模式是一种创建型设计模式,它通过定义一个创建对象的接口并让子类决定具体要实例化的类,从而在不指定具体类的情况下创建对象,增加了代码的灵活性和扩展性
在这个类图中:
为了应用工厂方法模式,我们定义一个工厂接口,负责根据包裹的特性创建合适的物流策略。
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
只关注于处理包裹的逻辑,与策略实现类解耦
通过引入策略模式、工厂模式(包括简单工厂和工厂方法模式),我们成功地将一个复杂且难以维护的物流系统重构成了一个灵活、可扩展且易于维护的系统。这个过程展示了设计模式在实际编程中的强大作用,特别是在面对复杂系统时,它们能够帮助我们更好地组织和优化代码
希望这篇文章能够帮助你更好地理解和应用设计模式。切记,理论知识的学习是基础,但只有通过实际的应用和实践,才能真正掌握。希望每个开发者都能在日常的编程工作中尝试和应用这些设计模式。如果你觉得这篇文章对你有帮助,请给我点赞和关注。感谢阅读!