Шаблоны проектирования часто встраивают в популярные фреймворки. Например, шаблон MVC (Model-View-Controller, Модель-Представление-Контроллер) можно встретить буквально повсюду. В JavaScript трудно отделить фреймворк от реализованного в нём шаблона проектирования, причём, часто авторы фреймворков интерпретируют MVC по-своему и навязывают программистам своё видение вопроса.



То, как именно будет выглядеть конкретная реализация MVC, полностью зависит от фреймворка. В результате мы получаем массу разных реализаций, что сбивает с толку и ведёт к беспорядку. Особенно это заметно, когда в одном проекте используется несколько фреймворков. Эта ситуация заставила меня задаться вопросом: «А есть ли способ лучше?».

Шаблон MVC хорош для клиентских фреймворков, однако, полагаясь на нечто «современное», нужно помнить о том, что уже завтра появится что-то новое, а то, что современно сегодня, устареет. Это — тоже проблема, и мне хотелось бы исследовать альтернативы фреймворкам и посмотреть, к чему всё это может привести.

Шаблон проектирования MVC появился несколько десятков лет назад. Полагаю, в его изучение стоит вложить время любому программисту. Этот шаблон можно использовать без привязки к каким-либо фреймворкам.

Реализация MVC — это ещё один фреймворк?


Для начала мне хотелось бы развеять один распространённый миф, когда ставят знак равенства между шаблонами проектирования и фреймворками. Это — разные вещи.

Шаблон — это строгий подход к решению некоей задачи. Для реализации шаблона, за детали которой отвечает программист, нужен определённый уровень квалификации. Шаблон MVC позволяет следовать принципу разделения ответственностей и способствует написанию чистого кода.

Фреймворки не привязаны к конкретным шаблонам проектирования. Для того, чтобы отличить фреймворк от шаблона, можно воспользоваться так называемым голливудским принципом: «не звоните нам, мы сами вам позвоним». Если в системе имеется некая зависимость и при этом в определённой ситуации вы вынуждены её использовать — это фреймворк. Схожесть фреймворков с голливудом заключается в том, что разработчики, пользующиеся ими, похожи на актёров, которые вынуждены строго следовать сценариям фильмов. У таких программистов нет права голоса.

Стоит ли избегать клиентских фреймворков? Каждый сам ответит на этот вопрос, однако, вот несколько веских причин от них отказаться:

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

Шаблон MVC


Шаблон проектирования MVC родом из 1970-х. Он появился в научно-исследовательском центре Xerox PARC в ходе работы над языком программирования Smalltalk. Шаблон прошёл проверку временем в деле разработки графических пользовательских интерфейсов. Он пришёл в веб-программирование из настольных приложений и доказал свою эффективность в новой сфере применения.

По сути, MVC — это способ чёткого разделения ответственностей. В результате конструкция решения, основанного на нём, оказывается понятной даже новому программисту, который по каким-то причинам присоединился к проекту. Как результат, даже тому, кто с проектом знаком не был, легко в нём разобраться, и, при необходимости, внести вклад в его разработку.

Пример реализации MVC


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

При создании приложения пользоваться мы будем шаблоном MVC, строго следуя его принципам. Кроме того, в процессе решения задачи будет задействована методология экстремального программирования, а также модульные тесты. Всё будет сделано на JS, HTML и CSS — никаких фреймворков, ничего лишнего.

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

Мы будем придерживаться стандарта ES5 для обеспечения кросс-браузерной совместимости. Полагаем, шаблон MVC вполне заслужил того, чтобы для его реализации использовались широко известные, проверенные возможности языка.

Итак, приступим.

Общий обзор проекта


Демонстрационный проект будет состоять из трёх основных компонентов: контроллера, представления и модели. Каждый из них имеет собственную сферу ответственности и ориентирован на решение конкретной задачи.

Вот как это выглядит в виде схемы.


Схема проекта

Контроллер PenguinController занимается обработкой событий и служит посредником между представлением и моделью. Он выясняет, что произошло, когда пользователь выполняет некое действие (например, щёлкает по кнопке или нажимает клавишу на клавиатуре). Логика клиентских приложений может быть реализована в контроллере. В более крупных системах, в которых нужно обрабатывать множество событий, этот элемент можно разбить на несколько модулей. Контроллер является входной точкой для событий и единственным посредником между представлением и данными.

Представление PenguinView взаимодействует с DOM. DOM — это API браузера, с помощью которого работают с HTML. В MVC только представление отвечает за изменения DOM. Представление может выполнять подключение обработчиков событий пользовательского интерфейса, но обработка событий — прерогатива контроллера. Основная задача, решаемая представлением — управлять тем, что пользователь видит на экране. В нашем проекте представление будет выполнять манипуляции с DOM, используя JavaScript.

Модель PenguinModel отвечает за работу с данными. В клиентском JS это означает выполнение Ajax-операций. Одно из преимуществ шаблона MVC заключается в том, что всё взаимодействие с источником данных, например — с сервером, сосредоточено в одном месте. Такой подход помогает программистам, которые не знакомы с проектом, разобраться в нём. Модель в этом шаблоне проектирования занята исключительно работой с JSON или объектами, которые поступают с сервера.

Если при реализации MVC нарушить вышеописанное разделение сфер ответственности компонентов, мы получим один из возможных анти-паттернов MVC. Модель не должна работать с HTML. Представление не должно выполнять Ajax-запросов. Контроллер должен играть роль посредника, не заботясь о деталях реализации других компонентов.

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

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

Пожалуй, довольно рассуждений, пришло время взглянуть на рабочий пример, код которого размещён на CodePen. Можете поэкспериментировать с ним.


Приложение на CodePen

Рассмотрим этот код.

Контроллер


Контроллер взаимодействует с представлением и моделью. Компоненты, необходимые для работы контроллера, имеются в его конструкторе:

var PenguinController = function PenguinController(penguinView, penguinModel) {
  this.penguinView = penguinView;
  this.penguinModel = penguinModel;
};

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

Затем подключаются события, связанные со взаимодействием с пользователем:

PenguinController.prototype.initialize = function initialize() {
  this.penguinView.onClickGetPenguin = this.onClickGetPenguin.bind(this);
};

PenguinController.prototype.onClickGetPenguin = function onClickGetPenguin(e) {
  var target = e.currentTarget;
  var index = parseInt(target.dataset.penguinIndex, 10);

  this.penguinModel.getPenguin(index, this.showPenguin.bind(this));
};

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

Когда происходит событие, контроллер считывает данные и принимает решения о дальнейших действиях. В данный момент речь идёт о функции обратного вызова this.showPenguin():

PenguinController.prototype.showPenguin = function showPenguin(penguinModelData) {
  var penguinViewModel = {
    name: penguinModelData.name,
    imageUrl: penguinModelData.imageUrl,
    size: penguinModelData.size,
    favoriteFood: penguinModelData.favoriteFood
  };

  penguinViewModel.previousIndex = penguinModelData.index - 1;
  penguinViewModel.nextIndex = penguinModelData.index + 1;

  if (penguinModelData.index === 0) {
    penguinViewModel.previousIndex = penguinModelData.count - 1;
  }

  if (penguinModelData.index === penguinModelData.count - 1) {
    penguinViewModel.nextIndex = 0;
  }

  this.penguinView.render(penguinViewModel);
};

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

Представленные здесь модульные тесты построены по модели AAA (Arrange, Act, Assert — размещение, действие, утверждение). Вот модульный тест для стандартного сценария показа информации о пингвине:

var PenguinViewMock = function PenguinViewMock() {
  this.calledRenderWith = null;
};

PenguinViewMock.prototype.render = function render(penguinViewModel) {
  this.calledRenderWith = penguinViewModel;
};

// Arrange
var penguinViewMock = new PenguinViewMock();

var controller = new PenguinController(penguinViewMock, null);

var penguinModelData = {
  name: 'Chinstrap',
  imageUrl: 'http://chinstrapl.jpg',
  size: '5.0kg (m), 4.8kg (f)',
  favoriteFood: 'krill',
  index: 2,
  count: 5
};

// Act
controller.showPenguin(penguinModelData);

// Assert
assert.strictEqual(penguinViewMock.calledRenderWith.name, 'Chinstrap');
assert.strictEqual(penguinViewMock.calledRenderWith.imageUrl, 'http://chinstrapl.jpg');
assert.strictEqual(penguinViewMock.calledRenderWith.size, '5.0kg (m), 4.8kg (f)');
assert.strictEqual(penguinViewMock.calledRenderWith.favoriteFood, 'krill');
assert.strictEqual(penguinViewMock.calledRenderWith.previousIndex, 1);
assert.strictEqual(penguinViewMock.calledRenderWith.nextIndex, 3);

Объект-заглушка PenguinViewMock реализует тот же контракт, что и реальный модуль представления. Это позволяет писать модульные тесты и проверять, в блоке Assert, всё ли работает так, как нужно.

Объект assert взят из Node.js, но можно воспользоваться аналогичным объектом из библиотеки Chai. Это позволяет писать тесты, которые можно выполнять и на сервере, и в браузере.

Обратите внимание на то, что контроллер не заботится о деталях реализации. Он полагается на контракт, который предоставляет представление, вроде this.render(). Именно такого подхода необходимо придерживаться для написания чистого кода. Контроллер, при таком подходе, может доверить компоненту выполнение тех задач, о возможности выполнения которых заявил этот компонент. Это делает структуру проекта прозрачной, что улучшает читаемость кода.

Представление


Представление заботится лишь об элементах DOM и о подключении обработчиков событий. Например:

var PenguinView = function PenguinView(element) {
  this.element = element;

  this.onClickGetPenguin = null;
};

Вот как реализуется в коде воздействие представления на то, что видит пользователь:

PenguinView.prototype.render = function render(viewModel) {
  this.element.innerHTML = '<h3>' + viewModel.name + '</h3>' +
    '<img class="penguin-image" src="' + viewModel.imageUrl +
      '" alt="' + viewModel.name + '" />' +
    '<p><b>Size:</b> ' + viewModel.size + '</p>' +
    '<p><b>Favorite food:</b> ' + viewModel.favoriteFood + '</p>' +
    '<a id="previousPenguin" class="previous button" href="javascript:void(0);"' +
      ' data-penguin-index="' + viewModel.previousIndex + '">Previous</a> ' +
    '<a id="nextPenguin" class="next button" href="javascript:void(0);"' +
      ' data-penguin-index="' + viewModel.nextIndex + '">Next</a>';

  this.previousIndex = viewModel.previousIndex;
  this.nextIndex = viewModel.nextIndex;

  // Подключение обработчиков событий щелчков по кнопкам и передача задачи обработки событий контроллеру
  var previousPenguin = this.element.querySelector('#previousPenguin');
  previousPenguin.addEventListener('click', this.onClickGetPenguin);

  var nextPenguin = this.element.querySelector('#nextPenguin');
  nextPenguin.addEventListener('click', this.onClickGetPenguin);
  nextPenguin.focus();
}

Обратите внимание на то, что основная задача представления заключается в том, чтобы превратить данные, полученные из модели, в HTML, и поменять состояние приложения. Ещё одна задача — подключение обработчиков событий и передача функций их обработки контроллеру Обработчики событий подключаются к DOM после изменения состояния. Этот подход позволяет просто и удобно управлять событиями.

Для того, чтобы всё это протестировать, мы можем проверить обновление элементов и изменение состояния приложения:

var ElementMock = function ElementMock() {
  this.innerHTML = null;
};

// Функции-заглушки, необходимые для того, чтобы провести тестирование
ElementMock.prototype.querySelector = function querySelector() { };
ElementMock.prototype.addEventListener = function addEventListener() { };
ElementMock.prototype.focus = function focus() { };

// Arrange
var elementMock = new ElementMock();

var view = new PenguinView(elementMock);

var viewModel = {
  name: 'Chinstrap',
  imageUrl: 'http://chinstrap1.jpg',
  size: '5.0kg (m), 4.8kg (f)',
  favoriteFood: 'krill',
  previousIndex: 1,
  nextIndex: 2
};

// Act
view.render(viewModel);

// Assert
assert(elementMock.innerHTML.indexOf(viewModel.name) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.imageUrl) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.size) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.favoriteFood) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.previousIndex) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.nextIndex) > 0);

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

Модель


В шаблоне MVC модель занята взаимодействием с источником данных. В нашем случае — с севером. Например:

var PenguinModel = function PenguinModel(XMLHttpRequest) {
  this.XMLHttpRequest = XMLHttpRequest;
};

Обратите внимание на то, что модуль XMLHttpRequest внедрён в конструктор модели. Это, кроме прочего, подсказка для других программистов касательно компонентов, необходимых модели. Если модель нуждается в различных способах работы с данными, в неё можно внедрить и другие модули. Так же, как и в рассмотренных выше случаях, для модели можно подготовить модульные тесты.

Получим данные о пингвине, основываясь на индексе:

PenguinModel.prototype.getPenguin = function getPenguin(index, fn) {
  var oReq = new this.XMLHttpRequest();

  oReq.onload = function onLoad(e) {
    var ajaxResponse = JSON.parse(e.currentTarget.responseText);
    // Индекс должен быть целым числом, иначе это работать не будет
    var penguin = ajaxResponse[index];

    penguin.index = index;
    penguin.count = ajaxResponse.length;

    fn(penguin);
  };

  oReq.open('GET', 'https://codepen.io/beautifulcoder/pen/vmOOLr.js', true);
  oReq.send();
};

Тут осуществляется подключение к серверу и загрузка с него данных. Проверим компонент с помощью модульного теста и условных тестовых данных:

var LIST_OF_PENGUINS = '[{"name":"Emperor","imageUrl":"http://imageUrl",' +
  '"size":"36.7kg (m), 28.4kg (f)","favoriteFood":"fish and squid"}]';

var XMLHttpRequestMock = function XMLHttpRequestMock() {
  // Для целей тестирования нужно это установить, иначе тест не удастся
  this.onload = null;
};

XMLHttpRequestMock.prototype.open = function open(method, url, async) {
  // Внутренние проверки, система должна иметь конечные точки method и url
  assert(method);
  assert(url);
  // Если Ajax не асинхронен, значит наша реализация весьма неудачна :-)
  assert.strictEqual(async, true);
};

XMLHttpRequestMock.prototype.send = function send() {
  // Функция обратного вызова симулирует Ajax-запрос
  this.onload({ currentTarget: { responseText: LIST_OF_PENGUINS } });
};

// Arrange
var penguinModel = new PenguinModel(XMLHttpRequestMock);

// Act
penguinModel.getPenguin(0, function onPenguinData(penguinData) {

  // Assert
  assert.strictEqual(penguinData.name, 'Emperor');
  assert(penguinData.imageUrl);
  assert.strictEqual(penguinData.size, '36.7kg (m), 28.4kg (f)');
  assert.strictEqual(penguinData.favoriteFood, 'fish and squid');
  assert.strictEqual(penguinData.index, 0);
  assert.strictEqual(penguinData.count, 1);
});

Как видите, модель заботят лишь необработанные данные. Это означает работу с Ajax и с JavaScript-объектами. Если вы не вполне владеете темой Ajax в JavaScript, вот полезный материал об этом.

Модульные тесты


При любых правилах написания кода важно выполнять проверку того, что получилось. Шаблон проектирования MVC не регламентирует способ решения задачи. В рамках шаблона очерчены границы, довольно свободные, не пересекая которых можно писать чистый код. Это даёт свободу от засилья зависимостей.

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

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

О развитии учебного проекта


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

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

Если вы хотите, в качестве упражнения, развить этот проект, учитывайте, что выше приведены лишь несколько идей. Вы вполне можете, с использованием MVC, реализовать и другой функционал.

Итоги


Надеемся, вы оценили преимущества, которые дают шаблон проектирования MVC и дисциплинированный подход к архитектуре и коду проекта. Хороший шаблон проектирования, с одной стороны, не мешает работе, с другой, способствует написанию чистого кода. Он, в процессе решения задачи, не даёт вам свернуть с правильного пути. Это повышает эффективность работы программиста.

Программирование — это искусство последовательного решения задач, разбиения системы на небольшие части, реализующие определённый функционал. В MVC это означает строгое следование принципу разделения сфер функциональной ответственности компонентов.

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

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

Уважаемые читатели! Какие шаблоны проектирования вы применяете в своих JS-проектах?
Поделиться с друзьями
-->

Комментарии (28)


  1. vasIvas
    21.07.2017 15:34
    -25

    Очень аккуратно оформлено, но к сожаления не правильно. Неправильно с самого начала и до самого конца.
    Объяснять конкретно в чем ошибки я не хочу, так как существует миллион намного более правильных статей, на том же Хабре.


    1. vasIvas
      21.07.2017 15:56
      -15

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


      1. GlukKazan
        21.07.2017 16:11
        +7

        Думаю, что то что вам «ставят минуса» связано не столько с правильностью статьи, сколько с Вашим комментарием «в духе Ферма»:

        Объяснять конкретно в чем ошибки я не хочу, так как ...
        Вы бы хоть сослались на парочку из этого миллиона статей, право слово.


        1. vasIvas
          21.07.2017 17:14
          -5

          Да в этой статье все задом наперед. Много статей о mvc содержать ошибки, но эта, полностью состоит из одних ошибок.


      1. AngelZeruel
        21.07.2017 16:33
        +6

        Скорее всего, минуса летят из-за "… но к сожаления не правильно… Объяснять конкретно в чем ошибки я не хочу" — звучит так же, как у Печкина про посылку.


        1. vasIvas
          21.07.2017 17:08
          -9

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


          1. GlukKazan
            21.07.2017 17:53
            +3

            Сказав А надо говорить Б. Это моё мнение и я его ни в коем случае не навязываю.


            1. vasIvas
              21.07.2017 18:19
              -7

              Люди которые умеют читать между строк, поймут что эту статью не нужно читать обратив внимания на мои коментария. А тем кому суждено стать теми, кто минусует мне и не плюсует тем, кто ниже аргументировал, не поможет уже ничто.


              1. GlukKazan
                21.07.2017 18:37
                +6

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


                1. vasIvas
                  21.07.2017 20:07
                  -6

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

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

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


                  1. GlukKazan
                    21.07.2017 20:22
                    +3

                    Север помнит Хабр знает

                    При чтении статей я привык пользоваться не поиском, а больше собственной головой (чего всем советую). Я против сектантства во всех формах. Если бы Вы потрудились аргументированно объяснить, с чем именно Вы в статье не согласны, уверяю Вас, реакция на Ваши комментарии была бы совсем иной. Но Вам ведь не этого надо?


                    1. vasIvas
                      21.07.2017 21:18
                      -6

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


                      1. GlukKazan
                        21.07.2017 23:39
                        +3

                        Скрин говорит только о том, что эта статья — перевод. Не надо искать подтекстов. Вы хотели знать чья это статья — я показал.


        1. TheShock
          21.07.2017 22:32

          Объяснять конкретно в чем ошибки я не хочу" — звучит так же, как у Печкина про посылку

          Мне еще больше похоже на
          Невероятно, но факт


    1. TheShock
      21.07.2017 22:34
      +8

      Очень аккуратно оформлено, но к сожаления не правильно. Неправильно с самого начала и до самого конца.
      Объяснять конкретно в чем ошибки я не хочу, так как существует миллион намного более правильных статей, на том же Хабре.

      Очень неаккуратный комментарий и, к сожалению, местами неправильный.
      Объяснять конкретно в чем ошибки я не хочу, так как существует миллион намного более правильных комментариев, на том же Хабре.


  1. Druu
    21.07.2017 16:34
    -1

    > Контроллер PenguinController занимается обработкой событий и служит посредником между представлением и моделью.

    Контроллер не служит посредником. MVC — это модель черного ящика. Есть входы (действия пользователя), есть выходы (это реакция интерфейса), есть внутренняя логика (модель). Входы обрабатывает контроллер, выходы формирует вид. Информация от входов к выходам (от контроллера в вид) пробрасывается моделью. Вся идея MVC — в том, что обработчик входов полностью отделен от обработчика выходов. Если у вас контроллер и вид что-либо знают друг о друге — это нарушает основной принцип MVC. При чем модель в mvc — это не слой взаимодействия с бекендом, это, скорее, аналог view model из mvvm. Слой взаимодействия с бекендом находится за пределами MVC (можно сказать, что к нему должна быть стрелочка от модели). А то, что получилось у вас — это немного кривая mvvm.


    1. DriverEntry
      21.07.2017 16:56

      Входы обрабатывает контроллер

      Вообще да, но в случае html ввод ведь поступает от тех же элементов dom, которые рисует представление. Совсем по классике слишком сурово получится — не 70-е ведь.


      1. Druu
        21.07.2017 17:16

        > Вообще да, но в случае html ввод ведь поступает от тех же элементов dom, которые рисует представление.

        Это не существенно, потому что:
        1. внутренняя реализация дом-элементов вам не доступна
        2. это поведение легко меняется тонкой прослойкой. Например, в реакте все эвенты сперва баблятся до рута и потом оттуда уже распределяются, куда нужно.

        > Совсем по классике слишком сурово получится — не 70-е ведь.

        Ну естественно, потому и создаются новые варианты архитектуры. В 70-е, чтобы понять, что кто-то кликнул по кнопке — надо было посмотреть, что там по координатам клика, выяснить, что кнопка, проверить, не перекрыта ли она какой-то открытой менюшкой и т.д.
        Это и делал (в том числе) контроллер, сейчас контроллеры сверх-тонкие, (click)=«doSomething()» — вот весь ваш контроллер (обычно, но нередко бывают и более сложные кейзы, конечно же), и он сидит в разметке, которая, вроде как, вид. + появилась необходимость отделять стейт приложения (то самое, какой из табов открыт, какой чек нажат и т.д.) от данных — так и появился mvvm (контроллер с видом склеили, за моделью вида разместили модель данных).

        Но выдумывать каких-то кадавров с контроллерами-прослойками — не нужно. Представьте себе классический веб — где у вас условный пхп выдает ответы в виде готовой страницы. У вас контроллер на клиенте (очевидно), модель — на беке, но и вид тоже на беке! Как бы вы сделали тут эту химероподобную MVC с контроллером-прослойкой? Отправили данные в модель на беке, оттуда отклик в контроллер на клиенте, который дергает вид с бека? Шиза же. А почему? Потому что поток данных вывернут. По какой такой причине у вас часть приложения, ответственная за обработку реакции пользователя, должна заниматься передачей данных от модели к виду? В этом ведь нету никакой логики.


    1. asmodeusta
      21.07.2017 21:56
      -1

      Не соглашусь насчет Вашего определения MVC: это никакой не черный ящик. Черным ящиком могут выступать отдельно Model, View и Controller друг для друга ввиду того, что логика самих компонентов недоступна и может меняться независимо от других, но сами определения каждого из этих компонентов дают понять кто за что отвечает. Controller именно представляет собой связующее звено между Model и View. В идеале из View не должно быть никакого доступа к Model, а все данные он получает от Controller, который и реализует основную логику приложения.


      1. Druu
        21.07.2017 23:15

        > Черным ящиком могут выступать отдельно Model, View и Controller

        Черным ящиком может выступать абсолютно все, что угодно, что данные откуда-то принимает, а потом куда-то передает. И потом описание этого чего-то может быть выражено в форме MVC.

        > Controller именно представляет собой связующее звено между Model и View.

        Нет, он связывает модель и вид с _устройствами ввода_, то есть входами, И это как раз основная отличающая фишка MVC. Из первой статьи по MVC:

        > Controllers contain the interface between their associated models and views and the input devices
        (keyboard, pointing device, time).

        Ну и сам цикл работы:

        > The standard interaction cycle in the Model-View-Controller metaphor, then, is that the user takes
        some input action and the active controller notifies the model to change itself accordingly. The
        model carries out the prescribed operations, possibly changing its state, and broadcasts to its
        dependents (views and controllers) that it has changed, possibly telling them the nature of the
        change. Views can then inquire of the model about its new state, and update their display if
        necessary. Controllers may change their method of interaction depending on the new state of the
        model.

        Небольшое пояснение по последнему пункту — у одной модели может быть много пар контроллер+вид, и контроллер отвечает за оркестрацию пар. Например, у каждого меню — свой контроллер (с-но, когда меню открывается, надо деактивировать текущий контроллер, активировать контроллер меню и инициализировать его вид), у какого-нибудь скроллбара тоже свой контроллер — при наведении на скроллбар будет активирован контроллер скроллбара.

        > все данные он получает от Controller, который и реализует основную логику приложения.

        Это считается одной из самых распространенных ошибок. Контроллер должен быть как можно тоньше, а логика приложения содержится как раз в модели.


    1. babylon
      22.07.2017 19:01

      Ну как не служит посредником. Контроллер это USB коннектор между моделью и видом. В принципе, он может вообще не обрабатывать никакие события. Его задача состоит в отделении моделей от видов. Со стороны вида реализация коннектинга модели и контроллера это черный ящик и со стороны модели реализация коннектинга вида и контроллера тоже черный ящик. Но со стороны контроллера ему доступы оба коннектора. То, что он обрабатывает по ссылкам события это никак не говорит о количестве его связей с конкретной моделью или конкретным видом. Чем обрабатываемых событий больше тем контроллер универсальнее. Т.е. вы его меньше рефакторите приспосабливая к новой модели или новому виду. А статья да слабовата как по мне. Не стоила усилий на перевод.


      1. Druu
        23.07.2017 01:24

        > Контроллер это USB коннектор между моделью и видом.

        Это не так, я выше уже цитировал:

        > Controllers contain the interface between their associated models and views and the input devices
        (keyboard, pointing device, time).

        Предназначение контроллера — именно изоляции системы от действий пользователя. Это основная его функция в MVC.

        > В принципе, он может вообще не обрабатывать никакие события.

        Тогда он не нужен, так как не выполняет свою основную функцию.

        > Его задача состоит в отделении моделей от видов.

        Модель от вида в MVC прослойкой не отделяется (более того — вид вообще по MVC вполне может делать запросы о состоянии модели напрямую). Потому что это приводит к огромным проблемам (см. выше пример), и при этом не приносит никакой пользы. Чтобы разместить прослойку между видом и моделью, необходимо сначала разбить связь между контроллером и моделью. Тогда контроллер естественным образом включается в вид, прослойка называется «Presenter», и вы получаете MVP вместо MVC.


        1. babylon
          23.07.2017 20:13

          Да есть MVC с активным видом. Как по мне это компонентный MVC Если Controller подписан на события модели и вида он их обрабатывает, если нет, то это просто проброс ссылок и связывание с методами в контроллере. См пример с роутером в контроллере. Модели и Виды коннектятся к контроллеру с помощью интерфейсов, абстрагируясь от проприетарных реализаций. Не вижу смысла пережовывать еще раз https://habrahabr.ru/post/321050/


          1. Druu
            23.07.2017 23:23

            > Да есть MVC с активным видом.

            Активный вид или не активный — это совершенно неважно и никак на наличие прослойки не влияет. В описании MVC нету указаний на то, какой там вид. Если вы прочитаете любую из ранних статей по MVC, то вы увидите ровно две вещи:
            1. везде контроллер определяется как прослойка между входами и остальной частью системы
            2. нигде контроллер не выступает в качестве прослойки между видом и моделью

            Если хотя бы одно из условий не выполнено — то у вас не MVC. Совершенно определенно, может быть разница в деталях (активный/пассивный вид, например, к слову, по вашей ссылке схема для пассивного вида неправильная — она от варианта с активным видом должна отличаться только лишь тем, что стрелочка «модель — вид» направлена в другую сторону), но идея паттерна (в случае MVC — отсутствие прослоек между видом и моделью и наличие элемента, который отвечает за обработку входов) — должна сохраняться.


  1. MadridianFox
    21.07.2017 16:45
    +2

    Он пришёл в веб-программирование из настольных приложений и доказал свою эффективность в новой сфере применения.

    В этом предложении есть неоднозначность, т.к. веб-программирование — очень широкое понятие. Веб-программированием можно назвать как создание backend, так и создание frontend. При этом MVC для backend действительно проявил себя в новой сфере, если конечно то что используется на стороне сервера можно назвать MVC. Программирование же пользовательского интерфейса на стороне клиента не стало новой сферой применения для архитектурного подхода с названием MVC, т.к. SPA по сути являются теми же desktop приложениями, просто запускаются в runtime в виде браузера.

    Модель в этом шаблоне проектирования занята исключительно работой с JSON или объектами, которые поступают с сервера.

    А вот это уже антипаттерн. Модель это больше чем просто объектная обёртка к данным. Это ещё и управление состоянием приложения, а оно, отнюдь, не сосредоточено на данных. Например, отображаемая в данный момент вкладка (tab), а точнее информация о том какая вкладка должна быть показана — тоже часть состояния приложения. А концентрация на работе с данными ведёт к тому, что работа с состоянием размазывается по контроллеру.

    Контроллер должен играть роль посредника, не заботясь о деталях реализации других компонентов.

    О деталях реализации вообще никто заботиться не должен. Но в контексте разделения ответственности между Моделью, Представлением и Контроллером ваше заявление в корне неверно и противоречит вашему же примеру. Как раз наоборот — именно контроллер «знает» за какие ниточки дёргать модель, а за какие представление.


  1. Sirion
    21.07.2017 16:54
    +9

    Не видел ещё ни одной статьи про MVC, чтобы в комментариях не было срача про то, что такое тру-MVC. Эта не стала исключением)


  1. vasiliy404alfertev
    21.07.2017 21:56

    А я надеялся увидеть в статье фишки современного js, позволяющие избавиться от необходимости фреймворков.


  1. bro-dev
    22.07.2017 04:06
    +1

    Я так и не понял какие проблемы использовать mvc в чистом js. Просто разделить комментами 1 файл на 3 части в одной всё обработчики событий это C, в другой всё манипуляции с дом это V, в другой всё остальное это M.
    А ООП или функциональное, работа с пространствами имен, это уже другой вопрос.