软件设计原则(七大原则)

引言:为什么要遵循设计原则?

什么是软件设计原则?

是一组指导方针,旨在帮助我们避开不良的软件设计。这些原则由 Robert Martin 在《敏捷软件开发:原则、模式与实践》一书中系统整理。

不良设计的三个特征:

  • 僵化:难以改动,一处修改波及整个系统。
  • 脆弱:每次改动都会引发意想不到的问题。
  • 死板:代码难以在其他应用中重用。

设计原则的作用:

  • 为编写高质量代码提供准则。
  • 是设计模式的基础。
  • 各原则相互补充,共同指导我们构建高内聚、低耦合的系统。

七大设计原则详解

开放封闭原则(Open-Closed Principle, OCP)

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

核心思想

  • 对扩展开放:当需求变化时,可以通过添加新代码来扩展功能。
  • 对修改关闭:已有的稳定代码不应被修改。

实现方法:通过抽象类接口定义稳定架构,用实现类扩展细节。

案例:搜狗输入法皮肤系统

// 抽象皮肤
abstract class Skin {
    public abstract void display();
}

// 具体皮肤实现
class DefaultSkin extends Skin {
    @Override public void display() { System.out.println("默认皮肤"); }
}
class KeFengSkin extends Skin {
    @Override public void display() { System.out.println("克峰皮肤"); }
}

// 输入法主体(依赖抽象)
class SougouInput {
    private Skin skin;
    public void setSkin(Skin skin) { this.skin = skin; }
    public void display() { skin.display(); }
}

// 使用
public class Test {
    public static void main(String[] args) {
        SougouInput input = new SougouInput();
        input.setSkin(new DefaultSkin()); // 可随时替换为 KeFengSkin
        input.display();
    }
}

意义:提高复用性和可维护性,是最重要的设计原则。

单一职责原则(Single Responsibility Principle, SRP)

定义:一个类应该有且仅有一个引起它变化的原因。

通俗解释:一个类只负责一项职责。

适用范围:类、接口、方法。

优点

  • 降低类的复杂度。
  • 提高可读性、可维护性。
  • 降低变更风险。

案例:DAO 类拆分

❌ 违反原则:XXXDao 同时处理 User 和 Order 表的操作。

img

✅ 符合原则:拆分为 UserDaoOrderDao,各司其职。

img

注意:只有逻辑足够简单时,才可在代码级别暂时放宽该原则。

接口隔离原则(Interface Segregation Principle, ISP)

定义

  • 客户端不应被迫依赖它不使用的方法。
  • 类间的依赖应建立在最小的接口上。

核心:接口要尽量细化,避免臃肿。

优点

  • 提高系统灵活性和可维护性。
  • 提高内聚性,降低耦合。
  • 减少代码冗余。

案例:动物行为接口

❌ 臃肿接口:

//行为
interface behavior{
    void Eating(); //进食
    void cannon(); //跑
    void fly(); //飞行
}
//鸟类
class bird implements behavior{
    @Override
    public void Eating() {}
    @Override
    public void cannon() {}
    @Override
    public void fly() {}
}
//狗类
class dog implements behavior{
    @Override
    public void Eating() {}
    @Override
    public void cannon() {}
    @Override
    public void fly() {}
}

✅ 隔离后:

//进食
interface eat{
    void Eating(); //进食
}
//飞禽
interface Birds{
    void fly(); //飞
}
//犬科
interface Canidae{
    void cannon(); //跑
}

//鸟类
class bird implements eat,Birds{
    @Override
    public void Eating() {}
    @Override
    public void fly() {}
}
//狗类
class dog implements eat,Canidae{
    @Override
    public void Eating() {}
    @Override
    public void cannon() {}
}

与单一职责原则的区别

  • 相同点:都强调拆分,减少耦合。
  • 不同点:SRP 关注职责,ISP 关注接口设计

依赖倒置原则(Dependency Inversion Principle, DIP)

定义

  • 高层模块不应依赖低层模块,两者都应依赖抽象。
  • 抽象不应依赖细节,细节应依赖抽象。

核心理念:面向接口编程,而非实现编程。

三种依赖注入方式

  1. 接口传递

    interface IMessage { void sendMessage(Producer producer); }
    interface Producer { void produce(); }
    
  2. 构造方法传递

    class Worker implements IMessage {
        private Producer producer;
        public Worker(Producer producer) { this.producer = producer; }
    }
    
  3. Setter 方法传递

    class Worker implements IMessage {
        private Producer producer;
        public void setProducer(Producer producer) { this.producer = producer; }
    }
    

案例:消息收发系统

interface IMessage { void send(); }

class Worker {
    public void receive(IMessage msg) { msg.send(); } // 依赖接口
}

class DingDing implements IMessage {
    @Override public void send() { System.out.println("钉钉消息"); }
}
class WeChat implements IMessage {
    @Override public void send() { System.out.println("微信消息"); }
}

注意

  • 低层模块尽量有抽象类或接口。
  • 变量声明类型尽量使用抽象。
  • 继承时遵循里氏替换原则。

里氏替换原则(Liskov Substitution Principle, LSP)

定义:所有引用基类的地方必须能透明地使用其子类对象。

通俗理解:子类可以扩展父类功能,但不要修改父类原有功能。

继承的双刃剑

  • 优点:代码复用、扩展性。
  • 缺点:侵入性、降低灵活性、增加耦合。

案例:计算器功能扩展

// 基类
class Base {}

class Calculator extends Base {
    public int add(int a, int b) { return a + b; }
    public int sub(int a, int b) { return a - b; }
}

class SuperCalculator extends Base {
    private Calculator calculator = new Calculator(); // 组合替代继承
    
    public int mul(int a, int b) {
        int sum = calculator.add(a, b);
        return 100 - sum; // 新功能
    }
}

注意事项

  • 子类可实现父类的抽象方法,但不要覆盖非抽象方法。
  • 子类可添加新方法。
  • LSP 是开闭原则的实现手段之一。

迪米特法则(Law of Demeter, LoD)

定义:又称最少知识原则。一个对象应对其他对象有最少的了解,只与直接朋友通信。

什么是“直接朋友”?

  • 成员变量
  • 方法参数
  • 方法返回值

案例:学校打印员工信息

// 学院管理类
class CollegeManager {
    public List<CollegeEmployee> getAllEmployees() { ... }
    
    // 将打印职责内聚到自身
    public void printEmployees() {
        getAllEmployees().forEach(e -> System.out.println(e.getId()));
    }
}

// 总部管理类
class SchoolManager {
    public void printAllEmployees(CollegeManager cm) {
        // 只与 CollegeManager 直接通信,不接触 CollegeEmployee
        cm.printEmployees();
    }
}

核心:降低类间耦合,被依赖者尽量封装内部逻辑,依赖者只依赖该依赖的对象。

合成复用原则(Composite/Aggregate Reuse Principle, CARP)

定义:尽量使用组合/聚合,而不是继承来达到复用目的。

三种复用方式

  1. 继承(耦合度高)
  2. 组合(整体拥有部分,生命周期相同)
  3. 聚合(整体包含部分,生命周期独立)
  4. 依赖(方法参数形式)

案例:类 A 有两个方法,类 B 想复用

image-20220808113657192

七大原则总结

原则 核心思想 目的
开闭原则 对扩展开放,对修改关闭 提高扩展性,保证稳定
里氏替换 子类不修改父类原有功能 规范继承的使用
依赖倒置 面向接口编程 降低模块间耦合
单一职责 一个类只负责一项职责 提高内聚性
接口隔离 接口最小化,避免臃肿 降低实现类的负担
迪米特法则 只与直接朋友通信 降低类间耦合
合成复用 优先使用组合而非继承 提高灵活性,降低耦合

核心思想提炼

  • 封装变化:找出应用中可能变化的部分,独立出来。
  • 面向接口:针对接口编程,而非实现。
  • 松耦合:为交互对象之间的松散耦合而努力。

最终目标:构建高内聚、低耦合的软件系统,使其易于维护、扩展和复用。

posted @ 2022-10-08 10:58  克峰同学  阅读(87)  评论(0)    收藏  举报