Проблема заключается в различии метода, определенного в интерфейсе (A.say
), от метода, определенного в прототипе интерфейса (A.prototype.say
).В системе типов Closure тег @interface
предоставляет следующую гарантию:
Любой класс, реализующий интерфейс, должен реализовывать все методы и свойства, определенные в прототипе интерфейса.
Обратите внимание, что заботятся только о методах прототипа.Его не волнуют методы самого интерфейса.
Предположим, вы создаете новый класс C
, который реализует Sayer
.Закрытие обязательно определит C.prototype.say
.Однако вы можете пропустить C.say
.Вы не обязаны это реализовывать.Следующее является совершенно действительным Sayer
.
/**
* @constructor
* @implements {Sayer}
*/
function C() {}
C.prototype.say = function() { console.log('Am a C'); }
Поскольку это действительное Sayer
, мы можем с уверенностью позвонить makeSayer(C)
.Теперь понятно, почему ваше определение makeSayer
неверно.Предполагается, что cls.say
определено, но Closure не дает такой гарантии.Закрытие обнаруживает проблему и выдает соответствующее сообщение об ошибке.
Так что же вам делать?
Если вы хотите следовать стандартному объектно-ориентированному подходу, то вам, вероятно, понадобятся два интерфейса, один дляфабрика SayerFactory
и одна для объектов, которые она создает Say
.Все ваши методы станут методами, определенными в прототипах, и Closure выполнит всю вашу проверку за вас.
Но если вам нравится идея иметь метод say
в ваших конструкторах, вы можете попробовать использовать утку, набравобеспечить наличие фабричного метода.Что-то вроде:
/**
* @param {{say:function():Sayer}} cls
*/
function makeSayer(cls) {
...
}
Конечно, подход по типу утки не очень хорошо масштабируется, если вам нужно определить более одного метода.Сигнатура типа будет увеличиваться для каждого определенного метода.
Удачи!