Pull to refresh

Comments 16

Пусть заклюют, не привыкать, но… всё это не CORE, всё это Фаулер…
конечно, фаулер )
однако, я тут скорее об удобном инструменте
Поздравляю! Вы описали паттерн MVVM. Две модели — Balance и WinHandler — подписываются на события модели вида UI, и с помощью IoC дергают ее методы. Модель вида, в свою очередь, использует вид (jQuery) для отображения данных пользователю и получения от него обратной связи. Рекомендую посмотреть на AngularJS.
Я описал принцип — сводить описание любой программерской задачи к двум паттернам, после чего их программировать будет очень легко. что получился MVVM, я не виноват )
Точно так же можно перегруппировать функциональность в соответствии с BDD, и получится такой же удобно структурированный код, но это уже не будет MVVM.
Эм, я, похоже, что-то не понимаю, но вроде BDD и MVVM вообще из разных областей, BDD — подход к процессу разработки, а MVVM — к архитектуре, и одно другому не мешает совершенно. А про подходы и паттерны я ниже отписал — не надо придумывать новые термины для одной и той же сущности.
этот паттерн получился из подхода. если с таким же подходом проектировать другие вещи — будут получаться другие паттерны. так что, одно другому не мешает.
в общем, наверное я непонятно объяснил и в следующий раз объясню по-другому.
ну и, я говорю о подходе к процессу разработки как раз, а не об архитектуре. то есть ваше же сравнение с MVVM некорректно.
Статья у вас об архитектуре, про подход там ничего не сказано, вернее, сказано, что надо «просто описать то, что было в задаче» — и это прекрасно реализуется в примере, который «еще хуже», опровергая ваш же тезис, что «языки программирования не похожи на язык описания задач, которым мы пользуемся при постановке». А далее следует (безо всякого обяснения, почему так надо) описание реализации той же задачи с использованием переименованных MVVM + IoC. Я молчу про спорные моменты конкретной реализации, где обработчик событий знает об источнике, и сам подписывается на соответствующие события (что перечеркивает идею событий как таковую), request (реализация IoC?), не привносящие ничего по сравнению с вызовом метода конкретного объекта и пр.
Ну и про реализацию. Описанный диспетчер зависимостей легко реализуется парой функций subscribe и trigger:

var handlers = {};

function subscribe(evt, handler) {
    if (!handlers[evt]) handlers[evt] = [];
    handlers[evt].push(handler);
}

function trigger(evt, data) {
    if (handlers[evt]) {
        handlers[evt].forEach(function(handler) { handler(data); });
    }
}


Вот пример реализации той же задачи про монетки:

function Balance() {
    this.balance = 0;
    subscribe('UI.coinClicked', this.addCoins.bind(this));
}

Balance.prototype.addCoins = function()  { 
    this.balance += 15;
    trigger('Balance.changed', this.balance);
};

function WinWatcher() {
    subscribe('Balance.changed', this.checkWin.bind(this));
}

WinWatcher.prototype.checkWin = function(balance) {
    if (balance > 20) {
        trigger('WinWatcher.userWins');
    }
};

function UI() {
    $('.coin').click(function() {
        trigger('UI.coinClicked');
    });

    subscribe('WinWatcher.userWins', this.showWin.bind(this));
}

UI.prototype.showWin = function() {
    $('.you_win').animate({opacity: 0});
};

subscribe('DOM.init', function () {
    var ui = new UI(),
        balance = new Balance(),
        winWatcher = new WinWatcher();
});


Тут также несложно добавить новый функционал:

function GatherStat() {
    subscribe('UI.coinClicked', this.sendClick.bind(this));
    subscribe('WinWatcher.userWins', this.sendWin.bind(this));
}

GatherStat.prototype.sendClick = function() {
    $.post('/stat/gather/ajax', {click: 1, date: new Date});
};

GatherStat.prototype.sendWin = function() {
    $.post('/stat/gather/ajax', {win: 1, date: new Date});
};


или разделить UI на два объекта:

function UI() {
    $('.coin').click(function() {
        trigger('UI.coinClicked');
    });
}

function UIWin() {
    subscribe('WinWatcher.userWins', this.showWin.bind(this));
}

UI.prototype.showWin = function() {
    $('.you_win').animate({opacity: 0});
};



Можете объяснить, почему ваше решение лучше, и зачем нужен ваш фреймворк?
объясню:
— посмотрите на ваши конструкторы. при усложнении задачи они станут монструозными и в одном месте будет слишком много знаний о разных задачах. это плохо
— рефакторинг в моей версии сводится к копи-пасте методов, в вашей — ещё и к переорганизации конструкторов. Это дополнительные минуты. зачем эта лишняя трата времени?
— в вашем случае код, относящийся к задаче (подписка на событие и обработка этого события), лежит в двух разных местах, что опять ведёт к трате времени при изучении кода. Зачем? Одна задача — один метод, зачем усложнять?
— в вашем случае события — это просто строки. об автодополнении средствами IDE можно забыть. в моём автодополнение есть.
— в вашем случае нужно сперва догадаться, что в задаче будет правильнее использовать события. в моём случае фреймворк и сама технология подталкивает к правильному решению.
— ваши конструкции вроде «this.showWin.bind» — это ещё одна зависимость от какого-то фреймворка. зачем? в моём случае решение полно и не требует внешних зависимостей, предоставляя полный и удобный стек.

И я не очень понимаю, почему, по-вашему, «спорные моменты конкретной реализации, где обработчик событий знает об источнике, и сам подписывается на соответствующие события (что перечеркивает идею событий как таковую)». В чём, по-вашему, идея событий и почему этот подход её перечёркивает?

— посмотрите на ваши конструкторы. при усложнении задачи они станут монструозными и в одном месте будет слишком много знаний о разных задачах. это плохо

Не понял, почему при усложнении задачи мое решение должно стать монструознее вашей реализации? Пока 0:0.

— рефакторинг в моей версии сводится к копи-пасте методов, в вашей — ещё и к переорганизации конструкторов. Это дополнительные минуты. зачем эта лишняя трата времени?

Из реорганизации — перенос подписки на событие. В вашем случае, например, при выносе handleCoinClick надо будет перенести и EventPoint, а в моем случае только cut&paste метода, так что паритет, 1:1

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

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

— в вашем случае события — это просто строки. об автодополнении средствами IDE можно забыть. в моём автодополнение есть.

Про IDE — ну, разве что да. Но подобные архитектуры очень завязаны на схему событий, так что в любом случае одной IDE не обойтись, придется вести документацию и с ней сверяться, но ладно, 2:2.

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

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

— ваши конструкции вроде «this.showWin.bind» — это ещё одна зависимость от какого-то фреймворка. зачем? в моём случае решение полно и не требует внешних зависимостей, предоставляя полный и удобный стек.

Function.prototype.bind — стандартная функция ECMAScript5, частичное применение и указание контекста. Тут полностью работающее решение, из зависимостей только jQuery, и то не принципиально и легко вырезаемо, т. к. нужно только в UI (к тому же у вас jQuery тоже есть в зависимостях). Так что в моем решении на одну зависимость меньше, что явно лучше, 3:2

Итого, мое наколенное решение ничуть не хуже, а то даже и лучше (как минимум, отсутствием дополнительных зависимостей).
подтасовка оценок налицо :)

Не понял, почему при усложнении задачи мое решение должно стать монструознее вашей реализации?

Речь шла о конструкторах,
function AnyConstr(){
    this.prop1 = 0;
    this.prop2 = '';
    ......
    subscribe('event1', this.method1.bind(this));
    subscribe('event2', this.method2.bind(this));
    subscribe('event3', this.method3.bind(this));
    subscribe('event4', this.method4.bind(this));
    subscribe('event4', this.method5.bind(this));
    subscribe('event4', this.method6.bind(this));
    .......
}

Вот так будут выглядеть ваши конструкторы при мало-мальском усложнении задачи. Я не считаю это хорошим кодом, и уже пару раз объяснил, почему — в одном месте будет сосредоточено. После этого нам захочется подписку по условию — и всё, мы погрязли в лапше, нам нужен рефакториниг. Так что, 1:0.

Из реорганизации — перенос подписки на событие. В вашем случае, например, при выносе handleCoinClick надо будет перенести и EventPoint, а в моем случае только cut&paste метода, так что паритет, 1:1

надо будет перенести и EventPoint

угу, тот же copy-paste, что займёт не более пяти секунд у программиста в здравом уме и с незагипсованной рукой ))

В моем случае можно повесить один обработчик на несколько событий, и несколько обработчиков на одно

Несколько событий на один обработчик можно и в моём случае, достаточно написать их через запятую
Core.CatchEvent(Event1, Event2, Event3, ...)

Несколько обработчиков на одно событие, описанных в одном месте — вот это как раз и противоречит концепции event-driven. Кстати, вы так и не объяснили, в чём, по-вашему, она заключается?
Несколько обработчиков на одно событие также противоречит концепции разделения обязанностей (ещё раз смотрим на конструкторы и убеждаемся, что конструктор «знает» слишком много).

Что же, ещё один балл в мою пользу

Так что в моем решении на одну зависимость меньше, что явно лучше, 3:2


функции trigger и subscribe — это тоже зависимости, только наколенные. вы наверняка захотите их перенести в отдельный файл. так что это вообще не аргумент.

в итоге, моё решение-таки лучше, по вашим же аргументам и методике оценки =)
Вот так будут выглядеть ваши конструкторы при мало-мальском усложнении задачи.

Это ни капли не хуже вашего кода. У вас будет ровно то же самое, но только еще с кодом метода-обработчика рядом.

var AnyConstr = {};

AnyConstr.Event1 = function () {
  Core.CatchEvent(Some.Event1);
  // ...
};
AnyConstr.Event2 = function () {
  Core.CatchEvent(Some.Event2);
  // ...
};
AnyConstr.Event3 = function () {
  Core.CatchEvent(Some.Event3);
  // ...
};
AnyConstr.Event4 = function () {
  Core.CatchEvent(Some.Event4);
  // ...
};
AnyConstr.Event5 = function () {
  Core.CatchEvent(Some.Event5);
  // ...
};
AnyConstr.Event6 = function () {
  Core.CatchEvent(Some.Event6);
  // ...
};


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

угу, тот же copy-paste, что займёт не более пяти секунд у программиста в здравом уме и с незагипсованной рукой ))


В моем варианте это займет не больше.

Несколько обработчиков на одно событие, описанных в одном месте — вот это как раз и противоречит концепции event-driven.


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

функции trigger и subscribe — это тоже зависимости,


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

В итоге имеем отличие от моего решения максимум во внешнем виде кода (и то спорном), и это ценой дополнительных 7кб.
ну и то, что вы называете минусом («обработчик событий знает об источнике») — это, наоборот, плюс — это его обязанность знать об источнике и о том, что он из источника получит. иначе придется где-то ещё описывать связь с источником — лишнее усложнение (что у вас закономерно в конструкторах и получилось)
Чем больше в интернете разных подходов описать одну и ту же проблему — тем проще новичкам будет учиться.
Задача опытных девелоперов тут — не допустить в статьях откровенных ошибок в решении проблемы.

и да, хорошо если в решении будут линки на хрестоматийную литературу

p.s. сори чет не прикрепилось куда надо, это ответ для maxatwork
Разные подходы — хорошо, но и плодить термины не за чем.
Sign up to leave a comment.

Articles

Change theme settings