当前位置: 首页 > news >正文

策略模式 + 反射工厂:优雅实现开闭原则的终极指南

1. 引言

在软件开发的漫长历史中,如何构建易于维护、易于扩展的系统始终是工程师们追求的核心目标。设计模式作为解决特定问题的经典方案,为我们提供了无数经过验证的架构思路。而在所有设计原则中,开闭原则(Open-Closed Principle, OCP)被誉为面向对象设计的“基石”,它指导我们如何设计出对扩展开放、对修改关闭的模块。

然而,单纯的理论往往难以落地。在实际项目中,我们经常面临这样的场景:业务规则频繁变化,算法需要动态切换,而每次修改都意味着对既有代码的改动,容易引入缺陷,破坏系统的稳定性。此时,策略模式(Strategy Pattern)反射工厂(Reflective Factory)的结合,成为了一种极其优雅且强大的解决方案。它不仅完美诠释了开闭原则,还能让系统在复杂业务迭代中保持健壮与灵活。

本文将用近2万字的篇幅,从基础概念到高级应用,深入剖析策略模式与反射工厂的精髓,并通过大量代码示例和实践经验,带你领略这一组合的无穷魅力。无论你是初学者还是资深架构师,都能从中获得启发。

2. 开闭原则(Open-Closed Principle)

2.1 定义与起源

开闭原则最早由 Bertrand Meyer 在 1988 年提出,其核心思想是:

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

通俗地说,就是当系统需求发生变化时,我们应当尽量通过增加新代码来满足变化,而不是修改已有的、经过测试的代码。这样既能保证原有功能的稳定性,又能快速响应业务变化。

2.2 为什么需要开闭原则?

  • 降低风险:修改已有代码可能引入新的缺陷,尤其是核心模块。遵循 OCP 可以减少修改,从而降低风险。

  • 提高可维护性:新功能的添加独立于旧代码,模块职责清晰,易于理解和维护。

  • 增强复用性:稳定的模块可以在多个项目中复用,不受具体场景变化的影响。

  • 便于测试:新增的扩展点可以独立测试,无需重复验证原有功能。

2.3 实现开闭原则的常见手段

  • 抽象与多态:通过接口或抽象类定义抽象层,让具体实现依赖于抽象,从而可以方便地替换实现。

  • 依赖注入:将依赖对象的创建和使用分离,由外部容器负责注入,降低耦合。

  • 设计模式:如策略模式、模板方法模式、观察者模式等,都天然符合 OCP。

  • 反射与配置:利用反射机制动态加载类,结合配置文件或注解,实现零修改扩展。

在本文中,我们将重点讨论最后一种方式——反射与策略模式的结合。

3. 策略模式(Strategy Pattern)

3.1 定义与意图

策略模式属于行为型设计模式,其定义如下:

定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。

通俗解释:当一个系统有多种处理方式,且需要根据条件动态选择时,我们可以将每种处理方式封装成独立的策略类,然后在运行时选择使用哪一种策略。

3.2 模式结构

策略模式包含三个核心角色:

  • Context(上下文):持有对策略对象的引用,负责调用策略的具体方法。客户端通常只与 Context 交互。

  • Strategy(抽象策略):定义所有策略类必须实现的公共接口,通常是一个接口或抽象类。

  • ConcreteStrategy(具体策略):实现 Strategy 接口,提供具体的算法实现。

下面是一个简单的 UML 类图描述(文字版):

text

+----------------+ +------------------+ | Context | | <<interface>> | | |--------->| Strategy | | - strategy: Strategy |------------------| | + setStrategy(Strategy) | + execute() | | + execute() | | +----------------+ +------------------+ ^ | +---------------+---------------+ | | | +---------+----+ +--------+------+ +----+----------+ | ConcreteStrategyA| | ConcreteStrategyB| | ConcreteStrategyC| | + execute() | | + execute() | | + execute() | +------------------+ +------------------+ +------------------+

3.3 代码示例:电商促销策略

假设我们有一个电商系统,需要根据不同的促销活动计算订单的最终价格。我们可以定义折扣策略接口,以及多种具体策略(如无折扣、满减、打折)。

3.3.1 抽象策略接口

java

public interface DiscountStrategy { double calculate(double originalPrice); }
3.3.2 具体策略实现

java

// 无折扣 public class NoDiscountStrategy implements DiscountStrategy { @Override public double calculate(double originalPrice) { return originalPrice; } } // 固定打折,比如8折 public class PercentageDiscountStrategy implements DiscountStrategy { private double percentage; // 0.8 表示8折 public PercentageDiscountStrategy(double percentage) { this.percentage = percentage; } @Override public double calculate(double originalPrice) { return originalPrice * percentage; } } // 满减,如满100减20 public class ThresholdDiscountStrategy implements DiscountStrategy { private double threshold; private double discount; public ThresholdDiscountStrategy(double threshold, double discount) { this.threshold = threshold; this.discount = discount; } @Override public double calculate(double originalPrice) { if (originalPrice >= threshold) { return originalPrice - discount; } return originalPrice; } }
3.3.3 上下文(Context)

java

public class Order { private double price; private DiscountStrategy discountStrategy; public Order(double price) { this.price = price; } public void setDiscountStrategy(DiscountStrategy discountStrategy) { this.discountStrategy = discountStrategy; } public double calculateFinalPrice() { if (discountStrategy == null) { throw new IllegalStateException("请先设置折扣策略"); } return discountStrategy.calculate(price); } }
3.3.4 客户端使用

java

public class Client { public static void main(String[] args) { Order order = new Order(200); // 使用8折策略 order.setDiscountStrategy(new PercentageDiscountStrategy(0.8)); System.out.println("8折后价格: " + order.calculateFinalPrice()); // 160 // 切换为满减策略 order.setDiscountStrategy(new ThresholdDiscountStrategy(100, 20)); System.out.println("满减后价格: " + order.calculateFinalPrice()); // 180 } }

3.4 策略模式的优缺点

优点

  • 符合开闭原则:新增策略无需修改原有代码,只需添加新策略类并让客户端选择。

  • 避免多重条件语句:消除了 if-else 或 switch-case,代码更清晰。

  • 策略可以复用:多个 Context 可以共享同一个策略对象。

缺点

  • 客户端必须了解不同策略的区别:客户端需要知道有哪些策略以及如何选择,增加了客户端的复杂性。

  • 策略类数量增多:每个策略一个类,可能导致类爆炸。

  • 策略之间的切换成本:如果策略是带状态的,切换时可能需要额外处理。

4. 工厂模式与反射

4.1 工厂模式简介

工厂模式用于封装对象的创建过程,将客户端与具体类的实例化解耦。常见的工厂模式有简单工厂、工厂方法和抽象工厂。这里我们主要关注简单工厂工厂方法,因为它们常与策略模式结合使用,用于创建策略对象。

简单工厂示例:

java

public class DiscountStrategyFactory { public static DiscountStrategy getStrategy(String type) { switch (type) { case "none": return new NoDiscountStrategy(); case "percentage": return new PercentageDiscountStrategy(0.8); case "threshold": return new ThresholdDiscountStrategy(100, 20); default: throw new IllegalArgumentException("未知策略类型"); } } }

但这种传统工厂存在一个问题:每增加一个新的策略,都需要修改工厂类的代码,违反了开闭原则。

4.2 反射技术概述

反射(Reflection)是 Java、C# 等语言提供的一种能力,允许程序在运行时检查或修改类的行为。通过反射,我们可以动态地获取类信息、创建对象、调用方法,甚至访问私有成员。

Java 反射常用 API:

  • Class.forName(String className):加载指定类名的 Class 对象。

  • clazz.newInstance():调用无参构造器创建实例(已过时,推荐使用clazz.getDeclaredConstructor().newInstance())。

  • clazz.getMethod()等获取方法并调用。

反射使得我们可以在运行时根据字符串类名创建对象,这正是实现动态扩展的关键。

4.3 反射工厂的实现

反射工厂的核心思想是:将策略类的类型标识与类全限定名映射起来,通过反射动态创建实例,从而避免修改工厂代码。

4.3.1 基本反射工厂

java

import java.util.Properties; public class ReflectDiscountStrategyFactory { private static Properties strategyProps = new Properties(); static { // 通常从配置文件加载映射关系 strategyProps.setProperty("none", "com.example.strategy.NoDiscountStrategy"); strategyProps.setProperty("percentage", "com.example.strategy.PercentageDiscountStrategy"); strategyProps.setProperty("threshold", "com.example.strategy.ThresholdDiscountStrategy"); } public static DiscountStrategy getStrategy(String type) { String className = strategyProps.getProperty(type); if (className == null) { throw new IllegalArgumentException("未找到策略类型: " + type); } try { Class<?> clazz = Class.forName(className); return (DiscountStrategy) clazz.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException("创建策略对象失败", e); } } }

这样,当增加新策略时,我们只需添加一个新的策略类,并在配置文件(或 Properties)中增加映射,工厂无需修改。完全符合开闭原则。

4.3.2 支持带参数的构造函数

上面的例子仅支持无参构造。如果策略需要参数(如百分比折扣需要折扣率),我们可以通过反射传递参数,但这会复杂化工厂。一种常见做法是让策略类提供无参构造,并通过 setter 或配置属性注入参数。或者使用更高级的依赖注入框架。

5. 策略模式 + 反射工厂:珠联璧合

5.1 为什么要结合?

策略模式本身已经符合开闭原则,因为添加新策略不需要修改 Context 和原有策略。但策略的创建过程却可能成为破坏 OCP 的源头。如果客户端直接new具体策略,那么当新增策略时,客户端代码仍然需要修改。即使使用简单工厂,工厂类也需要修改。

因此,我们需要一种机制,使得策略的创建也无需修改已有代码。反射工厂正好解决了这个问题:通过配置或注解动态加载策略类,实现了策略对象的“零修改”创建。

5.2 结合后的架构

结合后的系统包含以下组件:

  • 策略接口:定义行为。

  • 具体策略类:实现策略接口,每个类独立。

  • 策略工厂(反射版):负责根据标识动态创建策略实例。

  • 上下文:持有策略引用,可能通过工厂获取策略。

  • 配置源:如 properties、XML、注解,保存标识与策略类的映射。

  • 客户端:通过标识向工厂请求策略,设置给上下文。

架构图(文字描述):

text

客户端 -> 策略工厂 (反射) <-- 读取 --> 配置文件 | | | v | 创建策略实例 v | 上下文 <-------+ | v 执行策略

5.3 如何实现开闭原则?

  • 对扩展开放:新增业务规则只需添加一个新的策略类,并在配置文件中增加一行映射。无需修改任何现有代码。

  • 对修改关闭:原有的策略类、工厂类、上下文类、客户端代码均无需改动(除非客户端需要感知新策略类型,但通常客户端通过配置或用户选择动态传入类型)。

这样,整个系统对变化是完全封闭的,而对扩展是完全开放的。

6. 实战案例:构建灵活的验证框架

为了更深入地展示策略模式+反射工厂的威力,我们将构建一个通用的表单验证框架。该框架可以根据不同的验证规则(如非空、长度限制、正则表达式、自定义校验)对输入数据进行验证,并且能够轻松扩展新的验证规则。

6.1 需求分析

假设我们有一个用户注册表单,包含字段:用户名、密码、邮箱、手机号。我们需要对每个字段应用不同的验证规则:

  • 用户名:非空,长度3-20。

  • 密码:非空,长度6-18。

  • 邮箱:非空,符合邮箱格式。

  • 手机号:可选,如果填写则需符合手机号格式。

随着业务发展,可能会增加新字段(如年龄、地址)或新规则(如密码复杂度、唯一性检查)。我们需要保证框架的可扩展性。

6.2 设计策略接口

定义验证策略接口ValidationStrategy

java

public interface ValidationStrategy { /** * 执行验证 * @param value 待验证的值 * @return 验证结果,包含是否成功和错误信息 */ ValidationResult validate(Object value); }

验证结果类:

java

public class ValidationResult { private boolean valid; private String message; // 构造器、getter/setter public static ValidationResult ok() { return new ValidationResult(true, null); } public static ValidationResult fail(String message) { return new ValidationResult(false, message); } // ... 省略具体实现 }

6.3 实现具体策略

6.3.1 非空验证

java

public class NotNullStrategy implements ValidationStrategy { @Override public ValidationResult validate(Object value) { if (value == null || (value instanceof String && ((String) value).trim().isEmpty())) { return ValidationResult.fail("该字段不能为空"); } return ValidationResult.ok(); } }
6.3.2 长度验证

java

public class LengthStrategy implements ValidationStrategy { private int min; private int max; // 必须提供无参构造,以便反射创建;参数通过 setter 或额外方式注入 public LengthStrategy() { // 默认值,可通过配置覆盖 this.min = 0; this.max = Integer.MAX_VALUE; } public void setMin(int min) { this.min = min; } public void setMax(int max) { this.max = max; } @Override public ValidationResult validate(Object value) { if (value == null) { return ValidationResult.fail("输入值为空"); } String str = value.toString(); int len = str.length(); if (len < min || len > max) { return ValidationResult.fail("长度必须在 " + min + " 到 " + max + " 之间"); } return ValidationResult.ok(); } }
6.3.3 正则验证

java

public class RegexStrategy implements ValidationStrategy { private String pattern; public RegexStrategy() { } public void setPattern(String pattern) { this.pattern = pattern; } @Override public ValidationResult validate(Object value) { if (value == null) { return ValidationResult.fail("输入值为空"); } if (!value.toString().matches(pattern)) { return ValidationResult.fail("格式不正确"); } return ValidationResult.ok(); } }
6.3.4 手机号验证(复用正则)

我们可以专门定义一个手机号策略,继承自 RegexStrategy 并预设 pattern。

java

public class MobileStrategy extends RegexStrategy { public MobileStrategy() { setPattern("^1[3-9]\\d{9}$"); } }

6.4 反射工厂与配置

6.4.1 策略工厂

我们需要一个工厂,能够根据策略名称创建策略实例,并且支持向策略注入参数(如 min/max/pattern)。这里我们采用更灵活的设计:从配置中读取策略的类名和参数,然后通过反射设置属性。

首先定义策略配置的数据结构:

java

public class StrategyConfig { private String name; // 策略标识,如 "notNull" private String className; // 策略类全名 private Map<String, Object> params; // 参数键值对 // getter/setter ... }

然后实现反射工厂:

java

import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ValidationStrategyFactory { // 缓存策略配置,实际可从配置文件加载 private static Map<String, StrategyConfig> configMap = new ConcurrentHashMap<>(); static { // 模拟从配置文件加载 loadConfig(); } private static void loadConfig() { // 通常解析 properties、XML 或 JSON 文件 // 这里硬编码演示 StrategyConfig notNull = new StrategyConfig("notNull", "com.example.validation.NotNullStrategy", null); configMap.put("notNull", notNull); StrategyConfig length = new StrategyConfig("length", "com.example.validation.LengthStrategy", new HashMap<>()); length.getParams().put("min", 3); length.getParams().put("max", 20); configMap.put("length", length); StrategyConfig regex = new StrategyConfig("regex", "com.example.validation.RegexStrategy", new HashMap<>()); regex.getParams().put("pattern", "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"); configMap.put("email", regex); // 邮箱复用正则策略 StrategyConfig mobile = new StrategyConfig("mobile", "com.example.validation.MobileStrategy", null); configMap.put("mobile", mobile); } public static ValidationStrategy getStrategy(String name) { StrategyConfig config = configMap.get(name); if (config == null) { throw new IllegalArgumentException("未知策略: " + name); } try { Class<?> clazz = Class.forName(config.getClassName()); Object instance = clazz.getDeclaredConstructor().newInstance(); // 注入参数 if (config.getParams() != null) { for (Map.Entry<String, Object> entry : config.getParams().entrySet()) { String paramName = entry.getKey(); Object paramValue = entry.getValue(); // 寻找 setter 方法,例如 setMin String setterName = "set" + paramName.substring(0, 1).toUpperCase() + paramName.substring(1); Method setter = findSetter(clazz, setterName, paramValue.getClass()); if (setter != null) { setter.invoke(instance, paramValue); } else { // 尝试类型转换,比如 String 转 int // 简化处理,实际可以使用类型转换器 } } } return (ValidationStrategy) instance; } catch (Exception e) { throw new RuntimeException("创建策略失败", e); } } private static Method findSetter(Class<?> clazz, String setterName, Class<?> paramType) { try { return clazz.getMethod(setterName, paramType); } catch (NoSuchMethodException e) { // 尝试父类 return null; } } }
6.4.2 配置文件示例(properties)

我们可以将配置外移到文件,比如strategies.properties

text

notNull.class=com.example.validation.NotNullStrategy length.class=com.example.validation.LengthStrategy length.params.min=3 length.params.max=20 email.class=com.example.validation.RegexStrategy email.params.pattern=^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$ mobile.class=com.example.validation.MobileStrategy

工厂启动时加载此文件。

6.5 上下文:字段验证器

现在定义字段验证器FieldValidator,它包含字段名、字段值以及要应用的策略列表。

java

import java.util.ArrayList; import java.util.List; public class FieldValidator { private String fieldName; private Object fieldValue; private List<String> strategyNames = new ArrayList<>(); public FieldValidator(String fieldName, Object fieldValue) { this.fieldName = fieldName; this.fieldValue = fieldValue; } public void addStrategy(String strategyName) { strategyNames.add(strategyName); } public List<ValidationResult> validate() { List<ValidationResult> results = new ArrayList<>(); for (String name : strategyNames) { ValidationStrategy strategy = ValidationStrategyFactory.getStrategy(name); ValidationResult result = strategy.validate(fieldValue); if (!result.isValid()) { // 可以添加字段信息 result.setMessage(fieldName + ": " + result.getMessage()); } results.add(result); } return results; } }

6.6 客户端使用示例

java

public class RegistrationForm { public static void main(String[] args) { // 模拟表单数据 String username = "john"; String password = "123456"; String email = "john@example.com"; String mobile = "13800138000"; // 验证用户名:非空,长度3-20 FieldValidator usernameValidator = new FieldValidator("用户名", username); usernameValidator.addStrategy("notNull"); usernameValidator.addStrategy("length"); // 默认 min=3, max=20 // 验证密码:非空,长度6-18 FieldValidator passwordValidator = new FieldValidator("密码", password); passwordValidator.addStrategy("notNull"); // 密码长度我们想用6-18,但 length 策略默认配置是3-20,需要重新配置?我们可以定义新的策略配置,或允许传入参数。 // 为了演示,假设我们有一个 passwordLength 策略,配置为6-18。 // 验证邮箱:非空,正则 FieldValidator emailValidator = new FieldValidator("邮箱", email); emailValidator.addStrategy("notNull"); emailValidator.addStrategy("email"); // 验证手机:可选,如果有值则验证格式 FieldValidator mobileValidator = new FieldValidator("手机号", mobile); mobileValidator.addStrategy("mobile"); // 手机号策略内部有正则 // 执行验证 List<ValidationResult> results = new ArrayList<>(); results.addAll(usernameValidator.validate()); results.addAll(passwordValidator.validate()); results.addAll(emailValidator.validate()); results.addAll(mobileValidator.validate()); // 输出结果 for (ValidationResult result : results) { if (!result.isValid()) { System.out.println(result.getMessage()); } } } }

6.7 扩展新验证规则

假设新增需求:密码必须包含数字和字母。我们只需添加一个新的策略类:

java

public class PasswordComplexityStrategy implements ValidationStrategy { @Override public ValidationResult validate(Object value) { if (value == null) return ValidationResult.fail("密码不能为空"); String pwd = value.toString(); boolean hasDigit = pwd.matches(".*\\d.*"); boolean hasLetter = pwd.matches(".*[a-zA-Z].*"); if (!hasDigit || !hasLetter) { return ValidationResult.fail("密码必须包含数字和字母"); } return ValidationResult.ok(); } }

然后在配置文件添加:

text

passwordComplexity.class=com.example.validation.PasswordComplexityStrategy

客户端只需使用passwordValidator.addStrategy("passwordComplexity"),无需修改任何已有代码。完美符合开闭原则。

7. 深入分析与讨论

7.1 反射的性能考量

反射虽然强大,但性能开销不容忽视。每次通过反射创建对象都会涉及类加载、安全检查、方法调用等,比直接new慢得多。在高并发场景下,如果频繁创建策略对象,可能成为瓶颈。

优化策略

  • 缓存反射创建的对象:如果策略是无状态的,可以创建单例,从缓存中返回同一个实例。

  • 使用对象池:对有状态的策略,可以使用对象池复用实例。

  • 结合 Spring 等 IoC 容器:让容器管理策略 Bean,通过容器获取实例,既利用了反射又利用了容器的缓存和 AOP 能力。

  • 使用编译时代理:如 Java 的MethodHandle或字节码生成技术(如 CGLib),比纯反射略快。

在我们的验证框架中,策略大多是无状态的(LengthStrategy 虽然有 min/max,但这些参数一旦设置就固定,可以视为有状态但不可变),我们可以缓存创建好的策略实例。

改进工厂:增加策略缓存。

java

public class ValidationStrategyFactory { private static Map<String, ValidationStrategy> strategyCache = new ConcurrentHashMap<>(); public static ValidationStrategy getStrategy(String name) { // 先查缓存 if (strategyCache.containsKey(name)) { return strategyCache.get(name); } // 否则创建并缓存 ValidationStrategy strategy = createStrategy(name); strategyCache.put(name, strategy); return strategy; } // ... createStrategy 方法内部使用反射创建 }

7.2 策略注册方式的选择

除了配置文件(properties/XML),还有其他方式可以实现策略的注册:

  • 注解(Annotation):在策略类上使用自定义注解,标注策略标识。工厂启动时通过扫描包获取所有标注的类,建立映射。这种方式去除了配置文件,更加内聚,但需要编译时确定。

示例注解:

java

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Strategy { String value(); // 策略标识 }

在策略类上标注:

java

@Strategy("notNull") public class NotNullStrategy implements ValidationStrategy { ... }

工厂通过类路径扫描(如使用 Reflections 库)收集所有带有@Strategy注解的类,注册到映射中。

  • SPI(Service Provider Interface):利用 Java 的ServiceLoader机制,在META-INF/services下放置接口全名文件,内容为实现类。然后通过ServiceLoader.load加载所有实现。这种方式适合插件化架构。

  • 依赖注入容器:如 Spring,通过配置 Bean 或组件扫描,将策略类注入到 Map 中,根据 Bean 名称获取。

每种方式各有优缺点,需根据项目规模、技术栈和团队偏好选择。

7.3 与策略模式相关的其他模式

  • 状态模式:与策略模式结构相似,但意图不同。状态模式通过改变对象内部状态来改变行为,状态之间通常有关联;策略模式是算法族的替换,策略之间独立。

  • 模板方法模式:定义算法骨架,子类实现某些步骤。可以与策略结合,比如模板方法定义验证流程,策略实现具体验证逻辑。

  • 工厂方法模式:将对象的创建延迟到子类。可以用工厂方法创建策略,但通常结合反射工厂更灵活。

  • 组合模式:可以将多个策略组合成一个复合策略(如 AND 或 OR 逻辑),实现更复杂的规则。

7.4 开闭原则的局限性与注意事项

虽然策略模式+反射工厂很好地实现了 OCP,但在实际应用中仍需注意:

  • 过度设计:如果业务规则极少变化,使用反射工厂可能增加不必要的复杂度。应遵循 YAGNI 原则。

  • 配置管理:配置文件可能分散,需要统一管理,确保正确性。

  • 错误处理:反射可能抛出多种异常,需要妥善处理,给出友好的错误提示。

  • 类型安全:反射绕过了编译期类型检查,可能引入运行时错误。通过单元测试覆盖可以降低风险。

  • 性能开销:如前所述,需合理缓存。

8. 最佳实践与实战经验

8.1 项目结构建议

在大型项目中,策略类可能很多,建议按模块组织:

text

com.example.project ├── strategy │ ├── annotation │ │ └── Strategy.java │ ├── base │ │ └── ValidationStrategy.java │ ├── impl │ │ ├── NotNullStrategy.java │ │ ├── LengthStrategy.java │ │ └── ... │ └── factory │ ├── StrategyFactory.java │ └── StrategyRegistry.java ├── config │ └── strategy-config.xml └── context └── FieldValidator.java

8.2 结合 Spring 框架

如果项目使用 Spring,可以大大简化策略的注册和获取。

8.2.1 通过 @Component 自动注册

java

@Component("notNull") public class NotNullStrategy implements ValidationStrategy { ... } @Component("length") public class LengthStrategy implements ValidationStrategy { ... }

在工厂中注入所有策略 Bean:

java

@Component public class SpringStrategyFactory { @Autowired private Map<String, ValidationStrategy> strategyMap; // key为bean名称 public ValidationStrategy getStrategy(String name) { ValidationStrategy strategy = strategyMap.get(name); if (strategy == null) { throw new IllegalArgumentException("未找到策略: " + name); } return strategy; } }

Spring 会收集所有类型为ValidationStrategy的 Bean,Bean 名称默认是类名首字母小写(或通过@Component指定)。这种方式既不需要反射,也不需要手动维护配置,性能好,代码简洁。

8.2.2 使用 @Qualifier 区分

如果同一类型有多个 Bean,可以使用@Qualifier进行更精细的注入。

8.3 参数化策略的配置管理

对于带参数的策略(如 LengthStrategy),在 Spring 中可以通过@Value@ConfigurationProperties注入参数,或者使用原型作用域(@Scope("prototype"))每次创建新实例。

如果是非 Spring 项目,可以考虑使用一个统一的参数对象传递给策略,或者使用建造者模式创建策略。

8.4 策略的组合与链式调用

有时一个字段需要应用多个策略,并且希望它们按顺序执行,遇到失败就停止(短路)。我们可以设计一个组合策略:

java

public class CompositeStrategy implements ValidationStrategy { private List<ValidationStrategy> strategies; public CompositeStrategy(List<ValidationStrategy> strategies) { this.strategies = strategies; } @Override public ValidationResult validate(Object value) { for (ValidationStrategy s : strategies) { ValidationResult result = s.validate(value); if (!result.isValid()) { return result; // 失败即返回 } } return ValidationResult.ok(); } }

在工厂中,我们可以从配置中读取策略列表,组装成组合策略。这样客户端无需关心内部逻辑。

8.5 测试策略

由于策略类和工厂都是可独立测试的,建议编写单元测试覆盖每个策略的正确性,以及工厂的创建逻辑。

java

public class NotNullStrategyTest { @Test public void testNull() { NotNullStrategy strategy = new NotNullStrategy(); assertFalse(strategy.validate(null).isValid()); } @Test public void testEmptyString() { NotNullStrategy strategy = new NotNullStrategy(); assertFalse(strategy.validate("").isValid()); } @Test public void testValidString() { NotNullStrategy strategy = new NotNullStrategy(); assertTrue(strategy.validate("hello").isValid()); } }

对于反射工厂,可以 mock 配置文件,测试是否能正确创建预期类型。

8.6 注意事项

  • 避免过度反射:反射虽然灵活,但应尽量在启动时完成,减少运行时反射次数。

  • 参数类型转换:配置文件中的参数都是字符串,需要转换成策略 setter 所需的类型。可以使用类型转换器(如 Spring 的 TypeConverter)或约定只支持基本类型。

  • 线程安全:如果策略是有状态的(如保存了上次验证结果),则不能多线程共享。需要确保工厂返回的是新实例或使用线程封闭。

9. 总结

9.1 回顾核心思想

本文围绕“策略模式 + 反射工厂”这一组合,深入探讨了它们如何完美实现开闭原则。策略模式将算法封装成独立的类,使算法可以独立变化;反射工厂则将策略对象的创建过程与具体类解耦,通过配置或注解动态加载。两者结合,使得系统在增加新策略时,无需修改任何已有代码,达到了“对扩展开放,对修改关闭”的理想状态。

9.2 适用场景

这种组合特别适合以下场景:

  • 业务规则频繁变化:如促销活动、折扣计算、验证规则、计费方式等。

  • 算法族庞大且易变:需要支持多种算法,且未来可能增加。

  • 希望实现插件化架构:允许第三方开发者扩展功能。

  • 需要动态切换行为:运行时根据配置或用户选择切换策略。

9.3 与其他技术方案的对比

  • 简单工厂 + 条件判断:扩展时需要修改工厂,违反 OCP。

  • 注解扫描:无需配置文件,但需约定扫描路径,启动时略慢。

  • SPI:适合框架扩展,但加载所有实现,可能加载不需要的类。

  • 依赖注入容器:最简洁,但需要容器支持。

9.4 未来展望

随着微服务、云原生的发展,策略模式+反射工厂的思想也扩展到了分布式系统。例如,可以通过配置中心动态下发策略配置,实现运行时策略切换;或者结合函数式编程,将策略视为 lambda 表达式,减少类数量。

http://www.jsqmd.com/news/431116/

相关文章:

  • 场景化落地:国标GB28181视频平台EasyGBS+GB35114赋能多行业安防升级
  • 告别写作内耗!6款AI写作工具实测,效率与质量双在线
  • 从订阅枷锁到创作自由:PhotoGIMP如何重新定义开源图像编辑体验
  • 泛微强制执行字段联动
  • 2026年口碑好的管道电加热器/风道电加热器厂家推荐及选择指南 - 品牌宣传支持者
  • 基于微信小程序的智慧社区娱乐服务管理平台小程序设计与实现
  • 2026年靠谱的车载点烟器线/车载点烟器转换器厂家选购指南与推荐 - 品牌宣传支持者
  • 从此告别拖延,AI论文软件 千笔AI VS PaperRed,专科生写作更轻松!
  • 分析开式冷却塔厂家怎么选,无锡地区有哪些靠谱品牌值得推荐 - 工业品网
  • 如何选择靠谱的YYC齿条供应商?核心资质、参数匹配与服务能力全解析 - 深度智识库
  • 本地 PDF 合并统计丨 PDF JoinCount 1.3.2 汉化版 Win 版
  • 开发板无法开机,竟然是电源线有问题
  • 告别数据孤岛:AI驱动的短剧项目一体化运营系统定制指南
  • Java 8 开发的 4 大技巧
  • Adobe Acrobat Pro
  • 从外包到众包:灵活用工系统如何优化任务分配与支付链路(含代码解读)
  • LeetCode 718 最长重复子数组:python3 题解
  • 这次终于选对!备受推崇的AI论文写作软件 —— 千笔ai写作
  • 哪些工具可以快速识别一家企业是否为假冒国企?
  • 2026优秀海外ODI备案代办机构推荐榜 - 优质品牌商家
  • 5200美元的导电工作服:为输电线路工人安全护航
  • 2026年3月焊管厂家推荐,精准检测与稳定性能深度解析 - 品牌鉴赏师
  • 〘 1-1 〙软考高项 | 第8章:项目整合管理(上)
  • 2026年3月天然苏打水品牌推荐,水质检测与天然属性解析 - 品牌鉴赏师
  • 2026年3月山西自理老人住养老院推荐,服务评估与居住体验深度解析 - 品牌鉴赏师
  • monkey命令运行自动化,报安全异常“SecurityException”的解决方法
  • springboot中添加拦截器
  • 2026年商业空间香氛香薰品牌排名:打造沉浸式嗅觉体验 - 包罗万闻
  • 2026年天津阻燃电缆生产厂家推荐:涵阻燃、阻燃B1级、矿物质防火、柔性防火等 - 品牌2026
  • 大模型学习入门:收藏这份 Agent 应用实战指南,小白也能快速上手!