我在自学Flutter,之前学习过传统的面向对象语言。我还是一个初学者,但很明显,状态管理是Flutter中的一个关键问题,所以我正在学习它(主要是与提供商一起学习(。
我正在学习的大部分内容似乎都是使用全局变量,从其他类设置和调用全局变量(在后一种情况下使用notifyListener()
调用(。但当我了解OOP时,我被教导这是一件"糟糕"的事情。一个对象可能会无意中更改变量的值,从而破坏另一个对象。换句话说,封装是好的,全局变量是坏的——它们违反了封装思想。
我错过了什么?
提供者模型(或者更一般地说,订阅者-侦听器模型(不会破坏封装。对于要破坏的封装,一个对象中的更改直接导致另一个对象的突变。例如,如果您有以下两个类:
class A {
int x;
B b;
}
class B {
String s;
A a;
}
所以这里我们有一个A
和B
之间的共同依赖关系。现在说A
中有一种改变其状态的方法:
void changeState(int i) {
this.x = i;
b.s = i.toString();
}
这破坏了封装,因为A
直接改变了B
的状态,这可能会导致B
试图在外部突变状态下操作时出现一些功能中断。
现在假设没有明确定义的共同依赖,A
和B
通过事件总线进行通信:
// A
void changeState(int i) {
this.x = i;
fireEvent('A-changed', this);
}
// B
listenToEvent<A>('A-changed', handleEvent);
...
void handleEvent(A source) {
this.s = source.x.toString();
}
现在保持了封装,因为A
和B
正在通信它们的状态更改,并且每个只负责维护自己的状态。
这正是在具有ChangeNotifier
的提供程序中发生的情况。当一个对象更新其状态,然后调用notifyListeners
时,Flutter使用内部事件总线来通知正在侦听该对象的任何小部件,无论是显式的还是通过提供者的Provider.of
或使用Consumer
。对象并没有直接导致小部件重建,而是通过事件总线进行通信,并通知小部件应该重建自己。这保留了封装,因为所涉及的每个对象都只对自己的状态负责。
至于提供者与全局变量的区别,那是因为提供者使用了一种称为"依赖注入"(简称DI(的模式。使用DI,您可以获取一个非全局对象,并将其"注入"到"依赖"它的小部件中。这通常通过构造函数来完成,即:
class SomeService {
Database db;
SomeService(this.db);
}
在该示例中,SomeService
类需要与数据库通信,但它没有调用某些全局数据库服务,而是在创建时将Database
对象传递给它。这为SomeService
提供了一个可以在不依赖全局对象的情况下进行通信的数据库。(这也允许您出于测试目的模拟Database
对象。(
对于provider,它使用稍微不同的方法来实现DI。提供者不使用构造函数,而是将资源嵌入到小部件树中。然后,从树中该点向下的小部件将能够动态检索该资源,但位于该点以上或树的不同部分的小部件无法访问该资源。这就是提供者实现DI的方式,也是它与全局变量的区别。