一、为什么需要SOLID原则

想象你正在搭积木。如果随便堆叠,搭到第三层可能就倒了;但如果按照图纸和规则搭建,就能盖出摩天大楼。写代码也是同样的道理——SOLID原则就是Dart开发者的"搭积木指南"。

SOLID其实是五个设计原则的缩写:

  • S:单一职责原则
  • O:开闭原则
  • L:里氏替换原则
  • I:接口隔离原则
  • D:依赖倒置原则

这些原则能帮你写出像乐高积木一样灵活、可拼装的代码。下面我们用Dart语言,通过实际例子来看看它们怎么用。

二、单一职责原则:一个类只做一件事

这个原则最简单也最重要。就像厨房里的刀具——菜刀切菜、削皮刀去皮,如果强行用菜刀削土豆皮,效率低还容易伤手。

// 技术栈:Dart
// 不好的写法:用户类既处理数据又负责显示
class User {
  String name;
  int age;
  
  void saveToDatabase() {
    // 数据库操作代码...
  }
  
  void displayUserInfo() {
    print('$name, $age岁');
  }
}

// 好的写法:拆分成两个类
class User {
  String name;
  int age;
}

class UserRepository {
  void save(User user) {
    // 专精数据库操作
  }
}

class UserDisplay {
  void show(User user) {
    // 专精显示逻辑
  }
}

把不同职责拆分后,修改数据库逻辑时不会影响显示逻辑,测试也更容易。就像厨房分工,厨师专心炒菜,配菜员专门切菜,效率更高。

三、开闭原则:对扩展开放,对修改关闭

这个原则说的是:当需要添加新功能时,应该通过扩展而不是修改现有代码来实现。就像手机壳——要换样式不需要改手机本身,换个壳就行。

// 技术栈:Dart
// 不好的写法:每次新增形状都要修改AreaCalculator
class AreaCalculator {
  double calculate(dynamic shape) {
    if (shape is Square) {
      return shape.side * shape.side;
    } else if (shape is Circle) {
      return 3.14 * shape.radius * shape.radius;
    }
    // 每加一个新形状就要加一个if
  }
}

// 好的写法:使用抽象类
abstract class Shape {
  double area();
}

class Square implements Shape {
  final double side;
  Square(this.side);
  
  @override
  double area() => side * side;
}

class Circle implements Shape {
  final double radius;
  Circle(this.radius);
  
  @override
  double area() => 3.14 * radius * radius;
}

class AreaCalculator {
  double calculate(Shape shape) => shape.area();
  // 新增形状不需要修改这个类
}

现在要加三角形,只需新建Triangle类实现Shape接口,AreaCalculator完全不用动。就像乐高积木,新增零件不影响已有结构。

四、里氏替换原则:子类要能替换父类

这个原则要求子类不能破坏父类的行为约定。就像充电器——Type-C充电器应该能替换USB充电器,而不是插上去把手机烧了。

// 技术栈:Dart
// 违反原则的例子
class Bird {
  void fly() => print('飞行中');
}

class Penguin extends Bird {
  @override
  void fly() => throw Exception('企鹅不会飞!');
  // 子类破坏了父类行为
}

// 正确的写法
abstract class Bird {
  void move();
}

class Sparrow extends Bird {
  @override
  void move() => print('用翅膀飞');
}

class Penguin extends Bird {
  @override
  void move() => print('用脚走路');
}

现在所有鸟都有move方法,但实现方式不同。调用方可以安全地使用Bird类型,不用担心某些子类不能飞的问题。

五、接口隔离原则:客户端不应依赖不需要的接口

这个原则建议把大接口拆成小接口。就像多功能工具刀——虽然什么功能都有,但平时你只需要用其中一两个。

// 技术栈:Dart
// 不好的写法:臃肿的接口
abstract class Worker {
  void code();
  void test();
  void deploy();
  void cook(); // 程序员需要会做饭?
}

class Programmer implements Worker {
  @override void code() => print('coding...');
  
  @override
  void test() => print('testing...');
  
  @override
  void deploy() => throw UnimplementedError(); // 不需要
  
  @override
  void cook() => throw UnimplementedError(); // 不需要
}

// 好的写法:拆分接口
abstract class Coder {
  void code();
}

abstract class Tester {
  void test();
}

abstract class DevOps {
  void deploy();
}

class Programmer implements Coder, Tester {
  @override void code() => print('coding...');
  @override void test() => print('testing...');
}

现在每个类只需要实现自己真正需要的接口,不会被迫实现无用的方法。

六、依赖倒置原则:依赖抽象而不是具体实现

这个原则建议高层模块不应该依赖低层模块,二者都应该依赖抽象。就像用标准插座——电器不关心墙里是火电还是水电,只要电压符合标准就行。

// 技术栈:Dart
// 不好的写法:高层直接依赖低层
class LightBulb {
  void turnOn() => print('灯泡亮了');
}

class Switch {
  final LightBulb bulb;
  Switch(this.bulb);
  
  void operate() {
    bulb.turnOn();
  }
}
// 如果要换LED灯泡需要修改Switch类

// 好的写法:依赖抽象
abstract class Switchable {
  void turnOn();
}

class LightBulb implements Switchable {
  @override void turnOn() => print('灯泡亮了');
}

class LEDBulb implements Switchable {
  @override void turnOn() => print('LED亮了');
}

class Switch {
  final Switchable device;
  Switch(this.device);
  
  void operate() {
    device.turnOn();
  }
}

现在Switch不关心具体是什么设备,只要实现了Switchable接口都能用。新增设备类型时Switch完全不用修改。

七、实际应用场景与注意事项

把这些原则用在Flutter应用开发中特别有用。比如:

  1. 状态管理:用依赖倒置原则设计Store接口,方便切换不同状态管理方案
  2. 组件设计:遵循单一职责原则,把大组件拆成小组件
  3. 插件开发:通过接口隔离原则设计插件系统

需要注意:

  • 不要过度设计,简单场景可以直接写
  • 原则是指导不是教条,必要时可以变通
  • 在团队中要保持一致,避免各自为政

八、总结

SOLID原则就像编程界的交通规则:

  • 单一职责是"各行其道"
  • 开闭原则是"预留扩展"
  • 里氏替换是"安全驾驶"
  • 接口隔离是"专道专用"
  • 依赖倒置是"标准接口"

刚开始可能需要刻意练习,但熟练后会发现代码自然变得更灵活、更易维护。就像老司机不用刻意想交规也能安全驾驶一样,这些原则最终会变成你的编程本能。

记住:好代码不是一次性写出来的,而是像搭积木一样逐步构建出来的。SOLID原则就是你手中的搭建指南。