Dart 中的 SOLID 原则:构建稳健的 Flutter 应用
Source: Dev.to
请提供您希望翻译的正文内容,我将为您翻译成简体中文并保留原有的格式、Markdown 语法以及技术术语。
Flutter 中的 SOLID 原则
在飞速发展的 Flutter 开发世界里,将业务逻辑和 UI 代码混在一起是很诱人的。随着应用的增长,这些“快速修复”会导致代码库僵硬,单一次修改可能会破坏无关的功能。
SOLID 原则 是五条设计指南,帮助你的 Dart 代码保持灵活、可测试且可扩展。下面是针对 Flutter 的每条原则的简要说明。
1. 单一职责原则(SRP)
“一个类应该只有一个且唯一的改变理由。”
在 Flutter 中,最常见的 SRP 违规是 “上帝 Widget” —— 一个 StatefulWidget 同时负责 API 调用、业务逻辑以及复杂的 UI 渲染。
解决方案 – 将逻辑拆分到 Controller 或 BLoC 中,保持 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 违例。
#### ❌ BAD – LSP 违例(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 可以同时实现 Switchable 和 Thermostatic。
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 组件是解耦且专一的,你可以构建一个能够随市场需求快速演进的应用。