Flutter状态管理神器GetX探究之依赖注入?

513 阅读8分钟

高效学习模型

  • what-->why-->how 模型
  • 是什么?-->为什么使用?-->如何使用?-->实现原理-->总结分享(学以致用)

GetX三大功能之依赖注入DI(dependency-injection)

概念

  • Inversion of Control: 控制反转是一种重要的面向对象编程原则,控制反转就是,关于一个对象如何获取他所依赖的对象的引用,这个责任的反转。

    控制反转一般分为两种类型,依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。依赖注入应用比较广泛。

  • Dependency Injection: 依赖注入,依赖注入模式是指客户类Client不用自己来初始化(new)它所依赖的成员变量IServer,而是等待某个对象创建IServer的适当的(实现类)的对象并将它赋值给Client的成员变量。

sample

classA {
 AInterface a;  

 A(){}
 
 AMethod(){
 a = new AInterfaceImpl();
 }
}

这里面 Class AAInterfaceImpl就是依赖关系,如果想使用AInterface的另外一个实现就需要更改代码了,依赖注入就是为了解决这种耦合关系的

使用new(对象创建)是一种硬编码,是代码耦合度变得很高,不方便测试.依赖注入简单的讲就是通过外界传入依赖来进行成员变量的初始化

依赖注入的三种实现方式

  • Contructor Injection(构造函数注入)
 public interface IFather {
  //method
 }

public class Human {
 IFather father;
 public Human(IFather father) {
  this.father = father;
 }
}
  • 基于setter,通过JavaBean的属性(setter方法)为可服务对象指定服务
public class Human {
  IFather father;
  public void setIFather(IFather father) {
   this.father = father;
  }
}
  • 接口注入
// 注入[功能](https://www.jb51.cc/tag/gongneng/)的interface
public interface InjectFinder {
  void injectFinder(IFather father);
}
 // 让我们的Human实现接口
public class Human implements InjectFinder {
  IFather father;
  public void injectFinder(IFather father) {
   this.father = father;
  }
}

依赖注入的优点

  • 易于复用
    更容易换掉依赖项的实现。由于控制反转,代码重用得以改进,并且类不再控制其依赖项的创建方式,而是支持任何配置。

  • 易于重构
    依赖项的创建分离,可以在创建对象时或编译时进行检查、修改,一处修改,使用处不需修改。

  • 易于测试
    类不管理其依赖项,因此在测试时,可以传入不同的实现以测试所有不同用例。

GetX中如何使用依赖注入

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

//注入依赖
Controller controller = Get.put(Controller()); 
// 而不是 Controller controller = Controller();
说明: Get实例中实例化它,而不是在你正在使用的类中实例化你的类,这将使它在整个App中可用。
//获取依赖:
Get.find<PutController>();

GetX通过Get依赖管理器提供如下4种手动注入依赖的方式:

  • Get.put()
    立即注入内存的注入方法(最常用), 调用后已经注入到内存中, 例如:
Get.put<SomeClass>(SomeClass());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(ListItemController, tag: "some unique string");
//使用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 从内存中删除,这个实例也会被销毁。

  • Get.lazyPut()
    懒加载一个依赖,只有在使用时才会被实例化。适用于不确定是否会被使用的依赖或者计算高昂的依赖。类似 Kotlin 的 Lazy 代理。, 例如:
///只有当第一次使用Get.find<ApiMock>时,ApiMock才会被调用。
Get.lazyPut<ApiMock>(() => ApiMock());

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.putAsync<S>(
  // 必备:一个将被执行的异步方法,用于实例化你的类。
  AsyncInstanceBuilderCallback<S> builder,
  // 可选:和Get.put()一样,当你想让同一个类有多个不同的实例时,就会用到它。
  // 必须是唯一的
  String tag,
  // 可选:与Get.put()相同,当你需要在整个应用程序中保持该实例的生命时使用。
  // 默认值为false
  bool permanent = false
)
  • Get.create()
    这个方法可以创建很多实例。很少用到。可以当做Get.put
Get.create<S>(
  // 需要:一个返回每次调用"Get.find() "都会被新建的类的函数。
  // 示例: Get.create<YourClass>(()=>YourClass())
  FcBuilderFunc<S> builder,
  // 可选:就像Get.put()一样,但当你需要多个同类的实例时,会用到它。
  // 当你有一个列表,每个项目都需要自己的控制器时,这很有用。
  // 需要是一个唯一的字符串。只要把标签改成名字
  String name,
  // 可选:就像 Get.put() 一样,
  // 它是为了当你需要在整个应用中保活实例的时候
  // 区别在于 Get.create 的 permanent默认为true
  bool permanent = true

GetX通过Bindings类提供的自动注入依赖的方式:

上述通过Get实现的依赖注入和使用, 为了生命周期和使用的 Widget 绑定,需要在 Widget 里注入和使用,并没有完全解耦。要实现自动注入,就需要这个Bindings类。

Bindings类

  • 创建一个类并实现Binding,IDE会自动重写 "dependencies"方法,然后插入在该路由上使用的所有类。
class DetailsBinding implements Bindings {
  @override
  void dependencies() {
    Get.lazyPut<DetailsController>(() => DetailsController());
  }
}
  • 通知路由,使用该 Binding 来建立路由管理器、依赖关系和状态之间的连接。

  • 使用别名路由:

getPages: [
  GetPage(
    name: '/details',
    page: () => DetailsView(),
    binding: DetailsBinding(),
  ),
];
  • 使用正常路由。
Get.to(DetailsView(), binding: DetailsBinding())

至此,就不必再担心应用程序的内存管理,Get会自动做这件事。

上面注入依赖解耦了,但是获取还是略显不方便,可使用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());
    }),

上述两种方式都可以,可根据自己的编码习惯选择最适合的风格。

Bindings的工作原理

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

智能管理

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

如何改变配置? 方法如下。

void main () {
  runApp(
    GetMaterialApp(
      smartManagement: SmartManagement.onlyBuilders //这里
      home: Home(),
    )
  )
}
  • SmartManagement.full
    这是默认的。销毁那些没有被使用的、没有被设置为永久的类。在大多数情况下,你会希望保持这个配置不受影响。如果你是第一次使用GetX,那么不要改变这个配置。

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

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

    在默认行为下,即使是用 "Get.put "实例化的widget也会被移除,这与SmartManagement.onlyBuilders不同。

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