Skip to main content

层间通信

除了定义架构中每个组件的明确职责外,还必须考虑组件如何通信。这既指规定通信的规则,也指组件通信的技术实现。应用程序的架构应解答以下问题:

  • 哪些组件可以与哪些其他组件通信(包括相同类型的组件)?
  • 这些组件彼此公开哪些输出?
  • 如何将任何给定层“连接”到另一层?

显示应用程序架构组件的图表

使用此图作为指南,参与规则如下:

组件参与规则
视图
  1. 视图只知道一个视图模型,并且永远不知道任何其他层或组件。创建时,Flutter 将视图模型作为参数传递给视图,向视图公开视图模型的数据和命令回调。
视图模型
  1. 一个视图模型属于一个视图,该视图可以查看其数据,但模型不需要知道视图的存在。
  2. 视图模型知道一个或多个资源库,这些资源库被传入视图模型的构造函数。
资源库
  1. 资源库可以知道许多服务,这些服务作为参数传入资源库构造函数。
  2. 许多视图模型可以使用一个资源库,但它不需要知道它们。
服务
  1. 许多资源库可以使用一个服务,但它不需要知道资源库(或任何其他对象)。

依赖注入

#

本指南展示了这些不同组件如何通过使用输入和输出彼此通信。在每种情况下,两层之间的通信都是通过将组件传递到构造函数方法(使用其数据的组件的构造函数方法)来实现的,例如将 Service 传递到 Repository

dart
class MyRepository {
  MyRepository({required MyService myService})
          : _myService = myService;

  late final MyService _myService;
}

然而,缺少的一件事是对象创建。在应用程序中,MyService 实例在哪里创建,以便可以将其传递到 MyRepository?这个问题的答案涉及一种称为依赖注入 的模式。

在 Compass 应用程序中,依赖注入 使用 package:provider 处理。根据他们在构建 Flutter 应用程序方面的经验,Google 的团队建议使用 package:provider 来实现依赖注入。

服务和资源库作为 Provider 对象公开到 Flutter 应用程序的 widget 树的顶层。

dependencies.dart
dart
runApp(
  MultiProvider(
    providers: [
      Provider(create: (context) => AuthApiClient()),
      Provider(create: (context) => ApiClient()),
      Provider(create: (context) => SharedPreferencesService()),
      ChangeNotifierProvider(
        create: (context) => AuthRepositoryRemote(
          authApiClient: context.read(),
          apiClient: context.read(),
          sharedPreferencesService: context.read(),
        ) as AuthRepository,
      ),
      Provider(create: (context) =>
        DestinationRepositoryRemote(
          apiClient: context.read(),
        ) as DestinationRepository,
      ),
      Provider(create: (context) =>
        ContinentRepositoryRemote(
          apiClient: context.read(),
        ) as ContinentRepository,
      ),
      // 在 Compass 应用程序中,其他服务和资源库提供程序位于此处。
    ],
  ),
  child: const MainApp(),
);

服务仅公开,以便它们可以立即通过 provider 中的 BuildContext.read 方法注入到资源库中,如前面的代码片段所示。然后公开资源库,以便根据需要将其注入到视图模型中。

在 widget 树中稍低的位置,对应于全屏的视图模型在 package:go_router 配置中创建,其中再次使用 provider 来注入必要的资源库。

router.dart
dart
// 此代码已为演示目的修改。
GoRouter router(
  AuthRepository authRepository,
) =>
    GoRouter(
      initialLocation: Routes.home,
      debugLogDiagnostics: true,
      redirect: _redirect,
      refreshListenable: authRepository,
      routes: [
        GoRoute(
          path: Routes.login,
          builder: (context, state) {
            return LoginScreen(
              viewModel: LoginViewModel(
                authRepository: context.read(),
              ),
            );
          },
        ),
        GoRoute(
          path: Routes.home,
          builder: (context, state) {
            final viewModel = HomeViewModel(
              bookingRepository: context.read(),
            );
            return HomeScreen(viewModel: viewModel);
          },
          routes: [
            // ...
          ],
        ),
      ],
    );

在视图模型或资源库中,注入的组件应该是私有的。例如,HomeViewModel 类如下所示:

home_viewmodel.dart
dart
class HomeViewModel extends ChangeNotifier {
  HomeViewModel({
    required BookingRepository bookingRepository,
    required UserRepository userRepository,
  })  : _bookingRepository = bookingRepository,
        _userRepository = userRepository;

  final BookingRepository _bookingRepository;
  final UserRepository _userRepository;

  // ...
}

私有方法阻止具有访问视图模型权限的视图直接调用资源库上的方法。

这结束了 Compass 应用程序的代码演练。此页面仅演练了与架构相关的代码,但并没有讲述全部内容。大多数实用程序代码、widget 代码和 UI 样式都被忽略了。浏览Compass 应用程序资源库 中的代码,以获取遵循这些原则构建的健壮 Flutter 应用程序的完整示例。

反馈

#

由于本网站的这一部分正在不断发展,我们欢迎您的反馈