【问题标题】:JavaScript: Why so much abstraction/interfacing in Addy's Observer Pattern? [closed]JavaScript:为什么 Addy 的观察者模式中有这么多抽象/接口? [关闭]
【发布时间】:2023-04-11 05:23:02
【问题描述】:

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

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

  • 使用原生的push() 方法。
  • 创建ObjectList.add() 方法。
  • 使用Subject 对象继承/扩展ObjectList 对象。
  • 创建用作接口的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 对象带来了很多不必要的代码。

【问题讨论】:

  • 这里的Subject 对象需要更多的代码。在我看来,它既可以继承自ObserverList,也可以包含可公开访问的ObserverList,然后在这些情况下,它不必定义全新的观察者接口并重新实现一大堆已经存在的方法在ObserverList 对象中。
  • 这个问题对我来说感觉像是一个讨论问题 + 主要是基于意见的问题。
  • @jfriend00:我认为这个问题最适合 SO,因为它涉及理解如何在 JS 中实现这种设计模式的基本“理论”,这被认为是关于该主题的基础/权威书籍。这不仅仅是一个需要优化的任意代码块。
  • @shmuli - Subject 对象的接口与ObserverList 对象的接口分离。 Subject 对象的实现仍然高度依赖于ObserverList。不同之处在于,如果ObserverList 接口发生更改,您不必更改Subject 接口,但您必须修复Subject 实现以使用更改后的ObserverList。以这种方式解耦它会给您(Subject 对象的实现者)带来保持最新的负担,但允许您永远不会更改 Subject 接口。这就是解耦。
  • @shmuli - 这种接口解耦的代价是重新实现一个本来很好的接口,重新记录一个本来已经记录的接口并重新测试一个新实现的接口。所以解耦两个接口并不总是最好的选择。而且,不言而喻,无论何时您使用另一个对象,您都对该对象的接口具有实现依赖性。因此,所有其他代码的使用都带有这种依赖性。问题是你的接口是否耦合?

标签: javascript design-patterns observer-pattern observers


【解决方案1】:

难道不能更简单地通过继承 ObserverList 对象?

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

这是否实现了更大程度的解耦,以 Addy 的方式进行 有吗?

是的,因为Subject 对象的接口完全不依赖于ObserverList 接口(因为Subject 重新实现了自己的接口到这些方法,因此它的接口与@ 解耦987654325@ 接口。这有利也有弊。重新实现接口应该有充分的理由,因为它大多只是一堆额外的代码,没有添加任何实际有用的功能。

如果是这样,具体情况如何?

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

好像 Subject 对象带来了很多不必要的代码。

是的,确实如此。


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

  1. 您的对象可以从该其他对象继承,从而自动公开其整个接口(并允许您在需要时覆盖某些方法)。

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

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

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

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


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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-30
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    • 2012-07-22
    • 2012-06-26
    相关资源
    最近更新 更多