楼主: wtgyy
44 0

[教育经济学基本知识] Javascript学习笔记 [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

学前班

40%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
20 点
帖子
1
精华
0
在线时间
0 小时
注册时间
2018-6-8
最后登录
2018-6-8

楼主
wtgyy 发表于 2025-11-14 07:40:44 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

在常规的软件开发过程中,我们常常会遇到需要依据不同情况采取相应措施的情景,这导致代码中充斥着大量的 if/else 结构。这种情况不仅影响了代码的易读性和可维护性,还使得未来的扩展变得更加困难。

本文将探讨四种精巧的设计模式来改善这种“条件爆炸”现象:

  1. 策略模式

1. 策略模式

01 概念

首先,我们先了解一下策略模式的定义。

策略模式(Strategy Pattern)属于行为型设计模式的一种,它定义了一组算法,并将每个算法封装起来,让它们可以互相替换。通过这种方式,策略模式使算法能够独立于使用它们的客户端进行变化。

如何理解策略模式?

在软件开发过程中,实现某个功能往往存在多种算法或方法,我们根据不同的环境或条件选择最合适的算法来完成任务。这种选择过程听起来就像 if...else... 的逻辑判断:

[此处为图片1]

对于初学者来说,这样的实现方式是完全可以接受的,只要程序能够正常运行就足够了。但随着技术水平的提升,这段代码显然违背了面向对象编程(OOP)中的两个基本准则:

  • 单一职责原则:一个类或模块应该只承担一种责任。
  • 开闭原则:软件实体(如模块、类和方法等)应当“对扩展开放,对修改封闭”。

由于违反了这两个准则,当 if-else 结构中的逻辑变得越来越复杂时,代码将更加难以管理和维护,并且非常容易出错。

策略模式的解决方案是:

定义一系列独立的类来封装不同的算法,每个类专门负责一种具体的算法。通常会有一个抽象的策略类作为所有具体算法的基础接口,确保所有的策略具有一致性。

这一设计方法涉及三个主要角色:

  • Context: 环境类
  • Strategy: 抽象策略类
  • ConcreteStrategy: 具体策略类

[此处为图片2]

接下来,我们将通过一个简单的代码示例来演示这一过程。

02 例子

步骤 1:创建接口

public interface Strategy {
    public int doOperation(int num1, int num2);
}

步骤 2:创建实现相同接口的具体类

public class OperationAdd implements Strategy{
    @Override
    public int doOperation(int num1, int num2) {
        return num1 + num2;
    }
}
public class OperationSubtract implements Strategy{
    @Override
    public int doOperation(int num1, int num2) {
        return num1 - num2;
    }
}
public class OperationMultiply implements Strategy{
    @Override
    public int doOperation(int num1, int num2) {
        return num1 * num2;
    }
}

步骤 3:创建上下文类

public class Context {
    private Strategy strategy;

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public int executeStrategy(int num1, int num2) {
        return strategy.doOperation(num1, num2);
    }
}

步骤 4:使用上下文来观察策略改变时的行为变化

public static void main(String[] args) {
    Context context = new Context();
    context.setStrategy(new OperationAdd());
    System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
    context.setStrategy(new OperationSubtract());
    System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
}

context.setStrategy(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}

执行结果:
从上面的例子,我们简要总结下策略模式的优缺点:
1、优点
- 策略模式提供了对“开闭原则”的理想支持,用户可以在不修改原有系统的基础上选择算法或行为,也能灵活地增加新的算法或行为。
- 策略模式提供了一种管理相关的算法家族的方法。
- 使用策略模式可以避免使用复杂的条件转移语句。
2、缺点
- 客户端必须了解所有的策略类,并自行决定采用哪一个策略类。
- 策略模式会导致产生许多策略类,但可以通过应用享元模式在一定程度上减少对象的数量。

2 SPI 机制

01 概念
SPI 的全称是 Service Provider Interface,是一种服务发现机制。其核心是在文件中配置接口实现类的完整限定名,并通过服务加载器读取这些配置文件来加载实现类。这使得在运行时可以动态地为接口更换实现类。因此,我们可以通过 SPI 机制轻松地扩展程序功能。

02 Java SPI : JDBC Driver
在JDBC4.0之前,我们在开发中连接数据库时,通常首先需要加载相关的数据库驱动,然后执行获取连接等操作。
// STEP 1: 注册 JDBC 驱动
Class.forName("com.mysql.jdbc.Driver");
// STEP 2: 建立连接
String url = "jdbc:xxxx://xxxx:xxxx/xxxx";
Connection conn = DriverManager.getConnection(url, username, password);
自JDBC4.0起,Java引入了SPI扩展机制,不再需要通过 Class.forName("com.mysql.jdbc.Driver") 来手动加载驱动,而是可以直接获取 JDBC 连接。

接下来,我们探讨一下如何加载 MySQL JDBC 8.0.22 驱动:
首先,DriverManager 类作为驱动管理器,是驱动加载的入口。
/** * 加载初始的 JDBC 驱动通过检查系统属性 jdbc.properties 并使用 {@code ServiceLoader}机制 */
static {
loadInitialDrivers();
println("JDBC DriverManager 初始化完成");
}
在 Java 中,static 块用于静态初始化,在类加载到Java虚拟机时执行。它负责实例化驱动。
我们关注一下 loadInitialDrivers 方法的实现。

加载驱动的过程包括四个步骤:
- 从系统属性中获取与驱动相关的定义。
- 利用 SPI 获取驱动的具体实现(以字符串形式)。
- 遍历通过SPI获得的所有具体实现,实例化每个实现类。
- 根据第一步获取的驱动列表来创建具体的实现。

我们特别关注 SPI 的使用方式,首先看第二步,利用 SPI 获取驱动的具体实现,对应的代码如下:
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
这一过程并没有直接从 META-INF/services 目录下查找配置文件或加载具体的实现类,而是封装了接口类型和类加载器,并初始化了一个迭代器。

接着看第三步,遍历通过SPI获得的所有具体实现并实例化每个实现类,相应的代码如下:
Iterator driversIterator = loadedDrivers.iterator();
// 遍历所有驱动的实现
while (driversIterator.hasNext()) {
driversIterator.next();
}
在遍历时,首先调用 driversIterator.hasNext() 方法,这一步会搜索 classpath 下以及 jar 包中所有的 META-INF/services 目录下的 java.sql.Driver 文件,并找出文件中的实现类名。此时并未实例化具体的实现类。
然后是调用 driversIterator.next() 方法,这时根据驱动名称具体实例化各个实现类,现在驱动已经被找到并实例化。

这里有一个小问题:如果发现了多个 driver 的话,应该如何选择一个特定的实现呢?

那就是 JDBC URL 了,驱动程序的实现有一个约定,如果驱动根据 JDBC URL 判断不是自己可以管理的连接就直接返回空。DriverManager 基于这个约定直到找到第一个不返回 null 的连接为止。

JDBC 驱动是 Java SPI 机制的一个非常典型的应用场景,包含三个关键步骤: 定义 SPI 文件 ,指定 Driver 类全限定名; 通过 DriverManager 静态类自动加载驱动; 加载之后,当需要获取连接时,还需要根据 ConnectionUrl 判断使用哪一个已经加载的驱动。

因此,JDBC 驱动的 SPI 机制是需要多个步骤配合来实现的。同时基于 Java SPI 机制的不足,同样也无法按需加载。

03 按需加载:Dubbo SPI 机制

由于 Java SPI 的缺陷无法支持按需加载接口实现类,Dubbo 并未采用 Java SPI,而是重新开发了一套功能更强大的 SPI 机制。 Dubbo SPI 的相关逻辑封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。

Dubbo SPI 所需的配置文件需要放置在 META-INF/dubbo 路径下,配置内容如下: optimusPrime = org.apache.spi.OptimusPrime bumblebee = org.apache.spi.Bumblebee

与 Java SPI 实现类配置不同,Dubbo SPI 通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。 此外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。

下面来展示 Dubbo SPI 的用法:


public class DubboSPITest {
    @Test
    public void sayHello() throws Exception {
        ExtensionLoader extensionLoader =
            ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }
}
测试结果如下:

另外,Dubbo SPI 除了支持按需加载接口实现类,还增加了 IOC 和 AOP 等特性。

SPI 机制的优势:

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:Javascript script scrip 学习笔记 scri

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
扫码
拉您进交流群
GMT+8, 2026-1-18 01:32