Javascript ООП — рекомендации при проверке объектов через интерфейс или прототип

#javascript #oop #inheritance #interface #prototype-programming

#javascript #ооп #наследование #интерфейс #прототип-программирование

Вопрос:

Я изучаю более продвинутую тактику OO для javascript, исходя из фона C #, и мне интересно, как или если это даже хорошая идея для реализации проверки на основе прототипа. Например, когда объекту или функции требуется, чтобы один из его параметров удовлетворял определенному интерфейсу, вы можете проверить его интерфейс следующим образом,

 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 amp;amp; 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). Есть 2 основные причины, по которым я заинтересован в проверке в этом контексте,

1) Предоставление надежного общедоступного API для модификации игры, если это необходимо

2) Предотвращение (или, по крайней мере, сильно препятствующее) потенциальных мошенников

Что требует баланса между настраиваемостью и злоупотреблениями. В частности, одна ситуация может быть связана с разработкой физического движка, в котором объекты в игре реагируют на гравитацию. В реалистичной системе пользователи не должны иметь возможности добавлять в систему объекты, которые не реагируют на гравитацию. Система имеет функцию, которая выражает глобальный эффект гравитации в любой заданной точке:

 function getGravityAt(x, y) {
      // return acceleration due to gravity at this point
}
  

И объекты, которые реагируют, имеют метод, который использует это для обновления их ускорения:

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

Минимальное, что нужно сделать, это убедиться, что любой объект, добавленный в систему, имеет метод ‘update’, но вы все еще не гарантируете, что метод update () действительно предназначен для реагирования на гравитацию. Если разрешены только объекты, которые наследуются от прототипического метода update() , то вы знаете, по крайней мере, до некоторой степени, что все в системе реагирует реалистично.

Комментарии:

1. Лично я бы вообще не проверял. Динамически типизированные языки, такие как JS или Python, скорее следуют концепции утиного ввода … если это звучит и выглядит как утка, это утка. Если ваша функция ожидает параметр, совместимый с определенным интерфейсом, и не получает его … тогда он все равно выдаст ошибку. Ответственность за правильное использование вашего кода лежит на других программистах.

2. Что делать, если вы разрабатываете игру или API на JavaScript и хотите предотвратить злоупотребление фреймворком? Я бы подумал, что четко определенные интерфейсы — хорошая идея, особенно в дизайне API.

3. Конечно, интерфейсы должны быть четко определены и хорошо документированы. Не поймите меня неправильно, это всегда важно. Но имо вы не должны проверять их во время выполнения.

4. Если у вас есть функция (x, y), которая ожидает точку, и кто-то передает объект, который сам по себе уже содержит свойства ‘x’ и ‘y’, вы обычно выполняете: if(x.x amp;amp; x.y) { } для проверки наличия такого объекта, чтобыфункция может принимать либо точечный объект, либо каждую координату по отдельности. Разве это не проверка интерфейса? Цель моего класса — просто включить эту функциональность менее подробным способом (для более сложных объектов ваши операторы if будут очень длинными).

Ответ №1:

Это довольно субъективный вопрос. Я передам вопрос о том, является ли хорошей идеей вообще выполнять проверку на основе интерфейса в Javascript (для этого могут быть хорошие варианты использования, но это не стандартный подход в языке). Но я скажу, что, вероятно, не стоит проверять объекты на основе их прототипов.

Если вы вообще выполняете проверку с помощью интерфейса, вы, вероятно, работаете с объектами, созданными другими программистами. Существует множество способов создания объектов — некоторые полагаются на прототипы, некоторые нет, и хотя у каждого из них есть свои сторонники, все они являются допустимыми и вероятными подходами. Например:

 var Point = function(x,y) {
    return {
        x: function() { return x },
        y: function() { return y }
    };
};

var p = new Point(1,1);
  

Объект p соответствует интерфейсу, аналогичному вашему выше, за исключением того, что x и y являются функциями. Но нет способа проверить, что p удовлетворяет этому интерфейсу, проверив его конструктор (который есть Object() ) или Point.prototype . Все, что вы можете сделать, это проверить, что p атрибуты называются x и y и что они имеют тип "function" — то, что вы делаете выше.

Потенциально вы можете настаивать на том, что p в цепочке прототипов есть определенный предок, например AbstractPoint , который будет включать функции x and y — вы можете использовать instanceof для проверки этого. Но вы не можете быть уверены, что x и y не были переопределены в 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 amp;amp; point.y)) return false; // evasive action!
    // etc.
}
  

Ответ №2:

Я повторю, проверка типов не является идиоматическим javascript.

Если вы все еще хотите проверить тип, я рекомендую компилятор закрытия Google. Проверка типов выполняется статически 🙂 В нем есть соглашения для интерфейсов, а также (прото) проверка типов.