場(chǎng)景
在實(shí)際中存在全局狀態(tài)需要與局部狀態(tài)進(jìn)行交互,例如用戶(hù)登錄成功后需要通知業(yè)務(wù)模塊更新數(shù)據(jù)(比如后臺(tái)上傳位置,開(kāi)始刷新拉取數(shù)據(jù))。傳統(tǒng)的方式中需要在局部狀態(tài)向全局狀態(tài)主動(dòng)訂閱消息,當(dāng)全局狀態(tài)改變時(shí)再通知局部狀態(tài)處理業(yè)務(wù)。這樣存在如下缺陷:
- 未使用局部狀態(tài)前也需要?jiǎng)?chuàng)建局部狀態(tài)實(shí)例(因需要發(fā)起全局狀態(tài)消息訂閱)
- 局部狀態(tài)需要調(diào)用全局狀態(tài)的消息訂閱方法,出現(xiàn)了耦合。
主動(dòng)訂閱方式
以之前的主動(dòng)訂閱方式,使用用戶(hù)登錄為例。這里定義兩個(gè)狀態(tài)管理,一個(gè)是用戶(hù)登錄退出全局狀態(tài)UserLoginStore,一個(gè)是消息接收局部狀態(tài)MessageStore。在用戶(hù)登錄和用戶(hù)退出的時(shí)候需要MessageStore進(jìn)行登錄成功和退出登錄后的業(yè)務(wù)處理。原先的兩個(gè)類(lèi)定義如下:

代碼實(shí)現(xiàn)上,以Provider為例,首先是在頂層widget注冊(cè)全局狀態(tài)管理UserLoginStore,同時(shí)還需要將MessageStore實(shí)例化(或者也注冊(cè)為全局狀態(tài))。
Widget myApp = MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => UserLoginStore(),
lazy: false,
),
],
child: MyApp(),
);
UsreLoginStore的實(shí)現(xiàn)如下,在登錄成功和退出登錄時(shí),通過(guò)調(diào)用對(duì)應(yīng)listeners的Function對(duì)象,執(zhí)行相應(yīng)的函數(shù)進(jìn)行響應(yīng)。
class UserLoginStore with ChangeNotifier {
List<Function()> loginListeners = [];
List<Function()> logoutListeners = [];
void login() async {
//省略調(diào)用登錄接口代碼
if (loginListeners != null) {
loginListeners.forEach((func) {
func();
})
}
}
void logout() async {
//省略調(diào)用退出登錄接口代碼
if (logoutListeners != null) {
logoutListeners.forEach((func) {
func();
})
}
}
void addLoginListeners(Function func) {
loginListeners.add(func);
}
void addLogoutListeners(Function func) {
logoutListeners.add(func);
}
}
MessageStore的實(shí)現(xiàn)方法如下:
class MessageStore with ChangeNotifier {
void _requestMessage() async {
//省略后臺(tái)接口請(qǐng)求消息代碼
notifyListeners();
}
void _clearMessage() async {
//省略將消息列表清空方法
notifyListeners();
}
void registerLoginListener() {
final userLoginStore = Provider.of<UserLoginStore>();
//注冊(cè)登錄成功訂閱,成功后請(qǐng)求消息
userLoginStore.addLoginListeners(() {
_requestMessage();
});
}
void addLogoutListers(Function func) {
final userLoginStore = Provider.of<UserLoginStore>();
//注冊(cè)退出登錄訂閱,退出登錄后清空消息
userLoginStore.addLogoutListeners(() {
_clearMessage();
});
}
}
從代碼可以看出UserLoginStore的addLoginListeners和addLogoutListeners暴露給了MessageStore,增加了耦合。而且因?yàn)槭侵鲃?dòng)訂閱,會(huì)需要提前實(shí)例化MessageStore,哪怕是一開(kāi)始沒(méi)有任何用戶(hù)信息。
使用容器和通過(guò)接口解耦
為了對(duì)UserLoginStore和MessageStore進(jìn)行解耦,更改一下類(lèi)圖。

增加UserLoginService抽象類(lèi),定義登錄成功和登錄失敗的處理接口方法。此時(shí)MessageStore需要實(shí)現(xiàn)UserLoginService的loginHandler和logoutHandler方法。代碼修改如下:
class MessageStore with ChangeNotifier, UserLoginService {
void _requestMessage() async {
//省略后臺(tái)接口請(qǐng)求消息代碼
notifyListeners();
}
void _clearMessage() async {
//省略將消息列表清空方法
notifyListeners();
}
void loginHandler() {
_requestMessage();
}
void logoutHandler() {
_clearMessage();
}
}
從代碼里看,已經(jīng)完全不依賴(lài)于UserLoginStore。<br />這個(gè)時(shí)候UserLoginStore如何去通知MessageStore呢?容器登場(chǎng)。使用GetIt組件定義全局容器類(lèi),代碼如下:
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>();
}
}
代碼也很簡(jiǎn)單,首先為了保證在需要的時(shí)候拿到MessageStore的實(shí)例,通過(guò)registerLazySingleton<MessageStore>注冊(cè)一個(gè)懶加載的MessageStore。然后考慮UserLoginStore可能與多個(gè)局部Store關(guān)聯(lián),一次注冊(cè)一個(gè)List<UserLoginService>,注意這里使用的是抽象類(lèi)UserLoginService了,也是懶加載的方式返回一個(gè)List,這個(gè)List其實(shí)就是在用戶(hù)登錄成功或退出登錄需要通知的對(duì)象。這個(gè)List里通過(guò)getService<MessageStore>()返回了UserLoginStore需要通知的MessageStore示例。<br />再來(lái)看UserLoginStore的實(shí)現(xiàn)代碼。
class UserLoginStore with ChangeNotifier {
void login() async {
//省略調(diào)用登錄接口代碼
//從容器取出需要通知的listeners對(duì)象
List<UserLoginService> listners = GlobalServiceRepository.getService<List<UserLoginService>>();
listners.forEach((listener) {
listener.loginHandler();
});
}
void logout() async {
//省略調(diào)用退出登錄接口代碼
List<UserLoginService> listners = GlobalServiceRepository.getService<List<UserLoginService>>();
listners.forEach((listener) {
listener.logoutHandler();
});
}
}
從代碼里可以看到,UserLoginStore只需要關(guān)心容器里是否有需要通知的listeners即可,任何注冊(cè)到容器的UserLoginService的實(shí)現(xiàn)類(lèi)都可以接收到登錄和退出登錄的消息。