策略模式 + 工厂方法模式在项目中的实战应用

策略模式 + 工厂方法模式在项目中的实战应用

今天聊一聊策略模式。策略模式是我们在软件开发过程中最常用的设计模式之一。

不过在讲概念之前,我们先聊聊代码的逻辑组织。

在组织代码逻辑时,最常用的表达方式就是 if......else......,以及 for、while 等流程控制语句。其实,当代码中开始出现 if......else if...... 的多分支结构时,我们就应该停下来想一想:这里是否需要引入策略模式?当然,并不是所有的 if......else if...... 都要用策略模式来重构,关键要看每个分支中承载的业务逻辑是什么。

现在回到策略模式的概念。策略模式(Strategy Pattern)的定义是:

定义一系列算法类,将每一个算法封装起来,并让它们可以互相替换。策略模式让算法独立于使用它的客户而变化。它也称为政策模式(Policy),是一种对象行为型模式。

为什么 if......else if...... 和策略模式有天然的联系?因为这种多分支结构往往是在处理同一类事物的不同情况------每个分支对应一种处理方式,而这些不同的处理方式,恰恰就是可以被封装起来的业务行为(算法)。

因此,面对 if......else if...... 的多分支场景,我们可以考虑这样做:

  1. 抽象算法模型:把各个分支中的具体业务处理逻辑抽象出来,定义成统一的接口或抽象类;
  2. 封装具体算法:让每个分支的逻辑各自实现为一个独立的算法类;
  3. 引入工厂管理:配合工厂方法模式,把这些算法类统一管理和创建;
  4. 按需调用:业务调用时,通过工厂方法返回对应的具体算法,替代原有的分支判断。

下面,我们用一个"数据脱敏"的例子,来展示如何通过"策略模式 + 工厂方法模式"的组合,实现代码的高可扩展性。


一、业务场景描述

在用户信息管理中(可以是患者信息、居民健康档案信息等业务场景),在 C 端查询个人信息时,返回结果包含姓名、身份证号、手机号、邮箱、地址等敏感字段,出于个人隐私保护的需求,需要对上述敏感字段进行脱敏处理,具体规则如下:

  • 手机号:保留前 3 位 + 后 4 位(138****5678)
  • 身份证:保留前 6 位 + 后 4 位(110101********8888)
  • 邮箱:保留用户名前 3 位(zha***@example.com)
  • 姓名:保留姓氏(张**)
  • 地址:保留行政区划,对道路/门牌/地标做掩码

这时,如果不使用设计模式,很多人的实现方式可能如下------完全使用 if......else if...... 来处理这段逻辑:

typescript 复制代码
public String sensitive(String value, String type){
    if ("name".equals(type)) {
        return value.substring(0, 1) +
                "*".repeat(value.length() - 1);
    } else if ("idcard".equals(type)) {
        ......
    } else if ("email".equals(type)) {
        ......
    } else if ("phone".equals(type)) {
        ......
    } else if ("address".equals(type)) {
        ......
    }
    return value;
}

这种写法看上去简单,代码行数不多,结构也清晰。如果是小项目(比如给小公司做的小程序、内部管理系统),需求基本一次定型、变更不多,这样写没有问题。但如果是大规模的企业级软件产品,就可能出问题,比如:

  • 可变性需求:患者小程序返回患者信息,在给 A 医院上线时,要求姓名只保留姓,名字全部使用"*"脱敏;而到了 B 医院,要求只对名字中间的字符脱敏,两个字的名字则脱敏最后一个字。也就是说,同一套产品要在不同项目中应对不同的脱敏规则。
  • 可扩展性需求:随着项目的积累,需要脱敏的字段类型会越来越多,这个 if......else if...... 会越来越长,每次新增类型都要修改这个脱敏方法。

下面一步一步拆解:如何使用策略模式和工厂方法模式,让脱敏功能同时具备良好的可变性与可扩展性。

二、抽象算法

2.1 抽象算法策略

所有脱敏操作都有一个共同特征:输入一个字符串,根据脱敏规则,返回脱敏后的字符串。基于这个共同特征,可以抽象出统一的策略接口:

typescript 复制代码
/**
 * 脱敏策略
 *
 * @version 1.0
 * @author: big-bread
 * @date: 2025/8/13 10:45
 */
public interface SensitiveStrategy {
    String mask(String value);
}

抽象出这个接口后,算法"种类"的扩展问题就解决了:新增一种脱敏类型,只需新增一个接口实现类。但还有一个问题没解决------如果同一种算法在不同项目中有不同的参数要求(比如手机号脱敏,有的项目要求保留前 3 位,有的要求保留前 2 位),又该怎么办?

2.2 算法可变性分析

下面分析脱敏算法中常见的可变因素:

  • 脱敏位置:前面保留几位、后面保留几位;
  • 替换字符:*、# 等;
  • 特殊规则:比如需要借助正则表达式做分段处理。

实际项目中的可变因素可能更多,这里先考虑以上三类。既然可变因素可以归纳出来,就可以把它们从代码中抽离,纳入配置管理。本例将配置放在 yml 文件中(实际项目可放到配置中心或数据库,那需要更详细的设计):

yaml 复制代码
sensitive:
  strategies: # 敏感数据处理策略
    phone:  # 保留前 3 位 + 中间替换 + 保留后 4 位
      mask-char: "*"
      prefix-keep: 3
      suffix-keep: 4
    idcard: # 保留前 6 位(地区码)+ 中间替换 + 保留后 4 位
      mask-char: "*"
      prefix-keep: 6
      suffix-keep: 4
    email: # 用户名部分保留前 3 位,@ 及域名部分完整保留
      mask-char: "*"
      prefix-keep: 3
    name: # 保留第 1 个字,其余全部替换
      mask-char: "*"
      prefix-keep: 1
    address: # 优先用正则拆分行政区划和详细地址:行政区划完整保留,详细地址中的路名、门牌号、地标建筑分别替换脱敏。示例:北京市朝阳区建国路88号 → 北京市朝阳区****路**号
      prefix-keep: 4
      custom-pattern: '(.*?[区县旗])(.*)'
    default: # 通用兜底策略:保留前 2 位 + 保留后 2 位;长度 < 3 时仅保留首字符
      mask-char: "*"
      prefix-keep: 2
      suffix-keep: 2

说明:

  • mask-char :脱敏使用的替换字符,通常为 *;
  • prefix-keep:头部保留长度;
  • suffix-keep:尾部保留长度;
  • custom-pattern:自定义正则表达式。

配置抽离之后,策略接口也要相应调整:把可变的配置信息作为参数,传递给具体的策略。

2.3 算法参数抽象

将配置信息抽象为配置类:

typescript 复制代码
@Configuration
@ConfigurationProperties(prefix = "sensitive")
public class SensitiveConfig {
​
    private Map<String, StrategyConfig> strategies = new HashMap<>();
​
    @Data
    public static class StrategyConfig {
        private String maskChar;
        private Integer prefixKeep;
        private Integer suffixKeep;
        private String customPattern;
    }
​
    /**
     * 获取策略配置:未配置时使用 default 兜底策略
     */
    public StrategyConfig getStrategyConfig(String strategyKey) {
        return strategies.getOrDefault(strategyKey,
                strategies.getOrDefault("default", new StrategyConfig()));
    }
​
    /**
     * 默认策略的 key
     */
    public String getDefaultStrategy() {
        return "default";
    }
​
    public void setStrategies(Map<String, StrategyConfig> strategies) {
        this.strategies = strategies;
    }
​
    public Map<String, StrategyConfig> getStrategies() {
        return this.strategies;
    }
}

重新定义策略接口:

arduino 复制代码
public interface SensitiveStrategy {
    String mask(String value, SensitiveConfig.StrategyConfig config);
}

2.4 具体算法实现

具体实现需要考虑配置的缺省情况:配置项缺失时提供默认值(如果项目要求强校验,也可以直接抛出异常)。下面给出几个实现示例,手机号、邮箱等策略的实现方式类似,不再赘述。

2.4.1 姓名脱敏策略
typescript 复制代码
@Component
public class NameSensitiveStrategy implements SensitiveStrategy {
​
    @Override
    public String mask(String value, SensitiveConfig.StrategyConfig config) {
        if (value == null || value.isEmpty()) return value;
​
        int prefixKeep = config.getPrefixKeep() != null ? config.getPrefixKeep() : 1;
        String maskChar = config.getMaskChar() != null ? config.getMaskChar() : "*";
​
        if (value.length() == 1) return maskChar;
​
        // 防止保留位数超过字符串长度导致越界
        int actualPrefixKeep = Math.min(prefixKeep, value.length());
        return value.substring(0, actualPrefixKeep)
                + maskChar.repeat(value.length() - actualPrefixKeep);
    }
}
2.4.2 身份证脱敏策略
arduino 复制代码
@Component
public class IdCardSensitiveStrategy implements SensitiveStrategy {
​
    @Override
    public String mask(String value, SensitiveConfig.StrategyConfig config) {
        if (value == null || value.length() < 15) return value;
​
        int prefixKeep = config.getPrefixKeep() != null ? config.getPrefixKeep() : 6;
        int suffixKeep = config.getSuffixKeep() != null ? config.getSuffixKeep() : 4;
        String maskChar = config.getMaskChar() != null ? config.getMaskChar() : "*";
​
        // 防止前后保留位数之和超过总长度
        int actualPrefixKeep = Math.min(prefixKeep, value.length());
        int actualSuffixKeep = Math.min(suffixKeep, value.length() - actualPrefixKeep);
        int maskLength = value.length() - actualPrefixKeep - actualSuffixKeep;
​
        return value.substring(0, actualPrefixKeep)
                + maskChar.repeat(maskLength)
                + value.substring(value.length() - actualSuffixKeep);
    }
}
2.4.3 地址脱敏策略
ini 复制代码
/**
 * 地址脱敏策略
 *
 * @version 1.0
 * @author: big-bread
 * @date: 2025/8/15 11:08
 */
@Component
public class AddressSensitiveStrategy implements SensitiveStrategy {
    @Override
    public String mask(String value, SensitiveConfig.StrategyConfig config) {
        if (value == null || value.isEmpty()) return value;
​
        int prefixKeep = config.getPrefixKeep() != null ? config.getPrefixKeep() : 4;
        String maskChar = config.getMaskChar() != null ? config.getMaskChar() : "*";
​
        // 如果有自定义正则,优先使用
        if (config.getCustomPattern() != null) {
​
            Pattern pattern = Pattern.compile(config.getCustomPattern());
            Matcher matcher = pattern.matcher(value);
            if (matcher.find()) {
                // 行政区划部分(包含区字)
                String administrativeArea = matcher.group(1);
                // 详细地址部分(可能为空)
                String detail = matcher.groupCount() > 1 ? matcher.group(2) : "";
​
                // 只有在存在详细地址时才进行脱敏
                if (detail != null && !detail.isEmpty()) {
                    detail = processDetail(detail);
                } else if (detail == null) {
                    detail = "";
                }
​
                return administrativeArea + detail;
            }
​
            return processDetail(value);
        }
​
        // 确保 prefixKeep 不超过字符串长度
        int actualPrefixKeep = Math.min(prefixKeep, value.length());
        int maskLength = Math.max(0, value.length() - actualPrefixKeep);
​
        // 保留前缀部分
        String prefix = value.substring(0, actualPrefixKeep);
        // 生成掩码部分
        String mask = maskChar.repeat(maskLength);
​
        return prefix + mask;
    }
​
    private String processDetail(String detail) {
        // 处理道路名称
        detail = detail.replaceAll(
                "[\u4e00-\u9fa5]+?(路|街|道|巷|弄|段|里|坊|大街|大道|公路|高速)",
                "****$1"
        );
        // 处理门牌号
        detail = detail.replaceAll(
                "\d+(号|栋|幢|室|单元|楼|层|座|号楼|栋楼|幢楼)",
                "**$1"
        );
        // 处理地标建筑
        detail = detail.replaceAll(
                "[\u4e00-\u9fa5]{2,}(社区|广场|大厦|中心|花园|小区|公寓|酒店|商场|写字楼|科技园|工业园)",
                "****$1"
        );
        return detail;
    }
​
}
2.4.4 默认兜底策略

yml 中定义了 default 兜底配置,还需要一个对应的实现类,工厂才能按 default 这个 key 找到它:

arduino 复制代码
/**
 * 默认兜底脱敏策略
 *
 * @version 1.0
 * @author: big-bread
 * @date: 2025/8/15 11:20
 */
@Component
public class DefaultSensitiveStrategy implements SensitiveStrategy {
​
    @Override
    public String mask(String value, SensitiveConfig.StrategyConfig config) {
        if (value == null || value.isEmpty()) return value;
​
        int prefixKeep = config.getPrefixKeep() != null ? config.getPrefixKeep() : 2;
        int suffixKeep = config.getSuffixKeep() != null ? config.getSuffixKeep() : 2;
        String maskChar = config.getMaskChar() != null ? config.getMaskChar() : "*";
​
        // 长度过短时仅保留首字符
        if (value.length() < 3) {
            return value.substring(0, 1) + maskChar.repeat(value.length() - 1);
        }
​
        int actualPrefixKeep = Math.min(prefixKeep, value.length());
        int actualSuffixKeep = Math.min(suffixKeep, value.length() - actualPrefixKeep);
        int maskLength = value.length() - actualPrefixKeep - actualSuffixKeep;
​
        return value.substring(0, actualPrefixKeep)
                + maskChar.repeat(maskLength)
                + value.substring(value.length() - actualSuffixKeep);
    }
}

2.5 小节总结

以上实现只是简单示例(AI 时代,具体算法可以交给 AI 生成,但设计思想仍需要由我们传递给 AI)。本节的核心是做了两层抽象:

  • 算法抽象:将脱敏行为抽象为统一接口,解决算法种类的扩展性问题;
  • 参数抽象:将算法中的可变因素抽象为配置,解决同一算法在不同场景下的可变性问题。

通过算法抽象获得扩展性,通过参数抽象获得可变性。

三、策略管理

策略封装完成后,接下来的问题是:策略怎么使用?如果还是在业务代码里写 if......else if...... 来调用策略,那等于没有摆脱分支结构。下面从使用场景出发,分析脱敏能力应该怎么接入:

  • 脱敏的时机:脱敏通常针对返回给前端的 VO 对象。可以开发自定义注解标注需要脱敏的字段,再通过 AOP 切面在接口返回结果时统一处理。
  • 策略的调用:用工厂方法把策略统一注册到容器中;注解触发脱敏时,按策略 key 从工厂中获取对应策略执行,用脱敏后的数据替换原数据。

3.1 工厂方法封装脱敏策略

定义工厂类:

typescript 复制代码
/**
 * 脱敏策略工厂类
 *
 * @version 1.0
 * @author: big-bread
 * @date: 2025/8/13 18:40
 */
@Component
public class SensitiveContext {
    private final Map<String, SensitiveStrategy> strategyMap = new ConcurrentHashMap<>();
​
    /**
     * 构造函数,自动注册所有策略实现
     * @param strategies 策略实现列表
     */
    @Autowired
    public SensitiveContext(List<SensitiveStrategy> strategies) {
        // 自动注册所有策略实现
        for (SensitiveStrategy strategy : strategies) {
            String key = strategy.getClass().getSimpleName()
                    .replace("SensitiveStrategy", "")
                    .toLowerCase();
            registerStrategy(key, strategy);
        }
    }
​
    /**
     * 注册策略
     * @param key 策略的 key
     * @param strategy 策略实现
     */
    public void registerStrategy(String key, SensitiveStrategy strategy) {
        strategyMap.put(key, strategy);
    }
​
    /**
     * 执行指定的策略
     * @param key 策略的 key
     * @param value 待处理的数据
     * @param config 策略的配置
     * @return 脱敏后的数据
     */
    public String executeStrategy(String key, String value, SensitiveConfig.StrategyConfig config) {
        SensitiveStrategy strategy = strategyMap.get(key);
        if (strategy == null) {
            throw new IllegalArgumentException("No strategy registered for key: " + key);
        }
        return strategy.mask(value, config);
    }
​
    /**
     * 获取所有已注册的策略
     * @return 已注册的策略 key 集合
     */
    public Set<String> getAvailableStrategies() {
        return strategyMap.keySet();
    }
}

说明:

  • 自动注册机制 :新增策略只需编写实现类并标注 @Component,无需修改任何注册代码;
  • 策略管理 :使用 ConcurrentHashMap 管理策略,保证多线程环境下的读写安全(本例属于防御性设计,兼顾未来动态更新配置的可能);
  • 策略与配置分离 :策略本身不持有配置,由外部传入 StrategyConfig。同一个策略实现可以搭配不同配置参数复用(如 yml 中定义的 prefix-keep、suffix-keep 等),实现算法与参数的解耦。

3.2 脱敏注解封装

定义处理标记注解(类/方法级):

标注在 Controller 的类或方法上,用于精确圈定切面的拦截范围。

less 复制代码
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveProcess {
​
}

定义字段注解:

标注在 VO 的字段上,声明该字段使用哪种策略脱敏。

less 复制代码
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Sensitive {
    /**
     * 策略键值,对应注册的策略名称
     */
    String strategyKey() default "";
​
    /**
     * 是否启用,可动态控制
     */
    boolean enabled() default true;
}

定义注解切面:

kotlin 复制代码
/**
 * 脱敏注解切面
 *
 * @version 1.0
 * @author: big-bread
 * @date: 2025/8/13 18:48
 */
@Aspect
@Component
public class SensitiveAspect {
    @Autowired
    private SensitiveContext sensitiveContext;
    @Autowired
    private SensitiveConfig sensitiveConfig;
​
    // 缓存字段注解信息,避免重复反射
    private final Map<Class<?>, Map<Field, Sensitive>> annotationCache = new ConcurrentHashMap<>();
​
    @Around("@annotation(com.big.bread.export.common.sensitive.SensitiveProcess) || @within(com.big.bread.export.common.sensitive.SensitiveProcess)")
    public Object processControllerResponse(ProceedingJoinPoint joinPoint) throws Throwable {
​
        Object result = joinPoint.proceed();
​
        if (result instanceof ResponseResult) {
            Object data = processSensitiveData(((ResponseResult<?>) result).getData());
            return ResponseUtil.makeSuccess(data);
        }
​
        return processSensitiveData(result);
    }
​
    private Object processSensitiveData(Object data) {
        if (data == null) return null;
        // 处理集合
        if (data instanceof Collection) {
            return ((Collection<?>) data).stream()
                    .map(this::processSingleObject)
                    .collect(Collectors.toList());
        }
        // 处理数组
        if (data.getClass().isArray()) {
            return Arrays.stream((Object[]) data)
                    .map(this::processSingleObject)
                    .toArray();
​
        }
        // 处理单对象
        return processSingleObject(data);
    }
​
    private Object processSingleObject(Object obj) {
        if (obj == null) return null;
​
        Class<?> clazz = obj.getClass();
        Map<Field, Sensitive> desensitizationFields = getSensitiveFields(clazz);
​
        desensitizationFields.forEach((field, annotation) -> {
            if (!annotation.enabled()) return;
            try {
                field.setAccessible(true);
                Object value = field.get(obj);
                if (value instanceof String) {
                    // 获取策略配置
                    String strategyKey = getEffectiveStrategyKey(annotation);
                    SensitiveConfig.StrategyConfig config = sensitiveConfig.getStrategyConfig(strategyKey);
                    // 执行脱敏
                    String maskedValue = sensitiveContext.executeStrategy(
                            strategyKey,
                            (String) value,
                            config
                    );
                    field.set(obj, maskedValue);
                }
            } catch (IllegalAccessException e) {
                // 记录日志,避免静默失败
            }
        });
​
        return obj;
    }
​
    private String getEffectiveStrategyKey(Sensitive annotation) {
        if (!annotation.strategyKey().isEmpty()) {
            return annotation.strategyKey();
        }
        return sensitiveConfig.getDefaultStrategy();
    }
​
    private Map<Field, Sensitive> getSensitiveFields(Class<?> clazz) {
        return annotationCache.computeIfAbsent(clazz, k -> {
            Map<Field, Sensitive> fields = new HashMap<>();
            for (Field field : clazz.getDeclaredFields()) {
                Sensitive desensitization = field.getAnnotation(Sensitive.class);
                if (desensitization != null) {
                    fields.put(field, desensitization);
                }
            }
            return fields;
        });
    }
}

四、使用示例

下面是一个居民基本信息 VO 对象的脱敏注解使用示例:

typescript 复制代码
@Data
public class ResidentInfoVO {
    // 人员ID(主键)
    private String residentId;
    // 健康档案编号
    private String healthRecordNo;
    // 姓名
    @Sensitive(strategyKey = "name")
    private String name;
    // 证件类型
    private String idType;
    // 证件号码
    @Sensitive(strategyKey = "idcard")
    private String idNumber;
    // 出生日期
    private LocalDate birthDate;
    // 详细地址
    @Sensitive(strategyKey = "address")
    private String address;
    // 联系电话
    @Sensitive(strategyKey = "phone")
    private String phone;
    ......
}

然后在 Controller 的类或查询方法上标注 @SensitiveProcess,切面就会在接口返回结果时自动完成脱敏:

less 复制代码
@RestController
public class ResidentController {
​
    @SensitiveProcess
    @GetMapping("/resident/{id}")
    public ResponseResult<ResidentInfoVO> detail(@PathVariable String id) {
        ......
    }
}

五、总结

5.1 方案回顾

  1. 问题起点 :if......else if...... 的多分支写法简单直接,但在企业级产品中会遇到两个痛点------可变性 (同一算法在不同项目中的规则不同)和可扩展性(每新增一种类型都要修改原有代码)。
  2. 两层抽象 :通过算法抽象 (SensitiveStrategy 接口)解决扩展性;通过参数抽象 (StrategyConfig 配置类 + yml 配置)解决可变性。
  3. 策略管理 :通过工厂方法(SensitiveContext)自动注册和分发策略,调用方不再感知分支判断;再通过"注解 + AOP"(@SensitiveProcess / @Sensitive + SensitiveAspect)把脱敏能力无侵入地接入业务代码------VO 字段加上注解即可完成脱敏。

这套组合带来的收益:

  • 符合开闭原则:新增脱敏类型 = 新增一个实现类 + 一行配置,不改动任何既有代码;
  • 职责单一:每个策略类只关心自己的算法,可独立编写单元测试;
  • 规则配置化:同一套代码通过配置适配不同项目的规则差异,无需为每个项目拉分支改代码;
  • 业务无侵入:脱敏逻辑集中在切面中,业务代码零感知。

最后,几点使用时的注意事项:

  • 不是所有 if......else 都需要策略化:分支少、逻辑稳定、几乎不变的场景,直白的写法反而更好,引入模式本身也是成本;
  • 明确失败策略:脱敏涉及安全合规,脱敏失败时是"放行原值"还是"拒绝返回",需要按合规要求显式定义(本文示例为简化做了放行处理,生产环境建议失败时记录日志并按需熔断);
  • 策略膨胀的管理:策略和配置项变多之后,需要配套的命名规范与文档,并对配置做校验和默认值兜底;
  • 性能意识 :反射操作务必做缓存(本文示例已使用 annotationCache),避免影响接口性能。

设计模式的价值不在于背诵概念,而在于识别"代码中哪里会变",并把变化隔离出来。AI 时代,具体的算法实现可以交给 AI 生成,但"哪里会变、如何隔离变化"的判断,仍然来自工程师自己。

5.2 策略模式场景扩展

其实,我们在基于能力封装的时候,大多都可以使用到了策略模式,下面我列举一些场景供参考:

  • 文件导出:POI、EasyExcel、iText、Freemarker → 按 excel / pdf / word 选择策略
  • 消息队列:RocketMQ、RabbitMQ、Kafka → 按 MQ 类型选择生产者策略
  • 事件总线:Spring Event、Guava EventBus → 本质就是按事件类型路由,与策略注册表同构
  • 文件存储:本地、MinIO、OSS、S3 → local / minio / oss / s3
  • 通知触达:短信、邮件、App 推送、IM → sms / mail / push
  • 支付渠道:微信、支付宝、银联 → wxpay / alipay / unionpay
  • 另外还有登录认证、加解密算法、检索引擎、缓存体系

为什么上面的场景需要考虑策略?常见原因主要两方面:

  • 客户原因:客户现有环境利旧、受政策要求必须使用某些技术栈或中间件。
  • 能力提供方变化:能力提供方开源协议变化,如闭源等,有可能导致技术栈变更。

以上原因就需要针对能力的封装考虑策略模式,针对不同的能力提供方,抽象能力,业务端调用只使用能力而不关注能力的具体实现。

相关推荐
高晶1 小时前
C 语言实现 BMS 系统中 SPI 通信协议实战指南
设计模式·程序员
代钦塔拉1 小时前
【C++/Qt 进阶】设计模式系列(2):工厂模式——从游戏角色到披萨店
c++·算法·设计模式
高晶3 小时前
2A3 单端甲类功放从零制作全攻略:电路设计、焊接调试到煲机维护一站式指南
前端·设计模式
代钦塔拉6 小时前
【C++/Qt 进阶】设计模式系列(4):原型模式——用“克隆“代替“重新构造“
c++·qt·设计模式
博风8 小时前
11、设计模式应用举例:迭代器模式:使用多个集合存储对象
设计模式·迭代器模式
日和雫1 天前
面向对象与面向过程,谁才是“银弹”?
设计模式
geovindu1 天前
rust: Iterator Pattern
开发语言·后端·设计模式·rust·行为模式·迭代器模
高晶2 天前
ARM 嵌入式 Linux 系统启动流程实战指南
设计模式·程序员
高晶2 天前
BMS 通信开发实战:从 PMBus 协议到数据采集的完整指南
设计模式·程序员