Flutter 項目架構(gòu)技術(shù)指南

視頻
https://www.bilibili.com/video/BV1rx4y127kN/
前言
原文 https://ducafecat.com/blog/flutter-clean-architecture-guide
探討Flutter項目代碼組織架構(gòu)的關(guān)鍵方面和建議。了解設(shè)計原則SOLID、Clean Architecture,以及架構(gòu)模式MVC、MVP、MVVM,如何有機結(jié)合使用,打造優(yōu)秀的應(yīng)用架構(gòu)。
參考
https://www.freecodecamp.org/news/solid-principles-explained-in-plain-english/
https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
https://developer.mozilla.org/en-US/docs/Glossary/MVC
https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter
https://zh.wikipedia.org/zh-hant/MVVM
SOLID 原則

SOLID(單一功能、開閉原則、里氏替換、接口隔離以及依賴反轉(zhuǎn))是由羅伯特·C·馬丁在21世紀早期引入,指代了面向?qū)ο缶幊毯兔嫦驅(qū)ο笤O(shè)計的五個基本原則。
在 Flutter 中遵循 SOLID 設(shè)計原則具有重要性,因為這些原則有助于提高代碼質(zhì)量、可維護性和可擴展性,同時降低代碼的復(fù)雜度和耦合度。
-
單一職責原則 (Single Responsibility Principle):
每個類應(yīng)該只有一個責任。在 Flutter 中,您可以將不同功能拆分為不同的小部件(widget),每個小部件負責特定的 UI 展示或交互邏輯。
// 單一職責原則示例:一個負責顯示用戶信息的小部件 class UserInfoWidget extends StatelessWidget { final User user; UserInfoWidget(this.user); @override Widget build(BuildContext context) { return Column( children: [ Text('Name: ${user.name}'), Text('Age: ${user.age}'), ], ); } } -
開閉原則 (Open/Closed Principle):
軟件實體應(yīng)該對擴展開放,對修改關(guān)閉。在 Flutter 中,您可以通過使用組合、繼承和多態(tài)來實現(xiàn)這一原則。例如,通過創(chuàng)建可重用的小部件并根據(jù)需要進行擴展,而不是直接修改現(xiàn)有代碼。
// 開閉原則示例:通過繼承實現(xiàn)可擴展的主題切換功能 abstract class Theme { ThemeData getThemeData(); } class LightTheme extends Theme { @override ThemeData getThemeData() { return ThemeData.light(); } } class DarkTheme extends Theme { @override ThemeData getThemeData() { return ThemeData.dark(); } } -
里氏替換原則 (Liskov Substitution Principle):
子類應(yīng)該能夠替換其父類并保持行為一致。在 Flutter 中,確保子類可以替換父類而不會引起意外行為是很重要的。繼承關(guān)系應(yīng)該是 is-a 的關(guān)系,而不是 has-a 的關(guān)系。
// 里氏替換原則示例:確保子類可以替換父類而不引起問題 abstract class Shape { double getArea(); } class Rectangle extends Shape { double width; double height; @override double getArea() { return width * height; } } class Square extends Shape { double side; @override double getArea() { return side * side; } } -
接口隔離原則 (Interface Segregation Principle):
客戶端不應(yīng)該被迫依賴它們不使用的接口。在 Flutter 中,您可以根據(jù)需要創(chuàng)建多個接口,以確保每個接口只包含客戶端所需的方法。
// 接口隔離原則示例:將接口細分為更小的接口 abstract class CanFly { void fly(); } abstract class CanSwim { void swim(); } class Bird implements CanFly { @override void fly() { print('Bird is flying'); } } class Fish implements CanSwim { @override void swim() { print('Fish is swimming'); } } -
依賴反轉(zhuǎn)原則 (Dependency Inversion Principle):
高層模塊不應(yīng)該依賴于低層模塊,二者都應(yīng)該依賴于抽象。在 Flutter 中,您可以通過依賴注入、接口抽象等方式實現(xiàn)依賴反轉(zhuǎn),以減少模塊之間的耦合度。
// 依賴反轉(zhuǎn)原則示例:通過依賴注入實現(xiàn)依賴反轉(zhuǎn) class UserRepository { Future<User> getUser() async { // Fetch user data from API } } class UserBloc { final UserRepository userRepository; UserBloc(this.userRepository); Future<void> fetchUser() async { User user = await userRepository.getUser(); // Process user data } }
Clean Architecture 原則

在 Flutter 開發(fā)中,Clean Architecture(CA)清晰架構(gòu)是一種軟件架構(gòu)設(shè)計模式,旨在將應(yīng)用程序分解為不同的層級,每一層級都有明確定義的職責,以實現(xiàn)代碼的可維護性、可測試性和可擴展性。Clean Architecture 通過明確定義各層之間的依賴關(guān)系,將業(yè)務(wù)邏輯與框架、庫和外部依賴分離開來,從而使代碼更加靈活和獨立。
示例中其中包括實體層、數(shù)據(jù)層、領(lǐng)域?qū)雍捅硎緦印?/p>
實體層(Entities):
// 實體類
class User {
final String id;
final String name;
User({required this.id, required this.name});
}
數(shù)據(jù)層(Data Layer):
// 數(shù)據(jù)接口
abstract class UserRepository {
Future<User> getUserById(String userId);
}
// 數(shù)據(jù)實現(xiàn)
class UserRepositoryImpl implements UserRepository {
@override
Future<User> getUserById(String userId) {
// 實現(xiàn)獲取用戶邏輯
}
}
領(lǐng)域?qū)樱―omain Layer):
// 用例類
class GetUserByIdUseCase {
final UserRepository userRepository;
GetUserByIdUseCase(this.userRepository);
Future<User> execute(String userId) {
return userRepository.getUserById(userId);
}
}
表示層(Presentation Layer):
// Flutter 頁面
class UserPage extends StatelessWidget {
final GetUserByIdUseCase getUserByIdUseCase;
UserPage(this.getUserByIdUseCase);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('User Page'),
),
body: Center(
child: FutureBuilder<User>(
future: getUserByIdUseCase.execute('1'),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('User: ${snapshot.data!.name}');
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
return CircularProgressIndicator();
},
),
),
);
}
}
架構(gòu)模式
軟件架構(gòu)模式,用于組織代碼、分離關(guān)注點以及提高代碼的可維護性和可測試性。常見模式有 Model-View-Controller(模型-視圖-控制器)、Model-View-Presenter(模型-視圖-展示器)和Model-View-ViewModel(模型-視圖-視圖模型)。
1. MVC(Model-View-Controller):

- 模型(Model):代表應(yīng)用程序的數(shù)據(jù)和業(yè)務(wù)邏輯。
- 視圖(View):負責展示數(shù)據(jù)給用戶以及接收用戶輸入。
- 控制器(Controller):處理用戶輸入、更新模型和視圖之間的關(guān)系。
在 MVC 中,視圖和控制器之間通過雙向通信進行交互,控制器負責更新模型和視圖。MVC 幫助將應(yīng)用程序分解為三個獨立的部分,以便更好地管理代碼邏輯。
Model:
class UserModel {
String id;
String name;
UserModel({required this.id, required this.name});
}
View:
class UserView extends StatelessWidget {
final UserModel user;
UserView(this.user);
@override
Widget build(BuildContext context) {
return Text('User: ${user.name}');
}
}
Controller:
class UserController {
UserModel user = UserModel(id: '1', name: 'John Doe');
}
IOS 就是典型的 MVC 模式,通過事件觸發(fā)控制器最后內(nèi)部機制更新視圖
2. MVP(Model-View-Presenter):

- 模型(Model):同樣代表應(yīng)用程序的數(shù)據(jù)和業(yè)務(wù)邏輯。
- 視圖(View):負責展示數(shù)據(jù)給用戶以及接收用戶輸入。
- 展示器(Presenter):類似于控制器,負責處理用戶輸入、更新模型和更新視圖。
在 MVP 中,視圖和展示器之間通過接口進行通信,展示器負責從模型獲取數(shù)據(jù)并更新視圖。MVP 將視圖和模型解耦,使得更容易進行單元測試和維護。
Model:
同上
View:
class UserView extends StatelessWidget {
final UserModel user;
final UserPresenter presenter;
UserView(this.user, this.presenter);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('User: ${user.name}'),
ElevatedButton(
onPressed: () {
presenter.updateUserName();
},
child: Text('Update Name'),
),
],
);
}
}
Presenter:
class UserPresenter {
UserModel user = UserModel(id: '1', name: 'John Doe');
UserView view;
UserPresenter(this.view);
void updateUserName() {
user.name = 'Jane Smith';
view.updateView(user);
}
}
Presenter 中有視圖方法來更新
3. MVVM(Model-View-ViewModel):

- 模型(Model):同樣代表應(yīng)用程序的數(shù)據(jù)和業(yè)務(wù)邏輯。
- 視圖(View):負責展示數(shù)據(jù)給用戶以及接收用戶輸入。
- 視圖模型(ViewModel):連接視圖和模型,負責處理視圖邏輯、數(shù)據(jù)綁定以及與模型的交互。
在 MVVM 中,視圖模型充當了視圖和模型之間的中介,負責處理大部分視圖邏輯,同時通過數(shù)據(jù)綁定將視圖與模型連接起來。MVVM 的目標是將視圖的狀態(tài)和行為與業(yè)務(wù)邏輯分離,以實現(xiàn)更好的可維護性和可測試性。
Model:
同上
View:
class UserView extends StatelessWidget {
final UserViewModel viewModel;
UserView(this.viewModel);
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('User: ${viewModel.user.name}'),
ElevatedButton(
onPressed: () {
viewModel.updateUserName();
},
child: Text('Update Name'),
),
],
);
}
}
ViewModel:
class UserViewModel {
UserModel user = UserModel(id: '1', name: 'John Doe');
void updateUserName() {
user.name = 'Jane Smith';
notifyListeners();
}
}
與 MVP 最大的區(qū)別是 MVVM 可以同時更新多個視圖
Packages 優(yōu)秀插件
freezed
https://pub-web.flutter-io.cn/packages/freezed
一個用于數(shù)據(jù)類 / 聯(lián)合體 / 模式匹配 / 克隆的代碼生成器。
詳見 <flutter freezed json 轉(zhuǎn) model 代碼生成> https://ducafecat.com/blog/flutter_application_freezed
get_it
https://pub-web.flutter-io.cn/packages/get_it
依賴管理工具包 懶加載、單例、依賴注入、作用域、注入管理... 。
詳見 <在 getx 中使用 get_it 管理依賴注入> https://ducafecat.com/blog/use-get_it-in-getx
Equatable
https://pub-web.flutter-io.cn/packages/equatable
equatable 可以幫助開發(fā)人員輕松地重寫類的 == 和 hashCode 方法,從而簡化對象之間的相等性比較。
equatable 可以與狀態(tài)管理、數(shù)據(jù)模型等方面結(jié)合使用,幫助開發(fā)人員更輕松地處理對象的相等性比較。
狀態(tài)管理
- Provider
- Bloc
- GetX
- Riverpod
詳見 <盤點主流 Flutter 狀態(tài)管理庫2024>https://ducafecat.com/blog/flutter-state-management-libraries-2024
小結(jié)
本文探討了Flutter項目代碼組織架構(gòu)的關(guān)鍵方面,包括設(shè)計原則SOLID、Clean Architecture,以及架構(gòu)模式MVC、MVP、MVVM的有機結(jié)合。通過本文的指導(dǎo)和建議,讀者可以更好地了解如何打造優(yōu)秀的Flutter應(yīng)用架構(gòu),提高代碼可維護性和擴展性。務(wù)必在實際項目中靈活運用這些架構(gòu)原則,為應(yīng)用的長期發(fā)展奠定堅實基礎(chǔ)。
感謝閱讀本文
如果有什么建議,請在評論中讓我知道。我很樂意改進。
? 貓哥
ducafecat.com
end