Как работают интерфейсы с конструктивными сигнатурами?
У меня возникают проблемы с тем, как работают конструкторы в интерфейсах. Возможно, я совершенно ничего не понимаю. Но я искал ответы на какое-то время, и я не могу найти ничего, что связано с этим.
Как реализовать следующий интерфейс в классе TypeScript:
interface MyInterface {
new ( ... ) : MyInterface;
}
Андерс Хейлсберг создает интерфейс, содержащий нечто подобное этому видео (примерно 14 минут). Но для жизни меня я не могу реализовать это в классе.
Я, наверное, что-то недопонимаю, чего я не получу?
ИЗМЕНИТЬ:
Прояснить. С "новым (...)" я имел в виду "что угодно". Моя проблема в том, что я не могу получить даже самую базовую версию этой работы:
interface MyInterface {
new () : MyInterface;
}
class test implements MyInterface {
constructor () { }
}
Это не компиляция для меня. Я получаю "Тест класса", объявляющий интерфейс "MyInterface", но не реализующий его. Тип "MyInterface" требует наличия сигнатуры конструкции, но при попытке его скомпилировать тип "test" отсутствует ".
ИЗМЕНИТЬ:
Итак, после изучения этого немного больше, учитывая обратную связь.
interface MyInterface {
new () : MyInterface;
}
class test implements MyInterface {
constructor () => test { return this; }
}
Недействительно TypeScript, и это не решает проблему. Вы не можете определить возвращаемый тип конструктора. Он вернет "тест". Подпись: class test { constructor() {} }
Кажется, это "новый() = > тест" (полученный путем зависания над "классом" в онлайн-редакторе с помощью только того кода, который вставлен). И это то, что мы хотели бы и что я думал, что это будет.
Может ли кто-нибудь представить пример этого или чего-то подобного там, где он собственно компилируется?
EDIT (снова...):
Итак, я мог бы придумать, почему это можно определить в интерфейсе, но невозможно реализовать в классе TypeScript. Следующие работы:
var MyClass = (function () {
function MyClass() { }
return MyClass;
})();
interface MyInterface {
new () : MyInterface;
}
var testFunction = (foo: MyInterface) : void => { }
var bar = new MyClass();
testFunction(bar);
Итак, это только функция TypeScript, которая позволяет вам взаимодействовать с javascript? Или можно реализовать его в TypeScript без необходимости реализации класса с помощью javascript?
Ответы
Ответ 1
Построение подписей в интерфейсах не реализуется в классах; они предназначены только для определения существующих JS API, которые определяют "новую" функцию. Вот пример, содержащий интерфейсы new
, которые работают:
interface ComesFromString {
name: string;
}
interface StringConstructable {
new(n: string): ComesFromString;
}
class MadeFromString implements ComesFromString {
constructor (public name: string) {
console.log('ctor invoked');
}
}
function makeObj(n: StringConstructable) {
return new n('hello!');
}
console.log(makeObj(MadeFromString).name);
Это создает фактическое ограничение для того, что вы можете вызвать makeObj
с помощью:
class Other implements ComesFromString {
constructor (public name: string, count: number) {
}
}
makeObj(Other); // Error! Other constructor doesn't match StringConstructable
Ответ 2
В моем поиске точно такого же вопроса я пошел посмотреть, как это сделал TypeScript -Team...
Они объявляют интерфейс, а затем переменную с именем, соответствующим точному имени интерфейса. Это также способ ввода статических функций.
Пример из lib.d.ts
:
interface Object {
toString(): string;
toLocaleString(): string;
// ... rest ...
}
declare var Object: {
new (value?: any): Object;
(): any;
(value: any): any;
// ... rest ...
}
Я пробовал это, и он работает как шарм.
Ответ 3
Ну, интерфейс с сигнатурой конструкции не предназначен для реализации каким-либо классом (на первый взгляд это может показаться странным для парней с С#/Java-опытом, таких как я, но дать ему шанс). Это немного отличается.
На мгновение представьте, что это интерфейс с сигнатурой вызова (например, @FunctionalInterface в мире Java). Его цель - описать тип функции. Предполагается, что описанная сигнатура удовлетворяется функциональным объектом... а не какой-либо высокоуровневой функцией или методом. Это должна быть функция, которая знает, как создать объект, функция, которая вызывается при использовании ключевого слова new
.
Таким образом, интерфейс с сигнатурой конструкции определяет сигнатуру конструктора! Конструктор вашего класса, который должен соответствовать сигнатуре, определенной в интерфейсе (представьте, что конструктор реализует интерфейс). Это как фабрика!
Вот небольшой фрагмент кода, который пытается продемонстрировать наиболее распространенное использование:
interface ClassicInterface { // old school interface like in C#/Java
method1();
...
methodN();
}
interface Factory { //knows how to construct an object
// NOTE: pay attention to the return type
new (myNumberParam: number, myStringParam: string): ClassicInterface
}
class MyImplementation implements ClassicInterface {
// The constructor looks like the signature described in Factory
constructor(num: number, s: string) { } // obviously returns an instance of ClassicInterface
method1() {}
...
methodN() {}
}
class MyOtherImplementation implements ClassicInterface {
// The constructor looks like the signature described in Factory
constructor(n: number, s: string) { } // obviously returns an instance of ClassicInterface
method1() {}
...
methodN() {}
}
// And here is the polymorphism of construction
function instantiateClassicInterface(ctor: Factory, myNumberParam: number, myStringParam: string): ClassicInterface {
return new ctor(myNumberParam, myStringParam);
}
// And this is how we do it
let iWantTheFirstImpl = instantiateClassicInterface(MyImplementation, 3.14, "smile");
let iWantTheSecondImpl = instantiateClassicInterface(MyOtherImplementation, 42, "vafli");
Ответ 4
С точки зрения дизайна обычно не требуется указывать требования к конструктору в интерфейсе. Интерфейс должен описывать операции, которые вы можете выполнять на объекте. Для разных классов, реализующих интерфейс, должно быть разрешено требовать разные параметры конструктора, если им нужно.
Например, если у меня был интерфейс:
interface ISimplePersistence {
load(id: number) : string;
save(id: number, data: string): void;
}
У меня могут быть реализации для хранения данных в виде файла cookie, для которого не требуются параметры конструктора, и версия, которая хранит данные в базе данных, для которой требуется строка подключения в качестве параметра конструктора.
Если вы все еще хотите определить конструкторы в интерфейсе, есть грязный способ сделать это, что я использовал для ответа на этот вопрос:
Интерфейсы с сигнатурами конструкций не проверяются типом
Ответ 5
Чтобы достичь намеченного поведения, вы можете использовать Decorators, хотя это, вероятно, не то, для чего они должны использоваться.
Это
interface MyInterface {
new ();
}
function MyInterfaceDecorator(constructor: MyInterface) {
}
@MyInterfaceDecorator
class TestClass {
constructor () { }
}
компилируется без проблем. Напротив, следующее определение для TestClass
// error TS2345: Argument of type 'typeof TestClass' is not assignable to parameter of type 'MyInterface'.
@MyInterfaceDecorator
class TestClass {
constructor (arg: string) { }
}
не будет компилироваться.