在这种情况下,比中介模式更好地解耦小部件?

时间:2010-11-25 08:36:47

标签: javascript design-patterns mediator

我想弄清楚在某种情况下要遵循哪种模式。我有一个Web应用程序,包含几个主要的小部件,以某种方式相互交互。小部件遵循模块模式。

让代码说话:

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事件模式可以实现很多解耦。但这更适合A开始的事情,B可以做出反应。但我有一种情况,即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);

这样做要好一些,因为Widgets不直接通信,而是通过调解器间接通信。

然而,我仍然觉得我做错了,必须有更好的方法来实现小部件之间的脱钩。

修改

到目前为止,让我总结一下!

一般来说,我确实喜欢分离视图的MVC方法!但是,请将此示例视为复杂模块。那些并不一定是视觉上的“盒子”。用这种方式描述会更容易。

另一个给定的事实应该是,A独立启动一个动作 ,然后需要检查一些状态。它不能订阅B的状态变化并提供动作或不提供动作。它必须像A开始独立然后需要检查某个状态。可以把它想象成需要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 个答案:

答案 0 :(得分:3)

你应该查看一篇关于Addy Osmani Patterns For Large-Scale JavaScript Application Architecture提出的大型JS应用程序的精彩文章,这里有一个代码示例Essential js design patterns

答案 1 :(得分:1)

您仍然可以使用介体,但在其中实现业务逻辑。因此,不是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. 同时参考第三个控件(例如,删除按钮),并根据状态启用或禁用按钮。

答案 2 :(得分:1)

假设我理解“盒子”的性质是一个在你的页面上可见的盒子,那么盒子应该呈现一个代表你的应用程序或其中一部分状态的视图 - 底层状态本身应该由一个对象维护,该对象与表示UI中该状态的视图分开。

因此,例如,一个框视图可能会呈现一个人的视图,当该人睡觉时该框将为黑色,而当该人清醒时该框将为白色。如果您的另一个盒子负责显示该人正在吃的东西,那么您可能希望该盒子仅在该人醒着时起作用。 (很好的例子很难,我刚刚醒来。抱歉。)

这里的要点是你不希望视图互相询问 - 你希望它们关心底层对象的状态(在本例中是一个Person)。如果两个视图关注同一个Person,则可以将Person作为参数传递给两个视图。

很有可能你的需求有点复杂:)但是,如果你可以根据“有状态对象”的独立观点来思考问题,而不是两个需要直接关心彼此的观点,我认为你会好起来的。

答案 3 :(得分:0)

为什么不能以下列方式使用pub-sub模型

  1. LeftBox发出getStateFromRightBox个事件。

  2. RightBoxgetStateFromRightBox个订阅者,使用sendStateToLeftBoxAndExecute发布stateData个事件

  3. LeftBox有一个sendStateToLeftBoxAndExecute订阅者,可以提取stateData并有条件地执行操作。

答案 4 :(得分:0)

一些潜在的选择

我仍然建议使用Mediator - 但是,如果你更像是一个继承粉丝,你可能想要使用模板方法,状态或策略以及装饰模式 - 因为JavaScript会有接口,这些可能很有用。

后一种方法可能允许您将您的程序分类为更易于管理的策略,但是,我将继续介绍Mediator,因为它最有意义[对我来说,在这种情况下。

您可以将其实施为EDM(事件驱动的中介)或经典的Mediator:

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

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

我唯一可以建议的是打破你的程序,让Mediator有机会在这个过程中发表意见。调解可能看起来更像这样:

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必须获得Mediator的许可才能继续,因为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);

我认为你很亲密 - 我希望这会有所帮助。