场景
在实际中存在全局状态需要与局部状态进行交互,例如用户登录成功后需要通知业务模块更新数据(比如后台上传位置,开始刷新拉取数据)。传统的方式中需要在局部状态向全局状态主动订阅消息,当全局状态改变时再通知局部状态处理业务。这样存在如下缺陷:
- 未使用局部状态前也需要创建局部状态实例(因需要发起全局状态消息订阅)
- 局部状态需要调用全局状态的消息订阅方法,出现了耦合。
主动订阅方式
以之前的主动订阅方式,使用用户登录为例。这里定义两个状态管理,一个是用户登录退出全局状态UserLoginStore,一个是消息接收局部状态MessageStore。在用户登录和用户退出的时候需要MessageStore进行登录成功和退出登录后的业务处理。原先的两个类定义如下:
基本类图
代码实现上,以Provider为例,首先是在顶层widget注册全局状态管理UserLoginStore,同时还需要将MessageStore实例化(或者也注册为全局状态)。
Widget myApp = MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => UserLoginStore(),
lazy: false,
),
],
child: MyApp(),
);
UsreLoginStore的实现如下,在登录成功和退出登录时,通过调用对应listeners的Function对象,执行相应的函数进行响应。
class UserLoginStore with ChangeNotifier {
List<Function()> loginListeners = [];
List<Function()> logoutListeners = [];
void login() async {
//省略调用登录接口代码
if (loginListeners != null) {
loginListeners.forEach((func) {
func();
})
}
}
void logout() async {
//省略调用退出登录接口代码
if (logoutListeners != null) {
logoutListeners.forEach((func) {
func();
})
}
}
void addLoginListeners(Function func) {
loginListeners.add(func);
}
void addLogoutListeners(Function func) {
logoutListeners.add(func);
}
}
MessageStore的实现方法如下:
class MessageStore with ChangeNotifier {
void _requestMessage() async {
//省略后台接口请求消息代码
notifyListeners();
}
void _clearMessage() async {
//省略将消息列表清空方法
notifyListeners();
}
void registerLoginListener() {
final userLoginStore = Provider.of<UserLoginStore>();
//注册登录成功订阅,成功后请求消息
userLoginStore.addLoginListeners(() {
_requestMessage();
});
}
void addLogoutListers(Function func) {
final userLoginStore = Provider.of<UserLoginStore>();
//注册退出登录订阅,退出登录后清空消息
userLoginStore.addLogoutListeners(() {
_clearMessage();
});
}
}
从代码可以看出UserLoginStore的addLoginListeners和addLogoutListeners暴露给了MessageStore,增加了耦合。而且因为是主动订阅,会需要提前实例化MessageStore,哪怕是一开始没有任何用户信息。
使用容器和通过接口解耦
为了对UserLoginStore和MessageStore进行解耦,更改一下类图。
引入抽象类解耦
增加UserLoginService抽象类,定义登录成功和登录失败的处理接口方法。此时MessageStore需要实现UserLoginService的loginHandler和logoutHandler方法。代码修改如下:
class MessageStore with ChangeNotifier, UserLoginService {
void _requestMessage() async {
//省略后台接口请求消息代码
notifyListeners();
}
void _clearMessage() async {
//省略将消息列表清空方法
notifyListeners();
}
void loginHandler() {
_requestMessage();
}
void logoutHandler() {
_clearMessage();
}
}
从代码里看,已经完全不依赖于UserLoginStore。<br />这个时候UserLoginStore如何去通知MessageStore呢?容器登场。使用GetIt组件定义全局容器类,代码如下:
class GlobalServiceRepository {
static void resisterServices() {
GetIt getIt = GetIt.instance;
getIt.registerLazySingleton<MessageStore>(() => MessageStore());
getIt.registerLazySingleton<List<UserLoginService>>(() {
return [getService<MessageStore>()];
});
}
static T getService<T>() {
GetIt getIt = GetIt.instance;
return getIt<T>();
}
}
代码也很简单,首先为了保证在需要的时候拿到MessageStore的实例,通过registerLazySingleton<MessageStore>注册一个懒加载的MessageStore。然后考虑UserLoginStore可能与多个局部Store关联,一次注册一个List<UserLoginService>,注意这里使用的是抽象类UserLoginService了,也是懒加载的方式返回一个List,这个List其实就是在用户登录成功或退出登录需要通知的对象。这个List里通过getService<MessageStore>()返回了UserLoginStore需要通知的MessageStore示例。<br />再来看UserLoginStore的实现代码。
class UserLoginStore with ChangeNotifier {
void login() async {
//省略调用登录接口代码
//从容器取出需要通知的listeners对象
List<UserLoginService> listners = GlobalServiceRepository.getService<List<UserLoginService>>();
listners.forEach((listener) {
listener.loginHandler();
});
}
void logout() async {
//省略调用退出登录接口代码
List<UserLoginService> listners = GlobalServiceRepository.getService<List<UserLoginService>>();
listners.forEach((listener) {
listener.logoutHandler();
});
}
}
从代码里可以看到,UserLoginStore只需要关心容器里是否有需要通知的listeners即可,任何注册到容器的UserLoginService的实现类都可以接收到登录和退出登录的消息。
网友评论