JavaScript面向对象编程 - 在通过接口或原型验证对象时的最佳实践

4

我正在从C#背景中学习更高级的javascript面向对象技术,想知道是否实现基于原型的验证是一个好主意,或者如何实现。例如当一个对象或函数需要其参数满足特定接口时,可以像下面这样检查其接口:

var Interface = function Interface(i) {
   var satisfied = function (t, i) {
      for (var key in i) {
         if (typeof t !== 'object') {
            return false;
         }
         if (!(key in t && typeof t[key] == i[key])) {
            return false;
         }
      }
      return true;
  }
  this.satisfiedBy = function (t) { return satisfied(t, i); }
}

// the interface
var interfacePoint2D = new Interface({
    x: 'number',
    y: 'number'
}); 

// see if it satisfies
var satisfied = interfacePoint2D.satisfiedBy(someObject); 

我提出了一种策略,只通过其接口验证对象,忽略对象的内部实现。
另外,如果你正在使用基于原型的继承,你是否应该根据它们的原型函数来验证参数?我理解你会使用原型来实现默认功能,而接口不指定任何默认功能。有时,您传递到函数中的对象可能需要某些默认功能,以使该函数正常工作。是只针对接口进行验证更好,还是应该验证原型,如果是这样,最好的方法是什么?
编辑 - 我提供了更多上下文,解释为什么我在问这个问题,
例如,在在线游戏设计(大多数用JavaScript编写的游戏)中。我对此情况下的验证感兴趣的原因有两个主要原因,
1)如果需要,为修改游戏提供强大的公共API
2)防止(或至少大大减少)潜在的作弊行为
这需要在可定制性和滥用之间取得平衡。特别是在设计物理引擎时,游戏中的对象对重力产生反应。在真实系统中,用户不应能够添加不对重力产生反应的对象到系统中。该系统具有表达任何给定点处重力全局影响的功能:
function getGravityAt(x, y) {
      // return acceleration due to gravity at this point
}

而具有反应性的对象有一个使用此方法来更新其加速度的方法:

function update() {
      this.acceleration = getGravity(this.position); 
}

最基本的做法可能是确保系统中添加的任何对象都有一个“更新”方法,但您仍然不能确保update()方法真的旨在对重力做出反应。如果只允许从原型更新()方法继承的对象,则至少可以知道系统中的所有内容都以某种程度上逼真地做出反应。


6
就我个人而言,我不会进行验证。像JS或Python这样的动态类型语言更加遵循“鸭子类型”的概念......如果它听起来和看起来像一只鸭子,那么它就是一只鸭子。如果你的函数需要一个兼容某个接口的参数,但却没有得到它...那么它也会抛出错误。使用你的代码的正确性是其他程序员的责任。 - Felix Kling
1
如果你正在开发一个JavaScript游戏或API,并且想要防止框架被滥用,该怎么办呢?我认为明确定义接口是个好主意,特别是在API设计中。 - Sean Thoman
当然,接口应该清晰地定义并且有良好的文档说明。别误会,这一点始终很重要。但在我看来,你不应该在运行时检查它们。 - Felix Kling
1
如果你有一个期望接收点的函数(x, y),而有人传入了一个已经包含属性'x'和'y'的对象,你通常会这样做:if(x.x && x.y) { }来检查这样的对象,以便函数可以接受点对象或每个坐标单独。这不是本质上的接口检查吗?我的类的目的只是以更简洁的方式启用该功能(对于更复杂的对象,你的if语句会变得非常长)。 - Sean Thoman
2个回答

2
这是一个非常主观的问题。关于在Javascript中进行基于接口的验证是否是个好主意,我不打算回答(可能有很好的用例,但这不是语言中的标准方法)。但是我会说,基于原型来验证对象可能并不是一个好主意。
如果你正在通过接口进行验证,那么你可能正在使用其他程序员创建的对象。有很多创建对象的方法 - 有些依赖原型,有些不依赖,而且每种方法都有其支持者和合理的方法。例如:
var Point = function(x,y) {
    return {
        x: function() { return x },
        y: function() { return y }
    };
};

var p = new Point(1,1);

对象 p 符合与上面类似的接口,只是 xy 是函数。但是没有办法通过检查其构造函数(即 Object())或 Point.prototype 来验证 p 是否满足此接口。你所能做的就是测试 p 是否有名为 xy 的属性,并且它们的类型为 "function" - 就像你上面所做的那样。
你可以坚持认为 p 在其原型链中有一个特定的祖先,例如 AbstractPoint,其中包括 xy 函数 - 你可以使用 instanceof 检查这一点。但是你无法确定 xy 是否已在 p 中被重新定义:
var AbstractPoint = function() {};
AbstractPoint.prototype.x = function() {};
AbstractPoint.prototype.y = function() {};

var Point = function(x,y) {
    var p = new AbstractPoint(x,y);
    p.x = "foo";
    return p;
}

var p = new Point(1,1);
p instanceof AbstractPoint; // true
p.x; // "foo"

也许更重要的是,这使得放入自定义对象变得更加困难,尽管它们也满足接口但不继承于你的类。

因此,我认为你目前正在做的可能就是最好的了。根据我的经验,JavaScript程序员更倾向于使用即时的鸭子类型而不是试图模仿静态类型语言的能力:

function doSomethingWithUntrustedPoint(point) {
    if (!(point.x && point.y)) return false; // evasive action!
    // etc.
}

0

我再强调一遍,类型检查不是JavaScript的惯用法。

如果你仍然需要类型检查,我推荐使用Google的闭包编译器。它可以静态地进行类型检查 :) 它还有接口约定以及(原型)类型检查的功能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接