Модель поставщика (или, в более общем смысле, модель подписчик-слушатель) не нарушает инкапсуляцию. Для нарушения инкапсуляции изменения в одном объекте напрямую приводят к изменению другого объекта. Например, если у вас есть эти два класса:
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
в целях тестирования.)
С провайдером он реализует DI, используя несколько иной подход. Вместо использования конструкторов поставщик встраивает ресурсы в дерево виджетов. Виджеты из этой точки в дереве вниз смогут получить этот ресурс динамически, но виджеты, расположенные выше этой точки или в другом разделе дерева, не будут иметь к нему доступа. Вот как провайдер реализует DI, и это то, что отделяет его от глобальных переменных.