5代码大全(第2版)

软件构建中的设计

设计原则

设计抽象模块化

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第5章 软件构建中的设计

一、导读

1.1 本章学习目标

第5章讲解了软件设计在构建阶段的应用。McConnell 强调,设计不是一次性的前期活动,而是贯穿整个构建过程的持续活动。本章的核心学习目标包括:

  • 理解设计的层次:架构设计、详细设计和编码设计各自关注什么。
  • 掌握核心设计原则:模块化、信息隐藏、高内聚低耦合。
  • 学会在编码中应用设计原则:设计不是纸上谈兵,而是每天的编码实践。
  • 理解设计模式的价值:复用经过验证的设计方案。
  • 掌握设计改善的方法:重构是持续改善设计的关键技术。
  • 建立"设计是持续活动"的认知:设计不是一次性的,而是与编码并行的。

1.2 为什么本章如此重要

很多程序员认为"设计是架构师的事",自己只需要按照设计文档编码。这种想法是极其错误的。McConnell 指出:每个程序员每天都在做设计决策——类的划分、方法的组织、变量的命名、逻辑的安排,这些都是设计。

设计质量直接决定了代码的可维护性。糟糕的设计导致代码难以理解、难以修改、难以测试;优秀的设计让代码清晰、灵活、可扩展。

本章的重要性在于它将设计从"高大上"的概念拉回到日常编码实践中。你会理解设计不是遥不可及的理论,而是每天编码时都在做的事情。掌握设计原则,就是掌握了写出高质量代码的核心能力。

1.3 核心问题引导

为什么说"每个程序员都是设计师"?你在编码时做了哪些设计决策?

模块化的本质是什么?如何判断一个模块是否设计良好?

信息隐藏为什么如此重要?如何在实际编码中实现信息隐藏?

高内聚和低耦合如何同时实现?它们之间有什么权衡?

设计模式是"银弹"吗?如何避免过度使用设计模式?


二、核心概念详解

2.1 设计的层次

2.1.1 架构设计

架构设计关注系统的整体结构:

  • 组件划分:系统由哪些主要组件组成?
  • 组件关系:组件之间如何交互?
  • 技术选型:使用什么技术栈?
  • 关键决策:性能、安全、扩展性等关键问题的解决方案。
java
// 架构设计示例:电商系统
// 组件划分
public class ECommerceArchitecture {
  // 前端层
  private final WebFrontend webFrontend;
  private final MobileApp mobileApp;
  
  // API网关层
  private final APIGateway gateway;
  
  // 业务服务层
  private final OrderService orderService;
  private final ProductService productService;
  private final UserService userService;
  private final PaymentService paymentService;
  
  // 数据层
  private final OrderRepository orderRepo;
  private final ProductRepository productRepo;
  private final UserRepository userRepo;
  
  // 基础设施层
  private final MessageQueue messageQueue;
  private final CacheService cache;
}

2.1.2 详细设计

详细设计关注每个组件的内部结构:

  • 类设计:类的职责、接口、关系。
  • 数据结构:数据模型、存储结构。
  • 算法设计:关键算法的选择和设计。
  • 接口设计:API 的输入输出规格。
java
// 详细设计示例:订单服务
public class OrderServiceDesign {
  // 类设计
  // OrderService: 协调订单流程
  // OrderValidator: 验证订单数据
  // OrderPricer: 计算订单价格
  // OrderRepository: 持久化订单
  
  // 接口设计
  public interface OrderService {
    OrderResult createOrder(CreateOrderRequest request);
    OrderResult updateOrder(UpdateOrderRequest request);
    OrderResult cancelOrder(CancelOrderRequest request);
    Order getOrder(OrderId id);
  }
  
  // 数据结构设计
  public class Order {
    private final OrderId id;
    private final CustomerId customerId;
    private final List<OrderItem> items;
    private final Money totalAmount;
    private OrderStatus status;
    private final Address shippingAddress;
    private final LocalDateTime createdAt;
  }
}

2.1.3 编码设计

编码设计关注每个函数、每个语句的具体实现:

  • 函数设计:参数、返回值、内部逻辑。
  • 语句组织:条件判断、循环、异常处理。
  • 变量使用:命名、作用域、生命周期。
java
// 编码设计示例:计算订单折扣
public class OrderDiscountCalculator {
  
  // 函数设计:清晰的输入输出
  public DiscountResult calculateDiscount(Order order) {
    // 防御式编程
    Objects.requireNonNull(order);
    
    // 策略选择
    DiscountStrategy strategy = selectStrategy(order);
    
    // 计算折扣
    Money discount = strategy.apply(order);
    
    // 验证结果
    validateDiscount(discount, order.getTotalAmount());
    
    return new DiscountResult(discount, strategy.getName());
  }
  
  // 语句组织:清晰的逻辑流
  private DiscountStrategy selectStrategy(Order order) {
    if (order.hasCoupon()) {
      return new CouponStrategy(order.getCoupon());
    }
    if (order.getCustomer().isVip()) {
      return new VipStrategy();
    }
    if (order.getTotalAmount().exceeds(Money.of(500))) {
      return new ThresholdStrategy();
    }
    return new NoDiscountStrategy();
  }
}

2.2 核心设计原则

2.2.1 模块化

模块化的本质是将复杂系统分解为可管理的部分。每个模块应该:

  • 有明确的职责:每个模块只做一件事。
  • 有清晰的接口:模块之间通过定义良好的接口交互。
  • 有独立的实现:模块的内部实现对外部不可见。
java
// 良好的模块化
// 每个类有明确的单一职责

public class OrderProcessor {
  private final OrderValidator validator;
  private final PriceCalculator priceCalculator;
  private final InventoryManager inventoryManager;
  private final PaymentProcessor paymentProcessor;
  private final NotificationService notificationService;
  
  public OrderResult process(Order order) {
    // 每个步骤委托给专门的模块
    validator.validate(order);
    Money total = priceCalculator.calculate(order);
    inventoryManager.reserve(order.getItems());
    
    try {
      paymentProcessor.charge(order.getCustomer(), total);
      notificationService.sendConfirmation(order);
      return OrderResult.success(order.getId());
    } catch (PaymentException e) {
      inventoryManager.release(order.getItems());
      return OrderResult.paymentFailed(e.getMessage());
    }
  }
}

// 每个模块内部实现独立
public class PriceCalculator {
  private final DiscountEngine discountEngine;
  private final TaxCalculator taxCalculator;
  private final ShippingCalculator shippingCalculator;
  
  public Money calculate(Order order) {
    Money subtotal = calculateSubtotal(order);
    Money discount = discountEngine.apply(order);
    Money tax = taxCalculator.calculate(subtotal.minus(discount));
    Money shipping = shippingCalculator.calculate(order);
    return subtotal.minus(discount).plus(tax).plus(shipping);
  }
}

2.2.2 信息隐藏

信息隐藏(Information Hiding)是 David Parnas 提出的核心设计原则。它的核心思想是:每个模块应该隐藏其内部实现细节,只暴露必要的接口。

信息隐藏的好处:

  • 降低耦合:模块之间不依赖内部实现。
  • 提高可维护性:修改内部实现不影响其他模块。
  • 支持并行开发:不同团队可以独立开发不同模块。
java
// 信息隐藏的示例

// 差:暴露内部实现
public class BadOrder {
  // 直接暴露内部数据结构
  public List<OrderItem> items;
  public double totalAmount;
  public String status;
}

// 好:隐藏内部实现
public class GoodOrder {
  // 内部数据不可直接访问
  private final List<OrderItem> items;
  private Money totalAmount;
  private OrderStatus status;
  
  // 通过方法暴露行为,而非数据
  public Money getTotalAmount() {
    return totalAmount; // 返回不可变对象
  }
  
  public boolean isCancellable() {
    return status == OrderStatus.CREATED 
        || status == OrderStatus.CONFIRMED;
  }
  
  public void confirm() {
    if (!isCancellable()) {
      throw new IllegalStateException("Cannot confirm");
    }
    this.status = OrderStatus.CONFIRMED;
  }
  
  // 内部实现可以随意修改,不影响外部
  private void recalculateTotal() {
    this.totalAmount = items.stream()
        .map(item -> item.getPrice().multiply(item.getQuantity()))
        .reduce(Money.ZERO, Money::add);
  }
}

2.2.3 高内聚

高内聚(High Cohesion)意味着模块内部的元素紧密相关。内聚性有多种类型:

  • 功能内聚(最好):模块内所有元素共同完成一个功能。
  • 顺序内聚:一个元素的输出是另一个元素的输入。
  • 通信内聚:元素操作相同的数据。
  • 时间内聚(较差):元素在同一时间段执行。
  • 逻辑内聚(差):元素在逻辑上相似。
  • 巧合内聚(最差):元素之间没有关系。
java
// 功能内聚:所有方法围绕"订单处理"这一功能
public class OrderProcessor {
  public OrderResult process(Order order) { ... }
  private void validate(Order order) { ... }
  private Money calculateTotal(Order order) { ... }
  private void reserveInventory(Order order) { ... }
  private void chargePayment(Order order, Money amount) { ... }
  private void sendConfirmation(Order order) { ... }
}

// 巧合内聚(差):方法之间没有关系
public class UtilityClass {
  public void processOrder() { ... }
  public void formatReport() { ... }
  public void sendEmail() { ... }
  public void backupDatabase() { ... }
}

2.2.4 低耦合

低耦合(Low Coupling)意味着模块之间的依赖关系尽量少。耦合的类型:

  • 无直接耦合(最好):模块之间没有直接关系。
  • 数据耦合:通过参数传递数据。
  • 印记耦合:传递复杂数据结构的一部分。
  • 控制耦合:传递控制信息(如标志位)。
  • 公共耦合:共享全局数据。
  • 内容耦合(最差):直接访问另一个模块的内部数据。
java
// 数据耦合(好):通过参数传递简单数据
public class GoodDesign {
  public Money calculateTax(Money amount, TaxRate rate) {
    return amount.multiply(rate.getValue());
  }
}

// 内容耦合(差):直接访问内部数据
public class BadDesign {
  public void processOrder(Order order) {
    // 直接修改 order 的内部状态
    order.internalData.status = "PROCESSED";
    order.internalData.processedDate = new Date();
  }
}

// 使用接口降低耦合
public interface PaymentGateway {
  PaymentResult charge(CustomerId customer, Money amount);
}

// 依赖接口而非具体实现
public class PaymentService {
  private final PaymentGateway gateway; // 依赖抽象
  
  public PaymentService(PaymentGateway gateway) {
    this.gateway = gateway;
  }
}

2.3 设计模式的应用

2.3.1 何时使用设计模式

McConnell 强调,设计模式不是"银弹",不应该为了使用而使用。使用设计模式的正确时机是:

  • 问题匹配:当前问题确实符合模式的适用场景。
  • 复杂度合理:模式引入的复杂度在可接受范围内。
  • 团队理解:团队成员理解模式的含义和代价。
java
// 策略模式:当有多种算法可以互换时使用
public interface PricingStrategy {
  Money calculatePrice(Order order);
}

public class StandardPricing implements PricingStrategy {
  @Override
  public Money calculatePrice(Order order) {
    return order.getSubtotal();
  }
}

public class WholesalePricing implements PricingStrategy {
  @Override
  public Money calculatePrice(Order order) {
    return order.getSubtotal().multiply(0.85);
  }
}

// 观察者模式:当需要通知多个对象时使用
public interface OrderEventListener {
  void onOrderCreated(Order order);
  void onOrderCancelled(Order order);
}

public class OrderService {
  private final List<OrderEventListener> listeners = new ArrayList<>();
  
  public void addListener(OrderEventListener listener) {
    listeners.add(listener);
  }
  
  public OrderResult createOrder(CreateOrderRequest request) {
    Order order = buildOrder(request);
    // 通知所有监听者
    listeners.forEach(l -> l.onOrderCreated(order));
    return OrderResult.success(order.getId());
  }
}

2.3.2 避免过度设计

过度设计比不设计更糟糕。McConnell 建议:

  • YAGNI(You Aren't Gonna Need It):不要为假想的未来需求做设计。
  • KISS(Keep It Simple, Stupid):保持简单。
  • 三次法则:第一次直接实现,第二次复制,第三次再抽象。
java
// 过度设计:为假想的未来需求做准备
public class OverDesignedPaymentService {
  // 假设未来可能需要支持100种支付方式
  private final Map<String, PaymentStrategy> strategies;
  private final PaymentStrategyFactory factory;
  private final PaymentStrategyRegistry registry;
  private final PaymentStrategyValidator validator;
  // ...大量抽象层,但当前只需要支持信用卡
  
  public OverDesignedPaymentService() {
    // 复杂的初始化逻辑
  }
}

// 简单设计:只解决当前问题
public class SimplePaymentService {
  
  public PaymentResult chargeCreditCard(CreditCard card, Money amount) {
    // 直接实现,清晰简洁
    PaymentGateway gateway = new StripeGateway();
    return gateway.charge(card, amount);
  }
}

三、实战案例

3.1 案例一:从混乱到清晰的设计改善

java
// 改善前:混乱的设计
public class GodClass {
  // 一个类做了所有事
  public void processOrder(Order order) {
    // 验证
    if (order.getItems().isEmpty()) throw new RuntimeException();
    if (order.getCustomer() == null) throw new RuntimeException();
    
    // 计算价格
    double total = 0;
    for (Item item : order.getItems()) {
      total += item.getPrice() * item.getQuantity();
    }
    if (order.hasCoupon()) total *= 0.9;
    total *= 1.08; // 税
    
    // 保存
    database.save(order);
    
    // 发邮件
    Email email = new Email();
    email.setTo(order.getCustomer().getEmail());
    email.setSubject("Order Confirmation");
    email.send();
  }
}

// 改善后:清晰的设计
public class OrderProcessor {
  private final OrderValidator validator;
  private final PriceCalculator priceCalculator;
  private final OrderRepository repository;
  private final NotificationService notifier;
  
  public OrderProcessor(OrderValidator validator,
                        PriceCalculator priceCalculator,
                        OrderRepository repository,
                        NotificationService notifier) {
    this.validator = validator;
    this.priceCalculator = priceCalculator;
    this.repository = repository;
    this.notifier = notifier;
  }
  
  public OrderResult process(Order order) {
    validator.validate(order);
    Money total = priceCalculator.calculate(order);
    order.setTotal(total);
    repository.save(order);
    notifier.sendConfirmation(order);
    return OrderResult.success(order.getId());
  }
}

// 每个模块职责清晰
public class OrderValidator {
  public void validate(Order order) {
    if (order.getItems().isEmpty()) {
      throw new OrderValidationException("Order must have items");
    }
    if (order.getCustomer() == null) {
      throw new OrderValidationException("Order must have customer");
    }
  }
}

public class PriceCalculator {
  private static final TaxRate DEFAULT_TAX = TaxRate.of(0.08);
  
  public Money calculate(Order order) {
    Money subtotal = calculateSubtotal(order);
    Money discount = applyDiscount(order, subtotal);
    Money tax = calculateTax(subtotal.minus(discount));
    return subtotal.minus(discount).plus(tax);
  }
  
  private Money calculateSubtotal(Order order) {
    return order.getItems().stream()
        .map(item -> item.getPrice().multiply(item.getQuantity()))
        .reduce(Money.ZERO, Money::add);
  }
  
  private Money applyDiscount(Order order, Money subtotal) {
    if (order.hasCoupon()) {
      return subtotal.multiply(0.1);
    }
    return Money.ZERO;
  }
  
  private Money calculateTax(Money amount) {
    return amount.multiply(DEFAULT_TAX);
  }
}

四、本章小结

第5章讲解了软件设计在构建阶段的应用,核心要点包括:

设计有三个层次:架构设计、详细设计和编码设计,每个层次都很重要。

模块化是基础:将复杂系统分解为可管理的部分。

信息隐藏是关键:隐藏实现细节,暴露稳定接口。

高内聚低耦合是目标:让模块专注单一职责,减少模块间依赖。

设计模式是工具:在合适的时候使用,不要过度设计。

设计是持续活动:与编码并行,通过重构持续改善。

McConnell 的核心观点是:设计不是架构师的专利,而是每个程序员的日常实践。掌握设计原则,就是掌握了写出高质量代码的核心能力。


五、思考与练习

思考题

在你编写的代码中,哪些类/模块设计得好?哪些需要改善?为什么?

信息隐藏在实际项目中遇到了哪些挑战?如何克服?

你见过哪些过度设计的案例?如何避免?

设计模式在你的项目中是如何使用的?有没有滥用?

编程练习

找到一段"上帝类"代码,将其拆分为多个高内聚的类。

为一个"图书馆管理系统"设计类结构,体现模块化和信息隐藏原则。

使用策略模式重构一个包含大量 if-else 的代码。

5/12软件构建
0%