乐趣区

关于flutter:流动的观察者模式-Flutter-设计模式

观察者模式 ,又称公布订阅模式,是一种行为设计模式——你能够定义一种订阅机制,可在对象事件产生时告诉多个 察看 该对象的其余对象。

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。

这个主题对象在状态上发生变化时,会告诉所有观察者对象,让它们可能自动更新本人。

从定义中,不难发现,观察者 被观察者 / 发布者 是这个模式中最重要的组成元素。

微信的公众号能够被视为生存中最典型的观察者模式的例子。如果你订阅了「Flutter 社区」,每当 Flutter 社区公布文章时,就会给你及其他订阅者推送这个音讯,这其中你就是 观察者 ,公众号「Flutter 社区」就是 被观察者 (Observable) 或发布者 (Subject)

观察者模式常常被利用在这类事件处理零碎中,从概念上了解,被观察者也常常被称作是 事件流 (stream of events) 或者说是 事件流的起源 (stream source of events),而观察者相当于 事件接收器 (sinks of events)

同时,观察者模式也是实现 响应式编程 的根底,RxDart、EventBus 等库都是观察者模式下的产物。

面向对象

面向对象中,观察者和和发布者 (被观察者) 别离对应两个类 (Observer 和 Subject) 的对象。

公布类 (Subject) 中通常会有提供给每个对象订阅或勾销订阅发布者事件流的 订阅机制,包含:

  1. 一个用于存储订阅者对象援用的列表成员变量;
  2. 几个用于增加或删除该列表中订阅者的私有办法。
// 被观察者
class Subject {
  List<Observer> _observers;
  Subject([List<Observer> observers]) {_observers = observers ?? [];
  }

  // 注册观察者
  void registerObserver(Observer observer) {_observers.add(observer);
  }
  
  // 解注册观察者
  void unregisterObserver(Observer observer) {_observers.remove(observer)
  }

  // 告诉观察者
  void notifyobservers(Notification notification) {for (var observer in _observers) {observer.notify(notification);
    }
  }
}

此时,每当事件产生,它只需遍历订阅者并调用其对象的特定告诉办法即可 (如下面代码中的 notifyobservers 办法)。

理论利用中,一个发布者通常会对应多个订阅者,且发布者与订阅者该当遵循面向对象的开发设计准则,因而:

  1. 为了防止耦合,订阅者们必须实现同样的接口;
  2. 发布者仅通过该接口与订阅者交互,接口办法能够申明参数,这样发布者在发出通知时就能传递一些上下文数据 (如上面代码中的 notification 对象)。
// 观察者
class Observer {
  String name;
  
  Observer(this.name);

  void notify(Notification notification) {print("[${notification.timestamp.toIso8601String()}] Hey $name, ${notification.message}!");
  }
}

这样,咱们能够得出如下这样用 Dart 语言实现的观察者模式了,上面是一个简略的利用:

// 具体的被观察者 CoffeeMaker
// 每当 Coffee 制作实现发出通知给观察者。class CoffeeMaker extends Subject {CoffeeMaker([List<Observer> observers]) : super(observers);
  
  void brew() {print("Brewing the coffee...");
    notifyobservers(Notification.forNow("coffee's done"));
  }
}

void main() {var me = Observer("Tyler");
  var mrCoffee = CoffeeMaker(List.from([me]));
  var myWife = Observer("Kate");
  mrCoffee.registerObserver(myWife);
  mrCoffee.brew();}

这里的 CoffeeMaker 继承自 Subject,作为一个具体的公布类,brew() 办法是其外部,每当咖啡制作实现后,用于告诉其余各个观察者的办法。下面代码中,咱们在 mrCoffee 这台咖啡机上注册了 myWife 这一个观察者,mrCoffee.brew(); 触发后,myWife 外部的 notify 办法就会被调用。

观察者模式很好的实现了他们两者之间公布订阅的关系,在理论利用中,被观察者正在解决的事件很可能是异步的,而作为观察者不用显示的去阻塞期待事件的实现,而是由被观察者告诉,当事件实现后,再将事件被动地「推」给关怀这个事件的观察者。与之绝对的,有一类观察者也会应用后盾线程时刻轮询地监听着其关怀的主题事件,这个话题咱们暂不开展。

观察者模式应用不慎的话,也很容易呈现传说中的 生效监听器 问题,导致内存透露,因为在根本实现中,被观察者仍然持有观察者的强援用,如果事件中途,被观察者曾经不存在时或不再关怀此事件,就会导致观察者无奈被回收,因而,咱们在这种状况下该当在被察看中做好勾销订阅的机制,及时开释无用的资源。

Dart

Stream 能够被看作是 Dart 语言原生反对的观察者模式的典型模型之一,它自身是 Dart:async 包中一个用于异步操作的类,响应式编程库 RxDart 也是基于 Stream 封装而成的。

从概念上讲,咱们能够将 Stream 看做是一个能够连贯两端的传送带,作为开发者,咱们能够在传送带的一端放入数据,Stream 就会将这些数据传送到另一端。

和事实中的状况相似,如果传送带的另一端没有人承受数据,这些数据就会被程序抛弃,因而,咱们通常会在传送到尾端安顿一个接收数据的对象,在响应式编程中,它被称为数据的观察者。

如果说上文 Dart 面向对象中,观察者和被观察者两者的关系是在尽量放弃低耦合的状况下而造成的,绝对独立。那么在响应式编程中,它们的关系就是变得更加严密的 上游与上游 的关系。

因为 Stream,顾名思义,就是「流」的含意,被观察者在流的入口产生事件,观察者则在流的进口期待数据或事件的到来。

在这套流程里,观察者的 订阅 与被观察者的 事件公布 等一系列操作都间接在 Stream 或者说是框架外部实现的。

Dart 中,咱们能够应用 StreamController 来创立流:

var controller = new StreamController<int>();

controller.add(1); // 将数据放入流中

如下面代码所示,创立 StreamController 时必须指定泛型类型来定义能够退出 Stream 的数据对象,下面的 controller 能够承受 int 类型的数据,咱们应用它的 add 办法就能够将数据放入到它的传送带中。

如果咱们间接运行下面的两行代码,最终并不会不到任何后果,因为咱们还没有为传送带设置接收数据的对象:

var controller = new StreamController<int>();

controller.stream.listen((item) => print(item)); // 数据观察者函数

controller.add(1);
controller.add(2);
controller.add(3);

下面的代码中,咱们通过调用 StreamController 外部的 stream 对象的 listen 办法,就能够为 controller 对象增加监听这个 Stream 事件的观察者,这个办法承受一个回调函数,这个回调函数又承受一个咱们在 new StreamController<int>() 泛型中申明的数据对象作为参数。

这时,每当咱们再次通过 add 办法将数据放入传送带后,就会告诉观察者,调用这个函数,并将传递的数据打印进去:

1
2
3

另外,咱们也能够使观察者在某个时间段后进行监听 Stream 中传递的数据,在下面代码中的 listen 函数会返回一个 StreamSubscription 类型的订阅对象,当咱们调用它的 .cancel() 后就会开释这个观察者,不再接收数据:

var controller = new StreamController<String>();

StreamSubscription subscription = controller.stream.listen((item) => print(item));

controller.add(1);
controller.add(2);
controller.add(3);

await Future.delayed(Duration(milliseconds: 500));

subscription.cancel();

Flutter

ChangeNotifier

ChangeNotifier 大略是 Flutter 中实现观察者模式最典型的例子了,它实现自 Listenable,外部保护一个 _listeners 列表用来寄存观察者,并实现了 addListenerremoveListener 等办法来实现其外部的订阅机制:

class ChangeNotifier implements Listenable {LinkedList<_ListenerEntry>? _listeners = LinkedList<_ListenerEntry>();

  @protected
  bool get hasListeners {return _listeners!.isNotEmpty;}
  
  @override
  void addListener(VoidCallback listener) {_listeners!.add(_ListenerEntry(listener));
  }

  @override
  void removeListener(VoidCallback listener) {for (final _ListenerEntry entry in _listeners!) {if (entry.listener == listener) {entry.unlink();
        return;
      }
    }
  }

  @mustCallSuper
  void dispose() {_listeners = null;}

  @protected
  @visibleForTesting
  void notifyListeners() {if (_listeners!.isEmpty)
      return;
    final List<_ListenerEntry> localListeners = List<_ListenerEntry>.from(_listeners!);
    for (final _ListenerEntry entry in localListeners) {
      try {if (entry.list != null)
          entry.listener();} catch (exception, stack) {// ...}
    }
  }
}

在理论应用时,咱们只须要继承 ChangeNotifier 便能具备这种订阅机制,如下这个 CartModel 类:

class CartModel extends ChangeNotifier {final List<Item> _items = [];

  UnmodifiableListView<Item> get items => UnmodifiableListView(_items);

  int get totalPrice => _items.length * 42;

  void add(Item item) {_items.add(item);
    notifyListeners();}

  void removeAll() {_items.clear();
    notifyListeners();}
}

CartModel 外部保护一个 _items 数组,addremoveAll 办法时提供给内部操作该数组的接口,每当 _items 扭转则会调用 notifyListeners() 告诉它的所有观察者。

ChangeNotifier 作为 flutter:foundation 中最根底的类,不依赖其余任何下层的类,测试起来也非常简单,咱们能够针对 CartModel 做一个简略的单元测试:

test('adding item increases total cost', () {final cart = CartModel();
  final startingPrice = cart.totalPrice;
  cart.addListener(() {expect(cart.totalPrice, greaterThan(startingPrice));
  });
  cart.add(Item('Dash'));
});

这里,当咱们调用 cart.add(Item('Dash')); 后,就是会触发观察者函数的调用,实现一种由数据的扭转驱动事件执行的机制。

Flutter 利用中最传统的状态治理计划是应用有状态 widget 的 setState 的办法,这种形式裸露进去的问题是,大型利用中的 widget 树会非常复杂,每当状态更新调用 setState 时,则会牵一发而动全身,重建所有子树,使性能大打折扣。

那么,当将 ChangeNotifier 观察者模式利用在状态治理计划中时,便能解决这个问题。构想让每一个最小组件充当观察者,察看利用的状态,每当状态扭转时即驱动该部分小组件更新,是不是就能达到这种目标。咱们罕用 provider 库就利用了这个原理。

provider 外部提供了一个 ChangeNotifierProvider widget,能够向其子组件裸露一个 ChangeNotifier 实例 (被观察者):

void main() {
  runApp(
    ChangeNotifierProvider(create: (context) => CartModel(),
      child: const MyApp(),),
  );
}

在子组件中,只须要应用 Consumer widget 注册观察者组件,就能接管到 CartModel 外部数据更新的告诉:

return Consumer<CartModel>(builder: (context, cart, child) {return Text("Total price: ${cart.totalPrice}");
  },
);

这里,应用 Consumer 必须指定要察看的 ChangeNotifier 类型,咱们要拜访 CartModel 那么就写上 Consumer<CartModel>,builder 最为 Consumer 惟一一个必要参数,用来构建展现在页面中的子组件。

ChangeNotifier 发生变化的时候会调用 builder 这个函数。(换言之,当调用 CartModelnotifyListeners() 办法时,所有相干的 Consumer widget 的 builder 办法都会被调用。),重建子树,达到部分更新状态的目标。

Navigator

路由是在 Flutter 利用中常去探讨的话题,在整个利用运行过程中,路由操作也都须要被时刻关注着,它是咱们理解用户行为的一种无效的形式。Flutter 提供了一套很不便的观察者模式的模型帮忙咱们实现这个功要求。

Flutter 中每个 Navigator 对象都承受一个 NavigatorObserver 对象的数组,在理论开发过程中,咱们能够通过根组件 MaterialApp (或 CupertinoPageRoute) 的 navigatorObservers 属性传递给根 Navigator 组件,用于察看根 Navigator 的路由行为,这一组 NavigatorObserver 对象就是一系列的路由观察者。

 Widget build(BuildContext context) {
    return new MaterialApp(navigatorObservers: [new MyNavigatorObserver()],
      home: new Scaffold(body: new MyPage(),
      ),
    );
  }

路由观察者们对立继承自 RouteObserver,范型类型为 PageRoute,这时,它就能监听 CupertinoPageRoute 和 MaterialPageRoute 两种类型的路由了:

class MyRouteObserver extends RouteObserver<PageRoute<dynamic>> {

  // 监听导航器的 push 操作
  @override
  void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {super.didPush(route, previousRoute);
    if (previousRoute is PageRoute && route is PageRoute) {print('${previousRoute.settings.name} => ${route.settings.name}');
    }
  }

  // 监听导航器的 replace 操作
  @override
  void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) {super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
    if (newRoute is PageRoute) {print('${oldRoute.settings.name} => ${oldRoute.settings.name}');
    }
  }

  // 监听导航器的 pop 操作
  @override
  void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {super.didPop(route, previousRoute);
    if (previousRoute is PageRoute && route is PageRoute) {print('${route.settings.name} => ${previousRoute.settings.name}');
    }
  }
}

在咱们做理论路由操作,调用 Navigatorpoppush 等办法时,就会依照常规遍历调用这些观察者对象对应的办法:

 Future<T> push<T extends Object>(Route<T> route) {
  // ...
  for (NavigatorObserver observer in widget.observers)
    observer.didPush(route, oldRoute);
    // ...
}

这样,观察者模式在 Flutter 路由中又实现了这个十分重要的工作。

本文小结

本文内容到这里就完结了,观察者模式的场景例子不可胜数,在理论开发中,咱们也会常常须要应用到,但咱们要记住的是设计模式的使用并不是套用模版,而是要依据理论场景找到最合适的解决方案。

对于行为型模式来说,观察者模式将被观察者与观察者这两件事物形象进去,实现了代码上的解藕,在理论场景中,观察者可能是关怀某种状态的组件,监听某个事件的监听器等等,整体的设计也会变得更加直观,心愿大家能在当前的开发中多多应用。

拓展浏览

  • 《Flutter 开发之旅从南到北》—— 第 8 章 路由治理 & 第 9 章 状态治理
  • 观察者模式 wikipedia
  • Design Patterns in Dart
  • 什么是 Stream
  • 简略的利用状态治理

对于本系列文章

Flutter / Dart 设计模式从南到北 (简称 Flutter 设计模式) 系列内容由 CFUG 社区成员、《Flutter 开发之旅从南到北》作者、小米工程师杨加康撰写并公布在 Flutter 社区公众号和 flutter.cn 网站的社区教程栏目。

本系列预计两周公布一篇,着重向开发者介绍 Flutter 利用开发中常见的设计模式以及开发方式,旨在推动 Flutter / Dart 语言个性的遍及,以及帮忙开发者更高效地开发出高质量、可保护的 Flutter 利用。

退出移动版