跳转至

软件设计

这是华为可信认证的一个科目,本版块将重点学习软件设计的知识,包括设计原则和设计模式;大部分语料由AI生成,我负责整理、发布

软件设计是在需求与实现之间建立结构的过程:把系统拆成合适的模块,明确模块之间的依赖与职责,并让结构在需求变化时仍然可控。

设计原则

设计原则是经验性的指导方针,不是硬性规则。它们共同的目标是降低耦合、提高内聚、让变化的影响范围可控。

SOLID 原则

原则 英文 核心思想 违反时的典型症状
单一职责原则 SRP (Single Responsibility) 一个模块应围绕一个主要职责进行组织,即只有一个引起它变化的原因 一个类里混着业务逻辑、持久化、格式化,改动一处牵连多处
开闭原则 OCP (Open/Closed) 尽量通过扩展来增加功能,而不是反复修改稳定的代码 每加一种类型就要改一遍 switch / if-else
里氏替换原则 LSP (Liskov Substitution) 子类型应能在约定的使用场景中替代父类型,且不破坏调用方的预期 子类抛出不支持的异常、静默改变父类语义
接口隔离原则 ISP (Interface Segregation) 不要强迫调用方依赖不需要的接口 一个庞大的接口,实现类被迫写一堆空方法
依赖倒置原则 DIP (Dependency Inversion) 高层策略不应直接依赖低层实现细节,二者都应依赖抽象 业务层直接 new 具体的数据访问类,难以替换和测试

补充说明:

  • SRP:职责的划分依据是"变化的来源",而不是代码行数。不是"一个类只能有一个方法"。
  • OCP:绝对的"开闭"不存在,关键是识别出哪些维度会变,在那些维度上预留扩展点。
  • LSP:契约包括前置条件、后置条件、不变量和异常语义。子类可以弱化前置条件、强化后置条件,反之则不行。
  • ISP:接口按"调用方需要什么"来切分,而不是按"实现方能提供什么"来切分。
  • DIP:"倒置"指依赖方向与控制流方向相反——业务层调用数据层,但依赖的是数据层的抽象接口,由外部注入实现。

其他常见原则

原则 英文 核心思想 适用边界
DRY Don't Repeat Yourself 避免重复维护相同的知识或逻辑 重复的是知识,不是字面代码;语义不同的相似代码不应强行合并
KISS Keep It Simple, Stupid 尽量采用简单、清晰的解决方案 简单不等于简陋,必要的抽象仍然要做
YAGNI You Aren't Gonna Need It 不要过早实现尚无实际需要的功能 对已知必然到来的需求,可以预留接口,但不要提前实现
迪米特法则 LoD (Law of Demeter) 只与直接的朋友通信,减少对象间的链式依赖 过度使用会导致大量转发方法
组合优于继承 Composition over Inheritance 优先用组合复用行为,避免继承带来的强耦合 存在真正的"is-a"且契约稳定时,继承仍然合理

这些原则之间存在张力:DRY 与 KISS 可能冲突,YAGNI 与 OCP 也可能冲突。设计是在具体上下文中做权衡,而不是机械地同时满足所有原则。

设计模式

设计模式是可复用的、针对特定上下文的解决方案模板。经典的 GoF(Gang of Four)设计模式共 23 种,按目的分为三大类。

总览

类别 关注点 包含模式
创建型 对象如何被创建,解耦"使用"与"实例化" 单例、工厂方法、抽象工厂、建造者、原型
结构型 类与对象如何组合成更大的结构 适配器、桥接、组合、装饰器、外观、享元、代理
行为型 对象之间如何协作、如何分配职责 责任链、命令、迭代器、中介者、备忘录、观察者、状态、策略、模板方法、访问者

创建型模式

模式 意图 典型场景 注意点
单例 Singleton 保证一个类只有一个实例,并提供全局访问点 配置中心、连接池、日志器 注意线程安全、可测试性与隐式全局状态
工厂方法 Factory Method 定义创建对象的接口,由子类决定实例化哪个类 框架扩展点,如 Collection.iterator() 每新增产品通常要新增一个工厂子类
抽象工厂 Abstract Factory 创建一系列相关或相互依赖的对象,而无需指定具体类 跨平台 UI 组件族、不同数据库的 DAO 族 新增"产品族"容易,新增"产品种类"困难
建造者 Builder 将复杂对象的构建过程与表示分离,分步构造 参数多、可选参数多的对象,如 StringBuilder、HTTP 请求 参数较少时反而增加复杂度
原型 Prototype 通过复制已有实例来创建新对象 创建成本高、状态初始化复杂,如大对象、依赖快照 浅拷贝/深拷贝语义必须明确
// 单例:静态内部类实现,兼顾懒加载与线程安全
public final class Config {
    private Config() {}

    private static class Holder {
        static final Config INSTANCE = new Config();
    }

    public static Config getInstance() {
        return Holder.INSTANCE;
    }
}

// 建造者:链式构造,避免超长参数列表
public final class HttpRequest {
    private final String url;
    private final Map<String, String> headers;

    private HttpRequest(Builder b) {
        this.url = b.url;
        this.headers = Map.copyOf(b.headers);
    }

    public static class Builder {
        private String url;
        private final Map<String, String> headers = new HashMap<>();

        public Builder url(String url) { this.url = url; return this; }
        public Builder header(String k, String v) { headers.put(k, v); return this; }
        public HttpRequest build() {
            Objects.requireNonNull(url, "url");
            return new HttpRequest(this);
        }
    }
}

结构型模式

模式 意图 典型场景 注意点
适配器 Adapter 将一个类的接口转换成调用方期望的另一种接口 接入第三方 SDK、兼容旧接口 只做接口转换,不承担额外业务逻辑
桥接 Bridge 将抽象部分与实现部分分离,使二者可以独立变化 两个以上独立变化的维度,如图形 × 渲染 API 识别变化维度是前提,过早使用会增加复杂度
组合 Composite 将对象组合成树形结构,统一处理单个对象与组合对象 文件系统、组织架构、UI 组件树 需要统一的叶子/容器接口,可能牺牲类型安全
装饰器 Decorator 动态地给对象添加职责,比继承更灵活 Java I/O 流、缓存/日志/重试的层层包装 会产生较多小对象,包装顺序影响语义
外观 Facade 为子系统提供一致的高层接口,简化使用 复杂 SDK 的入口类、模块门面 不要演变成"上帝类",避免吞掉子系统的能力
享元 Flyweight 共享细粒度对象的内部状态,减少内存占用 大量相同对象,如字符、图标、棋盘棋子 内外部状态要清晰分离,线程安全需额外处理
代理 Proxy 为对象提供替身以控制访问 远程代理、虚拟代理、保护代理、AOP 注意与装饰器的区别:代理强调访问控制,装饰器强调增强
// 装饰器:在不改动原有类的前提下叠加职责
public interface Reader {
    String read();
}

public class PlainReader implements Reader {
    public String read() { return "data"; }
}

public class CachedReader implements Reader {
    private final Reader delegate;
    public CachedReader(Reader delegate) { this.delegate = delegate; }
    public String read() { /* 先查缓存,未命中再 delegate.read() */ return delegate.read(); }
}

行为型模式

模式 意图 典型场景 注意点
责任链 Chain of Responsibility 让多个处理器依次处理请求,直到被处理或传递到链尾 过滤器、拦截器、审批流、异常处理 链过长会影响性能,注意断链与兜底处理
命令 Command 将请求封装为对象,从而参数化调用方、排队或记录请求 撤销/重做、任务队列、宏命令 命令类数量可能膨胀
迭代器 Iterator 顺序访问聚合对象中的元素,而不暴露其内部表示 集合遍历、自定义数据结构 遍历过程中修改集合需要明确的失败策略
中介者 Mediator 用一个中介对象封装一系列对象之间的交互 聊天室、GUI 事件调度、消息总线 中介者容易演变为难以维护的巨型类
备忘录 Memento 在不破坏封装的前提下捕获并恢复对象状态 撤销操作、事务回滚 快照的内存开销需要评估
观察者 Observer 定义一对多依赖,主题变化时自动通知所有观察者 事件监听、发布订阅、响应式更新 注意通知顺序、异常隔离与循环通知
状态 State 允许对象在内部状态改变时改变其行为 订单状态机、连接状态、工作流 状态过多时应考虑用表驱动替代
策略 Strategy 定义一系列算法并使其可互相替换 多种支付方式、多种排序/压缩算法 策略与调用方的选择逻辑需要解耦(结合工厂)
模板方法 Template Method 在父类中定义算法骨架,将部分步骤延迟到子类 框架回调、AbstractList 等 依赖继承,钩子方法不宜过多
访问者 Visitor 在不修改元素类的前提下,为其定义新的操作 编译器 AST 处理、报表导出 元素类增加时需要在所有访问者中同步修改(双分派)
// 策略:把"做什么"从"怎么选"中抽离
public interface PricingStrategy {
    BigDecimal price(BigDecimal base);
}

public class VipPricing implements PricingStrategy {
    public BigDecimal price(BigDecimal base) { return base.multiply(new BigDecimal("0.8")); }
}

public class OrderService {
    private final PricingStrategy strategy;
    public OrderService(PricingStrategy strategy) { this.strategy = strategy; }
    public BigDecimal total(BigDecimal base) { return strategy.price(base); }
}

模式选择速查

  • 对象创建太复杂:先考虑工厂方法;对象族要成套创建时用抽象工厂;参数多且可选时用建造者。
  • 想在运行时叠加能力:装饰器;想控制访问或增加间接层:代理;想简化子系统调用:外观。
  • 行为随类型/状态变化:状态(状态间有迁移)或策略(可自由选择算法)。
  • 一对多通知:观察者;请求沿链传递:责任链。
  • 两个维度独立变化:桥接;大量相似小对象:享元。

不要为了"用模式"而用模式。模式的价值在于提供共同词汇、降低沟通成本;如果问题简单,直接写清楚的代码就是最好的方案。

常见反模式

反模式 表现 改进方向
上帝类 God Object 一个类承担了系统中大部分职责 按职责拆分,引入协作对象
过度设计 Over-engineering 为假设中的未来需求堆叠抽象层 遵循 YAGNI,先让简单版本通过测试
单例滥用 任何东西都做成全局单例 改用依赖注入,显式传递依赖
意大利面条代码 Spaghetti 控制流跳转混乱、跨模块直接访问 分层、收敛入口、统一依赖方向
贫血模型 vs 充血模型偏执 要么全是 Getter/Setter,要么业务逻辑散落各处 根据领域复杂度选择,保持职责边界一致

最后更新: 2026-10-23 01:34:52
创建日期: 2026-10-23 01:34:52

广告

人要恰饭的嘛🤑🤑

评论