Skip to content

Instantly share code, notes, and snippets.

@fesor

fesor/MVC.md Secret

Created April 23, 2016 13:16
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save fesor/e7ccc0467bcb9e6c6a471b9e558d2a6c to your computer and use it in GitHub Desktop.
Save fesor/e7ccc0467bcb9e6c6a471b9e558d2a6c to your computer and use it in GitHub Desktop.
MVC или что скрывается за этими тремя буквами

MVC или что скрывается за этими тремя буквами

Казалось бы, к чему что-то говорить об MVC? Его критикует чуть ли не каждый! Да и 2016-ый год на дворе, а MVC придумали аж в 1979-ом году! Вроде ж уже все разобрались? Вместо того что бы рассказывать что это такое, попробуем поставить себя на место разработчиков в начале 80-х и попробовать проследить ход мысли.

Содержание:

  • Зачем придумали MVC
    • Возможность реюзать элементы графического интерфейса
    • Снижение связанности
  • Именуем вещи
    • Элементы пользовательского интерфейса: представление и редакторы
    • Объект, хранящий состояние: модель
    • Редакторы и модель: контроллер
  • MVC
  • А мне MVC не так удобно
    • MVP
    • MVVM
    • MVA
    • FLUX
  • WEB: тут уж точно все не так... или нет?
  • А как там игры?
  • Выводы?

Зачем придумали MVC?

Итак, для того что бы разобраться что такое MVC, нужно пожалуй вспомнить, каких целей пытались достигнуть ребята из Xerox. Напоминаю, шел 1979-ый год. Приложения с графическим интерфейсом все еще были чем-то новым. Для того что бы удешивить и упростить разработку приложений с графическим интерфейсом, нужно было что-то придумать. Давайте попробуем сформулировать основные идеи, которые позволяют этого достичь.

Возможность реюзать элементы графического интерфейса

Тут все довольно просто. Нам нужна возможность как-то отделить UI контролы от контекста их применения, дабы была возможность легко и просто их реюзать в будущем. Например, вспомним такой чудесный компонент, так grid view:

Мало какое приложение обходилось без этого UI компонента, и у каждого оно чуть чуть отличалось. Где-то нужен поиск, а где-то нет. Где-то нужна пагинация, а где-то нет. И конечно же должна быть возможность как-то управлять тем, какие колонки сейчас используются.

Представьте себе стоимость разработки такого контрола с нуля. И представьте, что вы можете сделать это ровно один раз, и использовать его в тысячах приложений. Это очень существенная экономия средств для индустрии.

Давайте теперь подумаем. как мы можем "абстрагировать" этот контрол от приложения. Для этого мы можем сделать декомпозицию задачи и разбить этот элемент интерфейса на более маленькие элементы.

Снижение связанности

Скажем навигация по страницам грида - это отдельный элемент. Мы можем использовать его, а можем не использовать. Если мы используем его, он будет зависеть от какого-то хранилища информации. Хранилище мы можем представить в виде какого-то объекта, предоставляющего нам возможность отправлять ему сообщения. В сообщениях мы можем запрашивать состояние, или отправлять просьбы это состояние поменять. Например мы можем попросить хранилище "сменить страницу", и оно обновит свое внутреннее состояние, загрузит новые данные и т.д. А может и не менять состояние, поскольку больше страниц нет. Решение о изменении состояния - это решение объекта, которое за него отвечает.

Точно так же сама табличка с данными будет зависеть от какого-то объекта, хранящего данные. Задача таблички, просто отобразить все строки и колонки, которые хранит этот объект. Скажем хранилище данных будет предоставлять коллекцию рядов одного типа, массив строк если хотите. Колонками будут поля объекта строки. Хранилище внутри может не содержать этого массива, а, например, хранить что-то другое. Но поскольку все взаимодействие в плане получения и изменения состояния достигается за счет отправления сообщений, нам это не нужно знать. Мы знаем что отправив сообщение мы в ответ получим массив рядов. Слава инкапсуляции!

Но у нас же есть навигация по страницам, а значит состояние объекта, которое хранит данные, может измениться по его просьбе. А стало быть нам нужно как-то обновлять табличку. Для этого мы можем воспользоваться событиями. То есть всякий раз, как меняется состояние объекта, хранящего данные, он выбрасывает событие об этом. Наша табличка подписывается на это событие и обновляет себя всякий раз, как происходит изменение состояния, дабы всегда отображать только актуальные данные.

Я думаю общую идею вы уже уловили. (или нет?)

Именуем вещи

В программировании существует лишь два характерных затруднения: инвалидация кеша, наименование сущностей и ошибка на единицу

Как-то не очень удобно называть вещи "объект, хранящий состояние". или "элемент пользовательского интерфейса". Давайте придумаем этому простые слова, которые описывают из значение в системе.

Элементы пользовательского интерфейса

Итак, элементы пользовательского интерфейса можно условно разделить на две категории:

  • Элементы, отображающие какое-то состояние данных. Примером этому может быть табличка, которая отображает какие-то данные.
  • Элементы, предоставляющие пользователю возможность воздействовать на состояние. Это кнопки, поля ввода форм и т.д.

Итак, задача первых - представление состояния. Потому назовем это "представлением" (или на английском - view). Представление подписывается на изменение состояния и обновляет само себя. При этом состояние ничего не знает о существовании представления. Так же для каждого объекта, хранящего состояния, может быть более одного представления. Так в нашем примере и табличка и панель навигации по страницам работают с одним и тем же объектом, хранящим состояние.

Задача вторых - непрямое изменение состояния пользователем. Назовем их "редакторы" (или editors на английском). Почему непрямое? Этим я хочу подчеркнуть, что изменение состояния объектов дело самих объектов. Наши редакторы могут лишь просить это сделать другие объекты. Мы вернемся к этому чуть позже.

Объект, хранящий состояние

Итак, давайте выберем хорошее название, которое описывает эту сущность. Для начала вспомним что такое состояние.

Состояние - это то, что меняется со временем. Если нет изменений, нет смысла говорить о состоянии, как и нет смысла говорить о времени. С точки зрения представления, мы имеем дело с состоянием каких-то данных. Представление интересует состояние данных на данный момент времени, и с этой целью оно запрашивает состояние у объекта, который это состояние хранит. Как же назвать этот объект?

Есть такое интересное слово как "модель" (или Model на английском), которым обозначают какую-то совокупность информации или данных. Давайте посмотрим общее определение в википедии:

Модель есть абстрактное представление реальности в какой-либо форме (например, в математической, физической, символической, графической или дескриптивной), предназначенное для представления определенных аспектов этой реальности и позволяющее получить ответы на изучаемые вопросы.

В нашем случае модель есть представление каких-либо данных, что вполне себе подходит под это определение. В выборе этого слова есть определенный нюанс. Оно описывает абсолютно абстрактную сущность, никакой конкретики. Мы можем назвать моделью что угодно, чья задача - управление информацией. Модели могут быть анемичными, то есть лишенными поведения, и в этом случае представлять собой тупую структуру данных, а может быть полноценной моделью предметной области. В этом случае модель это уже полноценная сущность предметной области, со своими бизнес правилами и ограничениями.

Однако представлению глубоко плевать, как именно мы решили реализовывать модель. Как тупую структуру данных или же как полноценный объект, инкапсулирующий свое состояние. Представлению так или иначе нужно всего-лишь получить состояние, которое предоставляет модель. А как - дело десятое.

Тут пожалуй возникает самая большая доля недопониманий. Как мне кажется чаще всего это вызвано тем, что мы, как люди проектирующие систему, видим все ее внутреннее устройство в разрезе, и нам тяжело поставить себя на место представления в том плане, как оно видит модель.

Предположим что у нас есть модель списка контактов:

// анемичная модель, структурка для контакта
class Contact {
   firstName;
   lastName;
   constructor(firstName, lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
   }
}

// провайдер данных
class ContactsProvider {
   constructor(storage) {
      this.storage = storage;
   }

   getItems() {
      return this.storage.get('contacts')
        .map(contact => new Contact(
          contacts.first_name, 
          contacts.last_name
        ));
   }
}

// модель списка контактов
// она ничего не знает о том, как именно реализую
class ListModel {
    constructor(dataProvider) {
       this.dataProvider = dataProvider;
       this.page = 1;
       this.perPage = 10;
    }

    get totalPages() {
       return Math.floor(this.contactsProvider.getTotalCount() / tis.perPage);
    }
    
    getList() {
       // вернет массив объектов типа Contact
       return this.contactsProvider.getItems(
         (this.page-1) * this.perPage,
         this.perPage
       );
    }
}

С этой точки зрения мы видим довольно много всего. Это и какой-то провайдер, который предоставляет нам список контактов, и моделька одного контакта, и моделька списка... А еще есть storage который может быть чем угодно, как просто каким-то объектом, который хранит все внутри себя в памяти, так может использовать и базу данных, и отправлять запросы на удаленный сервер, и еще черти знает что делать внутри.

Но вот с точки зрения объекта, который занимается формированием представления, мы видим только методы getList, и свойства page, perPage и totalPage. Реализация нас вообще не волнует. Нам важно что getList вернет массив объектов, свойства которых будут содержать значения для колонок рядов. То есть "модель" - это вершина айсберга. Точка соприкосновения между представлением и приложением, если хотите. Мы не видим что там внутри и в принципе мы и не хотим это видеть на данном уровне.

Редакторы и модель

И так, мы разобрались как представление взаимодействует с моделью. Но как происходит мутация состояния? Например в пагинаторе мы нажали на кнопку, и хотим что бы изменилось состояние списка. Вроде как ничего сложного, по нажатию на кнопку мы должны дернуть метод модели, так?

Вроде бы так, но сама по себе кнопка не должна ничего знать о модели. Вместо этого, кнопка предоставляет нам событие, о том что кто-то ее нажал. Кнопку не волнует что должно происходить по нажатию, выкинули ивент и занимаемся своими делами.

Мы так же знаем что модель не должна ничего знать о представлении, так что ловить это событие прямо в модели мы не можем. Иначе у нас возникает связь между представлением и моделью и мы уже не сможем использовать модель в другом контексте. Это не удобно и плохо масштабируется.

Давайте тогда введем новую сущность, которая будет обрабатывать эти события, и слать сообщения модели с целью изменения ее состояния. И назовем мы ее - контроллер. Его задача - преобразовывать асинхронные действия пользователей (то есть мы не знаем в какой момент времени пользователь тыкнет кнопку) в синхронные вызовы методов модели (отправка сообщений с предложением изменить состояние).

В 2001-ом году Крэйг Ларман описал паттерн "Контроллер" более детально в своей книге Применение UML 2.0 и шаблонов проектирования.. Там он детально описывает применение этого паттерна для снижения связанности между моделью и редакторами.

MVC

Итак, у нас появилось четыре сущности:

  • Модель (Model)
  • Представление (View)
  • Редакторы (Editors)
  • Контроллеры (Controllers)

Давайте нарисуем схемку, как это все вместе взаимодействует:

При виде этой картинки многих людей начинает смущать направление стрелочек. Это направление зависимостей. Модель и представление связаны observable-отношениями. Так же как контроллеры и редакторы. Редакторы ничего не знают о контроллера, а модель ничего не знает о контроллере и представлении.

Поток данных же идет в этой схеме таким образом:

Так почему же MVC называется именно так? Куда делись редакторы? Всему виной мода. На дворе 80-е, и в моде аббривиатуры из 3-х букв! А посколькуо Editors это часть VIew, можно наверное выкинуть это слово из общей картины...

А мне MVC не так удобно

TODO: описать недостатки MVC

MVP

MVVM

MVA

FLUX

WEB: тут уж точно все не так... или нет?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment