Typescript: Поддержка ключевого слова переопределения в методах класса

Созданный на 10 февр. 2015  ·  210Комментарии  ·  Источник: microsoft/TypeScript

(Обновление @RyanCavanuagh)

Пожалуйста, ознакомьтесь с этим комментарием , прежде чем запрашивать «какие-либо обновления», «пожалуйста, добавьте это сейчас» и т. д. Комментарии, не являющиеся содержательным добавлением к обсуждению, будут удалены, чтобы сохранить разумную длину темы.


(ПРИМЕЧАНИЕ, это _не_ дубликат проблемы № 1524. Предложение здесь больше похоже на спецификатор переопределения C++, что имеет гораздо больше смысла для машинописного текста)

Ключевое слово override было бы чрезвычайно полезно в машинописном тексте. Это будет необязательное ключевое слово для любого метода, который переопределяет метод суперкласса, и, подобно спецификатору переопределения в C++, будет указывать на намерение, что «_имя+подпись этого метода всегда должно соответствовать имени+подписи метода суперкласса_» . Это улавливает целый ряд проблем в больших базах кода, которые в противном случае можно легко пропустить.

Опять же, как и в C++, _не является ошибкой отсутствие ключевого слова override в переопределенном методе_. В этом случае компилятор просто действует точно так же, как сейчас, и пропускает дополнительные проверки времени компиляции, связанные с ключевым словом override. Это позволяет использовать более сложные нетипизированные сценарии javascript, где переопределение производного класса не совсем соответствует сигнатуре базового класса.

Пример использования

class Animal {
    move(meters:number):void {
    }
}

class Snake extends Animal {
    override move(meters:number):void {
    }
}

Пример ошибок компиляции

// Add an additional param to move, unaware that the intent was 
// to override a specific signature in the base class
class Snake extends Animal {
    override move(meters:number, height:number):void {
    }
}
// COMPILE ERROR: Snake super does not define move(meters:number,height:number):void

// Rename the function in the base class, unaware that a derived class
// existed that was overriding the same method and hence it needs renaming as well
class Animal {
    megamove(meters:number):void {
    }
}
// COMPILE ERROR: Snake super does not define move(meters:number):void

// Require the function to now return a bool, unaware that a derived class
// existed that was still using void, and hence it needs updating
class Animal {
    move(meters:number):bool {
    }
}
// COMPILE ERROR: Snake super does not define move(meters:number):void

IntelliSense

Наряду с дополнительной проверкой времени компиляции ключевое слово override предоставляет механизм машинописного интеллекта, позволяющий легко отображать и выбирать доступные суперметоды, когда цель состоит в том, чтобы специально переопределить один из них в производном классе. В настоящее время это очень неуклюже и включает просмотр цепочки суперклассов, поиск метода, который вы хотите переопределить, а затем копирование и вставку его в производный класс, чтобы гарантировать совпадение подписей.

Предлагаемый механизм IntelliSense

В объявлении класса:

  1. переопределение типа
  2. Появляется раскрывающийся список с автоматическим завершением всех суперметодов для класса.
  3. Метод выбирается в раскрывающемся списке
  4. Подпись для метода выдается в объявлении класса после ключевого слова override.
Add a Flag Revisit Suggestion

Самый полезный комментарий

Пожалуйста, извините за нытье, но, честно говоря, хотя ваш аргумент применим к ключевому слову public в языке, где все является общедоступным по умолчанию, что действительно разделяет мир, имея такие вещи, как abstract и необязательный override Ключевое слово

Переопределение — один из немногих оставшихся аспектов языка, очень чувствительных к опечаткам, потому что опечатка в имени переопределяющего метода не является очевидной проблемой времени компиляции. Преимущество override очевидно, так как вы можете заявить о своем намерении переопределить - если базовый метод не существует, это ошибка времени компиляции. Все приветствуют систему типов. Почему кто-то этого не хочет?

Все 210 Комментарий

Действительно хорошее предложение.

Однако как насчет следующих примеров, которые для меня являются допустимыми переопределениями:

class Snake extends Animal {
    override move(meters:number, height=-1):void {
    }
}
class A {...}
class Animal {
    setA(a: A): void {...}
    getA(): A {...}
}

class B extends A {...}
class Snake extends Animal {
    override setA(a: B): void {...}
    override getA(): B {...}
}

Кроме того, я бы добавил флаг компилятора, чтобы ключевое слово override присутствовало (или сообщалось как предупреждение).
Причина в том, чтобы поймать при переименовании метода в базовом классе, который унаследованные классы уже реализуют (но не должны быть переопределением).

О, хорошие примеры. Вообще говоря, я ожидаю, что использование ключевого слова override обеспечит _точное_ соответствие подписей, поскольку цель его использования — поддерживать строгую иерархию типизированных классов. Итак, чтобы обратиться к вашим примерам:

  1. Добавление дополнительного параметра по умолчанию. Это вызовет ошибку компиляции: Snake super не определяет move(meters:number):void. Хотя производный метод функционально непротиворечив, клиентский код, вызывающий Animal.move, может не ожидать, что производные классы также будут учитывать высоту (поскольку базовый API не предоставляет этого).
  2. Это будет (и должно всегда) генерировать ошибку компиляции, поскольку оно функционально несовместимо. Рассмотрим следующее дополнение к примеру:
class C extends A {...}
var animal : Animal = new Snake();
animal.setA(new C());
// This will have undefined run-time behavior, as C will be interpreted as type B in Snake.setA

Таким образом, пример (2.) на самом деле является отличной демонстрацией того, как ключевое слово override может обнаруживать тонкие пограничные случаи во время компиляции, которые в противном случае были бы пропущены! :)

И я хотел бы еще раз подчеркнуть, что оба примера могут быть допустимы в определенных управляемых/расширенных сценариях javascript, которые могут потребоваться... в этом случае пользователи могут просто опустить ключевое слово override.

Это будет полезно. В настоящее время мы работаем над этим, включив фиктивную ссылку на метод super:

class Snake extends Animal {
    move(meters:number, height?:number):void {
         super.move; // override fix
    }
}

Но это защищает только от второго случая: суперметоды переименовываются. Изменения в подписи не вызывают ошибки компиляции. Кроме того, это явно взлом.

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

@rwyborn
Кажется, мы не ожидаем такого же поведения.
Вы бы использовали это ключевое слово переопределения, чтобы обеспечить ту же подпись, тогда как я бы использовал его больше как вариант готовности (поэтому мой запрос на добавление параметра компилятора, чтобы принудительно использовать его).
На самом деле я действительно ожидал бы, что TS обнаружит недопустимые методы переопределения (даже без использования переопределения).
Обычно:

class Snake extends Animal {
    move(meters:number, height:number):void {}
}

должно вызвать ошибку, потому что это действительно переопределение Animal.move() (поведение JS), но несовместимое (поскольку высота не должна быть необязательной, тогда как она будет неопределенной, если вызывается из «ссылки» Animal).
На самом деле использование override только подтвердит (компилятором), что этот метод действительно существует в базовом классе (и, следовательно, с соответствующей сигнатурой, но из-за предыдущего пункта, а не из-за ключевого слова override).

@stephanedr , говоря как один пользователь, я действительно согласен с вами в том, что компилятор должен просто всегда подтверждать подпись, поскольку лично мне нравится применять строгую типизацию в моей иерархии классов (даже если javascript этого не делает !!).

Однако, предлагая, что это поведение является необязательным с помощью ключевого слова override, я пытаюсь помнить, что в конечном итоге javascript нетипизирован, и, следовательно, принудительное соблюдение строгого соответствия подписи по умолчанию приведет к тому, что некоторые шаблоны проектирования javascript больше не будут выражаться в Typescript.

@rwyborn Я рад, что вы упомянули реализацию C ++, потому что именно так я представлял, как она должна работать, прежде чем я попал сюда - необязательно. Хотя флаг компилятора, заставляющий использовать ключевое слово override, хорошо бы вписался в мою книгу.

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

class Base {
    protected commitState() : void {

    }
}


class Implementation extends Base {
    override protected comitState() : void {   /// error - 'comitState' doesn't exist on base type

    }
}

В настоящее время (начиная с версии 1.4) вышеприведенный класс Implementation просто объявляет новый метод, и разработчик ничего не узнает, пока не заметит, что его код не работает.

Обсуждается при рассмотрении предложений.

Мы определенно понимаем варианты использования здесь. Проблема в том, что добавление его на этом этапе в язык вносит больше путаницы, чем устраняет. Класс с 5 методами, 3 из которых помечены как override , не будет означать, что остальные 2 _не_ переопределяют. Чтобы оправдать свое существование, модификатор действительно должен делить мир более четко, чем это.

Пожалуйста, извините за нытье, но, честно говоря, хотя ваш аргумент применим к ключевому слову public в языке, где все является общедоступным по умолчанию, что действительно разделяет мир, имея такие вещи, как abstract и необязательный override Ключевое слово

Переопределение — один из немногих оставшихся аспектов языка, очень чувствительных к опечаткам, потому что опечатка в имени переопределяющего метода не является очевидной проблемой времени компиляции. Преимущество override очевидно, так как вы можете заявить о своем намерении переопределить - если базовый метод не существует, это ошибка времени компиляции. Все приветствуют систему типов. Почему кто-то этого не хочет?

Я согласен на 100% с @hdachev , небольшое несоответствие, на которое также ссылается @RyanCavanaugh , легко перевешивается преимуществами ключевого слова в переносе проверок времени компиляции на переопределения методов. Я хотел бы еще раз отметить, что C++ успешно использует необязательное ключевое слово override точно так же, как это предлагается для машинописного текста.

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

Наконец, я бы добавил, что если несоответствие необязательного ключевого слова действительно вызывает беспокойство, то можно использовать подход С#, то есть обязательное использование ключевых слов «новое» или «переопределить»:

class Dervied extends Base {

    new FuncA(newParam) {} // "new" says that I am implementing a new version of FuncA() with a different signature to the base class version

    override FuncB() {} // "override" says that I am implementing exactly the same signature as the base class version

    FuncC() {} // If FuncC exists in the base class then this is a compile error. I must either use the override keyword (I am matching the signature) or the new keyword (I am changing the signature)
}

Это не аналог public , потому что известно, что свойство без модификатора доступа является общедоступным; метод без override _не_ известен как непереопределяемый.

Вот проверенное во время выполнения решение с использованием декораторов (входит в TS1.5), которое выдает хорошие сообщения об ошибках с очень небольшими накладными расходами:

/* Put this in a helper library somewhere */
function override(container, key, other1) {
    var baseType = Object.getPrototypeOf(container);
    if(typeof baseType[key] !== 'function') {
        throw new Error('Method ' + key + ' of ' + container.constructor.name + ' does not override any base class method');
    }
}

/* User code */
class Base {
    public baseMethod() {
        console.log('Base says hello');
    }
}

class Derived extends Base {
    // Works
    <strong i="9">@override</strong>
    public baseMethod() {
        console.log('Derived says hello');
    }

    // Causes exception
    <strong i="10">@override</strong>
    public notAnOverride() {
        console.log('hello world');
    }
}

Запуск этого кода приводит к ошибке:

Ошибка: метод notAnOverride Derived не переопределяет какой-либо метод базового класса.

Поскольку этот код запускается во время инициализации класса, вам даже не нужны модульные тесты, специфичные для рассматриваемых методов; ошибка произойдет, как только ваш код загрузится. Вы также можете подключить «быструю» версию override , которая не выполняет никаких проверок для производственных развертываний.

@RyanCavanaugh Итак, мы находимся на Typescript 1.6, и декораторы все еще являются экспериментальной функцией, а не тем, что я хочу развернуть в крупномасштабной производственной кодовой базе в качестве хака, чтобы заставить работать переопределение.

Чтобы попробовать еще одну точку зрения, каждый популярный типизированный язык поддерживает ключевое слово «override»; Swift, ActionScript, C#, C++ и F# и многие другие. Все эти языки имеют общие незначительные проблемы, о которых вы говорили в этой теме о переопределении, но очевидно, что есть большая группа, которая видит, что преимущества переопределения намного перевешивают эти незначительные проблемы.

Ваши возражения основаны исключительно на затратах/выгодах? Если бы я действительно пошел дальше и внедрил это в PR, было бы это принято?

Это не просто вопрос соотношения цена/качество. Как объяснил Райан, проблема в том, что пометка метода как переопределения не означает, что другой метод _не_ является переопределением. Единственный способ, которым это имело бы смысл, — это если бы все переопределения нужно было помечать ключевым словом override (что, если бы мы указали, было бы критическим изменением).

@DanielRosenwasser Как указано выше, в C ++ ключевое слово override является необязательным (точно так же, как предложено для Typescript), но все используют его без проблем, и оно чрезвычайно полезно для больших баз кода. Кроме того, в Typescript на самом деле имеет смысл сделать его необязательным из-за перегрузки функций javascript.

class Base {
    method(param: number): void { }
}

class DerivedA extends Base {
    // I want to *exactly* implement method with the same signature
    override method(param: number): void {}
}

class DerivedB extends Base {
    // I want to implement method, but support an extended signature
    method(param: number, extraParam: any): void {}
}

Что касается всего аргумента «не означает, что другой метод не является переопределением», он в точности аналогичен «частному». Вы можете написать всю кодовую базу, даже не используя ключевое слово private. Доступ к некоторым переменным в этой кодовой базе будет осуществляться только в частном порядке, и все будет компилироваться и работать нормально. Однако «private» — это дополнительный синтаксический сахар, который вы можете использовать, чтобы сказать компилятору: «Нет, ошибка компиляции, если кто-то попытается получить к этому доступ». Точно так же «перегрузка» - это дополнительный синтаксический сахар, чтобы сообщить компилятору: «Я хочу, чтобы это точно соответствовало объявлению базового класса. Если это не ошибка компиляции».

Знаете что, я думаю, вы, ребята, зациклились на буквальном толковании слова «переопределить». На самом деле то, что он размечает, это «exactly_match_signature_of_superclass_method», но это не так читабельно :)

class DerivedA extends Base {
    exactly_match_signature_of_superclass_method method(param: number): void {}
}

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

+1, также инструменты станут намного лучше. Мой вариант использования использует реакцию. Я должен проверять определение каждый раз, когда использую методы ComponentLifecycle :

``` С#
интерфейс

{
componentWillMount?(): недействительным;
componentDidMount?(): недействительным;
componentWillReceiveProps? (nextProps: P, nextContext: любой): void;
shouldComponentUpdate?(nextProps: P, nextState: S, nextContext: любой): логическое значение;
componentWillUpdate? (nextProps: P, nextState: S, nextContext: любой): void;
componentDidUpdate?(prevProps: P, prevState: S, prevContext: любой): void;
componentWillUnmount?(): недействительным;
}

With override, or other equivalent solution,you'll get a nice auto-completion. 

One problem however is that I will need to override interface methods...

``` C#
export default class MyControlextends React.Component<{},[}> {
    override /*I want intellisense here*/ componentWillUpdate(nextProps, nextState, nextContext): void {

    }
}

@olmobrutall похоже, что ваш вариант использования лучше решается языковой службой, предлагающей рефакторинг, например «внедрение интерфейса», или предлагает лучшее завершение, а не добавлением нового ключевого слова в язык.

Давайте не будем отвлекаться :) Возможности языковых служб — это лишь малая часть поддержки иерархии интерфейсов в большой базе кода. Безусловно, самая большая победа на самом деле заключается в получении ошибок времени компиляции, когда класс, находящийся ниже в вашей иерархии, где-то не соответствует. Вот почему C++ добавил необязательное ключевое слово override (некритическое изменение). Вот почему Typescript должен делать то же самое.

Документация Microsoft для переопределения С++ хорошо подводит итог, https://msdn.microsoft.com/en-us/library/jj678987.aspx

Используйте переопределение, чтобы предотвратить непреднамеренное поведение наследования в вашем коде. В следующем примере показано, где без использования переопределения поведение функции-члена производного класса могло быть непреднамеренным. Компилятор не выдает никаких ошибок для этого кода.
...
При использовании переопределения компилятор генерирует ошибки вместо того, чтобы автоматически создавать новые функции-члены.

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

Я должен согласиться. Одна из ловушек, которая возникает в наших командах, заключается в том, что люди думают, что они переопределили методы, когда на самом деле они немного опечатались или расширили неправильный класс.

Изменения интерфейса в базовой библиотеке, когда работа над многими проектами сложнее, чем должна быть на языке с такой повесткой дня, как TypeScript.

У нас есть так много замечательных вещей, но есть такие странности, как эта, и нет константных свойств на уровне класса.

@RyanCavanaugh верно, но языковая служба может быть запущена после написания переопределения, как это делают многие языки, без ключевого слова гораздо сложнее понять, когда наступает подходящий момент.

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

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

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

Еще и абстракция уже есть, они будут офигенной парой :)

Я также почувствовал потребность в спецификаторе «переопределить». В средних и крупных проектах эта функция становится необходимой, и при всем уважении я надеюсь, что команда Typescript пересмотрит решение об отклонении этого предложения.

Для всех, кто заинтересован, мы написали пользовательское правило tslint, которое обеспечивает ту же функциональность, что и ключевое слово override, с помощью декоратора, аналогичного предложению Райана выше, но проверенного во время компиляции. Скоро мы выложим его в открытый доступ, я отпишусь, когда он будет доступен.

Я также сильно чувствовал необходимость ключевого слова «переопределить».
В моем случае я изменил некоторые имена методов в базовом классе и забыл переименовать некоторые имена переопределяющих методов. Конечно, это приводит к некоторым ошибкам.

Но, если бы была такая функция, мы могли бы легко найти эти методы класса.

Как упоминал @RyanCavanaugh , если это ключевое слово является необязательным ключевым словом, эта функция создает путаницу. Итак, как насчет того, чтобы сделать что-то в tsc, чтобы включить эту функцию?

Пожалуйста, пересмотрите эту функцию еще раз....

На мой взгляд, если ключевое слово override должно быть полезным, его необходимо применять, как в C#. Если вы указываете метод в C#, который переопределяет сигнатуру базового метода, вы _должны_ помечать его как переопределяющий или новый.

C++ раздражает и в некоторых местах уступает C#, потому что делает слишком много необязательных ключевых слов и, следовательно, исключает _согласованность_. Например, если вы перегружаете виртуальный метод, перегруженный метод может быть помечен как виртуальный или нет — в любом случае он будет виртуальным. Я предпочитаю последний, потому что он помогает другим разработчикам, которые читают код, но я не могу заставить компилятор принудительно вставить его, а это означает, что в нашей кодовой базе, несомненно, будут отсутствовать виртуальные ключевые слова там, где они действительно должны быть. Ключевое слово override также является необязательным. Оба дерьмо на мой взгляд. Что здесь упускается из виду, так это то, что код может служить документацией и улучшать удобство сопровождения за счет принудительного использования ключевых слов, а не подхода «принимай или оставляй». Ключевое слово throw в C++ похоже.

Чтобы достичь вышеуказанной цели в TypeScript, компилятору нужен флаг, чтобы «включить» это строгое поведение или нет.

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

Я перехожу на TS из AS3, поэтому, конечно же, я также собираюсь проголосовать за ключевое слово override . Для разработчика, плохо знакомого с какой-либо кодовой базой, наличие override является важным ключом к пониманию того, что может происходить в (дочернем) классе. Я думаю, что такое ключевое слово чрезвычайно повышает ценность. Я бы предпочел сделать его обязательным, но я понимаю, что это будет критическое изменение, и поэтому оно должно быть необязательным. Я действительно не вижу проблемы с навязываемой двусмысленностью, хотя я чувствителен к разнице между необязательным ключевым словом override и ключевым словом public по умолчанию.

Для всех +1: можете ли вы рассказать о том, чего недостаточно в решении декоратора, показанном выше?

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

очевидно, что каждый язык имеет свою собственную парадигму, и, будучи новичком в TypeScript, я медлителен со сменой парадигмы. Однако я должен сказать, что override ДЕЙСТВИТЕЛЬНО кажется мне лучшей практикой по ряду причин. Я перехожу на TypeScript, потому что я полностью проглотил koolaid со строгой типизацией и считаю, что стоимость (с точки зрения нажатий клавиш и кривой обучения) значительно перевешивается преимуществами как безошибочного кода, так и понимания кода. override — очень важная часть этой головоломки, которая сообщает очень важную информацию о роли переопределяющего метода.

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

@RyanCavanaugh некоторые проблемы, которые я вижу:

  • Синтаксис декоратора будет сложнее для IDE обеспечивать завершение кода (да, я думаю, это можно сделать, но им потребуются предварительные знания)
  • Производный класс может изменить видимость метода, что, строго говоря, не должно быть разрешено.
  • Декоратору сложно проверить список и типы аргументов, а также совместимый возвращаемый тип.

Однако компилятор уже проверяет список аргументов и возвращаемый тип. И я думаю, даже если бы override действительно существовало как ключевое слово первого класса, мы бы не применяли какое-то новое правило об идентичности подписи.

И я думаю, что даже если бы override существовал как ключевое слово первого класса, мы бы не применяли какое-то новое правило об идентичности сигнатур.

@RyanCavanaugh, тогда я думаю, что вы можете быть на другой странице о назначении ключевого слова. Весь смысл этого в тех случаях, когда вы _ хотите_ обеспечить идентичность подписи. Это для шаблона проектирования, в котором у вас есть глубокая сложная иерархия, основанная на базовом классе, который определяет контракт интерфейсных методов, и все классы в иерархии должны _точно_ соответствовать этим сигнатурам. Добавляя ключевое слово override к этим методам во всех производных классах, вы будете предупреждены о любых случаях, когда подпись отличается от контракта, установленного в базовом классе.

Как я постоянно говорю, это не какой-то эзотерический вариант использования. При работе с большими базами кода (скажем, с сотнями или тысячами классов) это происходит _каждый день_, т.е. кому-то нужно изменить сигнатуру базового класса (изменить контракт), и вы хотите, чтобы компилятор предупредил вас о любом случаев по всей иерархии, которые больше не совпадают.

Компилятор уже предупредит о недопустимых переопределениях. Проблема с
текущая реализация - это отсутствие намерения при объявлении переопределенного
метод.

Упомянутый ранее декоратор, похоже, идет вразрез с языком.
пытается добиться. Не должно быть затрат времени выполнения, понесенных для
что-то, с чем можно справиться во время компиляции, несмотря на небольшие затраты.
Мы хотим поймать как можно больше вещей, которые идут не так, как надо, без необходимости
запустите код, чтобы узнать.

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

Я думаю, что опциональность — это один из подходов, но, как и в случае с некоторыми компиляторами C++
есть флаги, которые вы можете установить, чтобы обеспечить его использование (например, предложить-переопределить,
несовместимое-отсутствующее-переопределение). Казалось бы, это лучший способ избежать
критические изменения и кажется совместимым с другими добавляемыми новыми функциями
недавно, такие как типы с нулевым значением и декораторы.

В среду, 23 марта 2016 г., в 21:31, Роуэн Уайборн, [email protected] написал:

И я думаю, что даже если бы override существовало как первоклассное ключевое слово, мы
не будет применять какое-то новое правило об идентичности подписи.

@RyanCavanaugh https://github.com/RyanCavanaugh , тогда я думаю, вы могли бы
быть на другой странице о намерении ключевого слова. Вся суть
это для случаев, когда вы _ хотите_ обеспечить идентичность подписи. Этот
для шаблона проектирования, в котором у вас есть глубокая сложная иерархия
на базовом классе, который определяет контракт интерфейсных методов, и все
классы в иерархии должны _точно_ соответствовать этим подписям. Добавлением
ключевое слово override для этих методов во всех производных классах, вы
предупреждены о любых случаях, когда подпись расходится с контрактом, изложенным
в базовом классе.

Как я постоянно говорю, это не какой-то эзотерический вариант использования. При работе над
большие базы кода (скажем, сотни или тысячи классов) это _every
день_вхождение, т.е. кому-то нужно изменить подпись базы
class (изменить контракт), и вы хотите, чтобы компилятор предупреждал вас о любых
случаев по всей иерархии, которые больше не совпадают.


Вы получаете это, потому что подписаны на эту тему.
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment-200551774

@kungfusheep между прочим, компилятор улавливает только определенный класс незаконных переопределений, то есть там, где происходит прямое столкновение между объявленными параметрами. Он _не_ перехватывает добавление или удаление параметров, а также не перехватывает изменения типа возвращаемого значения. Эти дополнительные проверки включаются любым ключевым словом override.

Компилятор правильно предупредит вас, если вы _добавите_ параметр к переопределяющей функции.

Однако удаление параметра _полностью допустимо_:

class BaseEventHandler {
  handleEvent(e: EventArgs, timestamp: number) { }
}

class DerivedEventHandler extends BaseEventHandler {
  handleEvent(e: EventArgs) {
    // I don't need timestamp, it's OK
  }
}

Изменение типа возвращаемого значения также _полностью допустимо_:

class Base {
  specialClone(): Base { ... }
}
class Derived extends Base {
  specialClone(): Derived { ... }
}

@RyanCavanaugh да, они действительны с точки зрения строгого языка, но именно поэтому я начал использовать термин «контракт» выше. Если базовый класс устанавливает определенную сигнатуру, когда я использую переопределение, я заявляю, что хочу, чтобы мой производный класс строго следовал этой сигнатуре. Если базовый класс добавляет дополнительные параметры или изменяет тип возвращаемого значения, я хочу знать каждую точку в моей кодовой базе, которая больше не соответствует, поскольку контракт, для которого они были первоначально написаны, теперь каким-то образом изменился.

Идея иметь большую иерархию классов, каждый со своими немного разными перестановками базовых методов (даже если они действительны с точки зрения языка), вызывает кошмар и возвращает меня к старым недобрым дням javascript до появления машинописного текста :)

Если необязательность является основной проблемой ключевого слова, то почему бы не сделать его обязательным, если метод базового класса определен как abstract ? Таким образом, если вы хотите строго применять шаблон, вы можете просто добавить абстрактный базовый класс. Чтобы избежать нарушения кода, переключатель компилятора может отключить проверку.

Кажется, сейчас мы говорим о двух разных наборах ожиданий. Есть ситуация с отсутствующим переопределением/незаконным переопределением, а есть ситуация с явной подписью. Можем ли мы все согласиться, что первое — это абсолютный минимум, который мы ожидаем от этого ключевого слова?

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

Хорошо, нет способа обеспечить явные сигнатуры переопределенных методов, но, учитывая, что компилятор обеспечивает, чтобы любые изменения сигнатур были по крайней мере «безопасными», кажется, что решение этой проблемы требует отдельного разговора.

Да согласен. Если бы мне пришлось выбирать, ситуация с отсутствующим/незаконным переопределением была бы более важной проблемой, которую нужно было решить.

Я немного опоздал на вечеринку ... Я думаю, что весь смысл Typescript заключается в том, чтобы применять правила во время компиляции, а не во время выполнения, иначе мы все использовали бы простой Javascript. Кроме того, немного странно использовать хаки/кладжи для выполнения вещей, которые являются стандартными для многих языков.
Должно ли быть ключевое слово override в Typescript? Я, конечно, верю в это. Это должно быть обязательным? По соображениям совместимости я бы сказал, что его поведение можно указать с помощью аргумента компилятора. Должен ли он обеспечивать точную подпись? Я думаю, что это должно быть отдельным обсуждением, но у меня пока не было проблем с текущим поведением.

Первоначальная причина для закрытия этого, по-видимому, заключается в том, что мы не можем внести критическое изменение, заключающееся в том, что все переопределенные методы нуждаются в спецификаторе override , что может привести к путанице, поскольку методы, не отмеченные override , также могут факт переопределяет.

Почему бы не применить его либо с помощью опции компилятора, и/или если в классе есть _по крайней мере один_ метод, помеченный как override , то все методы, которые являются переопределениями, должны быть помечены как таковые, иначе это ошибка?

Стоит ли снова открывать это, пока оно висит в воздухе?

В пятницу, 8 апреля 2016 г., в 14:38, Питер Палотас [email protected] написал:

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

Почему бы не применить его либо с помощью опции компилятора, и/или если есть в
хотя бы один метод, помеченный как override в классе, то все методы, которые
переопределения должны быть помечены как таковые, иначе это ошибка?


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment-207434898

Я говорю, пожалуйста, откройте! Я был бы счастлив с опцией компилятора.

да - пожалуйста, откройте заново

Откройте это снова!

Разве ES7 не требует переопределения/перегрузки нескольких методов, имеющих одинаковые
имя?
8 апреля 2016 г., 10:56, «Арам Тайеб» [email protected] написал:

Да, пожалуйста, откройте это снова!


Вы получаете это, потому что подписаны на эту тему.
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment-207466464

+1 - для меня это самый большой пробел в безопасности типов, который у меня есть в повседневной разработке TypeScript.

Каждый раз, когда я реализую метод жизненного цикла React, такой как «componentDidMount», я ищу соответствующую страницу документации по реакции и копирую/вставляю имя метода, чтобы убедиться, что у меня нет опечатки. Я делаю это, потому что это занимает 20 секунд, в то время как ошибки из-за опечатки являются косвенными и незаметными, и их отслеживание может занять гораздо больше времени.

класс с 5 методами, 3 из которых помечены как переопределенные, не будет означать, что остальные 2 не являются переопределениями. Чтобы оправдать свое существование, модификатор действительно должен делить мир более четко, чем это.

Если это является серьезной проблемой, вызовите ключевое слово check_override , чтобы дать понять, что вы соглашаетесь на переопределение проверки, и, следовательно, другие методы _не проверяются_, а не _не переопределяются_.

Как насчет использования implements. ? Быть чем-то вроде этого:

class MyComponent extends React.Component<MyComponentProps, void>{
    implements.componentWillMount(){
        //do my stuff
    }
}

Этот синтаксис имеет некоторые преимущества:

  • Он использует уже существующее ключевое слово.
  • После написания implements. в среде IDE появилась отличная возможность показать всплывающее окно с автозаполнением.
  • ключевое слово поясняет, что его можно использовать для принудительной проверки абстрактных методов базовых классов, а также реализованных интерфейсов.
  • синтаксис достаточно неоднозначен, чтобы его можно было использовать и для таких полей:
class MyComponent<MyComponentProps, MyComponentState> {
    implements.state = {/*auto-completion for MyComponentState here*/};

    implements.componentWillMount(){
        //do my stuff
    }
}

Примечание. В качестве альтернативы мы могли бы использовать base. , это сортировщик и более интуитивно понятен, но может быть более запутанным (определение или вызов?), И значение не так совместимо с реализацией интерфейса. Пример:

class MyComponent<MyComponentProps, MyComponentState> {
    base.state = {/*auto-completion for MyComponentState here*/};

    base.componentWillMount(){ //DEFINING
        //do my stuff
        base.componentWillMount(); //CALLING
        //do other stuff
    }
}

Я не думаю, что implements достаточной мере покрывает все варианты использования
упоминалось ранее, и это немного двусмысленно. больше не дает
информацию в IDE, которую override тоже не может, так что, похоже,
имеет смысл придерживаться терминологии, используемой многими другими языками для
добиться того же.
В среду, 13 апреля 2016 г., в 19:06, [email protected] написал:

А как насчет использования орудий? Быть чем-то вроде этого:

класс MyComponent расширяет React.Component{
реализует.componentWillMount () {
//делаю свои дела
}
}

Этот синтаксис имеет некоторые преимущества:

  • Он использует уже существующее ключевое слово.
  • После написания приборов. у IDE есть отличная возможность показать
    метод автозаполнения.
  • ключевое слово уточняет, что может использоваться для принудительной проверки абстрактных
    методы базовых классов, но и реализованные интерфейсы.
  • синтаксис достаточно неоднозначен, чтобы его можно было использовать и для таких полей, как
    это:

класс MyComponent{
mplements.state = {/_auto-completion для MyComponentState здесь_/};

implements.componentWillMount(){
    //do my stuff
}

}


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment-209571753

Проблема переопределения заключается в том, что если вы используете одно и то же ключевое слово, у вас возникают одинаковые ожидания:

  • это должно быть обязательным, по крайней мере, если метод abstract .
  • вы пропустите некоторое ключевое слово virtual для аннотации методов, которые должны быть переопределены, но имеют поведение по умолчанию.

Я думаю, что команда TS не хочет добавлять в язык столько объектно-ориентированного багажа, и я думаю, что это хорошая идея.

Используя implements. , вы получаете упрощенный синтаксис для получения основных преимуществ: автозаполнение и проверка имени во время компиляции без создания новых ключевых слов или увеличения количества понятий.

Также имеет преимущество работы с классами и интерфейсами, а также с методами (в прототипе) или прямыми полями.

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

Ключевое слово virtual на самом деле не имеет смысла в контексте языка, и при этом оно не названо интуитивно понятным для людей, которые раньше не использовали такие языки, как C++. Возможно, лучшим решением для обеспечения этого типа защиты, если это необходимо, было бы ключевое слово final .

Я согласен, что языковые функции не следует добавлять в спешке, поскольку это может создать «багаж», но override затыкает законную дыру в системе наследования, которую многие другие языки сочли необходимой. Его функциональность лучше всего достигается с помощью нового ключевого слова, особенно когда альтернативные подходы предполагают фундаментальные изменения синтаксиса.

Как насчет установки унаследованных полей и переопределения новых? React state — хороший пример.

Или реализовать необязательные методы интерфейса?

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

В четверг, 14 апреля 2016 г., в 11:58, [email protected] написал:

Как насчет установки унаследованных полей и переопределения новых? Состояние реакции
хороший пример.

Или реализовать необязательные методы интерфейса?


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment-209879217

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

Насколько я знаю, необязательные методы интерфейса — это концепция, довольно уникальная для TypeScript, и я думаю, что к ним должна применяться реализация «переопределений» TypeScript.

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

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

Именно так, чтобы они ничего не переопределяли. Я думаю, что мы запутались в том, что ключевое слово override предназначено для предоставления здесь, потому что, если вы просто ищете способ улучшить подсказку кода/интеллигентность, есть другие способы, которые могут быть достигнуты без добавления новые ключевые слова для языка.

Ребята, при всем уважении, давайте не будем отвлекаться. Считаю, что обсуждение альтернативных ключевых слов контрпродуктивно, тем более, что выпуск был начат с конкретным запросом.

Можем ли мы все либо согласиться с тем, что нам нужно override , и вежливо попросить команду Typescript добавить его, либо отказаться от запроса и оставить вопрос закрытым?

Я думаю, что всегда полезно ставить запрос в контексте проблемы, всегда есть разные пути решения проблемы. Камнем преткновения с переопределением было то, является ли оно обязательным или нет (некоторые отмечают, что это не является обязательным в C++). Проблема, на которую ссылаются многие люди, заключается в правильном названии переопределяемых функций (которые могут быть реализованы, а могут и не быть реализованы в суперклассе). Основной проблемой являются функции жизненного цикла React.

Если переопределение не работает, то, возможно, нам следует закрыть этот вопрос и открыть более общий вопрос об этой проблеме. Общая проблема здесь заключается в том, что контракт интерфейса с суперклассом не типизирован и не проверен, и это сбивает с толку разработчиков, и было бы здорово, если бы TS или инструментарий могли помочь.

@armandn Я не думаю, что отсутствие закрытия или привлекательность нашего предложения являются причиной отклонения запроса, но различия между семантикой C # и TS:

Переопределение базового метода С#:

  • Требуется ключевое слово override
  • Создает новую запись в VTable
  • Проверяет наличие обязательного абстрактного/виртуального в базовом методе
  • Проверяет идентичность подписи
  • IDE: запускает автозаполнение после переопределения записи

Реализация интерфейса С#:

  • Ключевое слово не требуется, но возможна явная реализация интерфейса с использованием имени интерфейса.
  • Создайте новую запись интерфейса в VTable.
  • Проверяет идентичность подписи
  • IDE: QuickFix для интерфейса реализации / интерфейса реализации явно.

Таким образом, поведение в C# сильно различается в зависимости от того, спрашиваете ли вы о классах или интерфейсах, но в любом случае вы получаете три основных преимущества:

  1. Проверка идентичности подписи
  2. Проверка того, что метод может/должен быть переопределен (опечатка в имени метода)
  3. IDE поддерживает написание функции

С немного другой семантикой у нас уже 1) в ТС, к сожалению 2) и 3) отсутствуют. Причина, по которой override была отклонена, на мой взгляд, заключается в том, что аналогичный синтаксис предполагает аналогичное поведение, а это нежелательно, потому что:

Класс с 5 методами, 3 из которых помечены как переопределенные, не будет означать, что остальные 2 не являются переопределенными.

Бывает, что 1) 2) и 3) у нас уже есть при написании _объектных литералов_, но не при написании членов класса, расширяющих другие классы или реализующих интерфейсы.

Имея это в виду, я думаю, мы все можем согласиться с этой семантикой:

  • _keyword_ должно быть достаточно двусмысленным, чтобы избежать проблемы "разделенного мира". (например: check или super. или MyInterface. )
  • Не проверяет наличие abstract/virtual , но проверяет наличие члена в базовом классе/реализованных интерфейсах. (Преимущество 2)
  • Проверяет достаточно схожую совместимость подписи, как это делает до сих пор TS. (Преимущество 1)
  • IDE: запускает автозаполнение после ключевого слова. (Преимущество 3)

Кроме того, я думаю, что эти два необходимы для полноты решения *:

  • Это должно работать для классов и интерфейсов , потому что TS в основном основан на интерфейсе, а классы - это ярлык для прототипного наследования. Необходимо учитывать необязательные методы интерфейса.
  • Это должно работать для методов и полей , потому что методы — это просто функции, находящиеся в поле.

Эти два момента полезны в очень реальном случае реализации компонента React:

``` С#
класс MyComponent{
mplements.state = {/ автодополнение для MyComponentState здесь /};

implements.componentWillMount(){
    //do my stuff
}

}
```

Решение на основе аннотаций от @RyanCavanaugh недостаточно, потому что:

  • Это не будет работать с интерфейсами.
  • С полями не получится.
  • Предполагая подобную Roslyn инфраструктуру для помощи с инструментами, нет возможности добавить QuickFix после написания списка автозаполнения после написания @override .

Учитывая семантику, нужно просто выбрать правильный синтаксис. Вот несколько альтернатив:

  • override componentWillMount() : Интуитивный, но вводящий в заблуждение.
  • check componentWillMount() : явный, но потребляющий ключевые слова.
  • super componentWillMount() :
  • implements componentWillMount() :
  • super.componentWillMount() :
  • implements.componentWillMount() :
  • this.componentWillMount() :
  • ReactComponent.componentWillMount() :

Мнения?

@olmobrutall хорошее резюме. Пара моментов:

Одна опция ключевого слова (взятая из C#) будет новой , указывающей на новый слот:

class MyComp extends React.Component<IProps,IState> {
...
    new componentWillMount() { ... }
    componentWillMount() { ...} // would compile, maybe unless strict mode is enabled
    new componentwillmount() { ... } <-- error

Другой мой момент - это проблема с обязательным характером использования вышеизложенного. В качестве контракта между родительским суперклассом и производным классом имеет смысл указать, какие точки интерфейса будут допустимы для приведенного выше синтаксиса. Это действительно внутренние точки расширения для суперкласса, поэтому что-то вроде:

class Component<P,S> {
    extendable componentWillMount() {...}
}

То же самое относится и к интерфейсу.

Спасибо :)

Как насчет того, чтобы просто написать this ?

class MyComponent<MyComponentProps, MyComponentState> {
    this.state = {/*auto-completion for MyComponentState here*/};

    this.componentWillMount(){
        //do my stuff
    }
}

Насчет extendable , в TS 1.6 уже есть abstract , и добавление виртуального, возможно, снова создаст проблему разделения мира?

Да, я думал об абстрактном , но может быть реализация в суперклассе, так что это не имеет смысла. То же самое и с virtual , поскольку это означало бы, что не виртуальные члены не являются виртуальными, что вводит в заблуждение.

this. работает, я думаю, вы могли бы даже (в виде длинной формы):

   this.componentWillMount = () => { }

Единственная проблема заключается в том, что он должен быть ограничен только выделенными точками расширения, а не всеми членами базового класса.

что здесь происходит...

TypeScript не является и никогда не был javascript-версией C#. Так что причина не в том, что предлагаемая функциональность отличается от семантики C#. Причины закрытия, изложенные Райаном, а затем разъясненные Даниэлем Р., были

Как объяснил Райан, проблема в том, что пометка метода как переопределения не означает, что другой метод не является переопределением. Единственный способ, которым это имело бы смысл, — это если бы все переопределения нужно было помечать ключевым словом переопределения (что, если бы мы указали, было бы критическим изменением).

Тем не менее, вы все еще упорствуете в своих проблемах с автозаполнением. Автозаполнению не нужно новое ключевое слово в языке, чтобы предоставить вам улучшенные предложения.

Причина существования этого потока заключалась в получении ошибок компилятора, когда функция была незаконно переопределена или когда метод был объявлен как переопределение, но фактически не переопределял функцию в базовом классе. Это простая и четко определенная концепция для многих языков, которая также поддерживает большинство, если не больше языковых функций, которые может предложить typescript. Ему не нужно решать все мировые проблемы, это просто должно быть ключевое слово переопределения для переопределений.

С тех пор больше людей проявили интерес к первоначальному предложению, поэтому давайте придерживаться темы, для которой была поднята проблема, и поднимать новые вопросы для новых идей.

TypeScript не является и никогда не был javascript-версией C#.

Я сравниваю его с C# в качестве эталонного языка, так как предполагаю, что это то поведение, которое вы, ребята, предполагаете.

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

Как вы предлагаете его запускать тогда? Если вы показываете поле со списком автозаполнения при написании случайного имени в контексте класса, это будет очень раздражать, когда мы просто хотим объявить новое поле или метод.

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

Я обязательно включил этот вариант использования в Преимущество 2 .

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

Итак, ваше предложение состоит в том, чтобы исправлять _один шаг за раз_ вместо того, чтобы возвращаться на один шаг назад и смотреть на похожие/связанные проблемы? Это может быть хорошей идеей для вашей Scrum Board, но не для разработки языков.

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

Некоторые ошибки проектирования в C# из-за отсутствия завершения:

  • Ковариация/контравариантность небезопасного массива.
  • var работает только для типов переменных, а не для автоматических универсальных параметров или возвращаемых типов. Mayble auto будет лучшим ключевым словом, как в C++?

Просто попробуйте использовать React на секунду, и вы увидите другую сторону картины.

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

Что хорошего в ключевом слове, которое может означать любую из трех разных вещей для разработчиков, впервые читающих код? Это двусмысленно и сбивает с толку, особенно если вы говорите об использовании ключевого слова вроде this , которое уже делает другие (совершенно не связанные!) вещи в языке - это не может быть более общим, это почти бесполезно.

Если вас больше всего беспокоит автозаполнение, у редакторов _сейчас_ достаточно информации, чтобы предлагать методы из базовых классов и реализованных интерфейсов «по мере ввода».

Переопределение метода, который уже был реализован в базовом классе, и реализация метода интерфейса — две совершенно разные вещи.

В общем случае да, но мы не говорим о реализации какого-либо метода интерфейса. Мы говорим о необязательном методе интерфейса, где родительский класс реализует интерфейс. В этом случае вы можете сказать, что 1) интерфейс позволяет реализовать метод как undefined , 2) родительский класс имеет неопределенную реализацию и 3) дочерний класс переопределяет неопределенную реализацию с реализацией метода.

@olmobrutall Я думаю, что ваш комментарий о разработке языков и о том, что это не скрам-доска, немного корыстен. Я видел около четырех обновлений TS менее чем за год.

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

Я не делаю этот комментарий с какой-либо клеветой на разработчиков/дизайнеров TS, потому что TS уже превосходен, и я бы боялся использовать вместо этого стандартный JS.

Да, TS — это не C# и не C++. Но ряд языков выбрал ключевое слово override для достижения обсуждаемых здесь целей, поэтому кажется контрпродуктивным предлагать совершенно чужой синтаксис.

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

Различия в подписи - это отдельный разговор. Новое ключевое слово кажется ненужным, потому что JS не может поддерживать несколько методов с одним и тем же именем (если только TS не создаст искаженные имена, производные от подписи, как C++, что маловероятно).

Я видел около четырех обновлений TS менее чем за год.

Я не имею в виду, что вы не можете быть быстрыми и быстро повторять. Я так же рад, как и все, что ES6 и TS быстро развиваются. Я имею в виду, что вы должны попытаться предсказать будущее, чтобы не загнать язык в тупик.

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

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

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

Другими словами: скажем, мы делаем то, что вы, кажется, предлагаете, и реализуем переопределение, не задумываясь. Затем я или кто-либо другой, использующий TSX, добавляет вопрос, почему override не работает с компонентами React. Каков твой план?

В общем случае да, но мы не говорим о реализации какого-либо метода интерфейса. Мы говорим о необязательном методе интерфейса, в котором родительский класс реализует интерфейс.

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

Вы могли бы, например, переопределить метод, который был реализован для соответствия интерфейсу в базовом классе; Если бы мы поместили все наши яйца в одно ключевое слово для этих двух разных вещей, как бы кто-нибудь узнал, было ли это начальным объявлением этой функции или переопределением ранее определенного в базовом классе? Вы бы этого не сделали, и это было бы невозможно узнать без дополнительной проверки базового класса, который может быть даже в стороннем файле .d.ts , что делает поиск абсолютным кошмаром, в зависимости от того, насколько глубоко в цепочке наследования изначально реализована функция.

Другими словами: скажем, мы делаем то, что вы, кажется, предлагаете, и реализуем переопределение, не задумываясь. Затем я или кто-либо другой, использующий TSX, добавляет вопрос, почему переопределение не работает с компонентами React. Каков твой план?

Зачем это нужно для исправления React? Если у React есть проблема, отличная от той, которую он пытается решить, то я не могу понять, почему override должен это исправить? Вы пытались открыть другую проблему, чтобы предложить что-то сделать с реализацией интерфейса?

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

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

Не? Посмотрите на эти два альтернативных определения BaseClass

class BaseClass {
     abstract myMethod(); 
}
interface ISomeInterface {
     myMethod?(); 
}

class BaseClass extends ISomeInterface {
}

И затем в вашем коде вы делаете:

``` С#
класс Бетонный класс {
переопределить мой метод () {
// Делаем вещи
}
}

You think it should work in just one case and not in the other? The effect is going to be 100% identical in Javascript (creating a new method in ConcreteClass prototype), from the external interface and from the tooling perspective. 

Even more, maybe you want to capture `this` inside of the method, implementing it with a lambda (useful for React event handling). In this case you'll write something like this:

``` C#
class ConcreteClass {
    override myMethod = () => { 
         // Do stuff
    }
}

Поведение снова будет идентичным, если метод абстрактный или исходит из интерфейса: добавьте в класс поле с реализующей его лямбдой. Но это выглядит немного странно для override поля, поскольку вы просто присваиваете значение.

Давайте посмотрим на это, используя super. (мой любимый синтаксис прямо сейчас, но я открыт для альтернатив).

``` С#
класс Бетонный класс {
super.myMethod() { // метод в прототипе
// Делаем вещи
}

super.myMethod = () => {  //method in lambda
     // Do stuff
}

}
```

Теперь концепция концептуально проще: мой суперкласс говорит, что есть метод или поле, и ConcreteClass может определить его в прототипе/назначить/прочитать/вызвать.

Зачем это нужно для исправления React?

Это не просто реакция, посмотрите на угловой:

  • React : 58 интерфейсов и 3 класса.
  • Angular : 108 интерфейсов и 11 классов.

Конечно, большинство интерфейсов не предназначены для реализации, как и все классы для переопределения, но ясно одно: в Typescript интерфейсы важнее классов.

Вы пытались открыть другую проблему, чтобы предложить что-то сделать с реализацией интерфейса?

Как мне это назвать? override для интерфейса и полей?

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

Языки, которые вы имеете в виду, совершенно разные. У них есть наследование, основанное на статической VTable.

В Typescript класс — это просто интерфейс + автоматическое наследование методов прототипа. А методы — это просто поля с функцией внутри.

Чтобы адаптировать функцию override к TS, необходимо учитывать два основных отличия.

Пожалуйста , @kungfusheep подумайте над решением моей проблемы. Если вы хотите добавить разные ключевые слова и внедрить их на втором этапе, это нормально, но потратьте секунду, чтобы представить, как это должно быть.

Я не могу придумать другого способа сказать то, что я уже сказал. Они не то же самое. Они похожи, но не одинаковы. Пожалуйста, посмотрите этот комментарий одного из разработчиков TS RE: ключевое слово только для чтения - https://github.com/Microsoft/TypeScript/pull/6532#issuecomment -179563753 - которое усиливает то, что я говорю.

С общей идеей согласен, но посмотрим на практике:

class MyComponent extends React.Component<{ prop : number }, { value: string; }> {

    //assign a field defined in the base class without re-defining it (you want type-checking)
    assign state = { value : number}; 

    //optional method defined in an interface implemented by the base class    
    implement componentDidMount(){ 
    }

    //abstract method defined in the base class 
    override render(){  
    }
}

Это похоже на VB или Cobol, не так ли?

По крайней мере, похоже, что это имеет смысл.

Рассмотрим этот пример, если бы было только ключевое слово override (или только одно).

interface IDo {
    do?() : void;
}
class Component implements IDo {
    protected commitState() : void {
        /// do something
    }
    override public do() : void {
        /// base implements 'do' in this case
    }
}

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

class MyComponent extends Component {
    override protected commitState(){
        /// do our own thing here
        super.commitState();
    }
    override do() : void {
        /// this is ambiguous. Am I implementing this from an interface or overriding a base method? I have no way of knowing. 
    }

}

Одним из способов узнать будет тип super :

  override do() : void {
        super.do(); // this compiles, if it was an interface then super wouldn't support `do`
    }

точно! значит схема неправильная. не должно быть расследования, связанного со скиммингом кода, это должно иметь смысл, когда вы его читаете.

это неоднозначно. Я реализую это из интерфейса или переопределяю базовый метод? У меня нет возможности узнать.

Какая разница на практике? Объект имеет только одно пространство имен, и вы можете поместить только одну лямбду в поле do . В любом случае, нет возможности явно реализовать интерфейс. Реализация будет:

MyComponent.prototype.do = function (){
    //your stuff
}

независимо от того, что вы пишете.

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

Какую ошибку или неожиданное поведение можно решить, если использовать два ключевых слова?

Давай, приятель. Ты явно умный парень. Я только что сказал «непреднамеренное переопределение некоторых функций в базовом классе»; не можем ли мы вывести из этого какое-либо неожиданное поведение, которое потенциально могло только что произойти?

Чтобы было ясно, я не предлагаю превратить этот вопрос в предложение по двум ключевым словам. Этот вопрос относится к ключевому слову переопределения — для всего остального потребуется новое предложение и собственное обсуждение семантики.

Чтобы было ясно, я не предлагаю превратить этот вопрос в предложение по двум ключевым словам. Этот вопрос относится к ключевому слову переопределения — для всего остального потребуется новое предложение и собственное обсуждение семантики.

На самом деле не имеет значения, в скольких вопросах она должна обсуждаться или от кого исходит идея. Вы предлагаете разделить две очень связанные мысли и даже не рассматривать согласованный синтаксис.

Аргументы о том, нужно ли нам 1, 2 или 3 ключевых слова, относятся к этой теме и не завершены (... но становятся повторяющимися). Тогда, возможно, мы сможем обсудить синтаксис в другой ветке (потому что семантика в любом случае будет идентичной :P).

В моем примере:

class MyComponent extends React.Component<{ prop : number }, { value: string; }> {

    //assign a field defined in the base class without re-defining it (you want type-checking)
    assign state = { value : number}; 

    //optional method defined in an interface implemented by the base class    
    implement componentDidMount(){ 
    }

    //abstract method defined in the base class 
    override render(){  
    }
}

Не делайте того, чтобы assign , implement и override делали одно и то же: проверяли, существует ли имя (в базовом классе, реализованных интерфейсах, интерфейсах, реализованных базовым классом). класс и др...).

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

Также подумайте о литерале объекта:

var mc = new MyComponent(); 
mc.state = null;
mc.componentDidMount =null;
mc.render = null;

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

Не назначайте, не реализуйте и не переопределяйте одно и то же: проверьте, существует ли имя (в базовом классе, реализованных интерфейсах, интерфейсах, реализованных базовым классом и т. д.).

Вы только что описали 3 разных сценария, так что, очевидно, они не одинаковы. У меня такое ощущение, что я могу весь день описывать, почему они отличаются от вас, а вы все равно будете сидеть и спорить, что это не так, поэтому я пока воздержусь от этой конкретной линии обсуждения. Нет ничего, что могло бы сказать, что ребята из TS все еще обдумывают это в данный момент.

С закрытием #6118, я думаю, есть повод обсудить, можно ли решать проблемы там и проблемы здесь одновременно.

Я не знал о https://github.com/Microsoft/TypeScript/pull/6118. Идея выглядит как возможная альтернатива добавлению override .

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

Не имея хорошего представления о последствиях, я был бы счастлив, если бы:

  • член создаст ошибку времени компиляции, которая требует явного объявления типа в дочернем классе
  • член возьмет пересечение всех возможных типов.

Более важным IMO было бы иметь какой-то способ запуска автозавершения при написании членов класса (Ctrl + Space). Когда курсор находится непосредственно внутри класса, вы можете определять новые члены или переопределять унаследованные, поэтому автозаполнение не может быть слишком агрессивным, но при ручном запуске поведение должно быть в порядке.

Относительно комментариев @RyanCavanaugh :

Мы определенно понимаем варианты использования здесь. Проблема в том, что добавление его на этом этапе в язык вносит больше путаницы, чем устраняет. Класс с 5 методами, 3 из которых помечены как переопределенные, не будет означать, что остальные 2 не являются переопределенными. Чтобы оправдать свое существование, модификатор действительно должен делить мир более четко, чем это.

Ввод переменной как any не означает, что какая-то другая переменная также является или не является any . Но есть плоскость компилятора --no-implicit-any , чтобы обеспечить его явное объявление. У нас также мог бы быть --no-implicit-override , который я, например, включил бы, если бы он был доступен.

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

Для всех +1: можете ли вы рассказать о том, чего недостаточно в решении декоратора, показанном выше?

Есть ли способ, которым декоратор лучше, чем ключевое слово переопределения? Есть много причин, почему это хуже: 1) Это добавляет накладные расходы во время выполнения, хотя и небольшие; 2) Это не проверка времени компиляции; 3) я должен включить этот код в каждую из моих библиотек; 4) Это не способ поймать функции, в которых отсутствует ключевое слово override.

Приведем пример. У меня есть библиотека с тремя классами ChildA , ChildB , Base . Я добавил некоторый метод doSomething() в ChildA и ChildB . После некоторого рефакторинга я добавил дополнительную логику, оптимизировал и переместил doSomething() в класс Base . Между тем, у меня есть еще один проект, который зависит от моей библиотеки. Там у меня есть ChildC с doSomething() . Когда я обновляю свои зависимости, я никак не могу обнаружить, что ChildC теперь неявно переопределяет doSomething() , но неоптимизированным образом, в котором также отсутствуют некоторые проверки. Вот почему декоратора @overrides никогда не будет достаточно.

Нам нужно ключевое слово override и флаг компилятора --no-implicit-override .

Ключевое слово override очень помогло бы мне, так как я использую в своем проекте простую иерархию базовых классов для создания всех своих компонентов. Моя проблема заключается в том, что этим компонентам может потребоваться объявить метод, который будет использоваться где-то еще, и этот метод может быть или не быть определен в родительском классе и может уже делать или не делать то, что мне нужно.

Например, предположим, что функция validate принимает класс с методом getValue() в качестве параметра. Чтобы построить этот класс, я могу унаследовать другой класс, который уже мог бы определить этот метод getValue() , но я не могу знать этого, пока не посмотрю на его исходный код. Что я инстинктивно сделал бы, так это реализовал бы этот метод в своем собственном классе, и пока подпись верна, никто мне ничего не скажет.

Но, возможно, мне не стоило этого делать. Все следующие возможности предполагают, что я сделал неявное переопределение:

  1. Базовый класс уже определил этот метод, как и я, поэтому я написал функцию зря. Не такая уж проблема.
  2. Я неправильно переписал функцию, в основном из-за того, что забыл сделать некоторые неочевидные вещи, которые уже обрабатывались базовым классом. Что мне действительно нужно было сделать здесь, так это вызвать super в моем переопределении, но никто не предложил мне это сделать.

Наличие обязательного ключевого слова переопределения сказало бы мне: «Эй, вы переопределяете, так что, возможно, вам следует проверить, как выглядит исходный метод, прежде чем делать что-то еще». Это значительно улучшило бы мой опыт наследования.

Конечно, как было предложено, его следует поместить под флаг --no-implicit-override , так как это будет критическое изменение, и что большинству людей все это безразлично.

Мне нравится сравнение @eggers с any и --no-implicit-any , потому что это один и тот же тип аннотаций, и он будет работать точно так же.

@olmobrutall Я смотрел некоторые ваши разговоры о переопределении абстрактных методов и методов интерфейса.

По моему мнению, override подразумевает существование super. Ни абстрактные методы, ни методы, определенные в интерфейсе, не могут быть вызваны через вызов super. , и поэтому не должны быть переопределяемыми ( там нечего перепрошивать). Вместо этого, если мы сделаем что-то более явное для этих случаев, это должно быть ключевое слово implements . Но это будет отдельная тема для обсуждения.

Вот проблемы, как я их вижу, ранжированные от наиболее важных к наименее. Я что-то пропустил?

Неспособность переопределить

Легко _думать_, что вы переопределяете метод базового класса, когда это не так:

class Base {
  hasFilename(f: string) { return true; }
}
class Derived extends Base {
  // oops
  hasFileName(f: string) { return false; }
}

Это, пожалуй, самая большая проблема.

Неспособность реализовать

Это очень тесно связано, особенно когда реализованный интерфейс имеет необязательные свойства:

interface NeatMethods {
  hasFilename?(f: string): boolean;
}
class Mine implements NeatMethods {
  // oops
  hasFileName(f: string) { return false; }
}

Это менее важная проблема, чем _failure to override_, но все же это плохо.

Случайное переопределение

Можно переопределить метод базового класса, не осознавая этого.

class Base {
  hasFilename(f: string) { return true; }
}
class Derived extends Base {
  // I didn't know there was a base method with this name, so oops?
  hasFilename(f: string) { return true; }
}

Это должно быть относительно редко только потому, что пространство возможных имен методов очень велико, и вероятность того, что вы также напишете совместимую сигнатуру _и_ не намеревались делать это в первую очередь, низка.

Случайные any s

Легко подумать, что методы переопределения получат типы параметров своей базы:

class Base {
  hasFilename(f: string) { return true; }
}
class Derived extends Base {
  // oops
  hasFilename(f) { return f.lentgh > 0; }
}

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

Читабельность

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

Опыт редактора

Вы должны вручную вводить имена базовых методов при написании методов переопределения производного класса, что раздражает. Мы могли бы легко исправить это, всегда указывая эти имена в списке завершения (я думаю, у нас уже есть ошибка в этом), поэтому нам действительно не нужна языковая функция для решения этой проблемы.

Не-проблемы

Перечисляя их как вещи, которые либо относятся к отдельной проблеме, либо просто не произойдут:

  • Точное соответствие подписи — это не то, что следует смешивать с override , и во многих хороших сценариях оно действительно имеет нежелательную семантику.
  • Недостаточно экзотический синтаксис (например, implements.foo = ... ). Здесь не нужно велопарковки

@RyanCavanaugh Я согласен со всем вышесказанным в том же порядке. В качестве комментария я не думаю, что ключевое слово override должно решать проблему «невозможности реализации». Как я сказал выше, я думаю, что эту проблему следует решать с помощью другого ключевого слова (например, implements ) как части другого билета. ( override должен подразумевать метод super. )

@RyanCavanaugh Я согласен со всеми пунктами, но я не вижу никаких ссылок на также очень связанную проблему инициализированных полей, объявленных в родительском типе, без переопределения типа (и затем потери проверки типа). Я думаю, что эта функция не должна ограничиваться объявлениями методов на языке, где методы — это просто функции в полях объекта.

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

переопределение должно подразумевать супер. метод

В C# переопределение может (и должно) использоваться также для абстрактных методов (без .super). В Java @override — это атрибут. Конечно, это разные языки, но с похожими ключевыми словами вы ожидаете аналогичного поведения.

Я согласен с точками @RyanCavanaugh , хотя я бы сказал, что проблема «случайного переопределения» может быть немного более распространенной, чем он считает (особенно при работе с расширением класса, который уже расширяет что-то еще, например компонент React, который уже определите метод componentWillMount в первом расширении).

Однако я считаю, как сказал @eggers , что проблема «невозможности реализации» - это нечто совершенно другое, и было бы странно использовать ключевое слово override для метода, которого нет в родительском классе. Я знаю, что это разные языки с разными проблемами, но в C# override не используется для интерфейсов. Я бы предположил, что если бы мы могли получить флаг --no-implicit-override , методы интерфейса должны были бы иметь префикс с именем интерфейса (что было бы очень похоже на C# и, таким образом, выглядело бы более естественно).

Что-то типа

interface IBase {
    method1?(): void
}

class Base {
    method2() { return true; }
}

class Test extends Base implements IBase {
    IBase.method1() { }
    override method2() { return true; }
}

Я думаю, что @RyanCavanaugh перечисляет фундаментальные проблемы. Я бы дополнил это словами «Каковы разногласия»:

  • Является ли объявление метода соответствия интерфейсу override ? Например:
    interface IDrawable
    {
        draw?( centerPoint: Point ): void;
    }

    class Square implements IDrawable
    {
        draw( centerPoint: Point ): void; // is this an override?
    }
  • объявление свойства, соответствующего интерфейсу, считается override ?
    interface IPoint2
    {
        x?: number;
        y?: number;
    }

    class Circle implements IPoint2
    {
        x: number; // is this an override?
        y: number; // is this an override?
        radius: number;
    }
  • Есть ли потребность в каком-то ключевом слове implements для обработки вышеперечисленных случаев вместо ключевого слова override и в форме, которую оно примет.

@ kevmeister68 спасибо за явное изложение разногласий.

Одна вещь: вы должны сделать члены интерфейса необязательными, чтобы действительно показать проблему, например, компилятор машинописного текста будет жаловаться, когда поле или метод не реализованы, решая «Сбой реализации».

@olmobrutall Спасибо за это. Я никогда не объявлял метод необязательным - может ли это быть (я работаю с С#, и такой концепции нет)? Я обновил приведенные выше примеры, чтобы изменить переменные-члены на необязательные — так лучше?

@kevmeister68 также метод draw в IDrawable :)

@РайанКавано

Недостаточно экзотичный синтаксис (например, items.foo = ...). Здесь не нужно велопарковки

Спасибо, что научил меня новому выражению . Я не вижу особых проблем в семантике фичи:

  • Нам всем нужны ошибки времени компиляции, если метод не существует.
  • Нам всем нужны ошибки времени компиляции, тип которых не соответствует.
  • Мы также хотим, чтобы параметры были неявно типизированы родительскому, как это делают лямбда-выражения.
  • Нам нужна помощь IDE, чтобы правильно написать метод.

Большинство проблем зависят от синтаксиса и поведения, которое они подразумевают:

  • Необязательные члены в интерфейсе и члены в базовых классах
  • Методы против полей (не знаю, где будут лямбды)

Давайте сравним дерево более перспективных синтаксисов:

переопределить / реализовать

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     dateOfBirth = new Date(); //what goes here?

     override talk(){/*...*/}
     walk = () => {/* force 'this' to be captured*/}  //what goes here

     implements fly() {/*...*/}
     altitude = 1000; //what goes here?
}

Преимущества:

  • Это естественно для программистов с OO фоном.
  • Чувствуется правильно при сравнении с extends / implements .

Недостатки:

  • Не дает решения проблемы поля, ни override , ни implements не кажутся правильными. Может быть, super. , но что тогда делать с интерфейсами?
  • Если методы переопределяются с помощью лямбда-выражения (полезно для этого) function() , возникают те же проблемы.

переопределить / InterfaceName.

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     dateOfBirth = new Date(); //what goes here?

     override talk(){/*...*/}
     walk = () => {/* force 'this' to be captured*/}  //what goes here

     ICanFly.fly() {/*...*/}
     ICanFly.altitude = 1000; //what goes here?
}

Преимущества:

  • Это также естественно для программистов с опытом работы с объектно-ориентированными программами.
  • Решает проблему полей для интерфейсов, но не для классов, но мы могли бы использовать для них super. .

Недостатки:

  • Имена интерфейсов могут быть длинными, особенно с дженериками, и отображение списка автоконкурсов также будет проблематичным, требуя некоторого Alt + Space, чтобы сделать его явным.
  • Создается впечатление, что вы можете реализовать два члена с одинаковыми именами из разных интерфейсов независимо друг от друга. Это работает в C#, но не будет работать в Javascript.

this.

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     this.dateOfBirth = new Date();

     this.talk(){/*...*/}
     this.walk = () => {/* force 'this' to be captured*/} 

     this.fly() {/*...*/}
     this.altitude = 1000;
}

Преимущества:

  • Решает классы, интерфейсы, методы и поля.
  • Использует (злоупотребляет?) только одно ранее существовавшее ключевое слово.
  • Использование this. для запуска автозаполнения кажется естественным.
  • Дает понять, что объект — это всего лишь одно пространство имен, и у вас может быть только одна вещь для каждого имени.

Недостатки:

  • Выглядит как выражение, а не объявление, что затрудняет разбор нетренированными глазами.

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

Однако я по-прежнему считаю, что override — это совершенно другое, и потенциально наличие двух ключевых слов вместо одного лучше передаст цель программы.

Например, рассмотрим сценарий, в котором разработчик _считает_, что он реализует метод интерфейса, хотя на самом деле он переопределяет метод, который уже был объявлен в базовом классе. С двумя ключевыми словами компилятор может предотвратить такую ​​ошибку и выдать ошибку в тон method already implemented in a base class .

interface IDelegate {
    execute?() : void;
}

class Base implements IDelegate {
    implement public execute() : void { /// fine, this is correctly implementing execute
    }
}

class Derived extends Base {
    implement public execute() : void { 
/// ERROR: `method "execute():void" already implemented in a base class`
    }
}

Я не знаю, уместно ли это в JS, но поля класса должны быть приватными в OO, поэтому я не думаю, что нам нужно явно переопределять поля, поскольку тот факт, что они приватные, уже не позволяет нам полностью переопределять .

Однако методы экземпляра — это поля, и, насколько я понимаю, многие люди используют их вместо методов-прототипов. Об этом,

class Person{
    walk(){ //...}
}
class SuperMan extends Person  {
     walk = () => {/* force 'this' to be captured*/}
}

является ошибкой компилятора, поскольку walk является методом-прототипом в Person и методом экземпляра в SuperMan .

В любом случае, я не уверен, что override здесь подойдет, потому что мы не переопределяем поля. Опять же, это будет похоже на C#, но я бы предпочел использовать здесь ключевое слово new вместо override . Потому что это не то же самое, что переопределение метода (я могу вызвать super.myMethod в переопределении, но не здесь).

Моим предпочтительным решением было бы что-то вроде (при условии, что мы находимся в строгом режиме переопределения):

class Person{
    dateOfBirth: Date;
    talk() { }
    walk = () => { }
}

interface ICanFly {
    fly?();
    altitude?: number;
}

class SuperMan extends Person implements ICanFly {
     new dateOfBirth = new Date();
     override talk() { }
     new walk = () => { }

     implements fly() {/*...*/}
     implements altitude = 1000;
}

Однако меня больше всего беспокоят интерфейсы. Нам не нужно писать implements для необязательных элементов интерфейса, потому что компилятор их уже поймает. Затем, если мы это сделаем, возникнет путаница между тем, что из интерфейса, а что нет, поскольку не все элементы интерфейса будут иметь префикс implements . И это слово на слуху. Я не готов написать что-то вроде этого:

class C extends React.Component {
    implements componentWillMount() { }
    implements componentDidMount() { }
    implements componentWillReceiveProps(props) { }
    /// ... and the list goes on
}

Ранее я предлагал префикс имени интерфейса, но @olmobrutall показал мне, что это худшая идея.

В любом случае, я почти уверен, что нам понадобятся 3 разных новых ключевых слова, чтобы правильно решить эту проблему.

Я также не думаю, что необходимо неявно вводить переопределения, поскольку компилятор уже не позволит нам написать что-то несовместимое, особенно потому, что это, очевидно, сложно сделать правильно.

Разве new , override и implement не являются слишком большими накладными ключевыми словами для практически той же семантики JS? Даже если в С# это разные вещи, в JS/TS практически нет разницы.

Тоже весьма неоднозначно:

  • Почему поля new для классов и implements для интерфейсов?
  • Если вы меняете метод из базового класса, который был реализован с интерфейсом, что вам следует использовать? Я вижу четыре возможности: override , implements , любой из двух или оба одновременно?

Также подумайте об этом:

class Animal {
}

class Human extends Animal {
}

class Habitat {
    owner: Animal;
}

class House extends Habitat {
    owner = new Human();
}

var house = new House();
house.owner = new Dog(); //Should this be allowed??  

Вопрос в том:

Переопределяет ли owner = new Human(); поле с новым (но совместимым) типом или просто присваивает значение.

Я думаю, что в целом вы переопределили поле, за исключением случаев, когда вы используете _magic ключевое слово_. Сейчас я предпочитаю this. .

class House extends Habitat {
    this.owner = new Human(); //just setting a value, the type is still Animal
}

@olmobrutall Это действительно может быть немного накладно на ключевые слова, но все 3 действительно _разные_ вещи.

  • override будет применяться к методам (определенным в прототипе), что означает, что он _не_ стирает исходный метод, который по-прежнему доступен за super .
  • new применяется к полям и означает, что исходное поле было стерто новым.
  • implements будет применяться к чему-либо из интерфейса и означает, что он не стирает и не заменяет ничего, что уже существовало в родительском классе, потому что интерфейс «не существует».

Если вы меняете метод из базового класса, который был реализован с интерфейсом, что вам следует использовать? Я вижу четыре возможности: переопределить, реализовать, любой из двух или оба одновременно?

Мне кажется довольно логичным, что это будет override , поскольку именно это вы здесь и делаете. implements будет означать, что вы реализуете что-то из интерфейса, которого иначе не существует. В некотором смысле override и new будут иметь приоритет над implements .

Что касается вашего примера переопределения поля, ничего не должно измениться в том, как он работает сегодня. Я не уверен, что здесь происходит, но что бы это ни было, оно не должно меняться (или, может быть, должно, но мы здесь не это обсуждаем).

Я чувствую, что override подойдет как для базовых методов, так и для свойств, в том числе абстрактных (поясняется ниже).

new — интересная концептуальная идея, но это конкретное ключевое слово можно спутать с созданием экземпляра класса, поэтому может создаться ложное впечатление, что оно работает только для классов, несмотря на то, что свойства могут иметь примитив, интерфейс, объединение или даже типы функций. Возможно, другое ключевое слово, такое как reassign , могло бы работать там лучше, но у него есть проблема, заключающаяся в том, что оно может дать ложное представление в случае, если значение только повторно объявлено, но фактически не присвоено чему-либо в производном классе ( new У redefine тоже интересно, но может привести к ошибочному ожиданию того, что свойство 'redefined' также может иметь тип, отличный от базового, поэтому я не уверен.. (_edit: на самом деле я проверил и он может иметь другой тип, пока новый является подтипом базового, так что это может быть не так уж плохо_).

implement (я предпочитаю именно эту форму глагола из-за согласованности с override ), кажется, работает для интерфейсов. Я считаю, что технически это может работать и для абстрактных базовых методов, но использование override может показаться более последовательным, несмотря на немного другую семантику. Другая причина заключается в том, что было бы немного неудобно менять метод с абстрактного на неабстрактный, так как нужно было бы перейти ко всем производным классам и изменить override на implement .

Могли бы быть идеи получше, но это все, что у меня есть на данный момент..

Я предложил ключевое слово new , потому что оно используется в C# именно для этой функции, но я согласен, что это не лучший вариант. implement действительно лучше, чем implements . Я не думаю, что мы должны использовать его для абстрактных методов, поскольку они являются частью базового класса, а не интерфейса. override + new -> базовый класс, implement -> интерфейс. Это кажется яснее.

@JabX

О переопределении прототипа с полем экземпляра

Я проверил, и вы правы:

class Person{
    walk(){ //...}
}
class SuperMan extends Person  {
     walk = () => {/* force 'this' to be captured*/}
}

Сбой с ошибкой компилятора: Class 'Person' defines instance member function 'walk', but extended class 'SuperMan' defines it as instance member property.

Я действительно не вижу причин для отказа в этом случае, поскольку контракт родительского типа выполнен, и вы все равно можете написать это:

var p = new SuperMan();
p.walk = () => { };

Или даже

class SuperMan extends Person {
    constructor() {
        super();
        this.walk = () => { };
    }
}

Около override

override будет применяться к методам (определенным в прототипе), что означает, что он не стирает исходный метод, который по-прежнему доступен за супер.

Это на самом деле аргумент. Между override и implements существует наименьшая _некоторая_ наблюдаемая разница ... но не совсем.

И override , и implements реализованы в prototype , возможность использования super независима, потому что вы вызываете super.walk() не просто super() и доступен в каждом методе (переопределения, реализации, новости и обычные определения).

class SuperMan extends Person implements ICanFly {
     new dateOfBirth = new Date();
     override talk() { } //goes to prototype
     new walk = () => { }

     implements fly() {/*...*/}  //also goes to the prototype
     implements altitude = 1000;
}

И возможность использовать super.walk() также работает, если вы назначаете экземпляру

class SuperMan extends Person {
    constructor() {
        super();
        this.walk = () => { super.walk(); };
    }

    //or with the this. syntax
    this.walk = () => { super.walk(); };
}

Уже существует четкий способ отличить поля prototype от полей экземпляра, и это токен = .

class SuperMan extends Person implements ICanFly {
     this.dateOfBirth = new Date(); //instance
     this.talk() { } //prototype
     this.walk = () => { } //instance

     this.fly() {/*...*/}  //prototype
     this.altitude = 1000; //instance
}

С синтаксисом this. проблема решается более элегантно:

  • this. означает проверку моего типа (базовые классы и реализованные интерфейсы).
  • = означает назначение экземпляру вместо прототипа.

Около New

new применяется к полям и означает, что исходное поле было стерто новым.

Учтите, что вы не можете изменить поле или метод на другой тип, потому что вы нарушите систему типов. В C# или Java, когда вы делаете new , новый метод будет использоваться только при статически отправленных вызовах нового типа. В Javascript все вызовы будут динамическими, и если вы измените тип, код, скорее всего, сломается во время выполнения (однако для практических целей может быть разрешено принуждение типа, но не расширение).

class Person {
    walk() { }

    run() {
        this.walk();
        this.walk();
        this.walk();
    }
}

class SuperMan extends Person {
    new walk(destination: string) { } //Even if you write `new` this code will break
}

Таким образом, более new ключевое слово должно быть assign , потому что это единственная безопасная для типов вещь, которую вы можете сделать:

class Person{
    dateOfBirth: Date;
}

class SuperMan extends Person implements ICanFly {
     assign dateOfBirth = new Date();
}

Обратите внимание, что reassign не имеет смысла, потому что Person только объявляет поле, но не устанавливает для него никакого значения.

Написание assign кажется излишним, однако ясно, что мы присваиваем, потому что у нас есть = на другой стороне.

Опять же, синтаксис this. изящно решает проблему:

class Person{
    dateOfBirth: Date;
}

class SuperMan extends Person implements ICanFly {
     this.dateOfBirth = new Date();
}

Я не уверен, как вы применяете это к полям таким образом, чтобы это имело смысл.

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

Typescript уже позволяет инициализировать поля в теле класса:

class Person {
    dateOfBirth : new Date();
}

Это также позволяет повторно инициализировать поля, объявленные в родительском классе, но ситуация плохая. Конечно, вы можете написать имя неправильно (аналогично override ), но и тип тоже сотрется. Это означает, что:

class Person {
    fullName: { firstName: string; lastName?: string };
}

class SuperMan extends Person {
    fullName = { firstName: "Clark" };

    bla() {
        this.fullName.lastName; //Error
    }
}

Этот код завершается ошибкой: свойство 'lastName' не существует для типа '{ firstName: string;

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

Также методы:

class Person {
    talk() { }
}

class SuperMan extends Person {

    talk() { }

    changeMe(){
        this.talk = () => { };      
    }

    changeMyPrototype() {
        SuperMan.prototype.talk = () => { };
    }
}

@kungfusheep Я согласен, поля можно переназначать, но и методы в прототипе тоже.
Основная причина, по которой нам нужно ключевое слово для «переопределения» полей, заключается в том, что методы экземпляра _являются_ полями, и они широко используются вместо надлежащих методов прототипа, потому что они являются лямбда-выражениями и, таким образом, автоматически привязываются к контексту.

@olmobrutall

О переопределении методов с полями

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

Об интерфейсах

Интерфейсы класса описывают сторону экземпляра класса, то есть поля, а также методы из прототипа. Я даже не думаю, что есть разница между методами экземпляра и прототипа в интерфейсе, я считаю, что вы можете описать метод как один или другой и реализовать его как один или другой. Таким образом, implement может (и должно) также применяться к полям.

О новом

new должен иметь ту же семантику, что и override , поэтому, очевидно, вы не можете изменить тип на что-то несовместимое. Я имею в виду, опять же, мы не меняем принцип работы языка. Я просто хотел отдельное ключевое слово, потому что это не то же самое переопределение. Но я согласен с тем, что, как вы показали мне, мы уже можем различать поля и методы с помощью = , поэтому, возможно, можно было бы использовать одно и то же ключевое слово/синтаксис без двусмысленности.

Об этом.

Однако унифицированный синтаксис переопределения поля/метода не должен быть this.method() или this.field , потому что он очень похож на то, что может быть действительным Javascript, хотя это не так. Добавление ключевого слова перед элементом было бы более ясным в отношении того факта, что это действительно вещь Typescript.
this — хорошая идея, так что, возможно, мы могли бы опустить точку и написать что-то менее двусмысленное, например

class Superman {
    this walk() { }
}

Но все же, это выглядит немного странно. И смешивание его с implement выглядело бы немного неуместным (потому что мы согласны с тем фактом, что этот синтаксис this не будет использоваться с интерфейсом?), и мне нравится implement Дуэт override . Меня все еще раздражает модификатор override в полях, но, возможно, это лучше, чем наличие третьего неоднозначного ключевого слова new . Поскольку присваивание ( = ) проясняет разницу между методом и полем, меня бы это устраивало сейчас (в отличие от того, что я говорил несколько часов назад).

Согласен, поля можно переназначить, но и методы на прототипе тоже.

Да, изменение прототипа напрямую, в TypeScript, очень похоже на то, чтобы возиться под капотом. Это далеко не так распространено, как простое переназначение свойства экземпляру чего-либо. Мы очень редко используем поля в качестве функций, более 150 тысяч строк кода, поэтому я думаю, что сказать, что это широко используется, возможно, субъективно.

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

Мы очень редко используем поля в качестве функций, более 150 тысяч строк кода, поэтому я думаю, что сказать, что это широко используется, возможно, субъективно.

Я тоже этого не делаю, но мне приходится прибегать к декоратору @autobind , чтобы правильно связать this в моих методах, что более громоздко, чем простое написание лямбды. И пока вы не используете наследование, использование поля работает так, как вы ожидаете. У меня сложилось впечатление, что, по крайней мере, в мире JS/React большинство людей, использующих классы ES6, использовали стрелочные функции в качестве методов.

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

Я считаю, что у полей есть те же проблемы, что и у методов, что касается переопределений.

class A {
    firstName: string;
    get name() {
        return this.firstName;
    }
}

class B extends A {
    firstname = "Joe" // oops
}

Возможный обходной путь здесь - назначить поле в конструкторе

class B extends A {
    constructor() {
        this.firstName = "Joe"; // can't go wrong
    }
}

но это не применимо к интерфейсам. Вот почему я (и другие здесь) считаю, что нам нужно что-то для проверки существования поля при его объявлении непосредственно в теле класса. И это не override . Теперь, когда я изложил здесь еще несколько мыслей, я считаю, что проблема с полями точно такая же, как и проблема с элементами интерфейса. Я бы сказал, что нам нужно ключевое слово override для методов, которые уже имеют реализацию в родительском классе (методы-прототипы и, возможно, методы экземпляра), а другое — для вещей, которые определены, но не реализованы (не функции). включены поля).

Мое новое предложение будет заключаться в том, чтобы использовать предварительное ключевое слово member (вероятно, не лучший выбор):

interface IBase {
    interfaceField?: string;
    interfaceMethod(): void
}

abstract class Base {
    baseField: number;
    baseMethod() { }
    baseLambda: () => { };
    abstract baseAbstractMethod();
}

class Derived extends Base implements IBase {
    member interfaceField = "Hello";
    member interfaceMethod() { }
    member baseField = 2;
    override baseMethod() { }
    override baseLambda = () => { };
    member baseAbstractMethod() { }
}

Обратите внимание, что я бы выбрал ключевое слово member для абстрактного метода, поскольку я сказал, что override будет для вещей с реализацией, указывая на то, что мы заменяем существующее поведение. Методы экземпляра должны будут использовать override в силу того, что они являются полем особого типа (которое уже распознается как таковое компилятором, поскольку с ним могут быть реализованы сигнатуры методов).

Я думаю, что Typescript должен быть версией JavaScript, проверенной во время компиляции, и, изучая TS, вы также должны изучать JS, точно так же, как изучая C#, вы получаете хорошее представление о CLR.

Наследование прототипов — классная концепция JS, и довольно центральная, и TS не должен пытаться скрыть ее концепциями из других языков, которые здесь не имеют особого смысла.

Наследование прототипов не имеет значения, если вы наследуете методы или поля.

Например:

class Rectangle {
       x: number;
       y: number;
       color: string;
}

Rectangle.prototype.color = "black";

Здесь мы устанавливаем простое поле в объекте-прототипе, поэтому все прямоугольники по умолчанию будут черными без необходимости иметь для них поле экземпляра.

class BoundingBox {
      override color = "transparent"; // or should be member? 
}

Также ключевое слово member вызывает зависть у других участников класса.

Что нам нужно, так это синтаксис, который разрешит в контексте объявления класса такое же поведение (проверка во время компиляции/автозаполнение/переименование), которое мы уже имеем в объектных литералах или выражениях-членах.

Возможно, лучшей альтернативой this. будет просто . :

class Derived {
   .interfaceField = "hello";
   .interfaceMethod() {}
   .baseField = 2;
   .baseMethod() {}
   .baseLambda = () => {};
   .baseAbstractMethod(){};

   someNewMethod(){}
   someNewField = 3;
}

В заключение, я не думаю, что система типов должна отслеживать, какие значения берутся из прототипа, а какие из экземпляра, потому что это сложная проблема, когда вы можете императивно получить доступ к прототипу, и потому что она не собирается решать какую-либо проблему, пока будет ограничить выразительность JS.

Таким образом, нет никакой разницы между полем функции и полем стрелочной функции и методом, ни в системе типов, ни в сгенерированном коде (если не используется this ).

Эй, ребята, это интересный материал, но он довольно не по касательной с точки зрения конкретно override . Я был бы рад, если бы мы могли более конкретно связать эти комментарии с первоначальным предложением.

Многое из того, что вы здесь говорите, на самом деле не относится к TypeScript, поэтому запуск темы ESDiscuss также может быть уместным. Конечно, они не меньше думали о подобных вещах (с точки зрения того, что происходит в прототипе, а не в экземпляре).

@olmobrutall
Классы ES6 уже скрывают материал прототипа, и, как сказал @kungfusheep , возиться с ним напрямую - не совсем обычное дело.

Таким образом, нет никакой разницы между полем функции, полем функции стрелки и методом, ни в системе типов, ни в сгенерированном коде (если это не используется).

Что ж, в сгенерированном коде методы класса помещаются в прототип, а все остальное (без префикса static ) помещается в экземпляр, что имеет значение при наследовании.

В любом случае, теперь я согласен с тем, что нам не нужно иметь отдельный синтаксис для обоих видов методов, но если бы мы использовали ключевое слово override , его следует ограничить методами, и нам пришлось бы найти что-то еще для остальных. Было бы неплохо иметь уникальное ключевое слово для всего, но оно должно быть очень четким в отношении его значения. override понятно, но только для методов. Ваш точечный синтаксис, такой как this. , на мой взгляд, все еще слишком близок к существующему JS.

@РайанКавано

Это довольно необычно с точки зрения override

Мои проблемы с переопределением заключаются в том, что он недостаточно общий для методов и полей интерфейса. Я вижу три варианта:

  • используйте override только для методов базового класса.
  • используйте override также для методов и полей интерфейса
  • рассмотрите альтернативные синтаксисы ( member , implement , this. , . и т.д...)
  • ничего не делать (это будет грустно)

Я думаю, что это решение должно быть принято здесь.

Многое из того, что вы говорите, на самом деле не относится к Typescript.

Абсолютно! мы все довольны текущим состоянием транспиляции, но не проверкой типов/инструментами.

Каким бы ни было ключевое слово, оно будет только Typescript (как абстрактное).

@JabX

Вы правы насчет сгенерированного кода, конечно. Я хотел сказать, что вы можете написать что-то вроде этого:

class Person {
    name: string = "John"; 

    saySomething() {
        return "Hi " + this.name;
    }
}

Мы объявляем класс, name перейдет к экземпляру, а saySomething — к прототипу. Тем не менее я могу написать это:

Person.prototype.name = "Unknown"; 

Потому что тип Person.prototype — это весь человек. Typescript для простоты не отслеживает, что и куда идет в его системе типов.

Было бы неплохо иметь уникальное ключевое слово для всего, но оно должно быть очень четким в отношении его значения.

Что я считаю более важным, и мы все могли бы согласиться, так это семантика:

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

Поскольку . или this. выглядят слишком чужеродными, а member избыточны, я думаю, что, вероятно, лучшим выбором будет злоупотребление override во всех случаях . Настройка полей или реализация методов интерфейса в любом случае является побочной функцией.

Есть много прецедентов:

  • В C# static class на самом деле больше не является _классом объектов_.
  • В полях static нет ничего _статического_.
  • Методы virtual вполне _конкретны_ (VB использует Overrideable ).

Это будет выглядеть так:

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     override dateOfBirth = new Date();

     override talk(){/*...*/}
     override walk = () => {/* force 'this' to be captured*/} 

     override  fly() {/*...*/}
     override altitude = 1000; 
}

Можем ли мы остановиться на этом?

Я бы предпочел видеть отдельное ключевое слово implement для всего, что исходит от интерфейсов (с приоритетом override , если оно есть в обоих), потому что это то, что есть: реализация, а не переопределение.
В противном случае я согласен, что лучше всего злоупотреблять override для всего из родительского класса.

Но семантической разницы между implement и override нет.

Оба будут иметь похожие автозаполнение/сообщения об ошибках/транспиляцию в JS... это просто философская разница.

Действительно стоит объяснить два ключевых слова и то, что вам скажет педантичный компилятор: Error you should use 'override' instead of 'implement' .

Как насчет этого случая:

interface IComparable {
     compare(): number;
} 

class BaseClass implements IComparable {
    implement compare(); 
}

class ChildClass extends BaseClass implements IComparable { //again 
     override compare(); // or implements... 
}

Вопросы... кого это волнует?

Также есть проблема implement / implements .

Давайте просто злоупотреблять override . Одна концепция одно ключевое слово, это главное.

Ну, я уже предлагал, чтобы override имел приоритет над implement , но, вероятно, я недостаточно ясно выразился.
Я до сих пор не верю, что это одно и то же. Другой пример: для обязательного члена интерфейса сбой реализации является ошибкой компилятора, тогда как сбой переопределения не является проблемой (если только базовый член не является абстрактным, то есть...).

Я просто не думаю, что override для чего-то, что не объявлено в родительском классе, имело бы какой-то смысл. Но, может быть, я единственный, кто хочет отличия. В любом случае, какое бы ключевое слово мы ни использовали для этого, я просто надеюсь, что мы сможем что-то решить и предоставить строгий режим для принудительного его использования.

Что ж, я уже предлагал, чтобы переопределение имело приоритет над реализацией, но я, вероятно, недостаточно ясно выразился.

Конечно, я просто хотел сказать, что есть четыре случая. Базовый класс, интерфейс, интерфейс в базовом классе, повторная реализация интерфейса в базовом классе.

@JabX

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

Вы не. Это две принципиально разные вещи, и есть преимущество в наличии такого разделения на языковом уровне.

Я думаю, что если интерфейсы должны поддерживаться в этой новой функциональности, то это не может быть какое-то полусырое решение, которое было прикручено к override . По той же причине, по которой extends существует как отдельная сущность для implements на уровне класса, override необходимо соединить с чем-то вроде implement , чтобы чтобы решить эту проблему. Это _замена функциональности_ против _определения функциональности_.

@olmobrutall

Действительно стоит объяснить два ключевых слова и то, что педантичный компилятор говорит вам: Ошибка, вы должны использовать «переопределить» вместо «внедрить».

Мне кажется, что я уже делал это различие примерно 4 раза, но это не педантизм; это действительно важная информация!

Рассмотрим собственный пример

interface IComparable {
     compare(): number;
} 

class BaseClass implements IComparable {
    implement compare(); 
}

class ChildClass extends BaseClass implements IComparable { //again 
     override compare(); // or implements... 
}

«или реализует...» — совершенно неверное предположение в данном случае. То, что вы сказали компилятору, что хотите реализовать тот же интерфейс, что и у расширяемого базового класса, не меняет того факта, что вы уже реализовали compare .
Если бы вы написали implement в ChildClass вместо override , тогда вы должны быть благодарны, что компилятор сообщит вам о вашем неверном предположении, потому что это большая проблема, которую вы собирались неосознанно стереть. из ранее реализованного метода!

В кодовых базах, за которые я отвечаю, это, без сомнения, будет большой проблемой; поэтому я приветствую любую функцию компилятора, которая может предотвратить подобные ошибки разработчиков!

@kungfusheep

Если бы вы написали реализацию в ChildClass вместо переопределения, тогда вы должны быть благодарны компилятору за то, что он сообщит вам о вашем неверном предположении, потому что это большое дело, что вы собирались неосознанно уничтожить ранее реализованный метод!

Если переопределение уже реализованного метода является таким важным решением, тогда implement следует использовать и для абстрактных методов. Если вы измените метод с abstract на virtual или наоборот, вам следует проверить (и изменить) все реализованные версии, чтобы рассмотреть возможность вызова super или просто удаления метода.

На практике это никогда не было проблемой в C#.

Я согласен, override для реализованных методов и implements для нереализованных методов (абстрактных/интерфейсных).

Его можно использовать для абстракции, да.

В C# не было сценария «необязательный» интерфейс, поэтому, возможно, это не считалось большой проблемой.

Но я хочу сказать, что в C# мы override реализовывали и не реализовывали методы, и я никогда не слышал, чтобы кто-то жаловался.

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

Ну, единственная причина, по которой нам нужно ключевое слово implement , заключается в том, что члены интерфейса могут быть необязательными, тогда как это невозможно в C# (и, вероятно, в большинстве языков OO). Там нет ключевого слова для этого, потому что вы не можете _не_ реализовать интерфейс полностью, так что не проблема. Не слишком основывайте свой аргумент на том, что «это то же самое в C#», потому что здесь у нас разные проблемы.

override используется для методов _базового класса_, независимо от того, абстрактны они или нет. Я полагаю, что мы должны сделать то же самое ( override вместо implement для абстрактных методов) здесь, потому что я считаю, что различие должно быть в происхождении метода (класс или интерфейс), а не в существовании реализации. И в будущем, если вы решите дать реализацию по умолчанию для своего абстрактного метода, вам не придется просматривать свой код (или, что еще хуже, код других пользователей, использующих ваш класс), чтобы заменить ключевые слова.

Мой главный вопрос сейчас заключается в том, должны ли мы иметь строгий флаг override / implement (я полностью этого хочу), должны ли мы принудительно использовать ключевое слово implement на обязательных членах интерфейса? Потому что это мало помогает (вы не можете не реализовать их, потому что иначе они не скомпилируются) и может привести к большому количеству ненужного многословия. Но, с другой стороны, может быть обманчиво иметь implement на некоторых элементах интерфейса, но не на всех.

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

Тем не менее, я не на 100% уверен в идее ключевого слова «инструменты». Компилятор уже предупредит вас, если вы что-то не реализовали. Единственное место, где это действительно полезно, - это необязательные методы.

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

Мой главный вопрос сейчас заключается в том, должны ли мы иметь строгий флаг переопределения/реализации (я полностью этого хочу), должны ли мы принудительно использовать ключевое слово реализации на обязательных членах интерфейса?

Я думаю, что не писать это должно быть предупреждением.

Единственное место, где это действительно полезно, - это необязательные методы.

Да в этом и весь смысл.

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

Это могло быть предупреждением или чем-то еще, но в настоящее время это очень болезненно.

Я добавляю аннотацию переопределения в представление класса UML в alm.tools :rose:

image

ссылки https://github.com/alm-tools/alm/issues/84

Я также добавил индикатор желоба для членов класса, которые переопределяют член базового класса в alm .

overrides

ссылки https://github.com/alm-tools/alm/issues/111

Мы считаем, что принятие PR для решения с ограниченной областью действия обеспечивает наибольшую ценность при наименьшей сложности:

  • Новое ключевое слово override допустимо для методов класса и объявлений свойств (включая get/set).

    • Все подписи (включая реализацию) должны иметь override , если есть

    • И get , и set должны быть помечены как override , если они

  • Будет ошибкой иметь override , если нет свойства/метода базового класса с таким именем.
  • Новый переключатель командной строки --noImplicitOverride (не стесняйтесь называть его здесь) делает override обязательным для вещей, которые являются переопределениями.

    • Этот переключатель не действует в окружении (например, в файлах declare class или .d.ts).

На данный момент вне поля зрения:

  • Наследование подписей
  • Контекстная типизация параметров или инициализаторов (пробовал это раньше, и это было катастрофой)
  • Соответствующее ключевое слово для указания «Я реализую член интерфейса с этим объявлением».

@РайанКавано
В настоящее время я пытаюсь реализовать это (только начал, и я буду рад любой помощи в этом, особенно для тестирования), но я не уверен, что понимаю, что стоит за

Все подписи (включая реализацию) должны иметь override , если есть

Поскольку у вас не может быть нескольких сигнатур метода в классе, вы говорите о контексте наследования? Вы имеете в виду это (без флага, который требует использования override , иначе это явно ошибка)

class A {
    method() {}
}

class B extends A {
    override method() {}
}

class C extends B {
    method() {} 
}

должен вывести ошибку в классе C, потому что я не написал override до method ?

Если это так, я не уверен, почему мы хотели бы обеспечить это. И должно ли работать наоборот? Вывести ошибку в классе B, если я указал override в C, а не в B?

Да, я думаю, что это относится к сигнатурам дерева наследования.
И я не думаю, что это будет работать наоборот, оно должно начать применять правило переопределения, как только оно будет использовано на уровне иерархии. В некоторых случаях решение о переопределении может быть принято для класса, производного от класса, не принадлежащего разработчику. Таким образом, после его использования он должен применяться только ко всем производным классам.

У меня есть еще вопрос о

И get , и set должны быть помечены как override , если они

Что происходит, если мой родительский класс определяет только геттер, а я хочу определить в своем производном классе сеттер? Если я буду следовать этому правилу, это означает, что мой сеттер должен быть определен как переопределение, но в родительском классе нет сеттера, и переопределение того, что не существует, должно быть ошибкой. Ну, на практике (в моей текущей наивной реализации) противоречия нет, поскольку геттер и сеттер относятся к одному и тому же свойству.
Я мог бы даже написать что-то вроде:

class A {
    get value() { return 1; }
}

class B extends 1 {
   override set value(v: number) {}
}

Что кажется мне совершенно неправильным.

Поскольку у вас не может быть нескольких подписей метода в классе

Вы можете иметь несколько сигнатур перегрузки для одного и того же метода:

class Base { bar(): { } }

class Foo extends Base {
  // Must write 'override' on each signature
  override bar(s: string): void;
  override bar(s?: number): void;
  override bar(s: string|number) { }
}

Я думаю, что ваша интуиция о других случаях верна.

Что касается геттеров и сеттеров, я думаю, что override относится только к слоту свойств. Написание переопределяющего сеттера без соответствующего геттера явно крайне подозрительно, но это не более или менее подозрительно при наличии override . Поскольку мы не всегда знаем, как был реализован базовый класс, я думаю, что правило заключается в том, что геттер или сеттер override должен иметь _некоторое_ соответствующее свойство базового класса, и если вы скажете override get { , любое объявление set также должно содержать override (и наоборот)

О, хорошо, я не знал, что мы можем писать сигнатуры методов в таких классах, это довольно изящно.

Я думаю, что virtual тоже имеет смысл.
Все ключевые слова: virtual , override , final на самом деле будут иметь большое значение, особенно при рефакторинге классов.
Я активно использую наследование, теперь так легко переопределить метод "по ошибке".
virtual также улучшит intellisense, так как вы можете предлагать только абстрактные/виртуальные методы после ключевого слова override.

Пожалуйста, отдайте приоритет этим. Спасибо!

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

«виртуальный», «переопределить» и «окончательный» были бы идеальными.

Это важный вопрос для меня.

@РайанКавано

Контекстная типизация параметров или инициализаторов (пробовал это раньше, и это было катастрофой)

Вы можете остановиться на этом? На самом деле я обнаружил эту проблему, пытаясь выяснить, почему TS не определяет тип моих параметров при переопределении методов, что стало для меня неожиданностью. Я не понимаю, когда неидентичный список параметров будет правильным при переопределении или тип возвращаемого значения, который несовместим.

@панклекс

Я думаю, что virtual тоже имеет смысл.

По своей природе в JS все «виртуально». Поддержки final будет достаточно IMHO: если у вас есть метод (или свойство), который не должен быть переопределен, вы можете пометить его как таковой, чтобы вызвать ошибку компилятора, когда это было нарушено. Тогда не нужно virtual , верно? Но это отслеживается в выпуске №1534.

@avonwyss здесь есть две проблемы.

Когда мы попытались контекстно ввести _property initializers_, мы столкнулись с некоторыми проблемами, описанными на https://github.com/Microsoft/TypeScript/pull/6118#issuecomment -216595207. Мы думаем, что у нас есть новый более успешный подход #10570.

Объявления методов — это другой шарик воска, и нам нужно выяснить еще кое-что, например, что должно произойти здесь:

declare class Base {
  method(x: string): string[];
  method(x: number, count: number): number[];
}
class Derived extends Base {
  method(x) { // x: ???
    return x;
  }
}

Мы можем просто решить, что только методы с одной сигнатурой получают типы параметров из базы. Но это не сделает всех счастливыми и на самом деле не решит частую проблему «Я хочу, чтобы мой производный класс имел метод с теми же _signatures_, но просто предоставлял другую _implementation_».

Что ж, в этом случае тип x будет string | number , но я понимаю, что это может быть довольно сложно определить последовательно.

Но вы, вероятно, не хотели бы, чтобы _внешне видимый_ тип x был string | number — предполагая, что Derived имеет ту же семантику, что и Base , это не будет нельзя вызывать с помощью (3) или ('foo', 3)

Хм, да, я пропустил это.

Я вижу, вы предложили ограничить его методами с одной подписью, но я считаю, что его можно «легко» расширить до методов «сопоставления подписи», как в вашем примере:

declare class Base {
  method(x: string): string[];
  method(x: number, count: number): number[];
}
class Derived extends Base {
  method(x, count) { // x: number, count: number
    return [];
  }
}

потому что есть только сигнатура в базовом классе, который соответствует.

Это было бы намного лучше, чем то, что у нас есть (ничего), и я не думаю, что это будет _это_ сложно сделать? (все еще выходит за рамки этой проблемы и, возможно, больше работы)

@RyanCavanaugh Спасибо за ответ. Как уже отмечалось, я надеюсь, что override устранит проблему с параметром (и типом возвращаемого значения). Но я не считаю ваш пример проблематичным, поскольку перегрузки всегда должны иметь одну единственную «частную» реализацию, совместимую со всеми перегрузками (поэтому предложение @JabX не будет работать концептуально).
Итак, у нас было бы что-то вроде этого:

declare class Base {
  method(x: string): string[];
  method(x: number, count: number): number[];
  // implies: method(x: string|number, count?: number): string[]|number[]
  // or fancier: method<T extends string|number>(x: T, count?: number): T[]
}
class Derived extends Base {
  override method(x, count?) { // may only be called like the method on Base
    return [x];
  }
}

Эта логика уже существует, и переопределение, очевидно, будет доступно только для «частного» метода реализации, а не для отдельных переопределений (точно так же, как вы все равно не можете явно реализовать одну перегрузку). Так что не вижу здесь проблемы - или я что-то упустил?

Методы, предназначенные для переопределения, обычно в любом случае не имеют перегрузок, поэтому даже простой выход и не делать вывод о параметрах и возвращаемых типах, если перегрузки существуют, было бы прекрасно и полезно. Таким образом, даже если бы поведение было как есть при переопределении перегруженных методов, вы получили бы ошибку при работе в режиме --no-implicit-any и вам пришлось бы указывать совместимые типы, что казалось бы совершенно прекрасным подходом.

Я, наверное, что-то упускаю, но в Javascript (и, следовательно, Typescript) у вас не может быть 2 методов с одинаковым именем, даже если они имеют разные подписи?

В С# вы могли бы

public string method(string blah) {};
public int method(int blah) {};

Но здесь у вас никогда не может быть двух функций с одинаковыми именами, независимо от того, что вы делаете с сигнатурами... так что ни один из ваших примеров в любом случае никогда не будет возможен, верно? Если только мы не говорим о нескольких расширениях одного и того же класса... но это не то, что показывают ваши примеры, так что, вероятно, нет? Это не проблема?

Прямо сейчас я использую наследование, и это очень расстраивает... Мне приходится комментировать функции, чтобы указать, что они виртуальные (т.е. я их где-то переопределяю) или переопределяю (т.е. что эта функция переопределяет базовую функцию). Супер раздражающий и грязный! И небезопасно!

@ sam-s4s Да, для метода можно объявить несколько сигнатур логической перегрузки, но все они в конечном итоге оказываются в одной и той же реализации (которая затем должна определить по переданным параметрам, какая «перегрузка» вызывается). Это активно используется во многих JS-фреймворках, например, в jQuery, где $(function) добавляет готовую функцию, но $(string) выполняет поиск в DOM с помощью селектора.

См. здесь документы: https://www.typescriptlang.org/docs/handbook/functions.html#overloads

@avonwyss Ах да, декларации, но не реализации, это имеет смысл :) Спасибо

Это продолжалось слишком долго, и меня раздражает, что мне приходится читать всю эту раздутую ветку, чтобы хотя бы начать понимать, почему в TS до сих пор нет этого базового утверждения времени компиляции.

Где мы находимся: (а) ключевое слово override или (б) аннотация jsdoc @override , которая сообщает компилятору, что «метод с таким же именем и сигнатурой типа ДОЛЖЕН существовать в суперклассе». ?

В частности, предполагает ли комментарий в https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -224776519 (8 июня 2016 г.) @RyanCavanaugh , что следующим шагом будет (сообщество) PR?

class Bar extends Foo {
  /**
   * <strong i="12">@override</strong>
   */
  public toString(): string {
     // ... 
  }

  override public toString(): string {
    // ...
  }
}

В частности, предполагает ли комментарий в # 2000 (комментарий) (8 июня 2016 г.) @RyanCavanaugh , что следующим шагом будет (сообщество) PR?

@pcj Верно

Я начал PR на этом в #13217. Любой, кто еще заинтересован в этой функции, может принять участие и/или внести предложения. Спасибо!

Я бы предпочел то же самое, что и для java с декоратором

<strong i="6">@Override</strong>
public toString(): string {
   // ...
}

@Chris2011 Согласитесь, это выглядит знакомо, но для декоратора это означает, что @Override будет оцениваться во время выполнения, а не во время компиляции. Я планирую javadoc /** <strong i="7">@override</strong> */ в отдельном PR, как только ключевое слово override #13217 ( public override toString() ) перейдет в обзор.

Пытаюсь следить за беседой в этой теме. Включает ли это статические функции? Я не вижу никаких причин, по которым мы не могли бы также разрешить переопределение статических функций в производных классах с совершенно другими сигнатурами. Это поддерживается в ES6. Если было class A { } ; A.create = function (){} , то class B extends A { } ; B.create = function (x,y) { } , вызов A.create() не вызовет B.create() и не вызовет проблем. По этой причине (и чтобы создать возможность использовать то же имя функции для типов, что и фабричные функции) вы также должны разрешить переопределение подписи базовых статических функций. Он ничего не ломает и добавляет возможность делать некоторые полезные вещи (особенно для фреймворков игровых движков, где «новое» что-либо действительно является злом, если используется все время без извлечения объектов из кеша для уменьшения зависаний сборщика мусора). Поскольку вызываемые конструкторы не поддерживаются в ES6, единственным другим вариантом является создание общего соглашения об именах для методов типов; однако для этого в настоящее время требуется, чтобы производная статическая функция отображала перегрузку сигнатуры базовой статической функции вместе со своей собственной, что бесполезно для фабричной функции для типа, который имеет дело только с ЭТИМ типом. :/ В то же время единственный вариант, который у меня есть, - это заставить пользователей моего фреймворка дублировать все сигнатуры статических функций базовой иерархии (такие как SomeType.create() ) в их производных типах и т. д., что становится действительно глупо .

Вот пример «глупости», о которой я говорю (которая работает, но не то, чем я бы гордился в расширяемой структуре):

class A {
    static create(s: string) {
        var inst: A;
        /* new or from cache */
        inst.init(s);
    }
    protected init(s: string) { }
}

class B extends A {
    static create(s: string);
    static create(n: number);
    static create(n:any) {
        var inst: B;
        /* new or from cache */
        inst.init(n);
    }
    protected init(s: string);
    protected init(n: number);
    protected init(n: any) {
        super.init(n.toString());
    }
}

class C extends B {
    static create(s: string)
    static create(n: number)
    static create(b: boolean)
    static create(b: any) {
        var inst: C;
        /* new or from cache */
        inst.init(b);
    }
    protected init(s: string);
    protected init(n: number);
    protected init(b: boolean);
    protected  init(b: any) {
        super.init(b ? 0 : 1);
    }
}

(https://goo.gl/G01Aku)

Это было бы куда приятнее:

class A {
    static create(s: string) {
        var inst: A;
        /* new or from cache */
        inst.init(s);
    }
    protected init(s: string) { }
}

class B extends A {
    new static create(n:number) {
        var inst: B;
        /* new or from cache */
        inst.init(n);
    }
    new protected init(n: number) {
        super.init(n.toString());
    }
}

class C extends B {
    new static create(b: boolean) {
        var inst: C;
        /* new or from cache */
        inst.init(b);
    }
    new protected  init(b: boolean) {
        super.init(b ? 0 : 1);
    }
}

@rjamesnw вас может заинтересовать полиморфное «это» для статических членов , имеющих контракт на фабричные функции, обсуждается как основной вариант использования в комментариях.

Итак, еще раз повторяя чувство, которое @pcj выразил ( комментарий ) более года назад, сбивает с толку необходимость читать так много PR и комментариев здесь и в других местах, чтобы определить, где находится этот запрос на функцию.

кажется, что # 13217 был так близок, а затем снова сбит @DanielRosenwasser и компанией как функция, которая может не соответствовать языку, по-видимому, снова вступая в круговой разговор здесь по этому вопросу о том, следует ли это делать. Может быть, эти декораторы «время разработки» (# 2900) решат эту проблему, а может и нет? Было бы неплохо узнать.

Вот еще несколько недостатков подхода декоратора среды выполнения, опубликованных несколько лет назад, о которых я не упоминал:

  • Это не будет работать со свойствами, поскольку они не являются частью прототипа.
  • Свойства и методы доступа могут (вроде...) переопределять друг друга, что также не будет работать.
  • Теперь у нас есть абстрактные классы, но поскольку абстрактные вещи на самом деле не существуют во время выполнения, он не может с ними работать.

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

Честно говоря, я не могу поверить, что эта функция все еще вызывает столько споров спустя 3 года с тех пор, как я впервые зарегистрировал проблему. Каждый "серьезный" объектно-ориентированный язык поддерживает это ключевое слово, C#, F#, C++... вы называете его.

Вы можете целый день спорить о гипотезах о том, почему javascript отличается и требует другого подхода. Но с практической точки зрения, ежедневно работая с очень большой базой кода Typescript, я могу сказать вам, что переопределение будет иметь огромное значение для удобочитаемости и обслуживания кода. Это также устранило бы целый класс ошибок из-за того, что производные классы случайно переопределяли методы базового класса с совместимыми, но слегка отличающимися сигнатурами.

Мне бы очень хотелось увидеть правильную реализацию virtual/override/final. Я бы использовал его все время, и это сделало бы код намного более читабельным и менее подверженным ошибкам. Я чувствую, что это важная функция.

Согласованный. Разочаровывает то, как добавляются относительно неясные / крайние функции, в то время как что-то такое ... фундаментальное отвергается. Можем ли мы что-нибудь сделать, чтобы подтолкнуть к этому?

Давай, люди! Если столько комментариев и более трех лет, то почему это еще не реализовано!

Давай :joy_cat:

Пожалуйста, будьте конструктивны и конкретны; нам не нужны десятки комментариев ПОЖАЛУЙСТА СДЕЛАЙТЕ ЭТО УЖЕ

Но, ребята, пожалуйста, будьте конкретны и с вашей стороны.

Очевидно, сообщество очень заинтересовано в этой функции, и команда TS не сообщает нам никаких подробностей о будущем этой функции.

Я лично полностью согласен с @armandn , последние выпуски TS привносят относительно редко используемые функции, пока проводится что-то подобное.

Если вы не планируете этого делать, просто сообщите нам. В противном случае, пожалуйста, дайте нам знать, как сообщество может помочь.

Здесь просто временная шкала, так как комментариев больше, чем GitHub готов отобразить при загрузке:

  • Первоначальный выпуск был открыт
  • Мы рассмотрели его и отклонили по соображениям сложности и ценности https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -98874744.
  • Мы снова открыли его после того, как # 6118 не сработал https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -216626012
  • Мы сказали, что примем PR после принятия некоторых решений https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -224776519
  • PR открылся на #13217
  • После тщательного рассмотрения PR (около 100 комментариев) мы вернули его на совещание по проектированию из-за проблем со сложностью # 20724.
  • Опять же, мы решили, что преимущества функции не перевешивают сложность.

Не то чтобы мы не учитывали здесь. Это самое близкое к добавлению функций, и мы отклонили наши собственные идеи функций по тем же причинам — см. # 24423 для недавнего примера.

Мы действительно хотим развивать язык намеренно. Это требует времени, и вы должны быть терпеливы. Для сравнения, С++ старше, чем многие люди в этой теме ; TypeScript еще недостаточно взрослый, чтобы находиться дома без присмотра взрослых. Как только мы что-то добавим в язык, мы не сможем убрать это снова, поэтому каждое добавление нужно тщательно взвешивать с учетом его плюсов и минусов. Я говорю по опыту, что функции, о которых люди ВОСКРИВАЛИ (методы расширения, я смотрю на вас), уничтожили бы нашу способность продолжать развивать язык (без типов пересечения, без типов объединения, без условных типов), если бы мы реализовали их только потому, что было много комментариев GitHub. Я не говорю, что добавление override опасно, просто мы всегда подходим к этому с максимальной осторожностью.

Если бы мне нужно было обобщить основные проблемы с override прямо сейчас:

  • Он не позволяет вам делать то, что вы не можете сделать сегодня с помощью декоратора (так что это «не ново» с точки зрения «вещей, которые вы можете выполнить»)
  • Семантика не может точно соответствовать override в других языках (увеличивает когнитивную нагрузку).
  • Он не «загорается» на 100% без нового флага командной строки, и это был бы флаг, который мы, вероятно, хотели бы установить ниже strict , но на самом деле не могли бы, потому что он был бы слишком большим. критическое изменение (уменьшает поставленную стоимость). И все, что подразумевает новый флаг командной строки, — это еще одно удвоение конфигурационного пространства, которое нам нужно серьезно взвесить, потому что вы можете удвоить что-то только столько раз, прежде чем это поглотит весь ваш умственный бюджет при принятии будущих решений.
  • Здесь довольно сильное внутреннее разногласие по поводу того, можно ли применить override к реализации члена интерфейса (уменьшает воспринимаемую ценность, поскольку ожидания не будут соответствовать действительности для некоторой части людей).

Общее преимущество здесь в том, что вы можете а) указать, что вы что-то переопределяете (что вы могли бы сделать сегодня с комментарием), б) поймать опечатку, с которой автозаполнение должно было вам помочь в любом случае, и в) с новым флагом, ловить места, где вы «забыли» поставить ключевое слово и теперь нужно (простая механическая задача, которая вряд ли найдет настоящие ошибки). Я понимаю, что b) очень разочаровывает , но опять же, здесь нам нужно соответствовать планке сложности.

В конце концов, если вы считаете, что классы JS чрезвычайно помогли бы ключевому слову override , то поддержка предложения TC39 по добавлению его в основную среду выполнения была бы хорошим началом.

Он не позволяет вам делать то, что вы не можете сделать сегодня с помощью декоратора (так что это «не ново» с точки зрения «вещей, которые вы можете выполнить»)

Может быть, я неправильно понимаю это, но есть довольно важные вещи, которые декоратор не может сделать, а ключевое слово может. О некоторых я упоминал в https://github.com/Microsoft/TypeScript/issues/2000#issuecomment-389393397 . Определенно поправьте меня, если это возможно, потому что я хотел бы использовать подход декоратора, но неабстрактные методы — это лишь малая часть моего варианта использования этой функции.

Еще одно преимущество, о котором не упоминалось, заключается в том, что ключевое слово override сделает изменение чего-либо в базовом классе намного более осуществимым. Изменить имя или разделить что-то на две части и т. д. сложно, зная, что любые производные классы будут прекрасно компилироваться без обновления для соответствия, но, вероятно, произойдет сбой во время выполнения. Кроме того, учитывая, что нет доступных ключевых слов final/sealed/internal, почти любой экспортируемый класс может быть где-то получен, что делает изменение практически всего, что не является частным, намного более рискованным, чем это было бы с доступным переопределением.

У меня сложилось впечатление, что правило TSLint было бы здесь самым гладким путем вперед. @kungfusheep кратко упомянул эту идею в https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -192502734, но я не вижу никаких дальнейших действий. Кто-нибудь еще знает о реализации этой функции в TSLint? Если нет, я, вероятно, начну взламывать один, когда у меня будет шанс. 😄

Моя текущая мысль состоит в том, что это будет просто комментарий, // @override , точно так же, как // @ts-ignore и различные другие директивы на основе комментариев, которые я видел. Лично я бы предпочел избегать декораторов, поскольку (в их нынешнем виде) они имеют семантику времени выполнения и поскольку они по-прежнему относятся к этапу 2 и отключены по умолчанию.

Если повезет, пользовательское правило TSLint было бы решением на 90%, только ему действительно не хватало эстетики, и, конечно, оно могло бы двигаться вперед, не привязываясь к каким-либо деталям языка. С учетом правил типов, tslint-language-service и автоматического исправления, с точки зрения разработчика, на самом деле нет большой разницы между ошибкой TSLint и встроенной ошибкой TS. Я надеюсь, что плагин TSLint (или несколько конкурирующих плагинов) даст сообществу некоторый опыт и шанс прийти к соглашению о наилучшей семантике. Тогда, возможно, его можно будет добавить в качестве основного правила TSLint, и, возможно, это обеспечит достаточную ясность и мотивацию, чтобы оправдать эстетическую выгоду ключевого слова override в основном языке.

Просто мысли нестандартно здесь. Здесь у нас есть два разных сценария. C# (просто в качестве примера) использует virtual и override , потому что по умолчанию методы НЕ являются виртуальными. В JavaScript все функции класса virtual по умолчанию (по своей природе). Разве не имеет смысла инвертировать процесс и вместо этого использовать модификатор типа nooverride ? Конечно, люди все еще могут заставить это сделать, но, по крайней мере, это поможет протолкнуть соглашение о том, что некоторые функции в базовых классах не должны трогаться. Опять же, просто мышление выходит за рамки нормы здесь. ;) Это также может быть менее критическим изменением.

Разве не имеет смысла инвертировать процесс и вместо этого использовать модификатор типа nooverride ?

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

А как насчет readonly ? Я считаю, что переопределение метода в JS на самом деле означает замену родительского метода дочерним во время создания экземпляра (когда применяется цепочка прототипов). В этом случае наличие метода readonly , означающего «Он доступен для всех, но я не хочу, чтобы кто-либо его менял, будь то путем наследования или динамически после создания экземпляра», имеет большой смысл. Это уже реализовано для членов, почему бы не сделать это и для методов?

Есть ли уже предложение по нему? Если нет, то это может стоить изучения в качестве альтернативы переопределению...

_edit:_ оказывается, что вы можете переопределить элемент только для чтения, поэтому весь этот аргумент рушится.

Возможно, еще одним вариантом является разрешение функций класса private ?

Редактировать: я думал «вместо того, чтобы отметить его окончательным», но я, должно быть, был в полусне (очевидно, «окончательный» означает общедоступный, но не может отменить), LOL; неважно.

@rjamesnw вы уже можете определить функции класса как общедоступные, защищенные, частные.

Я не думаю, что только наличие «финала» было бы решением. Проблема в том, что люди могут случайно создать новую функцию в классе, который наследуется от базового класса с уже используемым именем, и тогда вы будете молча ломать вещи, не зная почему. Это случилось со мной несколько раз, очень расстраивает, так как вы часто не получаете ошибок, и странные вещи просто происходят (или не происходят)...

Итак, я думаю, что на самом деле мы рассматриваем новую запись tsconfig.json, которая заставит компилятор выдавать ошибку, когда что-то переопределяется без пометки его как виртуального (а переопределяющий элемент помечается как переопределяемый или окончательный).

Я думаю, что без override TypeScript разочаровывает своих пользователей своим [воспринимаемым] обещанием безопасности во время компиляции и безопасности типов.
Аргумент «использовать команду IDE для переопределения метода» не работает по мере развития кода (как указывали другие люди).
Также похоже, что все основные сопоставимые языки добавили override в той или иной форме.
Чтобы не сделать это критическим изменением, это может быть правило tslint (аналогично, как в Java).
Кстати, почему бы не разрешить критические изменения между основными версиями языка, например 2.x -> 3.x. Мы не хотим застрять, не так ли?
Если выяснится, что что-то не так с некоторыми деталями override , и это требует некоторой ломающей-изменения-настройки в 3.x, так тому и быть. Я думаю, что большинство людей поймут и оценят компромисс между скоростью эволюции и совместимостью. Действительно без override я не могу рекомендовать TS как что-то более практичное, чем Java или C#...

Если люди хотят быть строгими, должен быть какой-то обязательный override (может быть необязательное правило tslint). Однако значение обязательного override намного ниже, потому что вероятность случайного переопределения метода из суперкласса кажется намного ниже, чем случайное непереопределение .

На самом деле, эта проблема, открытая с 2015 года, — это ведро холодной воды для моего энтузиазма и пропаганды TypeScript...

Это не обрабатывает методы переопределения от grand*-parents:

/* Put this in a helper library somewhere */ function override(container, key, other1) { var baseType = Object.getPrototypeOf(container); if(typeof baseType[key] !== 'function') { throw new Error('Method ' + key + ' of ' + container.constructor.name + ' does not override any base class method'); } }

Также грустно и обидно, что No one assigned на GH здесь по этому вопросу. Может быть, пришло время для форка TypeScript? ;) CompileTimeSafeScript...

Вся эта ветка выглядит как большой антипаттерн «Паралич анализа». Почему бы просто не сделать «значение 80% случаев с 20% работы» и попробовать это на практике и, если необходимо, настроить его в 3.x?

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

Таким образом, это можно проверить во время компиляции с помощью декораторов, хотя и не без небольшой помощи:

export const override = <P extends Function>() => <K extends keyof P["prototype"]>(
  target: Object,
  methodName: K,
  descriptor: TypedPropertyDescriptor<P["prototype"][K]>
) => {
  // this is a no-op. The checking is all performed at compile-time, so runtime checks are not needed.
}

class Bar {
  biz (): boolean {
    return true;
  }

  qux (): string {
    return "hi";
  }
}

class Foo extends Bar {
  // this is fine
  @override<typeof Bar>()
  biz (): boolean {
    return false;
  }

  // error: type '() => number' is not assignable to type '() => boolean'
  @override<typeof Bar>()
  biz (): number {
    return 5;
  }

  // error: argument of type '"baz"' is not assignable to parameter of type '"biz" | "qux"'
  @override<typeof Bar>()
  baz (): boolean {
    return false;
  }
}

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

@алангпирс

У меня сложилось впечатление, что правило TSLint было бы здесь самым гладким путем вперед.

[...]

Тогда, возможно, его можно будет добавить в качестве основного правила TSLint, и, возможно, это обеспечит достаточную ясность и мотивацию, чтобы оправдать эстетическую выгоду ключевого слова override в основном языке.

100% согласен. Давайте начнем уже!

Привет - немного позже, чем планировалось, но вот наше правило tslint, реализованное с помощью декоратора.

https://github.com/bet365/override-linting-rule

Мы использовали это в нашей кодовой базе TypeScript ~ 1mloc в течение нескольких лет и недавно обновили ее для использования языковой службы, что сделало ее намного быстрее для выполнения и более удобной для открытого исходного кода (предыдущая итерация требовала полного прохода проекта для сбора информации о наследии).

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

Спасибо

Я согласен. Я также считаю, что решение с участием компилятора TS было бы намного лучше, чем декораторы и правила lint.

Каков статус этой функции? Была ли она уже пересмотрена как функция компилятора?

Таким образом, это можно проверить во время компиляции с помощью декораторов, хотя и не без небольшой помощи:

Некоторые улучшения:

function override< Sup >( sup : { prototype : Sup } ) {
    return <
        Field extends keyof Sup ,
        Proto extends { [ key in Field ] : Sup[ Field ] } ,
    >(
        proto : Proto ,
        field : Field ,
        descr : TypedPropertyDescriptor< Sup[ Field ] > ,
    )=> {}
}

class Foo {

    bar( a : number ) {
        return a 
    }

    bar2( a : number , b : number ) {
        return a 
    }

}

class Foo2 {

    @override( Foo )
    bar( a : number ) {
        return 1
    }

    @override( Foo )
    bar2( a : number , b : number ) {
        return 1 
    }

    xxx() { return '777' }

}

class Foo3 extends Foo2 {

    @override( Foo ) // OK
    bar( a : number ) { return 5 }

    @override( Foo ) // Error: less args than should
    bar2( a : number ) { return 5 }

    @override( Foo ) // Error: accidental override Foo2
    xxx() { return '666' }

    @override( Foo ) // Error: override of absent method
    yyy() { return 0 }

}

Игровая площадка

Хорошо, нашел решение для переопределения, которое предотвращает ошибку компиляции.
Пример с потоком Node Readable:

// Interface so you will keep typings for all Readable methods/properties that are not overriden:
// Fileds that are `Omit`-ed should be overriden (with any signature you want, it do not have to be compatible with parent class)
interface ReadableObjStream<T> extends Omit<stream.Readable, 'push' | 'read'> {}

// Use extends (TYPE as any) to avoid compilation errors and override `Omit`-ted methods
class ReadableObjStream<T> extends (stream.Readable as any) {
    constructor()  {
        super({objectMode: true}); // force object mode. You can merge it with original options
    }
    // Override `Omit`-ed methods with YOUR CUSTOM SIGNATURE (can be non-comatible with parent):
    push(myOwnNonCompatibleSignature: T): string  { /* implementation*/ };
    read(options_nonCompatibleSignature: {opts: keyof T} ): string  { /* implementation*/ }
}

let typedReadable = new ReadableObjMode<{myData: string}>();
typedReadable.push({something: 'else'}); // will throw compilation error as expected
typedReadable.pipe(...) // non overloaded methods typings supported as expected

Единственным недостатком этого решения является отсутствие типизации при вызове super.parentMethod (но благодаря interface ReadableObjStream у вас есть все типизации при использовании экземпляра ReadableObjStream.

@nin-jin @bioball Спасибо за вклад в проверку декоратора во время компиляции.

К сожалению, это не работает с членами protected , только с членами public .

(пример ошибки в моем форке игровой площадки ниндзя

Спецификатор переопределения был убийственной функцией в С++ 11.
Это очень помогает и защищает код рефакторинга.
Я определенно хотел бы, чтобы это было в базовой поддержке TS без каких-либо препятствий (ГОЛОСОВАНИЕ!)

Мы определенно продемонстрировали бурную поддержку этой функции, но, похоже, они все еще не планируют добавлять эту функцию в ближайшее время.

Другая идея:

class Obj { 

    static override<
        This extends typeof Obj,
        Over extends keyof InstanceType<This> = never,
    >(this: This, ...overs: Over[]) { 
        return this as This & (
            new(...a:any[])=> InstanceType<This> & Protect< Omit<InstanceType<This> , Over > >
        )
    }

}

class Foo extends Obj {

    bar(a: number) {
        return 0
    }

    bar2(a: number) {
        return 0
    }

    foo = 1

}

class Foo2 extends Foo.override('bar') {

    foo = 2

    bar( a : number ) {
        return 1
    }

    // Error: Class 'Foo & Protect<Pick<Foo, "bar2" | "foo">>'
    // defines instance member property 'bar2',
    // but extended class 'Foo2' defines it as instance member function.
    bar2( a : number ) {
        return 1
    }

    bar3( a : number ) {
        return 1
    }

}

declare const Protected: unique symbol

type Protect<Obj> = {
    [Field in keyof Obj]:
    Object extends () => any
    ? Obj[Field] & { [Protected]: true }
    : Obj[Field]
}

Ссылка на игровую площадку

Добавляю сюда свои два цента.
У нас есть типичный компонент angular, который обрабатывает формы и должен отписываться от valueChanges и тому подобного. Мы не хотели повторять код повсюду (что-то вроде текущего состояния), поэтому я сделал «TypicalFormBaseComponent», который (среди прочего) реализует угловые методы OnInit и OnDestroy. Проблема в том, что теперь, если вы на самом деле используете его и добавляете свой собственный метод OnDestroy (что довольно стандартно), вы скрываете исходный и ломаете систему. вызов super.OnInit исправляет это, но в настоящее время у меня нет механизма, чтобы заставить детей делать это.

Если вы делаете это с помощью конструктора, это заставляет вас вызывать super()... Я искал что-то подобное, нашел эту тему и, честно говоря, я немного расстроен.
Реализация «нового» и «переопределения» в ts может быть критическим изменением, но исправить это было бы невероятно просто практически в любой кодовой базе (просто добавить «переопределить» везде, где это кричит). Это также может быть просто предупреждением .

В любом случае, есть ли другой способ заставить детей вызывать супервызов? Правило TSLint, предотвращающее скрытие, или что-то в этом роде?

PS: я не согласен с политикой «без комментариев». Это мешает людям бросать примеры здесь. Может быть, вы не будете реализовывать «переопределение», но вы реализуете что-то еще, что решит их проблемы... то есть, если вы действительно сможете их прочитать.

@GonziHere В других языках, таких как C # или Java, переопределение не подразумевает обязательного вызова суперметода - на самом деле это часто не так. Конструкторы - это специальные, а не обычные "виртуальные методы".

Ключевое слово override будет использоваться для указания того, что метод уже должен быть определен с совместимой сигнатурой в базовом классе, и компилятор может подтвердить это.

Да, но необходимость переопределения заставляет вас заметить, что метод существует.

@GonziHere Похоже, вам действительно может понадобиться создать абстрактные базовые классы с абстрактными функциями. Возможно, вы можете создать закрытые методы _onInit и _onDestroy , на которые можно больше положиться, а затем создать защищенные абстрактные функции onInit и onDestroy (или обычные функции, если они не требуется). Частные функции будут вызывать другие, а затем выполняться как обычно.

@GonziHere @rjamesnw Безопаснее всего каким-то образом обеспечить, чтобы onInit и onDestroy были _final_, и определить пустые защищенные абстрактные методы шаблона, которые вызывают методы final. Это гарантирует, что никто не сможет случайно переопределить методы, и попытка сделать это (при условии, что существует способ принудительного применения конечных методов) сразу же укажет пользователям правильный способ реализации того, что они хотят.

@shicks , @rjamesnw , я в той же ситуации, что и @GonziHere. Я не хочу, чтобы базовый класс имел абстрактные методы, потому что есть функции, которые я хочу выполнять для каждого из этих хуков жизненного цикла. Проблема в том, что я не хочу, чтобы эта базовая функциональность была случайно заменена , когда кто-то добавляет ngOnInit или ngOnDestroy в дочерний класс без вызова super() . Требование override привлекло бы внимание разработчиков к тому, что эти методы существуют в базовом классе и что они должны выбирать, нужно ли им вызывать super() ;

По моему опыту написания Java, @Override встречается настолько часто, что становится шумом. Во многих случаях переопределенный метод является абстрактным или пустым (где super() _нельзя_ вызывать), что делает присутствие override не особенно явным сигналом того, что super() необходим.

По моему опыту написания Java, @Override встречается настолько часто, что становится шумом. Во многих случаях переопределенный метод является абстрактным или пустым (где super() _нельзя_ вызывать), что делает присутствие override не особенно явным сигналом того, что super() необходим.

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

Для меня самая полезная часть override — получить ошибку при рефакторинге .
Прямо сейчас слишком легко переименовать какой-то метод в базовом классе и забыть переименовать те, которые переопределяют его.
Не забывать вызывать super не так уж и важно. В некоторых случаях даже правильно его не называть.

Я думаю, что есть недопонимание того, почему _override_ было бы важно. Не для того, чтобы заставить кого-то вызывать метод _super()_, а для того, чтобы сообщить им, что он существует, и позволить им добросовестно решить, хотят ли они подавить его поведение или расширить его.

Шаблон, который я использую для обеспечения правильности, заключается в использовании Parameters и ReturnType при очень явной ссылке на базовый класс, примерно так:

class Base {
    public methodName(arg1: string, arg2: number): boolean {
        return false; // base behaviour, may be stub.
    }
}
class Derived extends Base {
    public methodName(...args: Parameters<Base["methodName"]>): ReturnType<Base["methodName"]> {
        const [meaningful, variableNames] = args;
        return true; // implemented behaviour here.
    }
}

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

Я также думаю, что существует неправильное понимание того, почему override было бы важно, но я действительно не понимаю всех этих проблем с «нужна ли super() ».

Чтобы повторить то, что сказали @lorenzodallavecchia и первоначальный автор проблемы, пометка функции как override — это механизм, позволяющий избежать ошибок, которые происходят при рефакторинге суперкласса, особенно когда имя и/или подпись переопределенная функция изменяется или функция удаляется.

Имейте в виду, что человек, изменяющий сигнатуру переопределяемой функции, может не знать о существовании переопределений. Если (например, в C++) переопределения явно не помечены как переопределения, то изменение имени/сигнатуры переопределенной функции не приведет к ошибке компиляции, а просто приведет к тому, что эти переопределения больше не будут переопределениями. (и.. вероятно, больше не будут полезны, больше не будут вызываться кодом, который раньше их вызывал, и вводят кучу новых ошибок, потому что вещи, которые должны вызывать переопределения, теперь вызывают базовую реализацию)

Ключевое слово override, реализованное в C++, спасает человека, изменяющего базовую реализацию, от этих проблем, потому что сразу после рефакторинга ошибки компиляции укажут ему, что существует множество переопределений (которые переопределяют ныне несуществующую базовую реализацию) и ключ к разгадке. их к тому факту, что им, вероятно, также необходимо реорганизовать переопределения.

Наличие модификатора override также дает дополнительные преимущества использования IDE (также упомянутые первоначальным автором), а именно то, что при вводе слова override IDE может услужливо показать вам кучу возможностей того, что можно переопределить.

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

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

По моему опыту написания Java, @Override настолько распространен, что становится шумом.

После многих лет Java я категорически не согласен с приведенным выше утверждением. Переопределение — это способ общения, как проверенные исключения. Дело не в том, нравится или не нравится, а в качестве и ожиданиях.

И поэтому большой +1 к @lucasbasquerotto , но с другой точки зрения: если я ввожу метод в подкласс, я хочу иметь четкую семантику переопределения или непереопределения. Например, если я хочу переопределить метод, я хочу иметь способ указать это явно. Если что-то не так с моей реализацией, например, опечатка или неправильное копирование и вставка, я хочу получить отзыв об этом. Или по-другому: если я не ожидаю переопределения, я тоже хочу обратную связь, если переопределение иногда происходит.

Отзыв от другого разработчика Java... @override — единственная функция, которой мне действительно не хватает в Typescript.

У меня ~ 15 лет опыта работы с Java и 1-2 года или Typescript, так что мне довольно комфортно с обоими.

Дзен @override заключается в том, что вы можете удалить метод из интерфейса, и компиляция прервется.

Это похоже на обратную сторону жесткого связывания для новых методов. Позволяет удалять старые.

Если вы реализуете интерфейс и ДОБАВЛЯЕТЕ метод, компиляция завершится ошибкой, но обратного пути нет.

Если вы удалите метод, вы просто получите мертвый код.

(хотя, возможно, это можно исправить с помощью линтера)

Чтобы было ясно, когда я сказал, что @Override был шумом, я конкретно имел в виду комментарий о том, что это сигнал, который вам нужно вызвать super . Проверка, которую он обеспечивает, ценна, но для любого заданного переопределенного метода совершенно непонятно, требуется или нет вызывать super . Поскольку такая большая часть переопределенных методов _не должна_ выполняться, они неэффективны для этой цели.

Суть моей мысли в том, что если вы хотите, чтобы подклассы вызывали обратно ваши переопределяемые методы, единственный эффективный способ сделать это — сделать метод final (см., среди прочего, #33446) и вызвать его в _названный по-другому_ пустой метод шаблона, который можно безопасно переопределить _без_ вызова super . Просто нет другого разумного способа получить этот инвариант. Я полностью поддерживаю override , но как человек, который был обожжен пользователями, неправильно подклассировавшими мои API (и я на крючке, чтобы не сломать их), я считаю, что стоит переключить внимание на final как правильное решение этой проблемы, а не override , как было предложено выше.

Я немного смущен тем, почему люди продолжают предлагать одно из final или override вместо другого. Наверняка мы хотим оба , так как они решают разные задачи.

Переопределить
Не позволяет людям, которые расширяют класс, случайно перезаписать функцию своей собственной, что может привести к поломке в процессе, даже не подозревая об этом. Наличие ключевого слова override позволяет разработчику узнать, что он на самом деле переопределяет существующую функцию, а затем он может либо переименовать свою функцию, либо использовать переопределение (и тогда они могут узнать, нужно ли им вызывать super).

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

@ sam-s4s Мы тоже хотим определение :-)

Пример вопроса..

Исходный

class Base {}
class Entity extends Base {
    id() {
        return 'BUG-123' // busisess entity id
    }
}

Рефакторинг базового класса

class Base {
    id() {
        return '84256635572' // storage object id
    }
}
class Entity extends Base {
    id() {
        return '12' // busisess entity id
    }
}

У нас тут случайная перегрузка.

Случай с ключевым словом define

class Base {
    define id() {
        return '84256635572' // storage object id
    }
}
class Entity extends Base {
    define id() {
        return '12' // busisess entity id
    }
}

Должна быть ошибка: случайное переопределение.

Обходной путь с декораторами

@nin-jin не является define тем же, что и _not_ с использованием override ?

@ sam-s4s Мы тоже хотим определение :-)

О, я не видел, чтобы кто-нибудь упоминал здесь ключевое слово define , но из того, что вы описываете, я предполагаю, что вы имеете в виду слово virtual в C#?

(также я нашел ваш пример немного запутанным, так как ваши классы Base и Entity не связаны. Вы имели в виду, что Entity расширяет Base ?)

Я предполагаю, что есть 2 сценария...
а) вы пишете свой базовый класс, предполагая, что все без final может быть переопределено.
б) вы пишете свой базовый класс, предполагая, что все без virtual не может быть переопределено.

@ sam-s4s Entity, конечно же, расширяет Base. :-) Я исправил свое сообщение. virtual о другом.
@lorenzodallavecchia не использует override define | override . Использование define — это всего лишь define , и компилятор может это строго проверить.

@nin-jin В вашей модели это означает, что использование ключевого слова override по-прежнему разрешено, верно? Например:

class Base {
  myMethod () { ... }
}

class Overridden extends Base {
  // this would not fail because this is interpreted as define | override.
  myMethod () { ... }
}

В идеале приведенный выше пример выдает ошибку, если вы не используете ключевое слово override . Тем не менее, я предполагаю, что будет флаг компилятора для отказа от проверки переопределения, где отказ - это то же самое, что и override | define для всех методов.

@bioball да, это необходимо для совместимости с большим количеством существующего кода.

@ sam-s4s Entity, конечно же, расширяет Base. :-) Я исправил свое сообщение. virtual о другом.

Я до сих пор не уверен, что вы подразумеваете под определением... это определение virtual в C#:

The virtual keyword is used to modify a method, property, indexer, or event declaration and allow for it to be overridden in a derived class.

Возможно, вы имеете в виду обратное, где вместо того, чтобы помечать функцию как virtual для переопределения, вы можете пометить функцию как define , что означает, что вы должны использовать override ключевое слово

(почему define ? Я не видел это ключевое слово на другом языке)

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

class Animal {
    move(meters:number):void {
    }
}

class Snake extends Animal {
    override move(meters) {
    }
}

В приведенном выше примере move будет иметь тот же требуемый тип возврата ( void ), а meters также будет иметь тот же тип, но указывать его не нужно. Большая компания, в которой я работаю, пытается перенести весь наш javascript на машинописный текст, а не на типизацию компилятора Closure. Однако, в то время как в Closure мы можем просто использовать @override и все типы будут выводиться из суперкласса. Это весьма полезно для уменьшения возможности несоответствия и уменьшения дублирования. Можно даже представить себе реализацию этого так, что расширение метода с дополнительными параметрами все еще возможно, даже если не указывать типы для параметров, которые указаны в суперклассе. Например:

class Animal {
    move(meters:number):number {
    }
}

class Snake extends Animal {
    override move(meters, time:number) {
    }
}

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

FWIW, несмотря на то, что проще писать, опускание типов параметров (и других случаев вывода типов между файлами) затрудняет читаемость, поскольку требует, чтобы кто-то, незнакомый с кодом, копался, чтобы найти, где определен суперкласс, чтобы узнать, какой тип meters is (при условии, что вы читаете его вне среды IDE, что довольно часто встречается в незнакомых проектах, где у вас еще не настроена среда IDE). А код читают гораздо чаще, чем пишут.

@shicks Вы могли бы сказать это буквально о любой импортированной переменной или классе. Дублирование всей информации, которая может вам понадобиться, в один файл противоречит цели абстракции и модульности. Принудительное дублирование типов нарушает принцип DRY.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги