设计模式实战

简介: **设计模式的应用与案例**设计模式是解决常见软件设计问题的最佳实践,有助于提升代码质量和可维护性。有效利用设计模式的步骤包括:理解业务需求、识别问题、选择合适模式、学习研究和适时调整。在实际工作中,例如,通过结合工厂模式和策略模式,解决了多端页面配置筛选逻辑,避免接口爆炸;使用模板方法模式,将复杂业务逻辑拆分为可复用步骤,提高了代码扩展性。设计模式虽好,但应适度,避免过度复杂化。

一、设计模式的使用场景

设计模式(Design Patterns)是在软件开发中经过验证的最佳实践,用于解决常见的设计问题。它们提供了一种可复用的解决方案,可以帮助开发人员提高代码质量、可维护性和可重用性。那么在在工作中,如何有效利用设计模式帮我们解决问题呢?我觉得主要有以下几点:

  1. 理解业务需求:在开始编码之前,深入了解业务需求是非常重要的。这将帮助我们确定哪些设计模式最适合当前的问题。
  2. 识别问题:在开发过程中,时刻关注代码中可能出现的重复、冗余或难以维护的部分。这些问题通常是需要应用设计模式的候选者。
  3. 选择适当的设计模式:根据识别到的问题,拟合设计模式的解决方式,在脑子中模拟改写后的业务代码,大致逻辑通顺后就可以尝试改写了。有的一直设计模式可能还不好解决,那就要先拆解一部分,分批解决,因为工程是有时间、成本要求的,不可能一直等我们苦思冥想和尝试。而且也不要试图一次性将所有代码都改造成使用设计模式。相反,应该逐步改进代码,逐步引入设计模式。这样可以确保代码始终保持在一个稳定且可维护的状态。
  4. 学习和研究:不断学习新的设计模式,研究它们是如何解决特定问题的。这可以通过阅读书籍、在线教程、博客文章和开源代码库来实现。这样可以让我们脑子里有个设计模式的概念,尽快的能拟合业务场景找到合适的设计模式,这样才保证能在工作中用起来。
  5. 注意事项:设计模式并不是银弹,我们应该根据具体情况和需求进行选择和调整。不要盲目追求使用设计模式,而是要在理解它们的优缺点和项目排期的基础上做出决策。

二、案例

在之前的工作经历中我有过多次使用设计模式的经历,下面我讲述两个分别是使用工厂和策略两种设计模式和模版方法的案例,方便大家理解。

1、策略和工厂模式

1)背景

当时面临的问题是有多种类型的 app,app 上有不同的页面,不同页面根据不同的业务或者标识去查询配置的不同的页面,但是我们页面的数据逻辑又在其他系统,我们只能在自己系统内完成这样的筛选逻辑,在当时要么根据不同的客户端开放不同的接口,要么根据不同的业务类型开放不同的接口,那这样肯定会随着端的增多或者业务类型的增多会产生接口爆炸的情况,要么就是在一个接口内完成大量的判断来完成这样的逻辑。

2)引入设计模式

那有没有可能借助设计模式来解决呢?当时我就在寻找相关的案例,最后觉得工厂模式和策略模式的结合是很好的方式,简单工厂模式的作用是提供专门的工厂类用于创建对象,实现了对象创建和使用的职责分离,客户端不需知道所创建的具体产品类的类名以及创建过程,只需要知道具体产品类所对应的参数即可。这样不正是拟合了根据上送和客户端参数创建多端工厂的场景吗?而策略模式是把具体的算法实现从业务逻辑中剥离出来,成为一系列独立算法类,使得它们可以相互替换。那也正好拟合了我们根据上送的业务类型和页面类型寻找不同处理类的场景,所以我就在项目中着手使用设计模式来解决这样的问题。

  • 客户端
@SpringBootTest
public class ApplicationTestBizPage {
    @Autowired
     HandlerFeComponentFactory handlerFeComponentFactory;
    @Test
    public void test() {
        List<FeComponent> feComponentList = handlerFeComponentFactory.doWork("alipay", "home",  "biz");
        System.out.println("-----------------");
        System.out.println(feComponentList);
    }
}
  • 创建工厂
@Component
public class HandlerFeComponentFactory {
    @Autowired
    private List<HandlerFeComponentService> handlerFeComponentServices;
  
    public List<FeComponent> doWork(String app, String page, String bizType) {
        for (HandlerFeComponentService handlerFeComponentService : handlerFeComponentServices) {
            if (handlerFeComponentService.isSupport(app)) {
                return handlerFeComponentService.doWork(app, page,  bizType);
            }
        }
        return new ArrayList<>();
    }
}
  • 客户端接口
public interface HandlerFeComponentService {
    boolean isSupport(String app);
    List<FeComponent> doWork(String app, String page, String bizType);
  • 支付宝实现
@Component
public class AliappHandlerFeComponentServiceImpl implements HandlerFeComponentService {
    @Autowired
    private Map<String, AbstractAliappHandlerFeComponent> aliappHandlerFeComponentMap;
    @Override
    public boolean isSupport(String app) {
        return "alipay".equals(app);
    }
    @Override
    public List<FeComponent> doWork(String app, String page, String bizType) {
        String bizPage = String.format("%s:%s:%s", app, page, bizType);
        AbstractAliappHandlerFeComponent aliappHandlerFeComponent = aliappHandlerFeComponentMap.get(bizPage);
        if (Objects.nonNull(aliappHandlerFeComponent)) {
            return aliappHandlerFeComponent.doWorkForAliapp(app, page);
        }
        return null;
    }
  • 业务抽象类定义
public   abstract class AbstractAliappHandlerFeComponent {
    abstract List<FeComponent> doWorkForAliapp(String app, String page) ;
}
  • 业务抽象类实现
@Component("alipay:home:biz")
public class TestBizPage extends AbstractAliappHandlerFeComponent {
    @Override
    List<FeComponent> doWorkForAliapp(String app, String page) {
        List<FeComponent> feComponentList = new ArrayList<>();
        FeComponent feComponent = new FeComponent();
        feComponent.setMessage("alipay:home:biz");
        feComponentList.add(feComponent);
        return feComponentList;
    }
}
  • 实体类
public class FeComponent {
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    private  String message;
    @Override
    public String toString() {
        return "FeComponent{" +
                "message='" + message + '\'' +
                '}';
    }
}

从上面可以看出,调用者仍然只需要传入自己的参数,在寻找具体处理类和方法的路径都被包装在工厂方法和 springbean 和具体类的映射关系处理中。以后增加了新的客户端百度,只需要新增一个 BaiduService 和处理类即可。

2、模版方法

还有一次经历是是使用模版方法完成业务逻辑的组件化。

1)背景

之前一个业务逻辑有很多个版本,我还需要再增加2个版本,全部流程大致有15步流程,在不同的版本可能会有缺少不同的步骤,所以之前的逻辑就有在不同步骤中有各自版本的判断导致很难清晰的看出每个版本自己的逻辑是什么,导致我很难加自己的逻辑,也很不利于排查问题。

2)引入设计模式

那怎样解决这样一个问题呢?我是结合模版方法的模式来重构了一遍代码,我们先看下模版方法模式的定义,模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。它主要优点包括:

  • 封装不变部分:将算法的核心结构和不变部分封装在基本方法中,使得子类可以根据需求对可变部分进行扩展,而不影响不变部分。
  • 代码复用:抽象类中包含的基本方法可以避免子类重复实现相同的代码逻辑,提高了代码的复用性。
  • 更好的扩展性:由于具体实现由子类来完成,可以方便地扩展新的功能或变更实现方式,同时不影响模板方法本身。

这不正好拟合我遇到问题场景吗。之后我将业务逻辑中的步骤梳理出来,放到一个抽象类中,公共部分在抽象类中实现,不同版本的内容定义成抽象方法,由各个版本的子类完成实现,由传入的参数来和子类形成一个映射关系。这样我增加自己的两个版本,只需要增加两个子类即可,每个版本的逻辑也清晰可见,减少了排查的难度。我也来展示一个简化版本根据的 demo,方便大家理解的。

// 抽象类定义了算法的骨架  
public abstract class AbstractClass {  
  
    // 模板方法  
    public final void templateMethod() {  
        step1();  
        step2();  
        hook1(); // 钩子方法,子类可以选择是否覆盖  
        step3();  
        hook2(); // 另一个钩子方法  
    }  
  
    // 具体步骤,由抽象类实现  
    protected void step1() {  
        System.out.println("执行步骤1");  
    }  
  
    protected void step2() {  
        System.out.println("执行步骤2");  
    }  
  
    protected void step3() {  
        System.out.println("执行步骤3");  
    }  
  
    // 钩子方法,默认实现为空  
    protected void hook1() {  
    }  
  
    // 另一个钩子方法,默认实现为空  
    protected void hook2() {  
    }  
}  
  
// 子类实现了抽象类,并重写了某些步骤  
public class SubClass extends AbstractClass {  
  
    // 重写钩子方法  
    @Override  
    protected void hook1() {  
        System.out.println("执行钩子方法1");  
    }  
  
    // 重写另一个钩子方法  
    @Override  
    protected void hook2() {  
        System.out.println("执行钩子方法2");  
    }  
}  
  
// 客户端代码  
public class Client {  
  
    public static void main(String[] args) {  
        AbstractClass obj = new SubClass();  
        obj.templateMethod();  
    }  
}

在这个示例中,AbstractClass 是一个抽象类,它定义了业务的全部落哦,包括三个具体步骤(step1(), step2(), step3())和两个钩子方法(hook1(), hook2())。

SubClass 是 AbstractClass 的一个子类,它选择性地重写了两个钩子方法,但没有改变算法的骨架。

Client 类是客户端代码,它创建了一个 SubClass 的实例,并调用了模板方法templateMethod()。

当运行这个示例时,输出将是:

执行步骤1

执行步骤2

执行钩子方法1

执行步骤3

执行钩子方法2

模板方法还是比较常见的,大家可以看看 jdk 中的 map 实现和 springboot 的启动流程中都有体现的。

需要注意的是,虽然设计模式在软件开发中很有用,但过度使用或不当地使用它们也可能导致代码过度复杂化和难以理解。那怎样定义过度使用呢,遵循事不过三的原则即可,只要重复的代码或者方法没有超过三个的时候就不用优化。希望大家可以在实际业务中灵活运用。

目录
相关文章
|
11天前
|
设计模式 存储 Java
「全网最细 + 实战源码案例」设计模式——责任链模式
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,允许将请求沿着处理者链进行发送。每个处理者可以处理请求或将其传递给下一个处理者,从而实现解耦和灵活性。其结构包括抽象处理者(Handler)、具体处理者(ConcreteHandler)和客户端(Client)。适用于不同方式处理不同种类请求、按顺序执行多个处理者、以及运行时改变处理者及其顺序的场景。典型应用包括日志处理、Java Web过滤器、权限认证等。
48 13
「全网最细 + 实战源码案例」设计模式——责任链模式
|
11天前
|
设计模式 存储 算法
「全网最细 + 实战源码案例」设计模式——命令模式
命令模式(Command Pattern)是一种行为型设计模式,将请求封装成独立对象,从而解耦请求方与接收方。其核心结构包括:Command(命令接口)、ConcreteCommand(具体命令)、Receiver(接收者)和Invoker(调用者)。通过这种方式,命令的执行、撤销、排队等操作更易扩展和灵活。 适用场景: 1. 参数化对象以操作。 2. 操作放入队列或远程执行。 3. 实现回滚功能。 4. 解耦调用者与接收者。 优点: - 遵循单一职责和开闭原则。 - 支持命令组合和延迟执行。 - 可实现撤销、恢复功能。 缺点: - 增加复杂性和类数量。
49 14
「全网最细 + 实战源码案例」设计模式——命令模式
|
13天前
|
设计模式 算法 开发者
「全网最细 + 实战源码案例」设计模式——策略模式
策略模式(Strategy Pattern)是一种行为型设计模式,用于定义一系列可替换的算法或行为,并将它们封装成独立的类。通过上下文持有策略对象,在运行时动态切换算法,提高代码的可维护性和扩展性。适用于需要动态切换算法、避免条件语句、经常扩展算法或保持算法独立性的场景。优点包括符合开闭原则、运行时切换算法、解耦上下文与策略实现、减少条件判断;缺点是增加类数量和策略切换成本。示例中通过定义抽象策略接口和具体策略类,结合上下文类实现动态算法选择。
49 8
「全网最细 + 实战源码案例」设计模式——策略模式
|
13天前
|
设计模式 SQL 算法
「全网最细 + 实战源码案例」设计模式——模板方法模式
模板方法模式是一种行为型设计模式,定义了算法的骨架并在父类中实现不变部分,将可变部分延迟到子类实现。通过这种方式,它避免了代码重复,提高了复用性和扩展性。具体步骤由抽象类定义,子类实现特定逻辑。适用于框架设计、工作流和相似算法结构的场景。优点包括代码复用和符合开闭原则,缺点是可能违反里氏替换原则且灵活性较低。
60 7
「全网最细 + 实战源码案例」设计模式——模板方法模式
|
15天前
|
设计模式 存储 安全
「全网最细 + 实战源码案例」设计模式——组合模式
组合模式(Composite Pattern)是一种结构型设计模式,用于将对象组合成树形结构以表示“部分-整体”的层次结构。它允许客户端以一致的方式对待单个对象和对象集合,简化了复杂结构的处理。组合模式包含三个主要组件:抽象组件(Component)、叶子节点(Leaf)和组合节点(Composite)。通过这种模式,客户端可以统一处理简单元素和复杂元素,而无需关心其内部结构。适用于需要实现树状对象结构或希望以相同方式处理简单和复杂元素的场景。优点包括支持树形结构、透明性和遵循开闭原则;缺点是可能引入不必要的复杂性和过度抽象。
72 22
|
15天前
|
设计模式 存储 缓存
「全网最细 + 实战源码案例」设计模式——享元模式
享元模式(Flyweight Pattern)是一种结构型设计模式,旨在减少大量相似对象的内存消耗。通过分离对象的内部状态(可共享、不变)和外部状态(依赖环境、变化),它有效减少了内存使用。适用于存在大量相似对象且需节省内存的场景。模式优点包括节省内存和提高性能,但会增加系统复杂性。实现时需将对象成员变量拆分为内在和外在状态,并通过工厂类管理享元对象。
148 83
|
18天前
|
设计模式 前端开发 数据库
「全网最细 + 实战源码案例」设计模式——桥接模式
桥接模式(Bridge Pattern)是一种结构型设计模式,通过将抽象部分与实现部分分离,使它们可以独立变化,从而降低代码耦合度,避免类爆炸,提高可扩展性。其结构包括实现类接口、具体实现类、抽象类和精确抽象类。适用于多维度扩展类、隐藏实现细节、简化庞杂类以及运行时切换实现方法的场景。优点包括高扩展性、隐藏实现细节、遵循开闭原则和单一职责原则;缺点是可能增加代码复杂度。示例中展示了不同操作系统播放不同格式视频文件的实现。
47 19
|
18天前
|
设计模式 存储
「全网最细 + 实战源码案例」设计模式——装饰者模式
装饰者模式(Decorator Pattern)是一种结构型设计模式,通过“包装”现有对象来为其添加额外功能,而无需修改原有代码。它通过创建装饰类来扩展对象的功能,而非继承。该模式由抽象构件、具体构件、抽象装饰者和具体装饰者组成,允许在运行时动态组合功能。穿衣服的例子很好地解释了装饰者模式:你可以根据需要一层层添加衣物,如毛衣、夹克和雨衣,每件衣物都扩展了基本行为,但不是你的一部分,可以随时脱掉。 优点包括灵活性、避免子类爆炸和符合开闭原则;缺点是可能增加复杂性和难以理解。适用于希望在不修改代码的情况下为对象新增行为的场景,尤其当继承难以实现或不可行时。
51 15
|
20天前
|
设计模式 Java 开发者
「全网最细 + 实战源码案例」设计模式——适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,通过引入适配器类将一个类的接口转换为客户端期望的另一个接口,使原本因接口不兼容而无法协作的类能够协同工作。适配器模式分为类适配器和对象适配器两种,前者通过多重继承实现,后者通过组合方式实现,更常用。该模式适用于遗留系统改造、接口转换和第三方库集成等场景,能提高代码复用性和灵活性,但也可能增加代码复杂性和性能开销。
68 28
|
20天前
|
设计模式 缓存 Java
「全网最细 + 实战源码案例」设计模式——代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,通过代理对象控制对目标对象的访问并添加额外功能。它分为静态代理和动态代理,后者包括JDK动态代理和CGLIB动态代理。JDK动态代理基于接口反射生成代理类,而CGLIB通过继承目标类生成子类。代理模式适用于延迟初始化、访问控制、远程服务、日志记录和缓存等场景,优点是职责分离、符合开闭原则和提高安全性,缺点是增加系统复杂性。
71 25

热门文章

最新文章