美文网首页Flutter圈子flutter笔记Flutter
Flutter状态管理终极方案GetX第二篇——依赖注入

Flutter状态管理终极方案GetX第二篇——依赖注入

作者: A_si | 来源:发表于2020-12-27 21:07 被阅读0次

    GetX第三篇-依赖注入

    为什么要使用依赖注入

    依赖注入是什么

    本来接受各种参数来构造一个对象,现在只接受一个参数——已经实例化的对象。

    依赖注入的目的

    依赖注入是为了将依赖组件的配置和使用分离开,以降低使用者与依赖之间的耦合度。

    依赖注入的好处

    实现依赖项注入可为您带来以下优势:

    • 重用代码
      更容易换掉依赖项的实现。由于控制反转,代码重用得以改进,并且类不再控制其依赖项的创建方式,而是支持任何配置。
    • 易于重构
      依赖项的创建分离,可以在创建对象时或编译时进行检查、修改,一处修改,使用处不需修改。
    • 易于测试
      类不管理其依赖项,因此在测试时,您可以传入不同的实现以测试所有不同用例。

    举个例子

    老王的玛莎拉蒂需要换个v8引擎,他是自己拼装个引擎呢还是去改装店买一个呢?
    如果自己拼装个,引擎的构造更新了,他需要学习改进自己的技术,买新零件,而直接买一个成品,就是依赖注入。

    class Car(private val engineParts: String,val enginePiston: String) {
    
        fun start() {
            val engine= Engine(engineParts,enginePiston)
            engine.start()
        }
    }
    
    class Engine(private val engineParts: String,val enginePiston: String){
    }
    

    上面代码中的 Engine 类如果构造方法变动了,也需要去 Car 类里更改。而使用依赖注入就不需要改动 Car 类。

    手动实现依赖注入通常有两种,构造函数传入和字段传入。
    构造方法:

    class Car(private val engine: Engine) {
        fun start() {
            engine.start()
        }
    }
    
    fun main(args: Array) {
        val engine = Engine()
        val car = Car(engine)
        car.start()
    }
    

    字段传入:

    class Car {
        lateinit var engine: Engine
    
        fun start() {
            engine.start()
        }
    }
    
    fun main(args: Array) {
        val car = Car()
        car.engine = Engine()
        car.start()
    }
    

    上面虽然实现了依赖注入,但是增加了样板代码,如果注入实例多,也很麻烦。Android 上有 DaggerHilt 实现自动注入, GetX 也给我们提供了 Binding 类实现。

    使用依赖注入

    Get有一个简单而强大的依赖管理器,它允许你只用1行代码就能检索到 Controller 或者需要依赖的类,不需要提供上下文,不需要在 inheritedWidget 的子节点。

    注入依赖:

    Get.put<PutController>(PutController());
    

    获取依赖:

    Get.find<PutController>();
    

    就是这么简单。

    Get.put()

    这是个立即注入内存的注入方法。调用后已经注入到内存中。

    Get.put<S>(
      // 必备:要注入的类。
      // 注:" S "意味着它可以是任何类型的类。
      S dependency
    
      // 可选:想要注入多个相同类型的类时,可以用这个方法。
      // 比如有两个购物车实例,就需要使用标签区分不同的实例。
      // 必须是唯一的字符串。
      String tag,
    
      // 可选:默认情况下,get会在实例不再使用后进行销毁
      // (例如:一个已经销毁的视图的Controller)
      // 如果需要这个实例在整个应用生命周期中都存在,就像一个sharedPreferences的实例。
      // 默认值为false
      bool permanent = false,
    
      // 可选:允许你在测试中使用一个抽象类后,用另一个抽象类代替它,然后再进行测试。
      // 默认为false
      bool overrideAbstract = false,
    
      // 可选:允许你使用函数而不是依赖(dependency)本身来创建依赖。
      // 这个不常用
      InstanceBuilderCallback<S> builder,
    )
    

    permanent是代表是否不销毁。通常Get.put()的实例的生命周期和 put 所在的 Widget 生命周期绑定,如果在全局 (main 方法里)put,那么这个实例就一直存在。如果在一个 Widget 里 put ,那么这个那么这个 Widget 从内存中删除,这个实例也会被销毁。注意,这里是删除,并不是dispose,具体看上一篇最后的部分。

    Get.lazyPut

    懒加载一个依赖,只有在使用时才会被实例化。适用于不确定是否会被使用的依赖或者计算高昂的依赖。类似 Kotlin 的 Lazy 代理。

      Get.lazyPut<LazyController>(() => LazyController());
    
    

    LazyController 在这时候并不会被创建,而是等到你使用的时候才会被 initialized,也就是执行下面这句话的时候才 initialized

    Get.find<LazyController>();
    

    在使用后,使用时的 Wdiget 的生命周期结束,也就是这个 Widgetdispose,这个实例就会被销毁。

    如果在一个 Widget 里 find,然后退出这个 widget,此时这个实例也被销毁,再进入另一个路由的 Widget,再次 find,GetX会打印错误信息,提醒没有 put 。及时全局注入,也一样。可以理解为,Get.lazyPut 注入的实例的生命周期是和在Get.find时的上下文所绑定。

    如果想每次 find 获取到不同的实例,可以借助fenix参数。

    Get.lazyPut<S>(
      // 必须:当你的类第一次被调用时,将被执行的方法。
      InstanceBuilderCallback builder,
      
      // 可选:和Get.put()一样,当你想让同一个类有多个不同的实例时,就会用到它。
      // 必须是唯一的
      String tag,
    
      // 可选:下次使用时是否重建,
      // 当不使用时,实例会被丢弃,但当再次需要使用时,Get会重新创建实例,
      // 就像 bindings api 中的 "SmartManagement.keepFactory "一样。
      // 默认值为false
      bool fenix = false
      
    )
    

    Get.putAsync

    注入一个异步创建的实例。比如SharedPreferences

      Get.putAsync<SharedPreferences>(() async {
        final sp = await SharedPreferences.getInstance();
        return sp;
      });
    

    作用域参考Get.put

    Get.create

    这个方法可以创建很多实例。很少用到。可以当做Get.put

    Bindings类

    上面实现了依赖注入和使用,但是和前面讲的手动注入一样,为了生命周期和使用的 Widget 绑定,需要在 Widget 里注入和使用,并没有完全解耦。要实现自动注入,我们就需要这个类。

    这个包最大的区别之一,也许就是可以将路由、状态管理器和依赖管理器完全集成。 当一个路由从Stack中移除时,所有与它相关的控制器、变量和对象的实例都会从内存中移除。如果你使用的是流或定时器,它们会自动关闭,我们不必担心这些。Bindings 类是一个解耦依赖注入的类,同时 "Binding " 路由到状态管理器和依赖管理器。 这使得 GetX 可以知道当使用某个控制器时,哪个页面正在显示,并知道在哪里以及如何销毁它。 此外,Bindings 类将允许我们利用 SmartManager 配置控制。

    • 创建一个类并实现Binding
    class InjectSimpleBinding implements Bindings {}
    

    因为Bindings是抽象方法,所以要ide会提示要实现dependencies。在里面注入我们需要的实例:

    class InjectSimpleBinding implements Bindings {
      @override
      void dependencies() {
        Get.lazyPut<Api>(() => Api());
        Get.lazyPut<InjectSimpleController>(() => InjectSimpleController());
      }
    }
    
    • 通知路由,我们要使用该 Binding 来建立路由管理器、依赖关系和状态之间的连接。

    这里有两种方式,如果使用的是命名路由表:

        GetPage(
          name: Routes.INJECT,
          page: () => InjectSimplePage(),
          binding:InjectSimpleBinding(),
        ),
    

    如果是直接跳转:

    Get.to(InjectSimplePage(), binding: InjectSimpleBinding());
    

    现在,我们不必再担心应用程序的内存管理,Get将为我们做这件事。

    上面我们注入依赖解耦了,但是获取还是略显不方便,GetX 也为我们考虑到了。GetView完美的搭配 Bindings。

    class InjectSimplePage extends GetView<InjectSimpleController> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('MyPage')),
          body: Center(
            child: Obx(() => Text(controller.obj.toString())),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              controller.getAge();
            },
            child: Icon(Icons.add),
          ),
        );
      }
    }
    
    

    这里完全没有Get.find,但是可以直接使用controller,因为GetView里封装好了:

    abstract class GetView<T> extends StatelessWidget {
      const GetView({Key key}) : super(key: key);
    
      final String tag = null;
    
      T get controller => GetInstance().find<T>(tag: tag);
    
      @override
      Widget build(BuildContext context);
    }
    

    不在需要 StatelessWidget 和 StatefulWidget。这也是开发最常用的模式,推荐大家使用。

    当然,也许有时候觉得每次声明一个 Bingings 类也很麻烦,那么可以使用 BindingsBuilder ,这样就可以简单地使用一个函数来实例化任何想要注入的东西。

      GetPage(
        name: '/details',
        page: () => DetailsView(),
        binding: BindingsBuilder(() => {
          Get.lazyPut<DetailsController>(() => DetailsController());
        }),
    

    就是这么简单,Bingings 都不需要创建。两种方式都可以,大家根据自己的编码习惯选择最适合的风格。

    Bindings的工作原理

    Bindings 会创建过渡性工厂,在点击进入另一个页面的那一刻,这些工厂就会被创建,一旦路由过渡动画发生,就会被销毁。 工厂占用的内存很少,它们并不持有实例,而是一个具有我们想要的那个类的 "形状"的函数。 这在内存上的成本很低,但由于这个库的目的是用最少的资源获得最大的性能,所以Get连工厂都默认删除。

    智能管理

    GetX 默认情况下会将未使用的控制器从内存中移除。 但是如果想改变GetX控制类的销毁方式怎么办呢,可以用SmartManagement 类设置不同的行为。

    如何改变

    如果想改变这个配置(通常不需要),就用这个方法。

    void main () {
      runApp(
        GetMaterialApp(
          smartManagement: SmartManagement.onlyBuilders //这里
          home: Home(),
        )
      )
    }
    
    • SmartManagement.full
      这是默认的。销毁那些没有被使用的、没有被设置为永久的类。在大多数情况下,我们都使用这个,不需要更改。

    • SmartManagement.onlyBuilders
      使用该选项,只有在init:中启动的控制器或用Get.lazyPut()加载到Binding中的控制器才会被销毁。

      如果使用Get.put()或Get.putAsync()或任何其他方法,SmartManagement 没有权限也就是不能移除这个依赖。

    • SmartManagement.keepFactory
      就像SmartManagement.full一样,当它不再被使用时,它将删除它的依赖关系,但它将保留它们的工厂,这意味着如果再次需要该实例,它将重新创建该依赖关系。

    相关文章

      网友评论

        本文标题:Flutter状态管理终极方案GetX第二篇——依赖注入

        本文链接:https://www.haomeiwen.com/subject/cblanktx.html