一、为什么需要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应用开发中特别有用。比如:
- 状态管理:用依赖倒置原则设计Store接口,方便切换不同状态管理方案
- 组件设计:遵循单一职责原则,把大组件拆成小组件
- 插件开发:通过接口隔离原则设计插件系统
需要注意:
- 不要过度设计,简单场景可以直接写
- 原则是指导不是教条,必要时可以变通
- 在团队中要保持一致,避免各自为政
八、总结
SOLID原则就像编程界的交通规则:
- 单一职责是"各行其道"
- 开闭原则是"预留扩展"
- 里氏替换是"安全驾驶"
- 接口隔离是"专道专用"
- 依赖倒置是"标准接口"
刚开始可能需要刻意练习,但熟练后会发现代码自然变得更灵活、更易维护。就像老司机不用刻意想交规也能安全驾驶一样,这些原则最终会变成你的编程本能。
记住:好代码不是一次性写出来的,而是像搭积木一样逐步构建出来的。SOLID原则就是你手中的搭建指南。
评论