Переопределение статической функции класса приводит к ошибке

#typescript

#машинописный текст

Вопрос:

У меня есть следующий код, который просто возвращает ошибку.

 class Foo {
    public static logBar<T>(a: T): T {
        console.log(a);
        return a
    }
}

class Bar extends Foo {
    public static logBar<T extends number>(a: T): T {
        console.log(a);
        return a
    }
}
 
 Class static side 'typeof Bar' incorrectly extends base class static side 'typeof Foo'.
   Types of property 'logBar' are incompatible.
     Type '<T extends number>(a: T) => T' is not assignable to type '<T>(a: T) => T'.
       Types of parameters 'a' and 'a' are incompatible.
         Type 'T' is not assignable to type 'number'.(2417)
 

первоначально я думал, что это была общая ошибка, но я смог повторить это со следующим

 class Foo {
    public static logBar(a: string): string {
        console.log(a);
        return a
    }
}

class Bar extends Foo {
    public static logBar(a: number): number {
        console.log(a);
        return a
    }
}
 
 Class static side 'typeof Bar' incorrectly extends base class static side 'typeof Foo'.
  Types of property 'logBar' are incompatible.
    Type '(a: number) => number' is not assignable to type '(a: string) => string'.
      Types of parameters 'a' and 'a' are incompatible.
        Type 'string' is not assignable to type 'number'.(2417)
 

Является ли статическая функция с разными параметрами не перегружаемой в typescript? если да, то почему другие языки делают это постоянно? примеры в комментариях.

пожалуйста, обратите внимание, что работает следующее

 class Foo {
    public static logBar(a: string): string {
        console.log(a   'Foo');
        return a
    }
}

class Bar extends Foo {
    public static logBar(a: string): string {
        console.log(a   'Bar');
        return a
    }
}


Foo.logBar('hello');
Bar.logBar('world');
 

и результаты с

 helloFoo
worldBar
 

Ответ №1:

Экземпляр дочернего класса должен иметь возможность использоваться всякий раз, когда используется экземпляр базового класса. Это называется принципом подстановки Лискова, и он просит методы дочерних классов принимать в качестве входных данных все значения, принятые тем же методом базового класса в качестве входных данных (и, возможно, больше), и не возвращать значения, которые не может вернуть тот же метод базового класса. Тот же принцип также не позволяет дочернему классу ограничивать видимость свойства, унаследованного от базового класса (если оно находится public в базовом классе, его нельзя изменить на protected или private в дочернем классе; допускается только наоборот).

Статический метод Foo.logBar() может быть вызван string , например, с параметром типа, поскольку нет никаких ограничений на общий параметр T .

Но Foo не может быть заменено на Bar в выражении Foo.logBar('abc') , поскольку 'abc' является строкой и Bar.logBar() ожидает аргумент типа number (из <T extends number> -за определения метода logBar() в классе Bar ).

Это, на простом английском языке, то, что пытается сообщить вам сообщение об ошибке, которое вы процитировали.
T в базовом классе может быть что угодно, но не все значения, которые он принимает в базовом классе, принимаются для него в дочернем классе; дочерний класс принимает только number для T .

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

1. то, что вы говорите, имеет смысл для функций-членов. но я говорю о статических функциях. вы просто говорите о полиморфизме. статические функции не являются полиморфными или, по крайней мере, не должны быть.

2. вот пример, который я написал на c , который действительно быстро работает. что соответствует тем же строкам, что и функция выше. код

3. Классы JavaScript на самом деле являются функциями, а функции JavaScript являются объектами. Это означает, что «статические» методы TypeScript на самом деле являются свойствами объекта, следовательно, методами экземпляра. Дочерние классы JavaScript сохраняют ссылку на свой родительский класс; именно так они наследуют свойства родительского класса, и именно поэтому LSP, вероятно, реализован и для статических методов. Разные языки, разные способы реализации ООП.

4. я привел пример на C #, который делает то, о чем я говорю. gist.github.com вы можете скомпилировать и посмотреть, что это работает, используя dotnetfiddle.net

5. C # — это не TypeScript, даже если они оба от Microsoft (а C # был источником вдохновения для создателей TypeScript) 🙂 Я знаю, что компилятор TypeScript иногда раздражает, но он прав больше, чем мне хотелось бы признавать. Это защищает вас от ошибок, которые может быть сложнее обнаружить во время выполнения.

Ответ №2:

Если вы ориентируетесь на ES6 или новее, то это, вероятно, связано с тем, что компилятор TS генерирует классы ES в соответствии с вашими TypeScript. И согласно этому сообщению в блоге, классы ES6 наследуют статические члены при расширении других классов. Из-за такого поведения TS должен обеспечивать совместимость типов между статическими членами, чтобы предотвратить возможную путаницу типов во время выполнения.

Перегрузка и переопределение TS сопровождаются множеством предостережений по сравнению с другими объектно-ориентированными языками. На таком языке, как C # или Java (не script), приведенные выше примеры приведенных выше типов были бы полностью корректными. Foo будет иметь один статический метод logBar , который возвращает a string , и Bar будет иметь два статических метода — исходный logBar и перегрузку , которая возвращает a number . В TS это невозможно, поскольку JavaScript не поддерживает перегрузку. Для каждого имени свойства каждого объекта может быть только один метод, независимо от типа. Перегрузки методов в TS возможны только потому, что они существуют только во время компиляции. Компилятор удаляет определения перегрузки, и только реализация передается в JS. TS не допускает повторяющихся реализаций перегрузки. Поскольку оригинал logBar наследуется, по сути, существуют две конфликтующие реализации одного и того же метода. Я полагаю, что вы увидели бы ту же ошибку, даже если бы методы не были статическими.

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

1. Я обновил свой вопрос, чтобы отразить ваш комментарий. Статические функции наследуются, но это не означает, что они не могут быть переопределены.

2. В этом случае, я думаю, проблема просто в том, что вы пытаетесь переопределить logBar функцию, которая возвращает несовместимый возвращаемый тип. Даже с помощью обычного метода вы не можете переопределить метод, который возвращает string , с помощью метода, который возвращает number . Если вы измените Foo.logBar значение return string | number , исчезнет ли ошибка?

3. нет, все та же проблема typescriptlang

4. Я обновил свой ответ некоторой дополнительной информацией, которая может помочь. По сути, вы logBar перегружаете несовместимые типы, что недопустимо в TypeScript. Не имеет значения, что методы являются статическими, потому что они наследуются.

5. я думаю, что перегрузка, о которой вы говорите, отличается от того, о чем я говорю. вы говорите об использовании полиморфизма, я говорю о статических функциях. я привел пример на C #, который делает то, о чем я говорю. gist.github.com вы можете скомпилировать и посмотреть, что это работает, используя dotnetfiddle.net