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


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

    作者李俊才 (jcLee95)https://blog.csdn.net/qq_28550263
    邮箱 :291148484@163.com
    本文地址https://blog.csdn.net/qq_28550263/article/details/134006728



    1. 概述

    2. 关于依赖注入

    依赖注入(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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

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

    2. 使用依赖注入容器

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

    使用 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

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

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

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

    // 使用 inject 注解
    import 'package:inject/inject.dart';
    
    
    class MyModule {
      
      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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

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

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

    3. 什么是 Get.put

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    4. 有时候我还真的不喜欢过多使用Get.put

    从简单状态管理说起

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

    // your/path/count_controller.dart
    
    class Controller extends GetxController {
      int counter = 0;
      void increment() {
        counter++;
        update(); // 当调用增量时,使用update()来更新用户界面上的计数器变量。
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    import 'your/path/count_controller.dart'
    
    // ...其它代码
    
    GetBuilder<Controller>(
      init: Controller(), // 首次启动
      builder: (_) => Text(
        '${_.counter}',
      ),
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    Get.put 和 Get.find

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

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

    怎么办呢?对于全局共享的数据,很多情况下我们都是使用 Get.putGet.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 {
      
      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'),
              ),
            ),
          ),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    Get.find函数

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

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

    // 获取已注册的MyController对象实例
    final myController = Get.find<MyController>();
    
    // 使用myController来访问控制器的逻辑和状态
    
    • 1
    • 2
    • 3
    • 4

    使用 GetxController.find创建单例

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

    class Controller extends GetxController {
    
      /// 你不需要这个,我推荐使用它只是为了方便语法。
      /// 用静态方法:Controller.to.increment()。
      /// 没有静态方法的情况下:Get.find().increment();
      /// 使用这两种语法在性能上没有区别,也没有任何副作用。一个不需要类型,另一个IDE会自动完成。
      static Controller get to => Get.find(); // 添加这一行
    
      int counter = 0;
      void increment() {
        counter++;
        update();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

    FloatingActionButton(
      onPressed: () {
        Controller.to.increment(),
      } 
      child: Text("${Controller.to.counter}"),
    ),
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    错误信息提示你,你需要先使用 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());
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

    class Myapp extends StatelessWidget {
      const Myapp({super.key});
    
      Future<void> initialization(BuildContext context) async {
        // ... 其它需要初始化的内容
        DependencyInjection.init(); // 初始化依赖注入
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

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

    5. 手动使用单例

    简介

    这个办法就是在控制器类上做一些手脚,保证外部访问的都是同一实例——实际上使用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();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

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

  • 相关阅读:
    ubuntu 20.04 通过 samba 共享文件夹到 windows
    ISO20000认证流程是什么,ISO20000认证好处
    STM32G0 定时器PWM DMA输出驱动WS2812配置 LL库
    指针笔试题~走近大厂
    数据结构——二叉搜索树的实现、删除(最大值和最小值、最大值和最小值)
    【OneOS万耦启物】
    AI赋能药物研发的偶然与必然
    为什么说,网络安全工程师是网安行业的天花板?
    数字化门店| 旧衣回收店系统 | 智慧门店小程序开发教程
    【docker】网络模式管理
  • 原文地址:https://blog.csdn.net/qq_28550263/article/details/134006728