簡體   English   中英

在這種情況下,比調解器模式更好的方法來解耦小部件?

[英]Better way than mediator pattern for decoupling widgets in this situation?

我正在嘗試找出在特定情況下應遵循的模式。 我有一個網絡應用程序,該應用程序由幾個互相影響的主要小部件組成。 小部件遵循模塊模式。

讓代碼說話:

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

這樣會好一點,因為窗口小部件不直接進行通信,而是通過中介間接進行通信。

但是,我仍然覺得我做錯了,必須有一種更好的方法來實現小部件之間的解耦。

編輯

到目前為止,讓我總結一下!

總的來說,我喜歡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>

您應該查看由Addy Osmani Patterns撰寫的有關大型JS應用程序的精彩文章,有關大型JavaScript應用程序體系結構 ,這是代碼示例Essential js設計模式

您仍然可以使用調解器,但可以在其中實現業務邏輯。 所以,與其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. 還請參考第三個控件(例如,刪除按鈕),並根據狀態啟用或禁用該按鈕。

假設我了解“框”的本質是在頁面上可見的框,則框應呈現一個表示您的應用程序狀態或其一部分的視圖-底層狀態本身應由與表示UI中該狀態的視圖分開的對象。

因此,例如,方框圖可能會呈現一個人的視圖,而當人在睡覺時,該框將是黑色的,而當人醒着時,該框將變為白色。 如果您的另一個方框負責顯示此人在吃什么,那么您可能希望該方框僅在該人醒着時才起作用。 (好的例子很難,我剛剛醒來。很抱歉。)

這里的要點是,您不希望視圖互相詢問-您希望它們關心基礎對象(在本例中為Person)的狀態。 如果兩個視圖關心同一個Person,則只需將Person作為參數傳遞給兩個視圖。

很有可能您的需求會稍微復雜一些:)但是,如果您可以根據“有狀態對象”的獨立視圖來考慮問題,而不是需要彼此直接關心的兩個視圖,那么我認為您是會更好。

一些潛在的選擇

我仍然建議使用中保-但是,如果你更繼承風扇,你可能要玩的模板方法,國家或戰略,以及裝飾圖案-因為JavaScript 沒有接口,這些可能有用。

后一種方法可能使您可以將過程分類為更易於管理的策略 ,但是,我將繼續介紹Mediator,因為在這種情況下,它對我來說最有意義。

您可以將其實現為EDM(事件驅動的中介)或經典的中介器:

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的iEventHub在於您不必與Mediator本身耦合,而是所有模塊都實現iEventHub接口或與公共hub耦合。 另外,您可以通過重構widgetChanged方法來修改經典的Mediator以幫助進行模塊授權

// 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);

我認為您非常親密-希望能幫上忙。

為什么不能通過以下方式使用發布-訂閱模型

  1. LeftBox發出getStateFromRightBox事件。

  2. RightBox具有getStateFromRightBox訂閱者,該訂閱者使用stateData發出sendStateToLeftBoxAndExecute事件

  3. LeftBox具有sendStateToLeftBoxAndExecute訂閱服務器,該訂閱服務器提取stateData並有條件地執行操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM