Управление состоянием и глобальные переменные - PullRequest
1 голос
/ 08 мая 2020

Я учу себя Flutter, предварительно изучив традиционные объектно-ориентированные языки. Я все еще новичок, но ясно, что управление состоянием - ключевая проблема Flutter, поэтому я узнаю об этом (в основном с провайдерами).

Многое из того, что я изучаю, похоже на использование глобальных переменных, установленных и вызываемых из других классов (с вызовами notifyListener() в последнем случае). Но когда я узнал о OOP, меня учили, что это «плохо». Один объект может непреднамеренно изменить значение переменной, нарушив работу другого объекта. Другими словами, инкапсуляция - это хорошо, глобальные переменные - плохо - они нарушают идею инкапсуляции.

Что мне не хватает?

1 Ответ

2 голосов
/ 08 мая 2020

Модель поставщика (или, в более общем смысле, модель подписчик-слушатель) не нарушает инкапсуляцию. Для нарушения инкапсуляции изменения в одном объекте напрямую приводят к изменению другого объекта. Например, если у вас есть эти два класса:

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, и это то, что отделяет его от глобальных переменных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...