Flutter 項(xiàng)目架構(gòu)技術(shù)指南
視頻
https://www.bilibili.com/video/BV1rx4y127kN/
前言
原文 https://ducafecat.com/blog/flutter-clean-architecture-guide
探討Flutter項(xiàng)目代碼組織架構(gòu)的關(guān)鍵方面和建議圾旨。了解設(shè)計(jì)原則SOLID、Clean Architecture是整,以及架構(gòu)模式MVC、MVP、MVVM签赃,如何有機(jī)結(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(單一功能孵运、開(kāi)閉原則、里氏替換、接口隔離以及依賴(lài)反轉(zhuǎn))是由羅伯特·C·馬丁在21世紀(jì)早期引入怕品,指代了面向?qū)ο缶幊毯兔嫦驅(qū)ο笤O(shè)計(jì)的五個(gè)基本原則。
在 Flutter 中遵循 SOLID 設(shè)計(jì)原則具有重要性巾遭,因?yàn)檫@些原則有助于提高代碼質(zhì)量肉康、可維護(hù)性和可擴(kuò)展性,同時(shí)降低代碼的復(fù)雜度和耦合度灼舍。
-
單一職責(zé)原則 (Single Responsibility Principle):
每個(gè)類(lèi)應(yīng)該只有一個(gè)責(zé)任吼和。在 Flutter 中,您可以將不同功能拆分為不同的小部件(widget)骑素,每個(gè)小部件負(fù)責(zé)特定的 UI 展示或交互邏輯炫乓。
// 單一職責(zé)原則示例:一個(gè)負(fù)責(zé)顯示用戶(hù)信息的小部件 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}'), ], ); } }
-
開(kāi)閉原則 (Open/Closed Principle):
軟件實(shí)體應(yīng)該對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉献丑。在 Flutter 中末捣,您可以通過(guò)使用組合、繼承和多態(tài)來(lái)實(shí)現(xiàn)這一原則创橄。例如箩做,通過(guò)創(chuàng)建可重用的小部件并根據(jù)需要進(jìn)行擴(kuò)展,而不是直接修改現(xiàn)有代碼妥畏。
// 開(kāi)閉原則示例:通過(guò)繼承實(shí)現(xiàn)可擴(kuò)展的主題切換功能 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):
子類(lèi)應(yīng)該能夠替換其父類(lèi)并保持行為一致邦邦。在 Flutter 中,確保子類(lèi)可以替換父類(lèi)而不會(huì)引起意外行為是很重要的咖熟。繼承關(guān)系應(yīng)該是 is-a 的關(guān)系圃酵,而不是 has-a 的關(guān)系。
// 里氏替換原則示例:確保子類(lèi)可以替換父類(lèi)而不引起問(wè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):
客戶(hù)端不應(yīng)該被迫依賴(lài)它們不使用的接口馍管。在 Flutter 中郭赐,您可以根據(jù)需要?jiǎng)?chuàng)建多個(gè)接口,以確保每個(gè)接口只包含客戶(hù)端所需的方法。
// 接口隔離原則示例:將接口細(xì)分為更小的接口 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'); } }
-
依賴(lài)反轉(zhuǎn)原則 (Dependency Inversion Principle):
高層模塊不應(yīng)該依賴(lài)于低層模塊捌锭,二者都應(yīng)該依賴(lài)于抽象俘陷。在 Flutter 中,您可以通過(guò)依賴(lài)注入观谦、接口抽象等方式實(shí)現(xiàn)依賴(lài)反轉(zhuǎn)拉盾,以減少模塊之間的耦合度。
// 依賴(lài)反轉(zhuǎn)原則示例:通過(guò)依賴(lài)注入實(shí)現(xiàn)依賴(lài)反轉(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 開(kāi)發(fā)中豁状,Clean Architecture(CA)清晰架構(gòu)是一種軟件架構(gòu)設(shè)計(jì)模式捉偏,旨在將應(yīng)用程序分解為不同的層級(jí),每一層級(jí)都有明確定義的職責(zé)泻红,以實(shí)現(xiàn)代碼的可維護(hù)性夭禽、可測(cè)試性和可擴(kuò)展性。Clean Architecture 通過(guò)明確定義各層之間的依賴(lài)關(guān)系谊路,將業(yè)務(wù)邏輯與框架讹躯、庫(kù)和外部依賴(lài)分離開(kāi)來(lái),從而使代碼更加靈活和獨(dú)立缠劝。
示例中其中包括實(shí)體層潮梯、數(shù)據(jù)層、領(lǐng)域?qū)雍捅硎緦印?/p>
實(shí)體層(Entities):
// 實(shí)體類(lèi)
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ù)實(shí)現(xiàn)
class UserRepositoryImpl implements UserRepository {
@override
Future<User> getUserById(String userId) {
// 實(shí)現(xiàn)獲取用戶(hù)邏輯
}
}
領(lǐng)域?qū)樱―omain Layer):
// 用例類(lèi)
class GetUserByIdUseCase {
final UserRepository userRepository;
GetUserByIdUseCase(this.userRepository);
Future<User> execute(String userId) {
return userRepository.getUserById(userId);
}
}
表示層(Presentation Layer):
// Flutter 頁(yè)面
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)注點(diǎn)以及提高代碼的可維護(hù)性和可測(cè)試性。常見(jiàn)模式有 Model-View-Controller(模型-視圖-控制器)喉恋、Model-View-Presenter(模型-視圖-展示器)和Model-View-ViewModel(模型-視圖-視圖模型)沃饶。
1. MVC(Model-View-Controller):
- 模型(Model):代表應(yīng)用程序的數(shù)據(jù)和業(yè)務(wù)邏輯母廷。
- 視圖(View):負(fù)責(zé)展示數(shù)據(jù)給用戶(hù)以及接收用戶(hù)輸入轻黑。
- 控制器(Controller):處理用戶(hù)輸入、更新模型和視圖之間的關(guān)系琴昆。
在 MVC 中氓鄙,視圖和控制器之間通過(guò)雙向通信進(jìn)行交互,控制器負(fù)責(zé)更新模型和視圖业舍。MVC 幫助將應(yīng)用程序分解為三個(gè)獨(dú)立的部分抖拦,以便更好地管理代碼邏輯。
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 模式舷暮,通過(guò)事件觸發(fā)控制器最后內(nèi)部機(jī)制更新視圖
2. MVP(Model-View-Presenter):
- 模型(Model):同樣代表應(yīng)用程序的數(shù)據(jù)和業(yè)務(wù)邏輯态罪。
- 視圖(View):負(fù)責(zé)展示數(shù)據(jù)給用戶(hù)以及接收用戶(hù)輸入。
- 展示器(Presenter):類(lèi)似于控制器下面,負(fù)責(zé)處理用戶(hù)輸入复颈、更新模型和更新視圖。
在 MVP 中沥割,視圖和展示器之間通過(guò)接口進(jìn)行通信耗啦,展示器負(fù)責(zé)從模型獲取數(shù)據(jù)并更新視圖凿菩。MVP 將視圖和模型解耦,使得更容易進(jìn)行單元測(cè)試和維護(hù)帜讲。
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 中有視圖方法來(lái)更新
3. MVVM(Model-View-ViewModel):
- 模型(Model):同樣代表應(yīng)用程序的數(shù)據(jù)和業(yè)務(wù)邏輯衅谷。
- 視圖(View):負(fù)責(zé)展示數(shù)據(jù)給用戶(hù)以及接收用戶(hù)輸入。
- 視圖模型(ViewModel):連接視圖和模型似将,負(fù)責(zé)處理視圖邏輯获黔、數(shù)據(jù)綁定以及與模型的交互。
在 MVVM 中在验,視圖模型充當(dāng)了視圖和模型之間的中介肢执,負(fù)責(zé)處理大部分視圖邏輯,同時(shí)通過(guò)數(shù)據(jù)綁定將視圖與模型連接起來(lái)译红。MVVM 的目標(biāo)是將視圖的狀態(tài)和行為與業(yè)務(wù)邏輯分離预茄,以實(shí)現(xiàn)更好的可維護(hù)性和可測(cè)試性。
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 可以同時(shí)更新多個(gè)視圖
Packages 優(yōu)秀插件
freezed
https://pub-web.flutter-io.cn/packages/freezed
一個(gè)用于數(shù)據(jù)類(lèi) / 聯(lián)合體 / 模式匹配 / 克隆的代碼生成器侦厚。
詳見(jià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
依賴(lài)管理工具包 懶加載耻陕、單例、依賴(lài)注入刨沦、作用域诗宣、注入管理... 。
詳見(jiàn) <在 getx 中使用 get_it 管理依賴(lài)注入> https://ducafecat.com/blog/use-get_it-in-getx
Equatable
https://pub-web.flutter-io.cn/packages/equatable
equatable
可以幫助開(kāi)發(fā)人員輕松地重寫(xiě)類(lèi)的 ==
和 hashCode
方法想诅,從而簡(jiǎn)化對(duì)象之間的相等性比較召庞。
equatable
可以與狀態(tài)管理、數(shù)據(jù)模型等方面結(jié)合使用来破,幫助開(kāi)發(fā)人員更輕松地處理對(duì)象的相等性比較篮灼。
狀態(tài)管理
- Provider
- Bloc
- GetX
- Riverpod
詳見(jiàn) <盤(pán)點(diǎn)主流 Flutter 狀態(tài)管理庫(kù)2024>https://ducafecat.com/blog/flutter-state-management-libraries-2024
小結(jié)
本文探討了Flutter項(xiàng)目代碼組織架構(gòu)的關(guān)鍵方面,包括設(shè)計(jì)原則SOLID徘禁、Clean Architecture诅诱,以及架構(gòu)模式MVC、MVP送朱、MVVM的有機(jī)結(jié)合娘荡。通過(guò)本文的指導(dǎo)和建議,讀者可以更好地了解如何打造優(yōu)秀的Flutter應(yīng)用架構(gòu)驶沼,提高代碼可維護(hù)性和擴(kuò)展性炮沐。務(wù)必在實(shí)際項(xiàng)目中靈活運(yùn)用這些架構(gòu)原則,為應(yīng)用的長(zhǎng)期發(fā)展奠定堅(jiān)實(shí)基礎(chǔ)回怜。
感謝閱讀本文
如果有什么建議大年,請(qǐng)?jiān)谠u(píng)論中讓我知道。我很樂(lè)意改進(jìn)。
? 貓哥
ducafecat.com
end