阅读 496

Flutter 入门与实战(八十二):想切就切!看如何使用依赖注入快速切换Mock数据和真实接口

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

我们上一篇介绍了依赖注入的概念和使用抽象接口降低与依赖对象耦合,本篇我们以一个实例来讲解如何应用依赖注入这一特性。

抽奖程序的改造

科学揭秘为什么在掘金抽奖总是抽了个寂寞中我们使用了虚拟的奖品信息,这就是我们Mock 数据。但是这样有个缺陷,更改为真实接口数据的时候,会需要对整个程序进行修改;假设又要切回 Mock 数据又要改代码,这样的效率会极其低效。我们可以使用依赖注入的方式来解决这个问题。

抽象接口类

抽奖程序只有一个获取抽奖对象的行为,因此我们定义一个获取抽奖数据的抽象接口类,有一个列出全部抽奖奖品的的空方法 listAll

import '../lottery/lottery_entity.dart';

abstract class LotteryService {
  Future<List<LotteryEntity>?> listAll();
}
复制代码

接口实现类

接口实现类分为两个,一个是 Mock 数据,一个是真实的接口,其中真实的接口我们从掘金获取数据,接口地址为:api.juejin.cn/growth_api/…。 Mock 数据获取的实现代码如下:

import '../lottery/lottery_entity.dart';
import 'lottery_service.dart';

class LotteryServiceMock implements LotteryService {
  Future<List<LotteryEntity>?> listAll() async {
    return [
      LotteryEntity(
        id: '1',
        name: '66矿石',
        imageUrl:
            'https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/32ed6a7619934144882d841761b63d3c~tplv-k3u1fbpfcp-no-mark:0:0:0:0.image',
        probability: 7000,
      ),
      LotteryEntity(
        id: '2',
        name: '掘金限量徽章',
        imageUrl:
            'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3ae4860cad7e48468885b2912271e544~tplv-k3u1fbpfcp-no-mark:0:0:0:0.image',
        probability: 100,
      ),
      
      // ...省略其他奖品
    ];
  }
}

复制代码

真实接口实现的代码如下:

import '../utils/http_util.dart';
import '../lottery/lottery_entity.dart';
import 'lottery_service.dart';

class LotteryServiceImpl implements LotteryService {
  Future<List<LotteryEntity>?> listAll() async {
    var response = await HttpUtil.getDioInstance()
        .get('https://api.juejin.cn/growth_api/v1/lottery_config/get');
    if (response.statusCode == 200) {
      if (response.data['err_no'] == 0) {
        List<dynamic> _jsonItems = response.data['data']['lottery'];
        return _jsonItems.map((json) => LotteryEntity.fromJson(json)).toList();
      }
    }

    return null;
  }
}
复制代码

可以看到 Mock 代码和真实接口的代码实现是不同的,但是他们对外的接口都是一样 的,即使用 implements 关键字声明实现了 LotteryService 接口,并都实现LotteryServicelistAll方法——这是对外提供服务的方法。

使用依赖注入

我们这里对抽奖的 Controller 做一下改造,在构造方法中传入LotteryService对象给属性lotteryService。然后在 onReady 中请求奖品数据。

class LotteryController extends GetxController {
  final LotteryService lotteryService;
  LotteryController(this.lotteryService);

	// ...
  var _lotteries = Rx<List<LotteryEntity>>([]);
  get lotteries => _lotteries.value;
  
  @override
  void onReady() async {
    var lotteriesFetched = await lotteryService.listAll();
    if (lotteriesFetched != null) {
      _lotteries.value = lotteriesFetched;
    }

    super.onReady();
  }
  
  //...
}
复制代码

这样在构建 LotteryController 的时候就可以直接使用其构造方法注入具体的实现类了。

// 使用真实接口
final LotteryController lotteryController =
      LotteryController(LotteryServiceImpl());

// 使用 Mock数据
final LotteryController lotteryController =
      LotteryController(LotteryServiceMock());
复制代码

使用IoC容器

其实如果一个接口实现类只在一个地方使用,那么不使用IoC容器也没为什么问题,但是如果一个实现类在多处使用,那么每次构建去构建对象可能会造成资源浪费,而且一定程度上也暴露了实现类,这个时候就可以利用 IoC 容器。IoC的容器可以简单地理解为是一个 Map 对象,类名作为键,具体对象作为值存储,这样就可以根据类名(相同的类名可以加别的属性区分,但不推荐)来找到容器中对应的对象。 有了 IoC 容器后,我们的业务将更加简单,业务方需要什么服务,直接从容器中取就可以了,而不需要自己再去构建服务对象。 GetX 提供了4个方法往容器存储对象,除了create方法每次都会创建新的依赖对象以外,其他默认都是单例的(除非更改 tag 参数)。分别如下:

// 直接放入依赖对象,
Get.put(S dependency, ...);

// 懒加载方式,在使用的时候如果没有实例对象调用 builder 构建
void lazyPut<S>(InstanceBuilderCallback<S> builder, ...);

// 异步获取实例对象
Future<S> putAsync<S>(AsyncInstanceBuilderCallback<S> builder, ...);

// 每次构建新的实例对象
void create<S>(InstanceBuilderCallback<S> builder, ...);
复制代码

为避免造成资源浪费和提高对象复用性,没有特殊情况下使用懒加载方式lazyPut即可。当然,如果对象需要异步获取,那么可以使用putAsync

从 GetX 的容器获取对象使用 find()方法,通过泛型 Stag 可以精准获取容器的某一个实例对象。

S find<S>({String? tag})
复制代码

要在业务中使用容器中的对象前提是要在业务代码前向容器注册对象,因此实际使用中可以写一个容器注册类,统一在main 方法中完成容器对象注册。这里我们直接在 main方法注册了 LotteryService

void main() {
  Get.lazyPut<LotteryService>(() => LotteryServiceImpl());
  runApp(MyApp());
}
复制代码

然后在将对象注入到业务类的时候,只需要从容器中获取对应的对象即可。

final LotteryController lotteryController =
      LotteryController(Get.find<LotteryService>());
复制代码

通过这种方式,我们在切换 Mock 数据和真实接口时只需要修改容器注册的代码即可——即便这个服务对象在多个业务类使用,我们也只需要修改一处代码。比如我们要从真实接口切换为 Mock 数据,只需要修改 main 中的容器对象注册的实现类,把LotteryServiceImpl换成LotteryServiceMock即可:

void main() {
  Get.lazyPut<LotteryService>(() => LotteryServiceMock());
  runApp(MyApp());
}
复制代码

总结

本篇以实例的方式介绍了依赖注入和 IoC 容器的应用,这其中降低耦合的核心其实是面向接口编程的思想和容器。通过面向接口编程,使得业务是业务实现类解耦。通过容器,简化了一个对象被多个业务使用时的构建和使用。通过依赖注入和 IoC 容器,既降低了代码维护工作,同时也降低了业务代码和服务实现对象的耦合。

我是岛上码农,微信公众号同名,这是Flutter 入门与实战的专栏文章,提供体系化的 Flutter 学习文章。对应源码请看这里:Flutter 入门与实战专栏源码。如有问题可以加本人微信交流,微信号:island-coder

👍🏻:觉得有收获请点个赞鼓励一下!

🌟:收藏文章,方便回看哦!

💬:评论交流,互相进步!

文章分类
Android
文章标签