其他重要设计原则:从“知道”到“做到”

设计模式是招式,设计原则是心法。今天我们来聊聊那些容易被忽略,但实际项目中天天用到的原则。我个人习惯把这些原则称为“代码的卫生习惯”——不遵守不会立刻出问题,但时间长了代码就会“生病”。

迪米特法则(LoD):别和陌生人说话

迪米特法则,也叫最少知识原则。说白了就是:一个对象应该对其他对象有尽可能少的了解。

我在项目中遇到过这样的场景:一个订单类直接调用了用户类的地址类的城市字段。看起来没问题,但后来地址结构改了,订单类也得跟着改。这就是典型的“管得太宽”。

核心思想:只与你的直接朋友通信,不要跟“朋友的朋友”说话。

怎么判断是不是“直接朋友”?

  • 当前对象本身(this)
  • 作为参数传入的对象
  • 当前对象创建或实例化的对象
  • 当前对象的成员变量
// ❌ 违反迪米特法则
public class Order {
    public void process() {
        Address addr = customer.getAddress();
        String city = addr.getCity();  // 跟“朋友的朋友”说话了
    }
}

// ✅ 符合迪米特法则
public class Order {
    public void process() {
        String city = customer.getCity();  // 让朋友自己去做
    }
}

我的经验:写代码时多问自己一句“这个对象真的需要知道这些细节吗?”往往能避免很多后续的修改。

组合/聚合复用原则(CARP):少继承,多组合

这个原则我特别喜欢。它告诉我们:优先使用组合或聚合,而不是继承来达到复用目的。

为什么?继承是白箱复用,子类能看到父类的实现细节,耦合度高。组合是黑箱复用,通过接口交互,耦合度低。

// ❌ 继承导致的问题
public class Stack<E> extends Vector<E> {
    // 继承了Vector的所有方法,包括不该暴露的
}

// ✅ 组合更安全
public class Stack<E> {
    private Vector<E> vector = new Vector<>();
    
    public void push(E e) {
        vector.add(e);
    }
    
    public E pop() {
        // 只暴露需要的操作
    }
}

我曾经踩过的坑:用继承实现了一个“按钮”和“带图标的按钮”的关系。后来需求变成“按钮可以没有图标,但图标可以单独存在”。整个继承体系全乱了。改成组合后,问题迎刃而解。

好莱坞原则:别打电话,我们会打给你

这个原则的名字很有意思。它说的是:高层组件决定什么时候调用低层组件,而不是反过来。

你想想看,在框架设计中,框架是“导演”,你的代码是“演员”。导演说“开始”你才能演,你不能自己抢戏。这就是控制反转(IoC)的核心。

场景 违反好莱坞原则 符合好莱坞原则
事件处理 轮询检查事件 注册回调,由框架通知
依赖注入 自己new依赖对象 由容器注入依赖
模板方法 子类决定流程 父类定义骨架,子类填空

实际应用:Spring框架就是好莱坞原则的典型代表。你的Bean不需要主动去找依赖,容器会在合适的时机注入。

KISS原则:保持简单,保持愚蠢

KISS原则是我最常挂在嘴边的。不是开发者不够聪明,而是复杂的代码往往意味着更高的维护成本。

我记得有一次review代码,看到一个方法写了200行,里面嵌套了5层if-else。我问作者:“这个逻辑真的需要这么复杂吗?”他想了半天,最后用两个简单的策略模式就搞定了。

怎么判断代码是否“简单”?

  • 一个方法能不能在30秒内看懂?
  • 新人接手需要多久才能修改?
  • 测试用例好不好写?
// ❌ 过度设计
public class StringProcessor {
    private static final StringProcessor INSTANCE = 
        new StringProcessor();
    private final List<StringTransformer> transformers;
    // ... 工厂模式、策略模式、观察者模式全用上了
}

// ✅ KISS版本
public class StringProcessor {
    public static String trimAndLower(String input) {
        return input.trim().toLowerCase();
    }
}

注意:KISS不是让你不用设计模式。而是说,当简单方案够用时,别为了用模式而用模式。

YAGNI原则:你不会需要它的

YAGNI是“You Ain't Gonna Need It”的缩写。它告诉我们:只实现当前需要的功能,不要为未来可能的需求提前设计。

我见过太多项目死在“过度设计”上。团队花了两周时间设计一个“万能配置系统”,结果项目上线后只用到了最简单的key-value配置。那两周的时间,如果用来做核心业务,产品可能早就上线了。

核心问题:你怎么知道未来需要什么?大多数时候,你猜不准。

什么时候可以“提前设计”?

  • 这个扩展点几乎确定会用到(比如支付方式)
  • 现在不加,以后改造成本极高
  • 不影响当前功能的交付时间

DRY原则:别重复你自己

DRY原则可能是最广为人知,但也最容易被误解的原则。它不是说“代码不能重复”,而是说“知识不能重复”。

举个例子:业务规则“订单金额超过1000元打9折”出现在三个地方——订单计算、报表统计、优惠券发放。这就是知识重复。如果规则改成“满800打8折”,你得改三个地方,漏一个就出bug。

// ❌ 知识重复
public class OrderService {
    public double calculate(double amount) {
        if (amount > 1000) return amount * 0.9;
        return amount;
    }
}

public class ReportService {
    public double getDiscount(double amount) {
        if (amount > 1000) return amount * 0.9;  // 同样的规则
        return amount;
    }
}

// ✅ 知识集中
public class DiscountPolicy {
    public static double apply(double amount) {
        if (amount > 1000) return amount * 0.9;
        return amount;
    }
}

我的判断标准:如果同一个业务逻辑需要同步修改多处,那就是DRY没做好。如果只是代码长得像但含义不同,那重复也没关系。

原则之间的关系

这些原则不是孤立的。它们互相补充,有时候也会冲突。比如DRY和YAGNI就可能打架——为了DRY提前抽象,可能违反了YAGNI。

我个人习惯的做法是:先用KISS和YAGNI快速实现功能,等发现重复代码出现第三次时,再用DRY和CARP去重构。这样既不会过度设计,也不会让代码腐烂。

设计原则体系 迪米特法则 (LoD) 组合/聚合复用 (CARP) 好莱坞原则 KISS 原则 YAGNI 原则 DRY 原则 左侧:关注对象间关系与耦合度 右侧:关注代码质量与维护性 实际应用中需要根据场景权衡取舍

嗯,这些原则说起来简单,真正用好需要大量实践。我建议你从一个小项目开始,刻意练习每个原则。比如这个模块用迪米特法则重构一下,那个类用组合替代继承。慢慢你就会发现,这些原则已经变成了你的本能反应。

最后说一句:原则是指导,不是教条。理解背后的“为什么”,比死记硬背“是什么”重要得多。


公众号:蓝海资料掘金营,微信deep3321