自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

現(xiàn)代化Flutter架構(gòu)-Riverpod數(shù)據(jù)層

開發(fā) 架構(gòu)
使用Repository模式來隱藏?cái)?shù)據(jù)層的所有實(shí)現(xiàn)細(xì)節(jié)(如 JSON 序列化)。這樣,應(yīng)用程序的其余部分(領(lǐng)域?qū)雍捅憩F(xiàn)層)就可以直接處理類型安全的模型類/實(shí)體。您的代碼庫也將變得更有彈性,可以抵御您所依賴的包中出現(xiàn)的破壞性變化。

設(shè)計(jì)模式是幫助我們解決軟件設(shè)計(jì)中常見問題的有用模板。

說到應(yīng)用程序架構(gòu),結(jié)構(gòu)設(shè)計(jì)模式可以幫助我們決定如何組織應(yīng)用程序的不同部分。

在這種情況下,我們可以使用Repository模式從各種來源(如后端 API)訪問數(shù)據(jù)對象,并將它們作為類型安全的實(shí)體提供給應(yīng)用程序的領(lǐng)域?qū)樱次覀兊臉I(yè)務(wù)邏輯的所在層)。

在本文中,我們將詳細(xì)了解Repository Pattern:

  • 它是什么,何時(shí)使用
  • 一些實(shí)際示例
  • 使用具體類或抽象類的實(shí)現(xiàn)細(xì)節(jié)及其取舍
  • 如何使用Repository測試代碼

我還將分享一個(gè)帶有完整源代碼的天氣應(yīng)用程序示例。

準(zhǔn)備好了嗎?讓我們開始吧!

什么是Repository Pattern?

要理解這一點(diǎn),讓我們來看看下面的架構(gòu)圖:

圖片圖片

在這種情況下,Repository位于數(shù)據(jù)層。它們的任務(wù)是:

  • 將領(lǐng)域模型(或?qū)嶓w)與數(shù)據(jù)層中數(shù)據(jù)源的實(shí)現(xiàn)細(xì)節(jié)隔離開來。
  • 將數(shù)據(jù)傳輸對象轉(zhuǎn)換為領(lǐng)域?qū)涌衫斫獾挠行?shí)體
  • (可選)執(zhí)行數(shù)據(jù)緩存等操作

?

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

還要注意的是,Widget屬于表現(xiàn)層,與業(yè)務(wù)邏輯或網(wǎng)絡(luò)代碼無關(guān)。

?

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

何時(shí)使用Repository Pattern?

如果您的應(yīng)用程序有一個(gè)復(fù)雜的數(shù)據(jù)層,其中有許多不同的端點(diǎn)返回非結(jié)構(gòu)化數(shù)據(jù)(如 JSON),而您希望將這些數(shù)據(jù)與應(yīng)用程序的其他部分隔離開來,那么Repository Pattern就非常方便。

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

  • 與 REST API 通信
  • 與本地或遠(yuǎn)程數(shù)據(jù)庫(如 Sembast、Hive、Firestore 等)通信
  • 與特定設(shè)備的 API(如權(quán)限、攝像頭、位置等)通信

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

僅憑這一點(diǎn),Repository就值得 100%使用。??

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

實(shí)踐中的Repository Pattern

舉個(gè)例子,我構(gòu)建了一個(gè)簡單的 Flutter 應(yīng)用程序(這里是源代碼),從 OpenWeatherMap API 獲取天氣數(shù)據(jù)。

通過閱讀 API 文檔,我們可以找到如何調(diào)用 API,以及一些 JSON 格式響應(yīng)數(shù)據(jù)的示例。

Repository模式非常適合抽象掉所有網(wǎng)絡(luò)和 JSON 序列化代碼。

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

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

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

重要的是,該Repository允許我們?yōu)?,如何檢索給定城市的天氣定義一個(gè)接口。

我們需要用一個(gè)具體類來實(shí)現(xiàn) WeatherRepository,該類可以使用網(wǎng)絡(luò)客戶端(如 http 或 dio)進(jìn)行必要的 API 調(diào)用:

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
  }
}

所有這些實(shí)現(xiàn)細(xì)節(jié)都與數(shù)據(jù)層有關(guān),應(yīng)用程序的其他部分不應(yīng)該關(guān)心或知道這些細(xì)節(jié)。解析 JSON 數(shù)據(jù) 當(dāng)然,我們還必須定義氣象模型類(或?qū)嶓w),以及用于解析 API 響應(yīng)數(shù)據(jù)的 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 響應(yīng)可能包含許多不同的字段,但我們只需要解析將在用戶界面中使用的字段。我們可以手動編寫 JSON 解析代碼,或者使用代碼生成包(如 Freezed)。

在應(yīng)用程序中初始化Repository

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

import 'package:get_it/get_it.dart';

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

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

import 'package:flutter_riverpod/flutter_riverpod.dart';

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

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

import 'package:flutter_bloc/flutter_bloc.dart';

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

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

抽象類還是具體類?

在創(chuàng)建Repository時(shí),一個(gè)常見的問題是:你真的需要一個(gè)抽象類嗎?這是個(gè)非常合理的問題,因?yàn)樵趦蓚€(gè)類中添加越來越多的方法可能會變得相當(dāng)乏味:

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
}

正如軟件設(shè)計(jì)中經(jīng)常出現(xiàn)的情況一樣,答案是:視情況而定。

因此,讓我們來看看每種方法的優(yōu)缺點(diǎn)。

使用抽象類

優(yōu)點(diǎn):我們可以在一個(gè)地方看到Repository的接口,而不會感到雜亂無章。

優(yōu)點(diǎn):我們可以將Repository換成完全不同的實(shí)現(xiàn)(例如 DioWeatherRepository 而不是 HttpWeatherRepository),只需修改一行初始化代碼,因?yàn)閼?yīng)用程序的其他部分只知道 WeatherRepository。

缺點(diǎn):當(dāng)我們 “跳轉(zhuǎn)到引用 ”時(shí),VSCode 會有點(diǎn)困惑,它會把我們帶到抽象類中的方法定義,而不是具體類中的實(shí)現(xiàn)。

缺點(diǎn):更多模板代碼。

只使用具體類

優(yōu)點(diǎn):減少模板代碼。

優(yōu)點(diǎn):“跳轉(zhuǎn)到引用 ”只適用于一個(gè)類中的Repository方法。

缺點(diǎn):如果我們更改了Repository名稱,那么切換到不同的實(shí)現(xiàn)就需要進(jìn)行更多更改(不過使用 VSCode 對整個(gè)項(xiàng)目進(jìn)行重命名很容易)。

在決定使用哪種方法時(shí),我們還應(yīng)考慮如何為代碼編寫測試。

使用Repository編寫測試代碼

在測試過程中,一個(gè)常見的要求是將網(wǎng)絡(luò)代碼換成模擬代碼或 “偽代碼”,這樣我們的測試就能運(yùn)行得更快、更可靠。

然而,抽象類并不能給我們帶來任何優(yōu)勢,因?yàn)樵?Dart 中,所有類都有一個(gè)隱式接口。

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

// 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,就沒有必要創(chuàng)建抽象類。事實(shí)上,像 mocktail 這樣的包就利用了這一點(diǎn),我們可以這樣使用它們:

import 'package:mocktail/mocktail.dart';

class MockWeatherRepository extends Mock implements HttpWeatherRepository {}

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

模擬數(shù)據(jù)源

在編寫測試時(shí),可以模擬Repository并返回預(yù)制響應(yīng),就像我們上面做的那樣。但還有另一種方法,那就是模擬底層數(shù)據(jù)源。讓我們回顧一下 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 構(gòu)造函數(shù)的 http.Client 對象。下面是一個(gè)測試示例,展示了如何做到這一點(diǎn):

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(...));
  });
}

最后,你可以根據(jù)要測試的內(nèi)容,選擇是模擬Repository本身還是模擬底層數(shù)據(jù)源。

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

Repository可能不需要抽象類

一般來說,如果你需要許多符合相同接口的實(shí)現(xiàn),創(chuàng)建抽象類是有意義的。

例如,在 Flutter SDK 中,StatelessWidget 和 StatefulWidget 都是抽象類,因?yàn)樗鼈兛梢员蛔宇惢?/span>

但在使用Repository時(shí),您可能只需要一個(gè)給定Repository的實(shí)現(xiàn)。

您很可能只需要一個(gè)特定Repository的實(shí)現(xiàn),您可以將其定義為一個(gè)單一的具體類。

最小公分母

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

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

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

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

但有時(shí)會存在更廣泛的 API 差異。

例如,F(xiàn)irestore 支持事務(wù)和批量寫入。這類 API 在源碼中使用了構(gòu)建器模式,而這種模式不容易抽象為通用接口。

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

Repository橫向擴(kuò)展

隨著應(yīng)用程序的增長,您可能會發(fā)現(xiàn)自己向給定的Repository中添加的方法越來越多。

如果您的后端有很大的 API 列表,或者如果您的應(yīng)用程序連接到許多不同的數(shù)據(jù)源,就可能出現(xiàn)這種情況。

在這種情況下,可以考慮創(chuàng)建多個(gè)Repository,將相關(guān)的方法放在一起。例如,如果您正在構(gòu)建一個(gè)電子商務(wù)應(yīng)用程序,您可以為產(chǎn)品列表、購物車、訂單管理、身份驗(yàn)證、結(jié)賬等創(chuàng)建單獨(dú)的Repository。

保持簡單

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

您可以根據(jù)您需要使用的 API 來構(gòu)建您的版本庫接口模型,然后就可以收工了。如果需要,您可以隨時(shí)重構(gòu)。??

結(jié)論

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

使用Repository模式來隱藏?cái)?shù)據(jù)層的所有實(shí)現(xiàn)細(xì)節(jié)(如 JSON 序列化)。這樣,應(yīng)用程序的其余部分(領(lǐng)域?qū)雍捅憩F(xiàn)層)就可以直接處理類型安全的模型類/實(shí)體。您的代碼庫也將變得更有彈性,可以抵御您所依賴的包中出現(xiàn)的破壞性變化。

如果說有什么收獲的話,我希望這篇概述能鼓勵您更清晰地思考應(yīng)用程序架構(gòu),以及擁有邊界清晰的獨(dú)立表現(xiàn)層、應(yīng)用層、領(lǐng)域?qū)雍蛿?shù)據(jù)層的重要性。

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

責(zé)任編輯:武曉燕 來源: 群英傳
相關(guān)推薦

2023-02-08 11:07:56

數(shù)字時(shí)代數(shù)字運(yùn)營模式

2023-06-25 09:04:12

數(shù)字企業(yè)架構(gòu)EA

2020-08-05 07:00:00

數(shù)據(jù)架構(gòu)工具技術(shù)

2024-01-23 15:21:14

2021-04-13 16:13:38

大數(shù)據(jù)教育科學(xué)

2013-03-22 10:27:40

企業(yè)再現(xiàn)代化IBM論壇2013

2018-06-05 13:43:49

數(shù)據(jù)基礎(chǔ)設(shè)施

2022-07-26 06:57:07

數(shù)據(jù)管道端點(diǎn)API

2015-10-29 14:35:21

移動設(shè)備現(xiàn)代化

2019-08-30 08:23:47

基礎(chǔ)架構(gòu)IT架構(gòu)數(shù)據(jù)備份

2023-08-18 08:07:37

2022-07-11 05:34:19

云原生應(yīng)用程序

2017-11-23 05:50:14

2015-12-24 10:33:31

數(shù)據(jù)中心現(xiàn)代數(shù)據(jù)中心

2017-11-06 14:48:01

大數(shù)據(jù)法醫(yī)犯罪

2019-05-20 11:19:14

企業(yè)級云計(jì)算架構(gòu)

2019-07-29 09:29:26

2020-07-10 10:48:01

基礎(chǔ)架構(gòu)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號