Flutter笔记:GetX模块中不使用 Get.put 怎么办

举报
jcLee95 发表于 2023/10/24 12:43:54 2023/10/24
【摘要】 依赖注入(Dependency Injection,对于很多真的就是简单的局部共享状态的场景,自己实现单例我个人感觉反而更好。首先,你不需要集中于创建代码初期就从各个模块中导入你的各个控制器,也不需要预先在应用初始化时就创建它们的实例,从而将实例添加到GetX依赖中进行管理。这使得mian文件中的代码更加简洁。如果某个局部状态控制器被移除,你也不需要回到mian文件中来对代码进行改动,只需要删除不
Flutter笔记
GetX模块中不使用 Get.put 怎么办





依赖注入(Dependency Injection,DI)是一种编程模式,它旨在管理和注入类之间的依赖关系,以提高代码的可维护性、可测试性和可扩展性。

在 Dart 语言中,依赖注入可以通过不同的方式实现。下面大概看一下一些常见的实现依赖注入的方式。

1. 手写依赖注入

手写依赖注入的有以下三个步骤:

  1. 创建依赖项类:首先创建依赖项的类,这些类包括服务、存储库或其他应用程序组件。
  2. 创建依赖关系:在应用程序中创建依赖项之间的关系,这可以通过构造函数参数或依赖项属性来实现。
  3. 注入依赖项:将依赖项注入到需要它们的类中,通常在构造函数中进行注入。

比如:

// 步骤 1:创建依赖项类
class DataService {
  String fetchData() {
    return "Data from DataService";
  }
}

class Logger {
  void log(String message) {
    print(message);
  }
}

class MyService {
  final DataService dataService;
  final Logger logger;

  // 步骤 2:创建依赖关系
  MyService(this.dataService, this.logger);

  void doSomething() {
    String data = dataService.fetchData();
    logger.log(data);
  }
}

void main() {
  // 步骤 3:注入依赖项
  final dataService = DataService();
  final logger = Logger();
  final myService = MyService(dataService, logger);

  myService.doSomething();
}

这个例子中,MyService 类依赖于 DataService  Logger 类,这两个依赖项被通过构造函数注入到 MyService 中。对于大型应用程序,可能需要考虑使用依赖注入容器或注解处理器来更方便地管理依赖项。下面两个小节继续介绍。

2. 使用依赖注入容器

依赖注入容器是管理和注入依赖项的工具,它们可以简化依赖管理过程并提供更高级的功能。在 Dart 中,一些流行的依赖注入容器包括 get_itinjector  kiwi 等。

使用 get_it 的示例:

import 'package:get_it/get_it.dart';

class DataService {
  String fetchData() {
    return "Data from DataService";
  }
}

class MyService {
  final DataService dataService;

  MyService(this.dataService);

  void doSomething() {
    String data = dataService.fetchData();
    print(data);
  }
}

void main() {
  final getIt = GetIt.instance;
  getIt.registerSingleton<DataService>(DataService());
  getIt.registerFactory<MyService>(() => MyService(getIt<DataService>()));
  
  final myService = getIt<MyService>();
  myService.doSomething();
}

在上述示例中,使用 get_it 容器注册并获取依赖项。 get_it 允许您注册单例和工厂方法来创建依赖项,并且能够在整个应用程序中轻松访问这些依赖项。

3. 注解处理器和代码生成

另一种方式是使用注解处理器和代码生成工具,如 inject  get_it 的 Generator,来自动生成依赖注入代码。这些工具可以根据您的类和注解自动生成依赖注入的代码,减少手动编写依赖注入的工作。

// 使用 inject 注解
import 'package:inject/inject.dart';

@module
class MyModule {
  @provide
  DataService provideDataService() => DataService();
}

class DataService {
  String fetchData() {
    return "Data from DataService";
  }
}

class MyService {
  final DataService dataService;

  MyService(this.dataService);

  void doSomething() {
    String data = dataService.fetchData();
    print(data);
  }
}

void main() {
  final injector = Injector<MyModule>().injector;
  final myService = injector.get<MyService>();
  myService.doSomething();
}

在上述示例中,使用了 inject 注解处理器来生成依赖注入代码,简化了依赖项的注册和获取。

无论您选择手动注入、依赖注入容器还是注解处理器,依赖注入都有助于将应用程序的组件解耦,提高代码的可测试性和可维护性。这种模式在构建大型、复杂的应用程序时特别有用,使代码更易于扩展和维护。


Get.put 是 GetX 状态管理库中的一个方法,它用于将一个控制器(Controller)实例放入 GetX 的依赖注入容器中,使得该控制器可以在整个应用程序中被共享和访问。

  1. 获取依赖: Get.put 的主要目的是将一个控制器实例添加到 GetX 的依赖注入系统中。这可以让您的控制器在整个应用程序中被轻松访问和共享。

  2. 依赖注入: GetX 使用依赖注入来管理应用程序的状态。依赖注入是一种设计模式,它有助于管理对象之间的依赖关系。在这种情况下,Get.put 用于将一个控制器添加到应用程序的依赖项容器中,以便其他部分可以访问它。

  3. 单例模式: 默认情况下,Get.put 创建的控制器是单例的。这意味着无论应用程序的哪个部分使用 Get.find 或其他方法获取该控制器,都将获得同一个实例。这对于共享应用程序状态和逻辑非常有用。

使用示例:

class MyController extends GetxController {
  var count = 0.obs;
}

// 在应用程序的某个地方将 MyController 放入依赖注入容器中
Get.put(MyController());

// 在其他部分可以轻松获取 MyController 实例
MyController myController = Get.find<MyController>();

// 使用 MyController 实例
myController.count.value = 42;

总之,Get.put 允许您在整个应用程序中访问和共享控制器,是 GetX 库的关键部分,用于实现轻量级的状态管理和依赖注入。


从简单状态管理说起

 GetX 模块中提供了太多的解决方案,其中甚至包括 StatefulWidget 的替代方案。GetX 模块官方文档中就直接说,通过 简单状态管理, 你 不再需要 StatefulWidget。比如,经典的几乎在每一个响应式框架都喜欢给出的计数器例子:

// your/path/count_controller.dart

class Controller extends GetxController {
  int counter = 0;
  void increment() {
    counter++;
    update(); // 当调用增量时,使用update()来更新用户界面上的计数器变量。
  }
}
import 'your/path/count_controller.dart'

// ...其它代码

GetBuilder<Controller>(
  init: Controller(), // 首次启动
  builder: (_) => Text(
    '${_.counter}',
  ),
)

这个例子中,使用的就是GetX提供的所谓 简单状态管理 的方式。它的状态保存在 Controller 类中,然后在需要用到的地方使 GetBuilder进行包装。

Get.put 和 Get.find

但是——考虑下面一个问题:

你此定义的 Controller 是一个全局状态,它可能不是在一个类、甚至一个文件中的代码中需要使用。那么,你会每次都创建 Controller 类的实例吗?显然这就不能共享数据状态了。

怎么办呢?对于全局共享的数据,很多情况下我们都是使用 Get.put  Get.find 来实现的,它们是 GetX 框架提供了一种方便的方式来管理应用程序状态和访问依赖项。

Get.put函数

Get.put 函数用于将一个对象注册为单例,以便在整个应用程序中重用。它通常用于注册 控制器、服务、数据存储类 等全局性的依赖项,使它们可以在整个应用程序的生命周期内被访问。

使用Get.put时,您需要提供一个对象的实例。通常,这将是一个控制器类的实例。比如,下面的例子展示了如何在GetX中使用Get.put来注册和访问一个控制器:

// 1. 创建一个控制器类
class MyController extends GetxController {
  // 控制器的逻辑和状态
}

// 2. 在应用程序初始化时使用Get.put注册控制器
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 注册MyController控制器为单例
    Get.put(MyController());

    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: ElevatedButton(
            onPressed: () {
              // 3. 在应用程序的任何地方使用Get.find获取控制器实例
              final myController = Get.find<MyController>();
              // 使用myController来访问控制器的逻辑和状态
            },
            child: Text('Get Controller'),
          ),
        ),
      ),
    );
  }
}

Get.find函数

`Get.find 函数用于查找和获取已经注册的单例对象(通常是控制器)的实例。它用于在应用程序的任何地方访问注册的对象,而无需手动传递它们。

使用Get.find时,需要提供对象的类型,以获取已注册对象的实例。可以在应用程序的任何部分使用Get.find来获取已注册对象的实例,而无需显式传递它们。例如

// 获取已注册的MyController对象实例
final myController = Get.find<MyController>();

// 使用myController来访问控制器的逻辑和状态

使用 GetxController.find创建单例

依据GetX官方的介绍,如果你需要在许多其他地方使用你的控制器,并且在GetBuilder之外,只需在你的控制器中创建一个get,就可以轻松地拥有它。(或者使用Get.find()) 例如:

class Controller extends GetxController {

  /// 你不需要这个,我推荐使用它只是为了方便语法。
  /// 用静态方法:Controller.to.increment()。
  /// 没有静态方法的情况下:Get.find<Controller>().increment();
  /// 使用这两种语法在性能上没有区别,也没有任何副作用。一个不需要类型,另一个IDE会自动完成。
  static Controller get to => Get.find(); // 添加这一行

  int counter = 0;
  void increment() {
    counter++;
    update();
  }
}

于是,你可以直接访问你的控制器:

FloatingActionButton(
  onPressed: () {
    Controller.to.increment(),
  } 
  child: Text("${Controller.to.counter}"),
),

哦,看起来似乎“贼简单”,不过接下来就是报错:

错误信息提示你,你需要先使用 Get.put 添加作为依赖添加给GetX框架管理。

—— 对的,我们使用的依然是 Get.find(),还需要见Get.put(); 一下。于是你很不情愿地又在 mian 的一大堆代码中添加了一个导入,创建了一个新的控制器实例,并添加了一条依赖注入。

替代StatefulWidget

既然简单状态管理在很多场景下用于替代StatelessWidget,而一旦遇到想考虑拆分组件避免一个组件写的过大时,就可能遇到简单状态需要在多个拆分后的组件中局部进行共享的问题。

由于,毕竟从需求上来说,这些状态还真的没有其它需要用到的地方了,如果都像 GetService一样去注册,写在 GetMaterialApp 中注册的就会特别多。

一般为了方便查找,我考虑写一个 app/injections.dart 文件,同意管理依赖注入项目,比如:

import 'package:get/get.dart';

import '../xxx.dart';


class DependencyInjection {
  static void init() {
    Get.put<GetConnect>(GetConnect());
    Get.put<GetProvider>(GetProvider());
    Get.put<GetProvider>(GetProvider());

    Get.put(AuthService(GetProvider()));

    Get.put(RecommendationController());
    Get.put(CartController());
  }
}

而在 mian.dart 的应用组件中(如“Myapp”),可以实现一个初始化方法,在这里和其它需要初始化的项目一起完成依赖初始化:

class Myapp extends StatelessWidget {
  const Myapp({super.key});

  Future<void> initialization(BuildContext context) async {
    // ... 其它需要初始化的内容
    DependencyInjection.init(); // 初始化依赖注入
  }
}

虽然这已经可以使得代码显得更加整洁清晰。但是,对于一些真的仅仅就是部分代码中共享的状态,我还是不想在 app/injections.dart 注册,我希望仅仅在这些代码中调用的地方才创建实例而不是在应用一启动就有GetX管理单例。

不仅是减少没必要的实例创建,同时我页不用大范围的找文件,尤其是项目变大的时候。那么如果不依赖注入,还有什么办法呢。请看下节。


简介

这个办法就是在控制器类上做一些手脚,保证外部访问的都是同一实例——实际上使用GetX的Get.put方法时,内部管理的也不就是我们注册的单例。

对于很多真的就是简单的局部共享状态的场景,自己实现单例我个人感觉反而更好。首先,你不需要集中于创建代码初期就从各个模块中导入你的各个控制器,也不需要预先在应用初始化时就创建它们的实例,从而将实例添加到GetX依赖中进行管理。这使得mian文件中的代码更加简洁。如果某个局部状态控制器被移除,你也不需要回到mian文件中来对代码进行改动,只需要删除不用的部分。其次,在Dart语言中,为面向对象的单例实现提供了很方便的支持,仅仅三个小步骤就可以实现严格管理单例。接下来就我们看一下具体该怎么搞。

实现过程

为控制器实现单例可以按照下面的三个步骤进行:

  1. 供一个私有静态属性用于存储唯一的控制器实例;
  2. 创建用于内部静态构造的构造器,尽量避免提供外部可访问的构造方法;
  3. 提供一个外部访问的访问器接口,在该接口中:
  • 如果还没有创建过控制器,则内部构建数以该类的唯一构造器实例后返回;
  • 如果存储的构造器已经非空,则返回该之前创建过的属于该类的唯一构造器实例。

具体案例

基于以上步骤,一个计数器控制器增加单例控制的面向对象实现如下:

import 'package:get/get.dart';

/// 计数器控制器类
class CounterController extends GetxController {
  // 提供一个私有静态属性用于存储唯一的控制器实例
  static CounterController? _instance;

  // 仅提供一个私有构造器防止外部创建实例
  CounterController._();

  // 提供一个外部访问的访问器接口
  static CounterController? get to {
    // 表示仅仅当 _instance 为 null 时,内部构造该控制器实例
    _instance ??= CounterController._();
    return _instance;
  }

  // 下面表示一些状态变量个状态相关的内容...
  int counter = 0;
  void increment() {
    counter++;
    update();
  }
}

接着,就可以在完全不通过 Get.put函数和Get.get函数情况下,改用 CounterController.to 访问器直接使用单例了。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。