0

我试图弄清楚在某种情况下要遵循哪种模式。我有一个网络应用程序,它由几个以某种方式相互交互的主要小部件组成。小部件遵循模块模式。

让代码说话:

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;
})();

如您所见,我在这里有紧密的耦合。众所周知,紧耦合是邪恶的。(除非你找到了唯一的一个!;-))

我知道通过遵循发布-订阅/自定义事件模式可以实现很多解耦。但这更适合 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);

这样稍微好一点,因为 Widget 不直接通信,而是通过中介间接通信。

但是,我仍然觉得我做错了,必须有更好的方法来实现小部件之间的解耦。

编辑

让我总结一下到目前为止的事情!

总的来说,我确实喜欢分离视图的 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>
4

5 回答 5

3

您应该查看 Addy Osmani Patterns For Large-Scale JavaScript Application Architecture提出的有关大型 JS 应用程序的精彩文章,这里是基本 js 设计模式的代码示例

于 2012-07-31T10:02:14.943 回答
1

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

因此,例如,一个盒子视图可能会渲染一个人的视图,当人睡觉时盒子是黑色的,当人醒来时盒子是白色的。如果您的另一个盒子负责显示此人正在吃什么,那么您可能希望该盒子仅在此人醒着时才起作用。(好的例子很难,我刚醒来。对不起。)

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

很有可能您的需求有点复杂:)但是,如果您可以从“有状态对象”的独立视图的角度来考虑问题,而不是两个需要直接相互关心的视图,我认为您会过得更好。

于 2010-11-26T16:04:36.593 回答
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. 还要引用第三个控件(例如,删除按钮),并根据状态启用或禁用按钮。
于 2010-11-25T08:49:28.667 回答
0

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

  1. LeftBox发出getStateFromRightBox事件。

  2. RightBoxgetStateFromRightBox订阅者,它sendStateToLeftBoxAndExecutestateData

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

于 2010-11-25T09:00:21.930 回答
0

一些潜在的选择

我仍然建议使用中介者——但是,如果你更喜欢继承,你可能想尝试一下模板方法、状态或策略以及装饰器模式——因为 JavaScript 没有接口,这些可能有用。

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

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

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 必须获得 Mediator 的许可才能继续,因为 Mediator 将拥有关于状态的高级视图。

使用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);

我认为你非常接近 - 我希望这会有所帮助。

于 2016-06-07T22:27:25.747 回答