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

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

視頻

https://youtu.be/n6cky-JyBSY

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

SOLID(單一功能、開閉原則、里氏替換、接口隔離以及依賴反轉(zhuǎn))是由羅伯特·C·馬丁在21世紀早期引入,指代了面向?qū)ο缶幊毯兔嫦驅(qū)ο笤O(shè)計的五個基本原則。

在 Flutter 中遵循 SOLID 設(shè)計原則具有重要性,因為這些原則有助于提高代碼質(zhì)量、可維護性和可擴展性,同時降低代碼的復(fù)雜度和耦合度。

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

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

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

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容