Лучше, чем шаблон посредника для развязки виджетов в этой ситуации? - PullRequest
0 голосов
/ 25 ноября 2010

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

Чтобы код говорил:

MyApp.Widgets.MainLeftBox = (function(){
    var self = {};

    self.doSomething = function(){
        var certainState = MyApp.Widgets.MainRightBox.getCertainState();
        if (certainState === 1){
            console.log(‘this action happens now’);
        }
        else {
             console.log(‘this action can’t happen because of a certain state in My.App.Widgets.MainRightBox’);
        }
    } 
    return self;
})();

Как видите, у меня здесь сильная связь. Как мы все знаем, тесная связь - это зло. (За исключением случаев, когда вы нашли один-единственный!; -))

Я знаю, что развязки можно достичь, следуя шаблону pub-sub / custom event. Но это лучше подходит для ситуаций, когда А начинает что-то, и В может реагировать. Но у меня есть ситуация, когда A запускает что-то независимо, но для проверки необходимо проверить определенное состояние из B.

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

Первое, что пришло мне в голову, - образец посредника.

Но все же мой код будет выглядеть так:

MyApp.Widgets.MainLeftBox = (function(mediator){
    var self = {};

    self.doSomething = function(){
        var certainState = mediator.getCertainState();
        if (certainState === 1){
            console.log(‘this action happens now’);
        }
        else {
             console.log(‘this action can’t happen because of a certain state in mediator’);
        }
    } 
    return self;
})(MyApp.Mediator);

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

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

EDIT

Позвольте мне подвести итоги!

В общем, мне нравится подход MVC разделения представлений! Однако, думайте об этом примере как о сложных модулях. Это не обязательно должны быть «коробки» в визуальном смысле. Просто так проще описать.

Еще одним фактом должно быть то, что A запускает действие независимо и тогда ему нужно проверить состояние. Он не может подписаться на изменение состояния B и предоставлять действие или нет. Это должно быть похоже на то, как А запускает его независимо , а затем необходимо проверить определенное состояние. Думайте об этом как о некоторой сложной операции, о которой нужно попросить B.

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

1.) Модуль не знает ни о каком другом модуле
2.) Модуль не знает ни о посреднике, ни
3.) Модуль, зависящий от некоторого внешнего состояния, знает только, что он зависит от некоторого внешнего состояния - не более
4.) Модулю действительно все равно кто предоставит это определенное состояние
5.) Модуль может определить, было ли предоставлено определенное состояние или нет
6.) Трубопровод запроса прямой. Другими словами, модуль является началом этой операции. он не просто подписывается на событие изменения состояния (помните, что A запускает действие, а затем ему нужно состояние из B (или где-то еще)

Я разместил здесь пример кода, а также предоставил jsFiddle: http://jsfiddle.net/YnFqm/

<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
</head>
<body>
<div id="widgetA"></div>
<div id="widgetB"></div>
<script type="text/javascript">

var MyApp = {};

(function (MyApp){

    MyApp.WidgetA = function WidgetA(){

        var self = {}, inner = {}, $self = $(self);

        //init stuff
        inner.$widget = $('#widgetA');
        inner.$button = $('<button>Click Me!</button>')
                            .appendTo(inner.$widget)
                            .click(function(){self.doAction();});


        self.doAction = function(){
            //Needs state from WidgetB to proceed

            /* Tight coupling
            if (MyApp.WidgetB.getState() == 'State A'){
                alert('all fine!');
            }
            else{
                alert("can't proceed because of State in Widget B");
            }
            */

            var state;
            $self.trigger('StateNeeded',function(s){state = s});
            if (state == 'State A'){
                alert('all fine!');
            }
            else{
                alert("can't proceed because of State in Widget B");
            }                   
        };

        return self;
    };

    MyApp.WidgetB = function WidgetB(){

        var self = {}, inner = {};

        //init stuff
        inner.$widget = $('#widgetB');
        inner.$button = $('<button>State A</button>')
                            .appendTo(inner.$widget)
                            .click(function(){
                                var buttonState = inner.$button.text();
                                if (buttonState == 'State A'){
                                    inner.$button.text('State B');
                                }
                                else{
                                    inner.$button.text('State A');
                                }
                            });


        self.getState= function(){
            return inner.$button.text();
        };

        return self;
    };

    MyApp.Mediator = (function(){
        var self = {}, widgetA, widgetB;

        widgetA = new MyApp.WidgetA();
        widgetB = new MyApp.WidgetB();

        $(widgetA).bind('StateNeeded', function(event, callback){
            //Mediator askes Widget for state
            var state = widgetB.getState();
            callback(state);
        });

        return self;
    })();

})(MyApp);

</script>
</body>
</html>

Ответы [ 5 ]

3 голосов
/ 31 июля 2012

Вы должны заценить большую статью о крупномасштабных приложениях JS, представленных Addy Osmani. Шаблоны для крупномасштабной архитектуры приложений JavaScript , а вот пример кода Основные шаблоны проектирования js

1 голос
/ 26 ноября 2010

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

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

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

Скорее всего, ваши потребности немного сложнее :) Однако, если вы можете думать о проблеме с точки зрения независимых представлений о "объектах с состоянием", а не двух представлений, которые должны заботиться непосредственно о каждомдругой, я думаю, тебе будет лучше.

1 голос
/ 25 ноября 2010

Вы все еще можете пойти с посредником, но внедрите в него свою бизнес-логику. Таким образом, вместо mediator.getCertainState() имеется метод mediator.canTakeAction(), который знает о виджете (ах) для запроса и определяет, разрешено ли действие.

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


РЕДАКТИРОВАТЬ: Предоставление примера в духе приведенных примеров кода.

MyApp.DeleteCommand=(function(itemsListBox, readOnlyCheckBox) {
  var self = {};

  self.canExecute = function() {
    return (not readOnlyCheckBox.checked()) && (itemsListBox.itemCount() > 0);
  }

  return self;
})(MyApp.Widgets.ItemsList, MyApp.Widgets.ReadOnly);

Вы можете сделать следующие два шага:

  1. Зарегистрируйтесь, чтобы сообщать об измененных событиях исходных виджетов, и обновляйте локальный кэш canExecute каждый раз, когда происходит изменение состояния на одном из исходных виджетов.
  2. Также возьмите ссылку на третий элемент управления (скажем, на кнопку удаления) и включите или отключите кнопку в соответствии с состоянием.
0 голосов
/ 08 июня 2016

Несколько возможных вариантов

Я бы по-прежнему рекомендовал использовать Mediator - однако, если вы в большей степени являетесь поклонником Наследования, вы можете поэкспериментировать с Методом Шаблонов, Состоянием или Стратегией и Шаблонами Декоратора - поскольку JavaScript поддерживает не имеют интерфейсы, это может быть полезно.

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

Вы можете реализовать его как EDM (Event-Driven Mediation) или как классический посредник:

var iEventHub = function iEventHub() {
  this.on;
  this.fire;
  return this;
};

var iMediator = function iMediator() {
  this.widgetChanged;
  return this;
};

Единственное, что я могу по-настоящему посоветовать, - это нарушить ваши процедуры, чтобы у посредника была возможность высказаться во время процесса. Посредничество может выглядеть так:

var Mediator = function Mediator() {
  var widgetA = new WidgetA(this)
    , widgetB = new WidgetB(this);

  function widgetChanged(widget) {
    identifyWidget(widget);  // magical widget-identifier

    if (widgetA.hasStarted) widgetB.isReady();
    if (widgetB.isReady) widgetA.proceed("You're proceeding!");

  }

  return this;
};

var WidgetA = function WidgetA(director) {

  function start() {
    director.widgetChanged(this);
  }

  function proceed(message) {
    alert(message);
  }

  this.start = start;
  this.proceed = proceed;

  return this;
};

var WidgetB = function WidgetB(director) {

  function start() {
    this.iDidMyThing = true;
    director.widgetChanged(this);
  }

  function isReady() {
    return iDidMyThing;
  }

  this.iDidMyThing = false;
  this.start = start;
  this.isReady = isReady;

  return this;
};

По сути, WidgetA должен получить разрешение от посредника, чтобы продолжить, так как посредник будет иметь высокоуровневое представление о состоянии.

С Classic Mediator вам, скорее всего, все равно придется звонить director.widgetChanged(this). Однако прелесть использования EDM заключается в том, что вы не обязательно подключаетесь к Mediator, но все модули реализуют интерфейс iEventHub или подключаются к общему hub. В качестве альтернативы вы можете изменить классический Mediator, чтобы помочь в авторизации модуля путем рефакторинга метода widgetChanged:

// Mediator
function widgetChanged(ACTION, state) {
    var action = actionMap[ACTION || 'NO_ACTION_SPECIFIED'];
    action && action.call && action.call(this, state);
}

// WidgetX
const changes = this.toJSON();
director.widgetChanged('SOMETHING_SPECIFIC_HAPPENED', changes);

Я думаю, вы очень близки - надеюсь, это поможет.

0 голосов
/ 25 ноября 2010

Почему вы не можете использовать модель pub-sub следующим образом

  1. LeftBox выдает событие getStateFromRightBox.

  2. RightBox имеет getStateFromRightBox подписчика, который выдает событие sendStateToLeftBoxAndExecute с stateData

  3. LeftBox имеет sendStateToLeftBoxAndExecute подписчик, который извлекает stateData и выполняет действие условно.

...