我创建了一个内存通知服务,供在我的 Dart/Flutter 代码库中使用。我开始使用观察者模式,但很快发现订阅深度嵌套视图模型中的更改变得痛苦且令人费解。
目的是创建一个可以以强类型方式发布通知的单例服务。消费者可以订阅有针对性的通知,还可以以强类型的方式接收有效负载。
我编写了两个不同的通知,它们有效地提供了相同的有效负载。第一个有效,第二个抛出以下运行时错误。
Unhandled exception:
type '(ExampleNotification) => void' is not a subtype of type '(PublishedNotification) => dynamic'
#0 NotificationService.publish (package:console/notification_service.dart:42:24)
#1 main (package:console/console.dart:15:23)
#2 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:297:19)
#3 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
每个通知都扩展一个基类
PublishedNotification
。
abstract class PublishedNotification {
String qualifier;
PublishedNotification(this.qualifier);
@override
String toString() {
return 'PublishedNotification{qualifier: $qualifier}';
}
}
import 'package:console/published_notification.dart';
class HelloWorldNotification extends PublishedNotification {
HelloWorldNotification() : super('Hello World Message');
}
import 'package:console/published_notification.dart';
class ExampleNotification extends PublishedNotification {
ExampleNotification() : super('This is an example');
}
每个通知都可以由任何人发布,并且只有订阅者才会收到通知。当对象订阅通知时,会创建一个订阅,其中包含要在订阅者上调用的回调以及订阅所针对的通知类型。
import 'dart:mirrors';
import 'package:console/published_notification.dart';
class Subscription<T extends PublishedNotification> {
final Function(T) subscriberCallback;
Type targetNotification = reflectClass(T).reflectedType;
Subscription(this.subscriberCallback);
}
订阅、发布和取消订阅是通过
NotificationService
完成的。调用 subscribe
方法会让订阅者传递一个回调,每次发布通知时都会调用该回调。它还返回 Subscription
本身,以便调用者可以在处置过程中取消订阅和清理。
当调用
publish
时,它需要一个扩展 PublishedNotification
的参数。使用一些反射来确定类型,然后我从订阅者集合中提取所有订阅并通过通知调用它们的回调。
import 'dart:mirrors';
import 'package:console/published_notification.dart';
import 'package:console/subscription.dart';
class NotificationService {
// List of subscribers grouped by notification type.
final Map<Type, List<Subscription>> subscriptions = <Type, List<Subscription>>{};
// Subscribe to a notification.
// notification is a required named parameter.
// A secondary 'condition' parameter will be added to support conditional pushing a notification to a subscriber based on if the condition passes or not.
Subscription<T> subscribe<T extends PublishedNotification>({required Function(T payload) notification}) {
// Wrap notification callback into a subscriber.
final newSubscriber = Subscription(notification);
ClassMirror notificationClass = reflectClass(T);
Type notificationType = notificationClass.reflectedType;
// Store the subscriber into our subscriptions collection.
late List<Subscription> subscribers;
if (subscriptions[notificationType] == null) {
subscribers = List.empty(growable: true);
subscriptions[notificationType] = subscribers;
}
subscribers.add(newSubscriber);
// Return so that it can be provided during an unsubscribe request.
return newSubscriber;
}
// Publish a notification to all subscribers
void publish<T extends PublishedNotification>(T notification) {
Type notificationType = notification.runtimeType;
if (subscriptions[notificationType] == null) {
return;
}
List<Subscription> subscribers = subscriptions[notificationType]!;
// Invoke the callback delegate we have stored within each subscriber.
for (Subscription targetSubscriber in subscribers) {
targetSubscriber.subscriberCallback(notification);
}
}
// Unsubscribe the
void unsubscribe(Subscription subscriber) {
subscriptions[subscriber.targetNotification]?.remove(subscriber);
}
}
我创建了一个示例控制台 Dart 应用程序来重现我所看到的问题。我订阅了两个不同的通知,发布通知,然后取消订阅。当
HelloWorldNotification
发布时,我的回调被调用,没有任何问题。当 ExampleNotification
发布时,我收到了上面显示的异常。
import 'package:console/example_notification.dart';
import 'package:console/hello_world_notification.dart';
import 'package:console/notification_service.dart';
import 'package:console/published_notification.dart';
import 'package:console/subscription.dart';
void main() {
var notificationService = NotificationService();
Subscription helloWorldSubcription =
notificationService.subscribe<HelloWorldNotification>(notification: handleHelloWorldNotification);
Subscription exampleSubscription =
notificationService.subscribe<ExampleNotification>(notification: handleExampleNotification);
notificationService.publish(HelloWorldNotification());
notificationService.publish(ExampleNotification());
notificationService.unsubscribe(helloWorldSubcription);
notificationService.unsubscribe(exampleSubscription);
// These should not hit the handlers below.
notificationService.publish(HelloWorldNotification());
notificationService.publish(ExampleNotification());
}
void handleHelloWorldNotification(PublishedNotification payload) {
print(payload.qualifier);
}
void handleExampleNotification(ExampleNotification payload) {
print(payload.qualifier);
}
我不明白为什么第一个通知可以正常工作,而第二个通知却不能。从实现的角度来看,它们是相同的,并且回调没有任何不同。我假设这是我如何推断
NotificationService
中的泛型的问题,但我不能 100% 确定为什么会出现这种情况。
我可以在 for 循环中为每个
Subscription
进行强制转换,问题就会消失,一切都会按预期进行。
// Publish a notification to all subscribers
void publish<T extends PublishedNotification>(T notification) {
Type notificationType = notification.runtimeType;
if (subscriptions[notificationType] == null) {
return;
}
List<Subscription> subscribers = subscriptions[notificationType]!;
// Invoke the callback delegate we have stored within each subscriber.
for (Subscription targetSubscriber in subscribers) {
var s = targetSubscriber as Subscription<T>; // Cast here
s.subscriberCallback(notification);
}
}
我的
publish
方法的实现以及我存储订阅回调的方式是否有明显的错误?我已经修复了,但我想了解为什么第一个发布有效而第二个发布无效,除非我强制强制转换。最终是否需要强制转换,或者是否有办法进行一些重构,以便不需要强制转换?
谢谢!
好吧,看来 Flutter 不支持
dart:mirrors
。我不得不寻找一种不同的方式来捕获类型信息,并发现我实际上不需要使用reflactClass
。当我更新 NotificationService
以删除 dart:mirrors
的使用时,异常停止发生。
import 'dart:mirrors';
import 'package:console/published_notification.dart';
import 'package:console/subscription.dart';
class NotificationService {
// List of subscribers grouped by notification type.
final Map<Type, List<Subscription>> subscriptions = <Type, List<Subscription>>{};
// Subscribe to a notification.
@override
Subscription<T> subscribe<T extends PublishedNotification>({required Function(T payload) notification}) {
// Wrap notification callback into a subscriber.
final newSubscriber = Subscription(notification);
// Store the subscriber into our subscriptions collection.
late List<Subscription> subscribers;
if (subscriptions[T] == null) {
subscribers = List.empty(growable: true);
subscriptions[T] = subscribers;
}
subscribers.add(newSubscriber);
// Return so that it can be provided during an unsubscribe request.
return newSubscriber;
}
// Publish a notification to all subscribers
@override
void publish<T extends PublishedNotification>(T notification) {
if (subscriptions[T] == null) {
return;
}
List<Subscription> subscribers = subscriptions[T]!;
// Invoke the callback delegate we have stored within each subscriber.
for (Subscription targetSubscriber in subscribers) {
targetSubscriber.notify(notification);
}
}
// Unsubscribe the
@override
void unsubscribe(Subscription subscriber) {
subscriptions[subscriber.targetNotification]?.remove(subscriber);
}
}
此更改也适用于我的
Subscription
课程。
class Subscription<T extends PublishedNotification> {
final Function(T) _subscriberCallback;
Type targetNotification = T;
Subscription(this._subscriberCallback);
void notify(T notification) {
_subscriberCallback(notification);
}
}
我不再需要演员阵容,一切似乎都按预期进行。我仍然有兴趣了解为什么
reflectClass.reflectedType
会导致问题,但只是将 T
直接传递到所有内容中却不会。
考虑以下代码:
class PublishedNotification {}
class Subscription<T extends PublishedNotification> { }
class GenericClass<T> {}
void main() {
print(GenericClass().runtimeType); // GenericClass<dynamic>
print(Subscription().runtimeType); // Subscription<PublishedNotification>
}
正如您在上面所看到的,由于您使用
T extends PublishedNotification
子句约束泛型类型,因此当您使用 Subscription
作为原始类型时,它现在相当于 Subscription<PublishedNotification>
,而不是您可能期望的 Subscription<dynamic>
。
考虑到这一点,以下代码
for (Subscription targetSubscriber in subscribers) {
targetSubscriber.subscriberCallback(notification);
}
确实是,
for (Subscription<PublishedNotification> targetSubscriber in subscribers) {
targetSubscriber.subscriberCallback(notification);
}
现在您正在使用上述内容和以下回调:
void handleHelloWorldNotification(PublishedNotification payload) {
print(payload.qualifier);
}
void handleExampleNotification(ExampleNotification payload) {
print(payload.qualifier);
}
第一个回调没有问题,因为类型与
PublishedNotification
匹配,但在第二个回调中出现问题。 Subscription<PublishedNotification>
应该能够对任何 PublishedNotification
使用回调,但实际回调仅限于仅接受 ExampleNotification
,这就是发生异常的原因。