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

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

視頻

https://youtu.be/n6cky-JyBSY

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原則

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ù)雜度和耦合度灼舍。

  1. 單一職責(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}'),
          ],
        );
      }
    }
    
  2. 開(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();
      }
    }
    
  3. 里氏替換原則 (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;
      }
    }
    
  4. 接口隔離原則 (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');
      }
    }
    
  5. 依賴(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 原則

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):

MVC
  • 模型(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):

MVP
  • 模型(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):

MVVM
  • 模型(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鲜戒,一起剝皮案震驚了整個(gè)濱河市专控,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌遏餐,老刑警劉巖伦腐,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異失都,居然都是意外死亡柏蘑,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)粹庞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)咳焚,“玉大人,你說(shuō)我怎么就攤上這事庞溜「锇耄” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵流码,是天一觀的道長(zhǎng)又官。 經(jīng)常有香客問(wèn)我,道長(zhǎng)漫试,這世上最難降的妖魔是什么六敬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮驾荣,結(jié)果婚禮上外构,老公的妹妹穿的比我還像新娘。我一直安慰自己播掷,他們只是感情好审编,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著叮趴,像睡著了一般割笙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上眯亦,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音般码,去河邊找鬼妻率。 笑死,一個(gè)胖子當(dāng)著我的面吹牛板祝,可吹牛的內(nèi)容都是我干的宫静。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼孤里!你這毒婦竟也來(lái)了伏伯?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤捌袜,失蹤者是張志新(化名)和其女友劉穎说搅,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體虏等,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弄唧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了霍衫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片候引。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖敦跌,靈堂內(nèi)的尸體忽然破棺而出澄干,到底是詐尸還是另有隱情,我是刑警寧澤柠傍,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布傻寂,位于F島的核電站,受9級(jí)特大地震影響携兵,放射性物質(zhì)發(fā)生泄漏疾掰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一徐紧、第九天 我趴在偏房一處隱蔽的房頂上張望静檬。 院中可真熱鬧,春花似錦并级、人聲如沸拂檩。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)稻励。三九已至,卻和暖如春愈涩,著一層夾襖步出監(jiān)牢的瞬間望抽,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工履婉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留煤篙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓毁腿,卻偏偏與公主長(zhǎng)得像辑奈,于是被迫代替她去往敵國(guó)和親苛茂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容