成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

現代化Flutter架構-Riverpod數據層

開發 架構
使用Repository模式來隱藏數據層的所有實現細節(如 JSON 序列化)。這樣,應用程序的其余部分(領域層和表現層)就可以直接處理類型安全的模型類/實體。您的代碼庫也將變得更有彈性,可以抵御您所依賴的包中出現的破壞性變化。

設計模式是幫助我們解決軟件設計中常見問題的有用模板。

說到應用程序架構,結構設計模式可以幫助我們決定如何組織應用程序的不同部分。

在這種情況下,我們可以使用Repository模式從各種來源(如后端 API)訪問數據對象,并將它們作為類型安全的實體提供給應用程序的領域層(即我們的業務邏輯的所在層)。

在本文中,我們將詳細了解Repository Pattern:

  • 它是什么,何時使用
  • 一些實際示例
  • 使用具體類或抽象類的實現細節及其取舍
  • 如何使用Repository測試代碼

我還將分享一個帶有完整源代碼的天氣應用程序示例。

準備好了嗎?讓我們開始吧!

什么是Repository Pattern?

要理解這一點,讓我們來看看下面的架構圖:

圖片圖片

在這種情況下,Repository位于數據層。它們的任務是:

  • 將領域模型(或實體)與數據層中數據源的實現細節隔離開來。
  • 將數據傳輸對象轉換為領域層可理解的有效實體
  • (可選)執行數據緩存等操作

?

上圖顯示的只是架構應用程序的多種可能方法之一。如果您采用不同的架構(如 MVC、MVVM 或簡潔架構),情況會有所不同,但概念是相同的。

還要注意的是,Widget屬于表現層,與業務邏輯或網絡代碼無關。

?

如果您的 widget 直接使用來自 REST API 或遠程數據庫的鍵值對,那您就做錯了。換句話說:不要將業務邏輯與用戶界面代碼混在一起。這會使你的代碼更難測試、調試和推理。

何時使用Repository Pattern?

如果您的應用程序有一個復雜的數據層,其中有許多不同的端點返回非結構化數據(如 JSON),而您希望將這些數據與應用程序的其他部分隔離開來,那么Repository Pattern就非常方便。

廣而言之,以下是我認為最適合使用Repository模式的幾種用例:

  • 與 REST API 通信
  • 與本地或遠程數據庫(如 Sembast、Hive、Firestore 等)通信
  • 與特定設備的 API(如權限、攝像頭、位置等)通信

這種方法的一大好處是,如果您使用的任何第三方應用程序接口發生重大變更,您只需更新版本庫代碼即可。

僅憑這一點,Repository就值得 100%使用。??

讓我們看看如何使用它們!??

實踐中的Repository Pattern

舉個例子,我構建了一個簡單的 Flutter 應用程序(這里是源代碼),從 OpenWeatherMap API 獲取天氣數據。

通過閱讀 API 文檔,我們可以找到如何調用 API,以及一些 JSON 格式響應數據的示例。

Repository模式非常適合抽象掉所有網絡和 JSON 序列化代碼。

例如,這里有一個抽象類,定義了Repository的接口:

abstract class WeatherRepository {
  Future<Weather> getWeather({required String city});
}

上述 WeatherRepository 只有一個方法,但也可以有更多方法(例如,如果您想支持所有 CRUD 操作)。

重要的是,該Repository允許我們為,如何檢索給定城市的天氣定義一個接口。

我們需要用一個具體類來實現 WeatherRepository,該類可以使用網絡客戶端(如 http 或 dio)進行必要的 API 調用:

import 'package:http/http.dart' as http;

class HttpWeatherRepository implements WeatherRepository {
  HttpWeatherRepository({required this.api, required this.client});
  // custom class defining all the API details
  final OpenWeatherMapAPI api;
  // client for making calls to the API
  final http.Client client;

  // implements the method in the abstract class
  Future<Weather> getWeather({required String city}) {
    // TODO: send request, parse response, return Weather object or throw error
  }
}

所有這些實現細節都與數據層有關,應用程序的其他部分不應該關心或知道這些細節。解析 JSON 數據 當然,我們還必須定義氣象模型類(或實體),以及用于解析 API 響應數據的 JSON 序列化代碼:

class Weather {
  // TODO: declare all the properties we need
  factory Weather.fromJson(Map<String, dynamic> json) {
    // TODO: parse JSON and return validated Weather object
  }
}

請注意,雖然 JSON 響應可能包含許多不同的字段,但我們只需要解析將在用戶界面中使用的字段。我們可以手動編寫 JSON 解析代碼,或者使用代碼生成包(如 Freezed)。

在應用程序中初始化Repository

一旦定義了Repository,我們就需要一種方法來初始化它,并使應用程序的其他部分可以訪問它。執行此操作的語法會根據您選擇的 DI/狀態管理解決方案而改變。下面是一個使用 get_it 的示例:

import 'package:get_it/get_it.dart';

GetIt.instance.registerLazySingleton<WeatherRepository>(
  () => HttpWeatherRepository(api: OpenWeatherMapAPI(), client: http.Client(),
);

下面是另一個使用 Riverpod 軟件包中的提供程序的例子:

import 'package:flutter_riverpod/flutter_riverpod.dart';

final weatherRepositoryProvider = Provider<WeatherRepository>((ref) {
  return HttpWeatherRepository(api: OpenWeatherMapAPI(), client: http.Client());
});

如果你喜歡 flutter_bloc 軟件包,這里也有相應的功能:

import 'package:flutter_bloc/flutter_bloc.dart';

RepositoryProvider<WeatherRepository>(
  create: (_) => HttpWeatherRepository(api: OpenWeatherMapAPI(), client: http.Client()),
  child: MyApp(),
))

底層是一樣的:一旦初始化了Repository,就可以在應用程序的其他任何地方(Widget、模塊、Controller等)訪問它。

抽象類還是具體類?

在創建Repository時,一個常見的問題是:你真的需要一個抽象類嗎?這是個非常合理的問題,因為在兩個類中添加越來越多的方法可能會變得相當乏味:

abstract class WeatherRepository {
  Future<Weather> getWeather({required String city});
  Future<Forecast> getHourlyForecast({required String city});
  Future<Forecast> getDailyForecast({required String city});
  // and so on
}

class HttpWeatherRepository implements WeatherRepository {
  HttpWeatherRepository({required this.api, required this.client});
  // custom class defining all the API details
  final OpenWeatherMapAPI api;
  // client for making calls to the API
  final http.Client client;

  Future<Weather> getWeather({required String city}) { ... }
  Future<Forecast> getHourlyForecast({required String city}) { ... }
  Future<Forecast> getDailyForecast({required String city}) { ... }
  // and so on
}

正如軟件設計中經常出現的情況一樣,答案是:視情況而定。

因此,讓我們來看看每種方法的優缺點。

使用抽象類

優點:我們可以在一個地方看到Repository的接口,而不會感到雜亂無章。

優點:我們可以將Repository換成完全不同的實現(例如 DioWeatherRepository 而不是 HttpWeatherRepository),只需修改一行初始化代碼,因為應用程序的其他部分只知道 WeatherRepository。

缺點:當我們 “跳轉到引用 ”時,VSCode 會有點困惑,它會把我們帶到抽象類中的方法定義,而不是具體類中的實現。

缺點:更多模板代碼。

只使用具體類

優點:減少模板代碼。

優點:“跳轉到引用 ”只適用于一個類中的Repository方法。

缺點:如果我們更改了Repository名稱,那么切換到不同的實現就需要進行更多更改(不過使用 VSCode 對整個項目進行重命名很容易)。

在決定使用哪種方法時,我們還應考慮如何為代碼編寫測試。

使用Repository編寫測試代碼

在測試過程中,一個常見的要求是將網絡代碼換成模擬代碼或 “偽代碼”,這樣我們的測試就能運行得更快、更可靠。

然而,抽象類并不能給我們帶來任何優勢,因為在 Dart 中,所有類都有一個隱式接口。

這意味著我們可以這樣做:

// note: in Dart we can always implement a concrete class
class FakeWeatherRepository implements HttpWeatherRepository {

  // just a fake implementation that returns a value immediately
  Future<Weather> getWeather({required String city}) { 
    return Future.value(Weather(...));
  }
}

換句話說,如果我們打算在測試中模擬我們的Repository,就沒有必要創建抽象類。事實上,像 mocktail 這樣的包就利用了這一點,我們可以這樣使用它們:

import 'package:mocktail/mocktail.dart';

class MockWeatherRepository extends Mock implements HttpWeatherRepository {}

final mockWeatherRepository = MockWeatherRepository();
when(() => mockWeatherRepository.getWeather('London'))
          .thenAnswer((_) => Future.value(Weather(...)));

模擬數據源

在編寫測試時,可以模擬Repository并返回預制響應,就像我們上面做的那樣。但還有另一種方法,那就是模擬底層數據源。讓我們回顧一下 HttpWeatherRepository 是如何定義的:

import 'package:http/http.dart' as http;

class HttpWeatherRepository implements WeatherRepository {
  HttpWeatherRepository({required this.api, required this.client});
  // custom class defining all the API details
  final OpenWeatherMapAPI api;
  // client for making calls to the API
  final http.Client client;

  // implements the method in the abstract class
  Future<Weather> getWeather({required String city}) {
    // TODO: send request, parse response, return Weather object or throw error
  }
}

在這種情況下,我們可以選擇模擬傳遞給 HttpWeatherRepository 構造函數的 http.Client 對象。下面是一個測試示例,展示了如何做到這一點:

import 'package:http/http.dart' as http;
import 'package:mocktail/mocktail.dart';

class MockHttpClient extends Mock implements http.Client {}

void main() {
  test('repository with mocked http client', () async {
    // setup
    final mockHttpClient = MockHttpClient();
    final api = OpenWeatherMapAPI();
    final weatherRepository =
        HttpWeatherRepository(api: api, client: mockHttpClient);
    when(() => mockHttpClient.get(api.weather('London')))
        .thenAnswer((_) => Future.value(/* some valid http.Response */));
    // run
    final weather = await weatherRepository.getWeather(city: 'London');
    // verify
    expect(weather, Weather(...));
  });
}

最后,你可以根據要測試的內容,選擇是模擬Repository本身還是模擬底層數據源。

了解了如何測試版本庫之后,讓我們回到最初關于抽象類的問題上來。

Repository可能不需要抽象類

一般來說,如果你需要許多符合相同接口的實現,創建抽象類是有意義的。

例如,在 Flutter SDK 中,StatelessWidget 和 StatefulWidget 都是抽象類,因為它們可以被子類化。

但在使用Repository時,您可能只需要一個給定Repository的實現。

您很可能只需要一個特定Repository的實現,您可以將其定義為一個單一的具體類。

最小公分母

把所有東西都放在接口后面,也會使你不得不在具有不同功能的 API 之間選擇最小公分母。

也許某個 API 或后端支持實時更新,這可以用基于 Stream 的 API 來建模。

但如果您使用的是純 REST(不含 websockets),您只能發送一個請求并獲得一個響應,這最好使用基于 Future 的 API 來建模。

處理這個問題非常簡單:只需使用基于流的 API,如果使用的是 REST,則只需返回包含一個值的流即可。

但有時會存在更廣泛的 API 差異。

例如,Firestore 支持事務和批量寫入。這類 API 在源碼中使用了構建器模式,而這種模式不容易抽象為通用接口。

如果遷移到不同的后端,新的 API 很可能會有很大不同。換句話說,面向未來的當前應用程序接口往往不切實際,而且會適得其反。

Repository橫向擴展

隨著應用程序的增長,您可能會發現自己向給定的Repository中添加的方法越來越多。

如果您的后端有很大的 API 列表,或者如果您的應用程序連接到許多不同的數據源,就可能出現這種情況。

在這種情況下,可以考慮創建多個Repository,將相關的方法放在一起。例如,如果您正在構建一個電子商務應用程序,您可以為產品列表、購物車、訂單管理、身份驗證、結賬等創建單獨的Repository。

保持簡單

與往常一樣,保持簡單總是個好主意。因此,不要對應用程序接口想得太多。

您可以根據您需要使用的 API 來構建您的版本庫接口模型,然后就可以收工了。如果需要,您可以隨時重構。??

結論

如果我想讓你從這篇文章中得到什么啟發,那就是:使用Repository模式來隱藏你的代碼:

使用Repository模式來隱藏數據層的所有實現細節(如 JSON 序列化)。這樣,應用程序的其余部分(領域層和表現層)就可以直接處理類型安全的模型類/實體。您的代碼庫也將變得更有彈性,可以抵御您所依賴的包中出現的破壞性變化。

如果說有什么收獲的話,我希望這篇概述能鼓勵您更清晰地思考應用程序架構,以及擁有邊界清晰的獨立表現層、應用層、領域層和數據層的重要性。

本文翻譯自:https://codewithandrea.com/articles/flutter-repository-pattern/

責任編輯:武曉燕 來源: 群英傳
相關推薦

2023-02-08 11:07:56

數字時代數字運營模式

2023-06-25 09:04:12

數字企業架構EA

2024-01-23 15:21:14

2020-08-05 07:00:00

數據架構工具技術

2021-04-13 16:13:38

大數據教育科學

2013-03-22 10:27:40

企業再現代化IBM論壇2013

2022-07-26 06:57:07

數據管道端點API

2018-06-05 13:43:49

數據基礎設施

2015-10-29 14:35:21

移動設備現代化

2017-11-23 05:50:14

2023-08-18 08:07:37

2022-07-11 05:34:19

云原生應用程序

2019-08-30 08:23:47

基礎架構IT架構數據備份

2017-11-06 14:48:01

大數據法醫犯罪

2015-12-24 10:33:31

數據中心現代數據中心

2023-05-03 21:47:22

2019-05-20 11:19:14

企業級云計算架構

2022-09-26 14:42:36

數據中心IT 行

2024-01-24 14:46:45

生成式人工智機器學習數據現代化
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 视频一区在线观看 | 亚洲国产一区视频 | 欧美一级免费 | 最新国产精品视频 | 91精品国产综合久久久久久首页 | 国产精品视频一区二区三区 | 国产一区二区三区网站 | 国产一区二区三区在线观看免费 | 欧美在线 | 欧美专区在线 | 久久精品国产一区二区电影 | 黄网站涩免费蜜桃网站 | 欧产日产国产精品视频 | 欧美成年网站 | 国产精品一二三区 | 欧美伊人影院 | 久久久久久中文字幕 | 欧产日产国产精品99 | 日韩视频91 | 亚洲精品乱码久久久久久久久 | 96av麻豆蜜桃一区二区 | 欧美亚洲国产日韩 | 三级免费网 | av电影一区二区 | 黄a网 | 国产精品日韩 | 成人在线免费电影 | 亚洲黄色av网站 | 久久久久一区二区 | 99综合在线 | 97超碰人人 | 国产伦精品一区二区三区视频金莲 | 日本黄视频在线观看 | 蜜桃av鲁一鲁一鲁一鲁 | 天天夜碰日日摸日日澡 | 欧美国产日韩一区 | 福利视频一区二区 | 欧美高清dvd| 免费看国产精品视频 | 精品国产一区二区三区观看不卡 | 日韩a在线|