JavaScript:为什么在Addy的观察者模式中有如此多的抽象/接口?

时间:2015-03-27 16:27:22

标签: javascript design-patterns observer-pattern observers

我正在研究Addy Osmani的书“JavaScript设计模式”中的观察者模式的设计模式示例。我的问题是为什么在他的模式实现中有如此多的抽象层次很重要?

例如,在他的例子中,只是为了添加一个观察者(将一个观察者“推”到一个数组),这涉及:

  • 使用原生push()方法。
  • 创建ObjectList.add()方法。
  • 使用ObjectList对象继承/扩展Subject对象。
  • 创建Subject.addObserver()方法,该方法将用作接口,但在引擎盖下使用ObjectList.add方法。
  • 扩展新对象的Subject.addObserver()方法。
  • 通过在新扩展对象上调用addObserver()方法来实现它。

以下是设计模式的完整代码示例:

function ObserverList(){
  this.observerList = [];
}

ObserverList.prototype.add = function( obj ){
  return this.observerList.push( obj );
};

ObserverList.prototype.count = function(){
  return this.observerList.length;
};

ObserverList.prototype.get = function( index ){
  if( index > -1 && index < this.observerList.length ){
    return this.observerList[ index ];
  }
};

ObserverList.prototype.indexOf = function( obj, startIndex ){
  var i = startIndex;

  while( i < this.observerList.length ){
    if( this.observerList[i] === obj ){
      return i;
    }
    i++;
  }

  return -1;
};

ObserverList.prototype.removeAt = function( index ){
  this.observerList.splice( index, 1 );
};

function Subject(){
  this.observers = new ObserverList();
}

Subject.prototype.addObserver = function( observer ){
  this.observers.add( observer );
};

Subject.prototype.removeObserver = function( observer ){
  this.observers.removeAt( this.observers.indexOf( observer, 0 ) );
};

Subject.prototype.notify = function( context ){
  var observerCount = this.observers.count();
  for(var i=0; i < observerCount; i++){
 this.observers.get(i).update( context );
  }
};

// The Observer
function Observer(){
  this.update = function(){
    // ...
  };
}

这是实施/用法:

HTML

<button id="addNewObserver">Add New Observer checkbox</button>
<input id="mainCheckbox" type="checkbox"/>
<div id="observersContainer"></div>

脚本

// Extend an object with an extension
function extend( extension, obj ){
  for ( var key in extension ){
    obj[key] = extension[key];
  }
}

// References to our DOM elements

var controlCheckbox = document.getElementById( "mainCheckbox" ),
  addBtn = document.getElementById( "addNewObserver" ),
  container = document.getElementById( "observersContainer" );


// Concrete Subject

// Extend the controlling checkbox with the Subject class
extend( new Subject(), controlCheckbox );

// Clicking the checkbox will trigger notifications to its observers
controlCheckbox.onclick = function(){
  controlCheckbox.notify( controlCheckbox.checked );
};

addBtn.onclick = addNewObserver;

// Concrete Observer

function addNewObserver(){

  // Create a new checkbox to be added
  var check  = document.createElement( "input" );
  check.type = "checkbox";

  // Extend the checkbox with the Observer class
  extend( new Observer(), check );

  // Override with custom update behaviour
  check.update = function( value ){
    this.checked = value;
  };

  // Add the new observer to our list of observers
  // for our main subject
  controlCheckbox.addObserver( check );

  // Append the item to the container
  container.appendChild( check );
}

现在我将他的实现与相同模式的其他实现(书籍和博客)进行了比较。似乎Addy比观察者模式的其他实现者增加了更多的抽象。问题是,为什么?通过继承ObserverList对象,难道不能更简单地实现它吗?这是否会像Addy那样实现更大程度的脱钩?如果是这样,究竟是怎么回事?设计模式本身不会产生脱钩吗?好像Subject对象带来了许多不必要的代码。

1 个答案:

答案 0 :(得分:3)

  

这不能通过继承来更简单地实现   ObserverList对象?

是。通过继承,将不会重新实现所有ObserverList方法。显着减少代码,减少测试和减少文档。

  

这是否实现了更大程度的解耦,就像Addy一样   呢?

是的,确实如此,因为Subject对象的接口完全不依赖于ObserverList接口(因为Subject已经重新实现了自己的接口,因此它接口与ObserverList接口分离。这有它的优点和缺点。重新实现接口只应该有充分的理由,因为它主要只是一堆额外的代码,没有添加实际有用的功能。

  

如果是这样,究竟是怎么回事?

通过重新实现自己的版本来隐藏ObserverList的实际接口将两个接口分离。 ObserverList接口可以隐藏对基础Subject接口的更改。虽然Subject实现仍然依赖并耦合到ObserverList接口,但Subject接口本身独立于ObserverList接口。但是,有很多理由不这样做,所以不要认为每个接口都应该与其他接口分离。那无处不在,这将是一场灾难。

  

似乎Subject对象带来了许多不必要的代码。

是的,确实如此。


当您想要使用其他对象的功能并且想要将部分或全部功能公开给您自己对象的客户时,您有许多设计选择。

  1. 您的对象可以继承该另一个对象,从而自动暴露其整个界面(并允许您根据需要覆盖某些方法)。

  2. 您的对象可以包含该另一个对象的实例,并公开公开该对象,以便您的对象的用户可以直接访问其他对象,而无需重新实现任何内容。这可能是我在这种特殊情况下的选择,因此使用Subject对象中公开可用的观察者的代码将如下所示:

    var s = new Subject(); s.observer.add(function() { // this gets called when subject is changed });

  3. 您的对象可以包含该另一个对象的私有实例,您可以在该私有实例之上手动创建自己的接口。这就是你书中的代码所做的事情。

  4. 在OO中,这三种选择有时被称为isA,hasA和hidesA。在第一种情况下,您的对象“是”ObserverList对象。在第二种情况下,您的对象“具有”ObserverList对象。在第三种情况下,您的对象“在其实现中隐藏了一个”ObserverList对象。


    每种设计选择都有利弊。没有任何选择总是正确或错误的做事方式,因为每个选择都有不同的优点/缺点,哪个是最佳选择取决于具体情况。

    选项1)继承的情况通常是当您的对象是基础对象的扩展时,从架构上讲,它被认为只是基础对象的更强大版本和/或它可能会覆盖基础对象上的方法。事实并非如此。 Subject()对象不是更强大的ObserverList对象。这是一种碰巧使用ObserverList的不同类型的对象。

    包含ObserverList的公共实例并允许对象的用户使用该公共实例的选项2)的情况是当您的对象实际上是另一种对象时,但是它想要使用并向其用户公开另一种对象的功能。在我看来,这主要是在这里发生的事情。

    选项3的情况是指您不希望对象的接口与任何其他接口之间存在任何接口依赖关系。在这种情况下,您不能公开其他对象的界面以让对象的用户使用它们。相反,您必须使用自己的接口覆盖任何其他接口。这意味着更多代码,更多测试,更多文档和更多维护。但是,您正在使用的底层接口的更改不一定会导致您自己的界面发生更改。您可以选择隐藏界面中的任何基础更改(以更多工作为代价)。但是,在获得控制权的同时,你还有更多的工作要做。如果ObserverList对象添加了三个新方法,则在其他两个选项中,这些方法可立即供您的用户使用而无需执行任何新工作,但在选项3)中,您必须为它们创建新的封面方法并在客户可以使用之前对其进行测试和记录。