Dart 中的 SOLID 原则:构建稳健的 Flutter 应用

发布: (2026年2月9日 GMT+8 01:00)
10 分钟阅读
原文: Dev.to

Source: Dev.to

请提供您希望翻译的正文内容,我将为您翻译成简体中文并保留原有的格式、Markdown 语法以及技术术语。

Flutter 中的 SOLID 原则

在飞速发展的 Flutter 开发世界里,将业务逻辑和 UI 代码混在一起是很诱人的。随着应用的增长,这些“快速修复”会导致代码库僵硬,单一次修改可能会破坏无关的功能。

SOLID 原则 是五条设计指南,帮助你的 Dart 代码保持灵活、可测试且可扩展。下面是针对 Flutter 的每条原则的简要说明。

1. 单一职责原则(SRP)

“一个类应该只有一个且唯一的改变理由。”

在 Flutter 中,最常见的 SRP 违规是 “上帝 Widget” —— 一个 StatefulWidget 同时负责 API 调用、业务逻辑以及复杂的 UI 渲染。

解决方案 – 将逻辑拆分到 ControllerBLoC 中,保持 UI 只在 StatelessWidget 中。

结果 – 你可以在不渲染任何像素的情况下测试业务逻辑。

❌ BAD – SRP 违规

class User {
  String name;
  String email;

  User(this.name, this.email);

  // Responsibility 1: User data validation
  bool isValidEmail() {
    return email.contains('@') && email.contains('.');
  }

  // Responsibility 2: Database operations
  void saveToDatabase() {
    print('Saving user to database...');
    // Database logic here
  }

  // Responsibility 3: Email notifications
  void sendWelcomeEmail() {
    print('Sending welcome email to $email...');
    // Email sending logic here
  }

  // Responsibility 4: Logging
  void logUserActivity(String activity) {
    print('[$name] $activity');
    // Logging logic here
  }
}

✅ GOOD – SRP 应用

// Responsibility: Hold user data
class User {
  final String name;
  final String email;

  User(this.name, this.email);
}

// Responsibility: Validate user data
class UserValidator {
  bool isValidEmail(String email) =>
      email.contains('@') && email.contains('.');

  bool isValidName(String name) => name.isNotEmpty && name.length >= 2;
}

// Responsibility: Handle database operations for users
class UserRepository {
  void save(User user) {
    print('Saving user ${user.name} to database...');
    // Database logic here
  }

  User? findByEmail(String email) {
    print('Finding user by email: $email');
    // Database query logic here
    return null;
  }
}

// Responsibility: Send email notifications
class EmailService {
  void sendWelcomeEmail(User user) {
    print('Sending welcome email to ${user.email}...');
    // Email sending logic here
  }

  void sendPasswordResetEmail(User user) {
    print('Sending password reset email to ${user.email}...');
    // Email sending logic here
  }
}

// Responsibility: Log application activities
class Logger {
  void logUserActivity(String userName, String activity) {
    final timestamp = DateTime.now();
    print('[$timestamp] [$userName] $activity');
    // Logging logic here
  }

  void logError(String error) {
    print('[ERROR] $error');
    // Error logging logic here
  }
}

2. 开放/封闭原则(OCP)

“软件实体应该对扩展开放,但对修改封闭。”

如果你需要添加一种新的支付方式,就不应该去修改已有的 PaymentProcessor 类。

解决方案 – 使用 抽象类(接口)来定义契约,让具体实现去继承它们。

❌ BAD – OCP 违规

class PaymentProcessor {
  void processPayment(String paymentType, double amount) {
    if (paymentType == 'creditCard') {
      print('Processing credit card payment of \$$amount');
      // Credit card processing logic
    } else if (paymentType == 'paypal') {
      print('Processing PayPal payment of \$$amount');
      // PayPal processing logic
    } else if (paymentType == 'stripe') {
      print('Processing Stripe payment of \$$amount');
      // Stripe processing logic
    }
    // Every time we add a new payment method, we need to modify this class!
  }
}

✅ GOOD – OCP 应用

// Abstract base class – CLOSED for modification
abstrac

### 3. 里氏替换原则(LSP

> *“超类的对象应该能够被其子类的对象替换,而不会导致程序出错。”*

Dart 中,如果 `Square` 继承自 `Rectangle`,但设置 `Square` 的宽度时会同时改变其高度,那么子类就不再表现得像基类——这就是典型的 LSP 违例。

#### ❌ BADLSP 违例(Square / Rectangle

```dart
class Rectangle {
  double width;
  double height;

  Rectangle(this.width, this.height);

  void setWidth(double w) => width = w;
  void setHeight(double h) => height = h;

  double area() => width * height;
}

// Square inherits from Rectangle but forces width == height
class Square extends Rectangle {
  Square(double side) : super(side, side);

  @override
  void setWidth(double w) {
    width = w;
    height = w; // Enforces square shape – breaks LSP
  }

  @override
  void setHeight(double h) {
    width = h;
    height = h; // Enforces square shape – breaks LSP
  }
}

✅ GOOD – 符合 LSP 的设计

// Define a shape contract that does not assume mutable dimensions
abstract class Shape {
  double area();
}

// Rectangle implements Shape
class Rectangle implements Shape {
  final double width;
  final double height;

  Rectangle(this.width, this.height);

  @override
  double area() => width * height;
}

// Square implements Shape independently (no inheritance from Rectangle)
class Square implements Shape {
  final double side;

  Square(this.side);

  @override
  double area() => side * side;
}

// Client code can use any Shape without caring about its concrete type
void printArea(Shape shape) {
  print('Area: ${shape.area()}');
}

继续对剩余的 SOLID 原则(接口分离原则 & 依赖倒置原则)采用同样的清理方式,以保持你的 Flutter 代码库健壮且易于维护。

Source:

Dart 中的 SOLID 原则

以下是简明示例,展示了三条 SOLID 原则——里氏替换原则 (LSP)、接口分离原则 (ISP) 和依赖倒置原则 (DIP)——的错误实现和正确实现。代码保持 Dart 语言,原始内容结构不变。

1. 里氏替换原则 (LSP)

“子类的对象应能够替换父类的对象,而不会影响程序的正确性。”

❌ 错误 – LSP 违规

class BadQuadrilateral {
  double _width;
  double _height;

  BadQuadrilateral(this._width, this._height);

  double get width => _width;
  double get height => _height;

  set width(double value) => _width = value;
  set height(double value) => _height = value;

  double get area => _width * _height;
}

// VIOLATION: Square changes the behavior of BadQuadrilateral
class BadSquare extends BadQuadrilateral {
  BadSquare(double side) : super(side, side);

  // VIOLATION: Setting width also changes height!
  @override
  set width(double value) {
    _width = value;
    _height = value; // side‑effect that violates LSP
  }

  // VIOLATION: Setting height also changes width!
  @override
  set height(double value) {
    _width = value;   // side‑effect that violates LSP
    _height = value;
  }
}

// This breaks when we substitute Square for BadQuadrilateral
void demonstrateBadLSP() {
  BadQuadrilateral rect = BadQuadrilateral(5, 10);
  print('Rectangle: ${rect.width} x ${rect.height} = ${rect.area}'); // 5 x 10 = 50

  rect.width = 20;
  print('After width change: ${rect.width} x ${rect.height} = ${rect.area}'); // 20 x 10 = 200

  // Now substitute with Square – BREAKS!
  BadQuadrilateral square = BadSquare(5);
  print('\nSquare: ${square.width} x ${square.height} = ${square.area}'); // 5 x 5 = 25

  square.width = 20;
  // Expected: 20 x 5 = 100 (if it truly behaved like BadQuadrilateral)
  // Actual:   20 x 20 = 400 (BROKEN!)
  print('After width change: ${square.width} x ${square.height} = ${square.area}');
}

✅ 正确 – 符合 LSP 的设计

abstract class Shape {
  double get area;
  String get description;
}

class GoodRectangle implements Shape {
  final double width;
  final double height;

  GoodRectangle(this.width, this.height);

  @override
  double get area => width * height;

  @override
  String get description => 'Rectangle: $width x $height';
}

class GoodSquare implements Shape {
  final double side;

  GoodSquare(this.side);

  @override
  double get area => side * side;

  @override
  String get description => 'Square: $side x $side';
}

void demonstrateGoodLSP() {
  void printShapeInfo(Shape shape) {
    print('${shape.description} = Area: ${shape.area}');
  }

  // Both work perfectly when treated as Shape
  Shape rect = GoodRectangle(5, 10);
  Shape square = GoodSquare(5);

  printShapeInfo(rect);
  printShapeInfo(square);
}

2. 接口分离原则 (ISP)

“客户端不应被迫依赖它们不使用的接口。”

❌ 错误 – 臃肿接口

abstract class SmartHomeDevice {
  void turnOn();
  void turnOff();
  void setTemperature(double temp); // Not all devices need this
}

✅ 正确 – 分离的接口

abstract class Switchable {
  void turnOn();
  void turnOff();
}

abstract class Thermostatic {
  void setTemperature(double temp);
}

现在 LightBulb 只需要实现 Switchable,而 Thermostat 可以同时实现 SwitchableThermostatic

3. 依赖倒置原则 (DIP)

“应依赖抽象,而不是具体实现。”

❌ 错误 – 直接依赖具体类

class FirebaseDatabaseService {
  Future<List<String>> fetchUsers() async {
    print('Firebase: Fetching users');
    await Future.delayed(const Duration(seconds: 1));
    return ['User1', 'User2', 'User3'];
  }

  Future<void> saveUser(String nam

✅ 良好 – 依赖抽象

// Abstractions (interfaces)
abstract class DatabaseService {
  Future<List<String>> fetchUsers();
  Future<void> saveUser(String name);
  Future<void> deleteUser(String name);
}

// Concrete implementation #1
class FirebaseDatabase implements DatabaseService {
  @override
  Future<List<String>> fetchUsers() async {
    print('Firebase: Fetching users');
    await Future.delayed(const Duration(seconds: 1));
    return ['Alice', 'Bob', 'Charlie'];
  }

  @override
  Future<void> saveUser(String name) async {
    print('Firebase: Saving user $name');
    await Future.delayed(const Duration(milliseconds: 500));
  }

  @override
  Future<void> deleteUser(String name) async {
    print('Firebase: Deleting user $name');
    await Future.delayed(const Duration(milliseconds: 500));
  }
}

// Concrete implementation #2 (easy to swap!)
class LocalDatabase implements DatabaseService {
  final List<String> _users = ['User1', 'User2'];

  @override
  Future<List<String>> fetchUsers() async {
    print('Local: Fetching users');
    return List.from(_users);
  }

  @override
  Future<void> saveUser(String name) async {
    print('Local: Saving user $name');
    _users.add(name);
  }

  @override
  Future<void> deleteUser(String name) async {
    print('Local: Deleting user $name');
    _users.remove(name);
  }
}

// GOOD: ViewModel depends on the abstraction, not the concrete class
class GoodUserViewModel {
  final DatabaseService _dbService;

  // Dependency injection (constructor injection shown here)
  GoodUserViewModel(this._dbService);

  Future<List<String>> getUsers() async => await _dbService.fetchUsers();

  Future<void> addUser(String name) async => await _dbService.saveUser(name);

  Future<void> removeUser(String name) async => await _dbService.deleteUser(name);
}
// BAD: ViewModel directly depends on a concrete implementation
class UserViewModel {
  final DatabaseService _databaseService = DatabaseService();

  Future<List<String>> getUsers() async {
    return await _databaseService.fetchUsers();
  }

  Future<void> addUser(String name) async {
    await _databaseService.saveUser(name);
  }

  void removeUser(String name) {
    _users.remove(name);
  }
}

// GOOD: ViewModels depend on abstractions
class UserViewModel {
  final DatabaseService _databaseService;

  UserViewModel(this._databaseService); // Dependency injection

  Future<List<String>> getUsers() async {
    return await _databaseService.fetchUsers();
  }

  Future<void> addUser(String name) async {
    await _databaseService.saveUser(name);
  }
}

结论

在 Dart 中应用 SOLID 并不是遵循教条式规则,而是为了降低变更成本。通过确保你的 Flutter 组件是解耦且专一的,你可以构建一个能够随市场需求快速演进的应用。

0 浏览
Back to Blog

相关文章

阅读更多 »