Typescript: Поддержка последних классов (не подклассифицируемых)

Созданный на 26 апр. 2016  ·  124Комментарии  ·  Источник: microsoft/TypeScript

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

В Java класс, помеченный как final, не может быть расширен, поэтому с тем же ключевым словом в TypeScript он будет выглядеть так:

final class foo {
    constructor() {
    }
}

class bar extends foo { // Error: foo is final and cannot be extended
    constructor() {
        super();
    }
}
Suggestion Won't Fix

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

Также должен быть модификатор final для методов:

class Foo {
  final fooIt():void{

  }
}

class Bar {
  fooIt():void {

  }
}
// => Method fooIt of Bar cannot override fooIt of Foo, because it is final.

Например, я часто использую следующий шаблон, где я хочу срочно избежать переопределения fooIt:

import Whatever ...

abstract class Foo {
  private ImportantVariable:boolean;

  protected abstract fooIt_inner:Whatever();

  public final fooIt():Whatever() {
    //do somestate change to aprivate member here, which is very crucial for the functionality of every Foo:
    ImportantVariable = true;
    //call the abstract inner functionality:
    return this.fooIt_inner();    
  }
}

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

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

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

Это новая функция, которая будет выпущена в TS 2.0, но вы можете попробовать ее, используя typescript@next . см. https://github.com/Microsoft/TypeScript/pull/6885 для получения дополнительных сведений.

Спасибо

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

Java и / или C # используют класс final для оптимизации вашего класса во время выполнения, зная, что он не будет специализированным. это, я бы сказал, главная ценность для окончательной поддержки. В TypeScript нет ничего, что мы могли бы предложить, чтобы ваш код работал лучше, чем это было без final.
Рассмотрите возможность использования комментариев, чтобы информировать ваших пользователей о правильном использовании класса и / или не раскрывать классы, которые вы намереваетесь стать окончательными, и вместо этого раскрывайте их интерфейсы.

Я не согласен с этим, вместо этого я согласен с дуаньяо. Private не решает эту проблему, потому что я также хочу, чтобы классы, которые являются final, можно было создать с помощью конструктора. Кроме того, отказ от их показа пользователю вынудит меня написать для них дополнительные фабрики. Для меня главная ценность финальной поддержки в том, что она не дает пользователям совершать ошибки.
Рассуждая так: что предлагает TypeScript, чтобы мой код работал быстрее, когда я использую типы в сигнатурах функций? Разве это не только для предотвращения ошибок пользователей? Я мог бы написать комментарии, описывающие, какие типы значений пользователь должен передавать в качестве параметра. Жалко, что такие расширения, как ключевое слово final, просто оттесняются, потому что, на мой взгляд, оно противоречит исходной цели машинописного текста: сделать JavaScript более безопасным, добавив уровень компиляции, который выполняет как можно больше проверок, чтобы избежать как можно большего количества ошибок. заранее, насколько это возможно. Или я неправильно понял назначение TypeScript?

Также должен быть модификатор final для методов:

class Foo {
  final fooIt():void{

  }
}

class Bar {
  fooIt():void {

  }
}
// => Method fooIt of Bar cannot override fooIt of Foo, because it is final.

Например, я часто использую следующий шаблон, где я хочу срочно избежать переопределения fooIt:

import Whatever ...

abstract class Foo {
  private ImportantVariable:boolean;

  protected abstract fooIt_inner:Whatever();

  public final fooIt():Whatever() {
    //do somestate change to aprivate member here, which is very crucial for the functionality of every Foo:
    ImportantVariable = true;
    //call the abstract inner functionality:
    return this.fooIt_inner();    
  }
}

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

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

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

В TypeScript нет ничего, что мы могли бы предложить, чтобы ваш код работал лучше, чем это было без final.

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

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

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

Точно. Так же, как private запрещает другим вызывать метод, final ограничивает других от переопределения метода.

Оба являются частью объектно-ориентированного интерфейса класса с внешним миром.

Полностью согласен с @pauldraper и @mindarelus. Пожалуйста, реализуйте это, это будет иметь большой смысл, я очень скучаю по этому сейчас.

Я не думаю, что final полезен только для производительности, он также полезен для дизайна, но я не думаю, что он вообще имеет смысл в TypeScript. Я думаю, что это лучше решить, отслеживая эффекты изменчивости Object.freeze и Object.seal .

@aluanhaddad Можете ли вы объяснить это поподробнее? Как вы думаете, почему это «вообще не имеет смысла в TypeScript»?
Замораживание или запечатывание объекта означает запрет на добавление новых свойств к объекту, но не предотвращает добавление свойств к производному объекту, поэтому, даже если я запечатываю базовый класс, я все равно могу переопределить метод в дочернем классе, который расширяет этот базовый класс . Кроме того, я не мог добавлять какие-либо свойства к базовому классу во время выполнения.

На мой взгляд, идея использования final в классе или методе класса в java больше связана с минимизацией изменчивости объекта для обеспечения безопасности потоков. (Пункт 15. Джошуа Блох, Эффективная Java)

Я не знаю, переносятся ли эти принципы в javascript, поскольку все в JS изменяемо (поправьте меня, если я ошибаюсь). Но Typescript - это не Javascript, да?

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

Конечно, я могу жить без этого, но это часть того, что такое машинописный текст, верно? Двойная проверка наших упущенных ошибок?

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

@ hk0i он также упоминается в

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

Лучшее решение этой проблемы - запретить создание подклассов в классах, которые не разработаны и не задокументированы для безопасного создания подклассов. Есть два способа запретить создание подклассов. Более простой из двух - объявить класс окончательным. Альтернативный вариант - сделать все конструкторы частными или частными для пакета и добавить общедоступные статические фабрики вместо конструкторов.

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

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

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

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

@mhegazy Частные конструкторы и классы sealed / final имеют очень разную семантику. Конечно, я могу предотвратить расширение, определив частный конструктор, но тогда я также не могу вызвать конструктор извне класса, что означает, что мне нужно определить статическую функцию, позволяющую создавать экземпляры.

Лично я бы выступил за то, чтобы компилятор проверял sealed / final, чтобы гарантировать, что классы и методы, помеченные как sealed / final, не могут быть расширены или переопределены.

В контексте моей проблемы я хочу иметь общедоступный конструктор, чтобы я мог создать экземпляр объекта, но не допустить расширения класса, и только добавление sealed / final позволит это.

Есть задача - написать код, который

  • работает с неизменяемыми объектами
  • бесплатно по наследству
  • бесплатные методы отражения

И ключевое слово final здесь необходимо, ИМХО.

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

@zigszigsdk Поскольку методы никогда не переопределяются, как это будет работать?

Для final :
Точно так же, как это работает сейчас, за исключением того, что транспилятор будет жаловаться, если кто-то скроет метод супергероя, который был объявлен окончательным, из контекста this .

Для override :
Точно так же он работает сейчас, за исключением того, что транспилятор будет жаловаться, если кто-то объявляет метод override , а его super не имеет метода с таким именем или у него есть один, но он объявлен как final .
Возможно, он также предупредит вас, если вы скроете метод super из контекста this и не переопределите состояние.

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

@mhegazy Не совсем! Еще одна важная функция - четко указать, какие части класса должны быть переопределены.

Например, см. Пункт 17 в «Эффективная Java»: «Разработайте и задокументируйте наследование или запретите его»:

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

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

Я считаю, что классы и методы final очень важны. Надеюсь, вы это реализуете.

Еще один убедительный пример использования @mhegazy . Сегодня я изучил шаблон метода шаблона и обнаружил, что final требуется, чтобы запретить подклассам изменять метод шаблона, как говорится в шаблоне метода шаблона в википедии . Но я не могу этого сделать в моем прекрасном TypeScript. Какая жалость!

Шаблон шаблона позволяет подклассам переопределять определенные шаги алгоритма без изменения структуры алгоритма.

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

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

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

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

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

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

Я бы использовал его, чтобы заставить клиентов библиотек создавать свои собственные конкретные классы, а не расширять уже существующие. Как сказал @lucasyvas ,

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

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

не будет ли последнее ключевое слово остановить пользователей / программистов от переопределения / наследования от указанного класса / метода?

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

@niokasgami Freeze относится к [data] (https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/freeze)
и уже доступен как часть ES6. Final применяется к классам и методам (переменная может быть доступна только для чтения и поэтому действует как «final», поскольку мы не можем переопределить суперпеременную).

Ключевое слово Final используется в JAVA, а sealed - в C # .

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

@Xample Я думаю, вы также можете использовать final для локальных переменных в Java. Я помню, что мне приходилось это делать, когда я писал приложение для Android. Я думаю, что это потребовало от меня сделать это с мероприятием, но я не уверен на 100%.

function(){
    let final myVariable = 'Awesome!'
    document.addEventListener('scroll', e => {
        console.log(myVariable)
        myVariable = 'Not Awesome' // Throws an error
    })
}

@TheColorRed Ну, последняя локальная переменная в Java означает константу, не так ли?

@EternalPhane Думаю, да, я всегда использовал const в глобальной области видимости ... Никогда не думал об использовании его в функции или методе ... Думаю, предыдущий комментарий недействителен ...

@Xample Да, я вижу вашу точку зрения на Sealed из C #, он не работает так же. Честно говоря, на мой взгляд, ОБА ключевое слово seal и final не могли действительно войти в категорию, так как оба могут сбивать с толку (запечатаны для печати и т. Д.) И Final, мы не уверены как его применить, поскольку в Java он используется как константа: /

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

Основные причины использования final в java:

  • Уменьшите неизменность переменных: для безопасности потоков
  • Завершите классы, чтобы они не могли быть унаследованы: форсирует композицию над наследованием, потому что наследование по своей сути сложно, когда ваши подклассы начинают непреднамеренно вызывать суперметоды
  • Предотвращает простые ошибки программирования: если вы не собираетесь изменять значение, хорошие новости: это больше невозможно
  • Используется для того, о чем думает большинство людей, представляя константы: повторно используемые значения, которые вы увидите повсюду, как в public static final String KEY = "someStringKeyPathHere" , полезно для уменьшения количества ошибок, но имеет дополнительное преимущество во время компиляции, так как не воссоздает несколько строковых объектов для одного и того же значения.

Возможно, я пропустил пару вариантов использования, но я старался сохранить их в общих чертах и ​​привести несколько примеров.

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

Несмотря на ее полезность, я не думаю, что мы увидим эту функцию в ближайшее время. Возможно, сейчас самое время взглянуть на другие решения или, как в моем случае, полностью отказаться от машинописного текста и разобраться с небрежностью в JS. У Javascript есть много «преимуществ», поскольку у него нет подсказок этого типа или чего-то еще, и в конечном итоге ваш код все равно будет преобразован в JS. Если вам нужна совместимость с браузером, вы можете написать ES6 и использовать Babel для его преобразования.

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

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

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

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

Хотя, когда вы выполняете / пишете свой код, это помогает увидеть ошибки в вашем коде и отслеживать их легче, чем с JS (а HECK JS может быть трудно отслеживать ошибки lol)

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

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

@ hk0i Я тоже не согласен, цель машинописного текста - структурировать код и, следовательно, предоставить инструменты для этого. Нам НЕ ОБЯЗАТЕЛЬНО использовать final в Java, мы обычно используем его, когда знаем, что расширение класса или метода может привести к появлению побочных эффектов (или по любой другой причине безопасности).

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

@TheColorRed да ... и в машинописном тексте мы различаем локальную переменную и переменную класса.

  • readonly для переменной класса
  • const : для любой другой переменной

Ни один из них не подходит для метода или класса. Переопределение метода не означает, что мы его переопределяем (подумайте о виртуальных методах в C ++), мы просто определяем, что метод будет первичным, вызываемым перед супер (который мы можем вызвать с помощью super ).

@niokasgami, возможно, именование не является проблемой, поскольку мы все равно поймем, что Object.seal применяется к… объекту, при этом класс и метод закрепляются за самой структурой.

TL; DR:

  • preventExtensions было бы хорошим явным именем для предотвращения расширения классов
  • preventOverrides - хорошее имя кандидата для предотвращения переопределения классов.
    НО: seal широко используется в C #, короче и имеет такое же ожидаемое поведение.

Кстати, я забыл упомянуть, что есть еще Object.preventExtensions()
Отличия можно увидеть здесь

чтение

  • всегда возможно ... кому нужна переменная только для записи?

обновление и удаление

  • предотвратить переопределение удаления переменной: только чтение для члена класса, const (для любого другого)
  • предотвратить переопределение метода: ничто не мешает вам сделать (в рамках метода класса) this.aMethod = ()=>{} . Должны ли методы быть readonly чтобы предотвратить такие взломы? то же самое для delete this.aMethod

создание

  • применяется только к объекту или, поскольку мы говорим здесь о структуре: class поскольку класс определил члены объекта, машинописный текст уже неявно предотвращает создание новых свойств на лету ... например, this.unKnownProperty = "something" ts вызовет ошибку времени компиляции TS2339, как и ожидалось.

расширение

  • применяется только к классам, мы еще ничего не делаем с самим объектом… (в отличие от создания, которое происходит во время выполнения). Здесь уместны final или seal . Вопрос в том, как оставаться устойчивым с замораживанием, печатью и preventExtensions ES6? (если нужно).
    Расширение класса означает, что мы предотвращаем создание другого класса из этого класса. Затем мы можем прочитать его, но не удалить (кто все равно удалит класс). Вопрос не в колонке "обновление". Обновляется ли класс final ? Что такое обновляемый класс? Я предполагаю, что да ... но тогда обновление будет заключаться в реализации этого класса (понимание интерфейса класса)? Всегда должна быть возможна реализация другого класса… но это не связано с обновлением исходного класса. Что ж ... возможно, они просто пришли к тем же вопросам, написав C #, и решили, что seal - хорошее ключевое слово.

преобладающий

  • предотвратить переопределение текущего метода в расширяющемся классе. Опять же, мы пока ничего не перезаписываем, поскольку говорим об определении (методе внутри класса). Поэтому запретите создание, разрешите чтение, разрешите обновление (мы могли бы предотвратить обновление метода или его удаление, используя только чтение, если бы это было применимо и к методам). Лучший нейминг? preventOverrides ? или снова seal как это используется в C #

БОНУС: поскольку запечатывание метода обычно предназначено для предотвращения того, чтобы люди переопределяли обязательный суперкод, я сделал предложение заставить людей вызывать суперкод (такое же поведение, как и для конструктора, но для любого метода), не стесняйтесь голосовать, если вы думаю было бы полезно.

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

... Если я правильно понимаю.

@ hk0i Generics, readonly и многие другие функции не являются функциями JavaScript, но они все еще добавляются в TypeScript, поэтому я не думаю, что это причина ...

TypeScript уклоняется только от функций, которые требуют генерации кода непростым способом. Generics, readonly и т. Д. - это чисто понятия времени компиляции, которые можно просто стереть для получения сгенерированного вывода, так что они честная игра.

final тоже можно «стереть» во время компиляции, так почему это не делает его «честной игрой»?

🤷‍♂️

@TheColorRed Я думаю, что это более или менее, либо у них есть другой приоритет для интеграции.
Или они не уверены, насколько предсказуемо это новое ключевое слово может работать во время компиляции / кодирования. если задуматься, то дизайн ключевого слова final может быть очень расплывчатым. final может быть использован для многих целей. Как видно из документации Jsdoc, вы можете использовать тег «@final», который сообщает интерпретатору jsdoc, что эта переменная заморожена, поэтому доступна только для чтения.

здесь конфликт дизайна. Только чтение применимо к переменной и геттеру, если я помню, какой из них «заморозил» переменную и сделал ее доступной только для чтения, а финал сделал ее окончательной, а значит, и только для чтения.

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

ЕСЛИ мы не зарезервировали это ключевое слово исключительно для объявлений классов и методов, я думаю, что поведение или final будут конфликтовать с readonly.

Я думаю, вместо sealed (который может конфликтовать с object.seal) или final (который в этом дизайне конфликтует с ключевым словом readonly) мы могли бы пойти более «прямым» способом назвать и спроектировать его.

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

`namespace Example {

export class myClass {

    constructor(){}

    // Make the func non ovveridable by inheritance. 
    public unoveridable myMethod(){

    }

    public myMethodB(){

    }
}

export class MyClassB extends myClass {

    // Nope not working will throw an error of an nonOverridable error
    myMethod(){
        super.myMethod();
    }

    // Nope will not work since unoverridable.
    myMethod(){

    }

    // Design could be implemented differently since this could complicate  ¸
    // the pattern of typescript
    public forceoverride myMethod(){
      // destroy previous pattern and ignore the parent method thus a true ovveride
    }

    // Yup will work since it's still "virtual".
    myMethodB(){
        super.myMethodB();
    }
}
// Can extend an existing Parent class
// Declaring an unextendable class make all is component / func nonOverridable by 
// inheritance. (A class component can still be overwritten by prototype usage.)
export unextendable class MyFinalClass extends Parent {

    constructor()

    // nonoverridable by default.
    public MyMethod(){

    }
}
// nope throw error that MyFinalClass is locked and cannot be extended
export class MyChildClass extends MyFinalClass{}

} `

Изменить: я не включил переменную и не получил, так как readonly уже существует.

@mhegazy Пожалуйста, рассмотрите возможность повторного открытия этого вопроса. Ответ сообщества, кажется, в подавляющем большинстве на стороне добавления ключевого слова final , например Java ( final ), C # ( sealed , readonly ), PHP ( final ), Scala ( final , sealed ) и C ++ ( final , virtual ) имеют. Я не могу придумать статически типизированный язык ООП, в котором нет этой функции, и я не слышал убедительных аргументов в пользу того, почему TS это не нужно.

@bcherny @mhegazy Полностью согласен с вышеизложенным. Я не вижу хорошо аргументированной причины, по которой final не может быть просто еще одним ограничением времени компиляции, как и большая часть синтаксического сахара, который мы видим в TypeScript - давайте посмотрим правде в глаза, статическая типизация, обобщения, модификаторы доступа, псевдонимы типов , интерфейсы ... ВСЕ САХАР! Я понимаю, что команда TypeScript, вероятно, захочет сосредоточиться на развитии языка в других направлениях, но если серьезно, это ООП 101 ... это должно было произойти очень давно, когда TypeScript был еще очень молод!

@mhegazy, могу я отослать вас к вашему раннему комментарию по этой проблеме?

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

На момент написания этот комментарий был отвергнут 21 раз.
Ясно, что это НЕ ТО, ЧТО СООБЩЕСТВО ХОЧЕТ В КАЧЕСТВЕ РЕШЕНИЯ!

вау, много отзывов по этому поводу. Не понимаю, почему нет интереса к реализации

Здесь происходит множество действительно непродуктивных жалоб. Пожалуйста, не показывайтесь только для того, чтобы выразить разочарование - здесь нам разрешено принимать решения, а сообществу разрешено давать свои отзывы, но просто праздное скрежетание зубами никого не изменит. Пожалуйста, будьте конкретны и действительны; нас не убедят люди, которые просто приходят, чтобы сказать « СДЕЛАЙТЕ ЭТУ ФУНКЦИЮ СЕЙЧАС» .

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

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

  • КЛАССЫ ЯВЛЯЮТСЯ ФУНКЦИЕЙ JAVASCRIPT, А НЕ ФУНКЦИЕЙ ТИПОВОГО СКРИПТА . Если вы действительно чувствуете, что классы совершенно бесполезны без final , это нужно обсудить с комитетом TC39 или вынести на es-Discussion. Если никто не хочет и не может убедить TC39 в том, что функция final обязательна, то мы можем рассмотреть возможность добавления ее в TypeScript.
  • Если законно вызывать new для чего-либо, это имеет однозначное соответствие времени выполнения с наследованием от него. Идея чего-то, что может быть new 'd, но не super ' d, просто не существует в JavaScript.
  • Мы изо всех сил стараемся не превращать TypeScript в суп с ключевыми словами ООП. Есть много более совершенных механизмов композиции, чем наследование в JavaScript, и нам не нужно иметь каждое ключевое слово, которое есть в C # и Java.
  • Вы можете тривиально написать проверку времени выполнения, чтобы обнаружить наследование от класса «должно быть окончательным», что вам действительно следует сделать в любом случае, если вы «беспокоитесь» о том, что кто-то случайно унаследует от вашего класса, поскольку не все ваши потребители могут использовать TypeScript .
  • Вы можете написать правило lint, чтобы обеспечить соблюдение стилистического соглашения о том, что определенные классы не наследуются от
  • Сценарий, который кто-то «случайно» унаследует от вашего класса, который должен быть запечатан, довольно странен. Я также утверждаю, что чья-то индивидуальная оценка того, «возможно» ли наследование от данного класса, вероятно, весьма сомнительна.
  • В основном есть три причины, чтобы запечатать класс: безопасность, производительность и намерение. Два из них не имеют смысла в TypeScript, поэтому мы должны учитывать сложность и выигрыш того, что выполняет только 1/3 работы, которую выполняет в других средах выполнения.

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

не слышал убедительного аргумента, почему TS это не нужно

Напоминаю, что это не так. Все функции начинаются с -100 . Из этого поста

В некоторых из этих вопросов задается вопрос, почему мы «убрали» или «упустили» конкретную функцию. Эта формулировка подразумевает, что мы начали с существующего языка (здесь наиболее популярны C ++ и Java), а затем начали удалять функции, пока не добрались до точки, которая нам понравилась. И, хотя некоторым может быть трудно поверить, язык был разработан не так.

У нас ограниченный бюджет сложности. Из-за всего, что мы делаем, каждое следующее действие происходит на 0,1% медленнее. Когда вы запрашиваете функцию, вы просите, чтобы все остальные функции стали немного сложнее, немного медленнее, немного менее возможными. Здесь ничто не мешает, потому что он есть в Java, или потому, что он есть в C #, или потому, что это всего лишь несколько строк кода, или вы можете добавить переключатель командной строки . Функции должны окупаться сами за себя и за всю свою ношу для будущего.

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

@RyanCavanaugh

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

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

Мы изо всех сил стараемся не превращать TypeScript в суп с ключевыми словами ООП. Есть много лучших механизмов композиции, чем наследование в JavaScript ...

Например, когда вы используете final для принудительного применения композиции вместо наследования в (вставьте сюда не имя языка машинописного текста) 😈
(Извините, я не удержался)

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

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

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

Главный вывод: если вам не нравится TypeScript, не используйте его.

@RyanCavanaugh

По пунктам, которые вы подняли выше ...

КЛАССЫ ЯВЛЯЮТСЯ ФУНКЦИЕЙ JAVASCRIPT, А НЕ ФУНКЦИЕЙ ТИПОВОГО СКРИПТА . Если вы действительно чувствуете, что классы совершенно бесполезны без final , это нужно обсудить с комитетом TC39 или вынести на es-Discussion. Если никто не хочет и не может убедить TC39 в том, что функция final обязательна, то мы можем рассмотреть возможность добавления ее в TypeScript.

Я слежу за TypeScript с версии 0.7, и тогда, IIRC, он был нацелен на ES3-ES5. Тогда классы определенно были функцией TypeScript. Тот же аргумент применим и к декораторам - они на картах для ES, но они уже здесь, в TypeScript, в качестве экспериментальной функции.

Как сообщество, мы не утверждаем, что компилятор TypeScript должен каким-то образом генерировать класс JavaScript, который final . Достаточно проверки во время компиляции, так же как достаточно проверки во время компиляции для модификаторов доступа; в конце концов, это тоже не возможности JavaScript, но TypeScript по-прежнему делает их возможными.

Если final действительно стало функцией ES, то, по-видимому, вы могли бы реорганизовать этот бит эмиттера при нацеливании на ES *, чтобы вывести final соответствующим образом (так же, как вы делали для классов, когда они были введены в ES6)?

Если допустимо вызывать new для чего-либо, это имеет однозначное соответствие времени выполнения с наследованием от него. Идея чего-то, что может быть new 'd, но не super ' d, просто не существует в JavaScript.

Опять же, я не думаю, что сообщество ожидает от вас чего-то волшебного здесь, чтобы обеспечить выполнение final во время выполнения. Как вы точно заявили, его «не существует в JavaScript», но, как уже упоминалось, ограничения времени компиляции было бы достаточно.

Мы изо всех сил стараемся не превращать TypeScript в суп с ключевыми словами ООП. Есть много более совершенных механизмов композиции, чем наследование в JavaScript, и нам не нужно иметь каждое ключевое слово, которое есть в C # и Java.

Это довольно известная фраза в сообществе разработчиков программного обеспечения в целом: «Отдавайте предпочтение композиции перед наследованием», однако «благосклонность» не означает, что не следует использовать наследование в качестве abstract (которые, кстати, "не существуют в JavaScript"), так почему бы и нет final классы?

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

То же самое можно сказать о C # и Java, но мне не нужны проверки во время выполнения, потому что у меня есть sealed и final чтобы сделать это за меня на уровне компилятора.

Если бы не все мои потребители использовали TypeScript, у меня было бы гораздо больше проблем, чем просто наследование от класса final ; например, доступ к элементам private или protected , отсутствие проверки статического типа или возможность создания класса abstract .

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

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

Это будет означать, что тот, кто использует ваш код, запускает линтер, так что это все еще не совсем правильное решение; это обходной путь.

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

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

Задавая вопрос, который начинается с вопроса « Могу ли я», я обычно Должен ли я» .

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

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

Я не думаю, что отсутствие final вредит моей способности эффективно использовать классы JavaScript, равно как и классы JavaScript непригодны для использования без него. Мне кажется, что final - это скорее функция "приятно иметь", чем "необходимая", но то же самое можно сказать и о многих функциях TypeScript.

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

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

Как разработчик у меня много шляп; на данный момент у меня есть шляпа C #, шляпа Kotlin и шляпа TypeScript.

  • С моей шляпой Kotlin мне не нужно беспокоиться о final потому что классы по умолчанию являются окончательными.
  • Надев шляпу на C #, я по привычке применяю sealed , заставляя своих потребителей спрашивать, могу ли я, а не спрашивать, нужно ли мне . Чаще всего классы C # должны быть sealed и это часто упускается из виду.
  • С моей шляпой TypeScript я должен скрыть конструктор в качестве обходного пути, потому что, по моему (и по мнению сообщества), языку не хватает чего-то полезного.

Ради интереса, когда команда TypeScript сидела за столом и обсуждала классы abstract , разве кто-нибудь поднял руку и сказал: «А как насчет final »?

@RyanCavanaugh Хм, я понимаю вашу точку

Я думаю, что в основном это было недоразумение (я думаю, из того, как мы объяснили)

Резюме @ series0ne очень хорошо, что наши намерения не слишком усложняли вывод машинописного текста, прося создать последний класс в JS (черт возьми, это было бы крушение поезда), а просто создать дополнительные правила для сообщения людям: Нет, вы можете ' не делай этого. и он сообщает пользователю API, что им, вероятно, не следует связываться с этим классом (для стабильности, соображений безопасности и т. д.) ВСЕХ файлов / компилятора машинописных текстов НЕ на выходе.

Думаю, было бы неплохо иметь эту функцию: да, неплохо иметь более быстрый способ выполнения какой-либо техники.

Это ДЕЙСТВИТЕЛЬНО нужно? Не совсем так, что это может быть что-то незначительное и не должно быть приоритетом, если команда Typescript может позволить себе реализовать это дополнение, тогда это было бы неплохо.

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

если я объясню это, я смогу полностью увидеть злоупотребление final, заблокировав ВСЕ классы от расширения. Провоцирование ИМО - большая головная боль в заднице. Я не хочу использовать API, в котором я заблокирован для внесения каких-либо изменений в пользовательский класс по наследству, потому что они были параноидальными из-за «безопасности».

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

После этого просто, хотя я могу неправильно понять ситуацию прямо сейчас, так что не стесняйтесь объяснять мне это :)

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

и это сказано в черновике спецификаций здесь:
https://www.ecma-international.org/activities/Languages/Language%20overview.pdf

A class that does not explicitly extend any other class is said to extend the class Object. As a consequence, all the classes that exist form an inheritance hierarchy that is a tree, the root of which is Object. A class designated as final cannot be extended, and final methods cannot be overridden: class C { final function f(n) { return n*2 } } final class D extends C { function g() { return 37 } }

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

У меня есть один вариант использования, с которым я только что столкнулся, и который заставил меня найти эту проблему. Он вращается вокруг использования this в качестве возвращаемого типа:

abstract class TileEntity {
    //constructor, other methods...
    abstract cloneAt(x: number, y: number): this;
}

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

class TurretTileEntity extends TileEntity {
    //constructor, other methods...
    cloneAt(x: number, y: number): this {
        return new TurretTileEntity(x, y, /* ... other properties */);
    }
}

Проблема в том, что без возможности объявить, что _ никогда_ не будет подкласс TurretTileEntity, возвращаемый тип this не может быть сужен только до типа TurretTileEntity.

Влияние этого недостатка на Typescript минимально, учитывая, что вы можете привести возвращаемое значение к <any> . Это объективно антипаттерн и полезный признак того, что система типов могла бы быть более выразительной. Должны поддерживаться классы final / sealed.

Ради интереса, когда команда TypeScript сидела за столом и обсуждала абстрактные классы, разве кто-нибудь поднял руку и сказал: «А как насчет final?»?

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

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

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

mhegazy закрыл https://github.com/Microsoft/TypeScript/issues/9264 в пользу этой проблемы. Есть ли отдельная проблема, которая не закрывается как дурак для отслеживания методов final / sealed?

@crcla Прочитав некоторые из комментариев @RyanCavanaugh выше, я считаю, что

Не могу поверить, что предложение для final помечено как Won't fix .

Измените метку обратно на In Discussion и примените ее, пожалуйста!

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

Для тех, кто видит в этом важную особенность «живи или умри»: на самом деле это не так. Если ваш API состоит из демонстрации реализации класса, который должен быть расширен потребителем, есть лучшие способы сделать это. Не следуйте плохому примеру React. Просто используйте функции и простые объекты и оставьте этот объектно-ориентированный бред там, где он должен быть: в прошлом.

А теперь давайте голоса против 😀

Может ли кто-нибудь объяснить мне, как это обсуждение не различает final классы и final методы? Конечно, предотвращение переопределения некоторых методов имеет огромное значение. Я хочу предоставить возможность разработчику плагина добавить функциональность, создав подклассы некоторых из моих классов фреймворков и реализуя абстрактные функции - но в то же время убедитесь, что он (зачем вообще!) Не переопределяет методы, вызывающие эти абстрактные функции (и убедитесь, что происходит обработка ошибок / вход в систему / проверка.

@pelotom Если вы не хотите использовать конструкции и шаблоны ООП, просто не делайте этого, никто не говорит вам делать иначе, однако то же самое должно применяться в противоположном направлении: если кто-то хочет использовать конструкции ООП, просто позвольте им.
Заставляя других использовать определенный (не-ООП) шаблон, вы принимаете то же поведение, что и тот, за который осуждаете ООП. По крайней мере, будьте последовательны :)

Добавление ключевого слова final в TypeScript не является критическим изменением и никоим образом не повлияет ни на один из ваших существующих проектов, однако это будет жизненно важной функцией для таких людей, как @thorek , я и многие другие.

если кто-то хочет использовать конструкции ООП, просто позвольте им.

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

fwiw, мы, вероятно, будем поощрять наших клиентов (Google) использовать @final в комментариях, чтобы он распространялся на компилятор закрытия: \

Добавление _final_ в классы и методы полезно, когда команда R&D создает библиотеки TS, которые могут использовать другие команды. Эти ключевые слова являются (важной) частью стабильности обслуживания таких инструментов.
Ключевое слово более важно для больших команд и менее важно для небольших проектов, imho.

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

Я открыт для исправлений по этому поводу!

Я больше забочусь о поддержке методов final (чтобы избежать их случайного переопределения подклассами), чем о последних классах. Правильный ли билет для голосования за это? Ticket https://github.com/Microsoft/TypeScript/issues/9264, похоже, подразумевает это.

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

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

class parent {
  public get finalMethod(): () => void {
    return () => {
      // final logic
    };
  }
}

class child extends parent {
  // ERROR "Class 'parent' defines instance member accessor 'finalMethod', but extended class 'child' defines it as instance member function"
  public finalMethod(): void { }
}

Вот еще один пример использования методов final реальном мире. Рассмотрим следующее расширение React.Component чтобы упростить реализацию простого контекста .

interface FooStore{
  foo: string;
}

const {Provider, Consumer} = React.createContext<FooStore>({
  foo: 'bar';
});

abstract class FooConsumerComponent<P, S> extends React.Component<P, S> {

  // implement this instead of render
  public abstract renderWithStore(store: FooStore): JSX.Element;

  public final render() {
    return (
      <Consumer>
        {
          (store) => {
            return this.renderWithStore(store);
          }
        }
      </Consumer>
    );
  }
}

Без final в методе render() ничто не мешает некорректно переопределить метод, что приведет к поломке всего.

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

Единственный обходной путь, который я нашел, - использовать декоратор методов, например:
function sealed(target, key,descriptor){ descriptor.writable = false; // Prevent method to be overriden }

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

class MyBaseClass {
<strong i="10">@sealed</strong>
public MySealedMethod(){}

public OtherMethod(){}

...
}

Таким образом, MySealedMethod нельзя (легко) переопределить в подклассе. Пример:

class AnotherClass extends MyBaseClass {
public MySealedMethod(){} ====>> ERROR at RUNTIME (not at compile time)
}

на самом деле, единственным недостатком этого обходного пути является то, что "запечатанный" виден только во время выполнения (ошибка в консоли javascript), но НЕ во время компиляции (поскольку свойства метода устанавливаются с помощью функции, я думаю)

@RyanCavanaugh

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

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

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

Позвольте мне просто оставить эти вопросы здесь, чтобы мы все обдумали.

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

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

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

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

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

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

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

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

Реальный случай, когда я бы использовал final, - это предотвратить переопределение метода чем-то, что никогда не вызовет супер-метод. Конечно, какой-то публичный метод. Но настоящее решение, которое я придумал, - это возможность заставить любой вспомогательный метод вызывать супер-метод. https://github.com/Microsoft/TypeScript/issues/21388

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

Я думаю, что выше есть множество отличных примеров того, когда / где final было бы не только полезно, но и обеспечило бы _реальную ценность_ и _ реальную безопасность_ строящейся платформе. Для меня это не исключение. Как я уже сказал, я не очень разбираюсь в сложностях языкового дизайна, но это явно функция, которую люди хотят / будут использовать / я действительно не вижу способа злоупотребить ею или что она создаст плохой код. Я понимаю концепцию боязни расползания масштабов, но разработчикам TS не нужно реализовывать все, что хочет сообщество.

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

  • Это может привести к ползучести прицела (это управляемо)
  • Люди будут просить другие функции (хорошо, но кого это волнует, это часть разработки языка, и это не юридический прецедент, который мы устанавливаем)
  • Это может снизить эффективность (я полагаю, что, поскольку команда TS полна негодяев, вы, вероятно, найдете способ сделать это управляемым)
  • Мнения комментариев "Не используйте концепции ООП"

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

Надеюсь, это конструктивно! Люблю TS и хочу, чтобы он продолжал улучшаться!

👍

--редактировать--

В ответ @RyanCavanaugh :

So what problem is being solved?

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

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

Большая часть недавних обсуждений была сосредоточена на методах final (в меньшей степени - на классах final). Конечные методы, безусловно, являются моим главным интересом. Я только что заметил, что если вы развернете ссылку «этот комментарий» в предыдущем посте @mhegazy предположил, что этот билет - правильное место (при закрытии # 9264). Итак, как скромный конечный пользователь, я не понимаю, где мне следует запрашивать / голосовать за окончательные методы . Руководство будет оценено.

@cbutterfield
Противоречие - это то, что я тоже заметил, но не указал в своем посте, опасаясь разглагольствования (на самом деле я нашел ЭТУ ветку через # 9264 и пришел сюда по
@RyanCavanaugh
@mhegazy
Согласно приведенному выше комментарию, где _это правильное место для обсуждения окончательных методов, потому что это действительно то, что меня больше всего интересует.

Оглядываясь назад на мой сильно отклоненный комментарий (обязательно добавьте свой 👎, если вы еще этого не сделали!) Я рад сообщить, что React _ не больше_ устанавливает плохой пример, заставляя API на основе class на своих пользователей! С предложением Hooks команда React опомнилась и приняла радикально более простой функциональный подход. С этим исчезла последняя причина, по которой многие разработчики вообще должны были использовать классы, и скатертью дорога. Повторюсь: все, что, по вашему мнению, вам нужно для занятий, можно было бы сделать лучше без них .

Будущее JS и TS бесклассовое, хвала!

@cbutterfield Я тоже это заметил, и если проблема № 9264 была закрыта для обсуждения в этом выпуске, я думаю, что заголовок должен измениться на Suggestion: Final keyword for classes and methods , иначе люди, которые ищут окончательные методы (исключительно), могут не добавить реакцию в первый пост.

Цитата @mhegazy :
Java и / или C # используют класс final для оптимизации вашего класса во время выполнения, зная, что он не будет специализированным. это, я бы сказал, главная ценность для окончательной поддержки. В TypeScript нет ничего, что мы могли бы предложить, чтобы ваш код работал лучше, чем это было без final.

Я принципиально не согласен. Я много лет разрабатывал на Java во многих командах, и мы никогда не использовали final классы или методы для оптимизации времени выполнения; на практике это в первую очередь идиома дизайна ООП. Иногда я просто не хочу, чтобы мой класс или метод расширялся / переопределялся внешними подклассами, будь то для защиты и гарантии функции, или для ограничения шума API библиотеки до самого необходимого, или для защиты пользователей API от неправильного понимания сложного API.

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

Несмотря на то, что этой проблеме уже почти 3 года, нам действительно нужно иметь ключевое слово final поскольку уже есть ключевое слово abstract .

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

abstract class A {
  protected abstract myVar: string;

  protected abstract myFunction(): void;
}

class B extends A {
  protected readonly myVar: string = "toto";

  constructor() {
    super();
    this.myFunction();
  }

  protected myFunction() {
    console.log(this.myVar);
  }
}

class C extends B {
  constructor() {
    super();
  }

  protected myFunction() {
    console.log("tata");
  };

  public callFunction = () => {
    this.myFunction();
  }
}

const myB = new B(); // toto

const myC = new C(); // toto
myC.callFunction(); // tata

Результат после компиляции:

toto
tata

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

Так что, если мы пойдем дальше в процессе компиляции Typescript и будем использовать readonly для защиты наших атрибутов (и притворимся, что наши методы являются свойствами)?

class B extends A {
  [...]

  protected readonly myFunction = () => {
    console.log(this.myVar);
  }
}

class C extends B {
  protected myVar: string = "I am not protected that much";
  [...]

  protected myFunction = () => { // using the readonly keyword doesn't prevent any modification
    console.log("tata"); // If you launch this code, tata will still be logged in the console.
  };
}

(Обратите внимание, что код скомпилирован с tsc, никаких ошибок при компиляции или выполнении не возникает)
Итак, теперь у нас есть две проблемы:

  • Мы не защищаем наши атрибуты B , поэтому нам нужен способ предотвратить любое неожиданное наследование.
  • Теперь мы знаем, что можем переопределить любые свойства readonly , расширив его класс, и ДА, Typescript знает, что это родительское свойство, которое мы меняем, используя private вместо protected в C class выдаст ошибку, поскольку это не тот же тип доступа / видимость.

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

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

tl; dr: Ключевое слово final решит две проблемы (хотя проверка readonly будет хорошим началом для предотвращения любой несанкционированной перезаписи)

Три года спустя ... это так смешно.

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

Java:
final class SomeClass { ... }
PHP:
final class SomeClass { ... }
C#:
sealed class SomeClass {...}
C++:
class SomeClass final { ... }
Delphi:
type SomeClass = class sealed() ... end;

Итак, мы видим, что во всех самых популярных языках ООП эта функция существует, потому что она исходит из логики наследования ООП.

Также я должен процитировать отрывок из TS, который говорит:

если у нас есть классы final, нам также «понадобятся» методы или свойства final.

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

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

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

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

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

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

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

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

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

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


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

  • КЛАССЫ ЯВЛЯЮТСЯ ФУНКЦИЕЙ JAVASCRIPT, А НЕ ФУНКЦИЕЙ ТИПОВОГО СКРИПТА . Если вы действительно чувствуете, что классы совершенно бесполезны без final , это нужно обсудить с комитетом TC39 или вынести на es-Discussion. Если никто не хочет и не может убедить TC39 в том, что функция final обязательна, то мы можем рассмотреть возможность добавления ее в TypeScript.
  • Если законно вызывать new для чего-либо, это имеет однозначное соответствие времени выполнения с наследованием от него. Идея чего-то, что может быть new 'd, но не super ' d, просто не существует в JavaScript.
  • Мы изо всех сил стараемся не превращать TypeScript в суп с ключевыми словами ООП. Есть много более совершенных механизмов композиции, чем наследование в JavaScript, и нам не нужно иметь каждое ключевое слово, которое есть в C # и Java.
  • Вы можете тривиально написать проверку времени выполнения, чтобы обнаружить наследование от класса «должно быть окончательным», что вам действительно следует сделать в любом случае, если вы «беспокоитесь» о том, что кто-то случайно унаследует от вашего класса, поскольку не все ваши потребители могут использовать TypeScript .
  • Вы можете написать правило lint, чтобы обеспечить соблюдение стилистического соглашения о том, что определенные классы не наследуются от
  • Сценарий, который кто-то «случайно» унаследует от вашего класса, который должен быть запечатан, довольно странен. Я также утверждаю, что чья-то индивидуальная оценка того, «возможно» ли наследование от данного класса, вероятно, весьма сомнительна.
  • В основном есть три причины, чтобы запечатать класс: безопасность, производительность и намерение. Два из них не имеют смысла в TypeScript, поэтому мы должны учитывать сложность и выигрыш того, что выполняет только 1/3 работы, которую выполняет в других средах выполнения.

не слышал убедительного аргумента, почему TS это не нужно

Напоминаю, что это не так. Все функции начинаются с -100 . Из этого поста

В некоторых из этих вопросов задается вопрос, почему мы «убрали» или «упустили» конкретную функцию. Эта формулировка подразумевает, что мы начали с существующего языка (здесь наиболее популярны C ++ и Java), а затем начали удалять функции, пока не добрались до точки, которая нам понравилась. И, хотя некоторым может быть трудно поверить, язык был разработан не так.

У нас ограниченный бюджет сложности. Из-за всего, что мы делаем, каждое следующее действие происходит на 0,1% медленнее. Когда вы запрашиваете функцию, вы просите, чтобы все остальные функции стали немного сложнее, немного медленнее, немного менее возможными. Здесь ничто не мешает, потому что он есть в Java, или потому, что он есть в C #, или потому, что это всего лишь несколько строк кода, или вы можете добавить переключатель командной строки . Функции должны окупаться сами за себя и за всю свою ношу для будущего.


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

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

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

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

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

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

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


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

Вы можете легко представить себе какое-то ключевое слово, которое было применено к методу и принудительно вызвало его производные методы через super . Если бы в C # и Java было это ключевое слово, люди абсолютно точно применили бы это ключевое слово там, где это имеет смысл. Фактически, это, возможно, будет гораздо чаще применяться, чем final , потому что невозможно применить проверку времени выполнения и является гораздо более тонким аспектом базового производного контракта, чем «производные классы могут не существовать. ". Это было бы полезно по-разному, в отличие от final . Я бы предпочел это ключевое слово, чем это (хотя я думаю, что ни одно из них не соответствует шкале сложности и значения).

Итак, что было бы подходящей причиной для рассмотрения реализации этого?

Когда мы смотрим на отзывы, сильные соображения выглядят следующим образом:

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

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

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

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

Я не «одинокий парень». Когда я здесь принимаю решения, это отражение процесса принятия решений всей командой TypeScript.

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

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

Я не думаю, что final - это первый шаг, поскольку уже есть предложение по ключевому слову private https://github.com/tc39/proposal-private-methods

Я скептически отношусь к You should a comment to say *dont do this* , это все равно что сказать в форме Hey don't write down specific characters , вы кодировали почти вечно годы, поэтому вы должны знать правило №1 разработчика: никогда не доверяйте пользователю ( и разработчик, который будет использовать ваш код)

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

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

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

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

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

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

Все действительные баллы! Но, пожалуйста, давайте разделим обсуждение между

а) Поддержка выпускных классов
б) Поддержка окончательных методов

Хотя все ваши аргументы нацелены на а) - ни одна не нацелена на b.

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

Am 28.01.2019 um 20:32 schrieb Ryan Cavanaugh [email protected] :

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

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

КЛАССЫ ЯВЛЯЮТСЯ ФУНКЦИЕЙ JAVASCRIPT, А НЕ ФУНКЦИЕЙ ТИПОВОГО СКРИПТА. Если вы действительно чувствуете, что занятия без финала совершенно бесполезны, это нужно обсудить с комитетом TC39 или вынести на обсуждение. Если никто не хочет или не может убедить TC39 в том, что final является обязательной функцией, мы можем рассмотреть возможность добавления его в TypeScript.
Если разрешено вызывать новое для чего-либо, это имеет однозначное соответствие времени выполнения с наследованием от него. Представления о том, что может быть новым, но не супер, просто не существует в JavaScript.
Мы изо всех сил стараемся не превращать TypeScript в суп с ключевыми словами ООП. Есть много более совершенных механизмов композиции, чем наследование в JavaScript, и нам не нужно иметь каждое ключевое слово, которое есть в C # и Java.
Вы можете тривиально написать проверку времени выполнения, чтобы обнаружить наследование от класса «должно быть окончательным», что вам действительно следует сделать в любом случае, если вы «беспокоитесь» о том, что кто-то случайно унаследует от вашего класса, поскольку не все ваши потребители могут использовать TypeScript .
Вы можете написать правило lint, чтобы обеспечить соблюдение стилистического соглашения о том, что определенные классы не наследуются от
Сценарий, который кто-то «случайно» унаследует от вашего класса, который должен быть запечатан, довольно странен. Я также утверждаю, что чья-то индивидуальная оценка того, «возможно» ли наследование от данного класса, вероятно, весьма сомнительна.
В основном есть три причины, чтобы запечатать класс: безопасность, производительность и намерение. Два из них не имеют смысла в TypeScript, поэтому мы должны учитывать сложность и выигрыш того, что выполняет только 1/3 работы, которую выполняет в других средах выполнения.
не слышал убедительного аргумента, почему TS это не нужно

Напоминаю, что это не так. Все функции начинаются с -100 https://blogs.msdn.microsoft.com/ericgu/2004/01/12/minus-100-points/ . Из этого поста

В некоторых из этих вопросов задается вопрос, почему мы «убрали» или «упустили» конкретную функцию. Эта формулировка подразумевает, что мы начали с существующего языка (здесь наиболее популярны C ++ и Java), а затем начали удалять функции, пока не добрались до точки, которая нам понравилась. И, хотя некоторым может быть трудно поверить, язык был разработан не так.

У нас ограниченный бюджет сложности. Из-за всего, что мы делаем, каждое следующее действие происходит на 0,1% медленнее. Когда вы запрашиваете функцию, вы просите, чтобы все остальные функции стали немного сложнее, немного медленнее, немного менее возможными. Здесь ничто не мешает, потому что он есть в Java, или потому, что он есть в C #, или потому, что это всего лишь несколько строк кода, или вы можете добавить переключатель командной строки. Функции должны окупаться сами за себя и за всю свою ношу для будущего.

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

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

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

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

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

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

Мы очень четко указываем, как мы проектируем язык https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals ; нецелевой номер один говорит именно об этом предположении.

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

Вы можете легко представить какое-то ключевое слово, которое было применено к методу и принудительно вызвало его производные методы через super https://github.com/Microsoft/TypeScript/issues/21388 . Если бы в C # и Java было это ключевое слово, люди абсолютно точно применили бы это ключевое слово там, где это имеет смысл. Фактически, это, вероятно, будет гораздо чаще применяться, чем final, потому что невозможно применить проверку времени выполнения и является гораздо более тонким аспектом базового производного контракта, чем «производные классы могут не существовать». Это было бы полезно по-разному, в отличие от final. Я бы предпочел это ключевое слово, чем это (хотя я думаю, что ни одно из них не соответствует шкале сложности и значения).

Итак, что было бы подходящей причиной для рассмотрения реализации этого?

Когда мы смотрим на отзывы, сильные соображения выглядят следующим образом:

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

Мы никогда не видели отзывов вроде «Я часами занимался отладкой, потому что пытался наследовать от класса, который на самом деле не подклассифицировался» - кто-то сказал бы, что это справедливо вызовет «ват», потому что это невозможно вообразить. Даже если бы модификатор существовал, это не помогло бы ситуации, потому что библиотеки не документируют, если классы должны быть окончательными - например, fs.FSwatcher https://nodejs.org/api/fs.html#fs_class_fs_fswatcher final ? Кажется, что даже авторы узла не знают. Итак, final достаточно, если авторы знают, что он окончательный, но это будет задокументировано, несмотря на то, что отсутствие final ничего вам не говорит, потому что это часто просто не известно в любом случае.

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

Да, дискуссия сейчас такая расплывчатая. Я хотел бы видеть эти проблемы в трекере, где вы можете голосовать, например, в том, который используется для проблем с IntelliJ / webstorm. Получите реальные цифры того, сколько людей хотели бы видеть final для классов и / или методов.

финал был бы очень полезен

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

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

Например, допустим, у вас есть приложение, поддерживающее сторонние плагины. Вы можете заставить все плагины расширять абстрактный класс, который реализует интерфейс для любых методов, которые они ДОЛЖНЫ создавать, и содержит несколько финальных методов, которые контролируют порядок операций и позволяют реализовать перехватчики между операциями. Из-за этого вашему приложению не нужно знать специфику какого-либо отдельного плагина и при этом знать некоторые его внутренние состояния.

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

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

@RyanCavanaugh Хотя есть хороший пример, когда render () хочет final, никто не упомянул принцип открытого-закрытого (теперь он сокращен на втором месте как часть SOLID).
Это было бы полезно для написания фреймворков, которые будут использовать другие.
Если нет, то каков предпочтительный подход к открытому-закрытому подходу в Typescript?

Мне бы НРАВИТСЯ final для методов (и я полагаю, что свойства и объявления в целом). Это вызывает настоящие проблемы - люди продолжают игнорировать вещи, случайно не понимая, что что-то было под ними ... ломающееся ...

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

Моя ситуация похожа на @ 0815fox, где у меня есть «внутренняя» версия метода, которая является абстрактной, но я хочу, чтобы «исходная» версия метода никогда не переопределялась, поскольку она содержит важное поведение, и я не хочу подкласс, чтобы переопределить тот.

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

возможно, остановкой будет добавление стандарта в TS-Doc, в котором будет указано, что кто-то не наследует конкретный класс или не отменяет конкретный метод. Например, аннотация @final или что-то подобное.

Что ж, что мне добавить к тому, что сказали все остальные, голосующие за "финал" ... Вот мой голос за это.

Согласовано. Конечно, вы можете использовать readonly с методами, если будете рассматривать его как свойство типа function, но этот обходной путь может быть довольно запутанным. Было бы неплохо иметь собственное решение этой проблемы.

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

final должно быть выходом

Банда - поскольку этот тикет (а) закрыт и (б) имеет вводящее в заблуждение название для обсуждений «финального метода», я решил создать новый тикет, ориентированный исключительно на финальные методы . Надеюсь, это будет труднее игнорировать и даже может стимулировать некоторый прогресс. О да, см. Https://github.com/microsoft/TypeScript/issues/33446

@RyanCavanaugh

сложность против шкалы значений

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

@vinayluzrao

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

// OK
declare const SomeParent: new() => { a: string };
class A extends SomeParent { }

Очевидно, что final класс new -able:

function make<T>(ctor: new() => T) {
  return ctor;
}
final class Done {
}
// No problem
const d = make(Done); // d: Done

Но теперь возникает противоречие: у нас есть вещь, которая может быть new , но незаконно помещать в предложение extends . Итак, один уровень косвенного обращения побеждает final :

function subvertFinal(ctor: new() => any) {
  class Mwhahaah extends ctor {
    constructor() {
      super();
      console.log("I extended a final class! So much for 'types'!")
    }
  }
}
final class Done {
}
subvertFinal(Done);

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

К вашему сведению, в 90% случаев, когда мы говорим «сложность» в этих обсуждениях, мы имеем в виду концептуальную сложность, которую испытывают пользователи TypeScript , а не сложность реализации с нашей стороны как разработчиков продукта. С последним можно иметь дело (например, условные типы и сопоставленные типы чрезвычайно сложны с точки зрения реализации); концептуальная сложность - это не то, с чем можно справиться, добавив к продукту больше и более умных инженеров. Каждый раз, когда мы добавляем к языку что-то, что вызывает неожиданное или трудно объяснимое поведение, это психологическая нагрузка для каждого пользователя языка.

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

@RyanCavanaugh, который действительно проясняет new и extends , возможно, это большая проблема , а не сам final . Интересно, почему было принято это решение о связывании, и насколько сложно было бы отменить его (вовсе не подразумевая, что это должно быть отменено) на данном этапе.

Я не уверен, что это может быть слишком косвенное обсуждение здесь.

Это проблема, с которой я столкнулся. Такой простой образец кода невозможно скомпилировать.
final ключевое слово ИДЕАЛЬНО решит

abstract class Parent {
    public abstract method(): this;
}

class Child extends Parent {
    public method(): this {
        return new Child();
    }
}

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

Детская площадка

можно открыть снова, пожалуйста? очевидно, что эта функция нужна

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

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

Здесь очень приветствуется конструктивное обсуждение вроде комментария

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

Я также поддерживаю добавление ключевого слова final для методов.
В настоящее время это стандарт объектно-ориентированного программирования.

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

Если я не ошибаюсь, добавление final могло бы помочь избежать проблем с ошибками 'myObj' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'MyClass' при работе с дженериками, потому что я мог бы сделать MyClass final и, следовательно, гарантировать это других подтипов нет.
Не уверен на 100%, но я думаю, что именно так можно решить эту проблему на Java.

У нас есть то же самое, мы делаем базовый компонент в angular, который многие другие пакеты могут или должны расширять как базовый.
В том, что у нас есть такие вещи, как ngOnChanges или даже ngOnInit angular, которые должен реализовывать только базовый класс, а подклассы не должны переопределять их, а действительно реализуют ngOnInitReal (или как мы его называем), поэтому мы полностью контролируем, когда это вызывается и с чем.
Поскольку мы не можем контролировать это, мы можем сделать это только с помощью документации. Если один производитель компонентов не прочитает это, его компонент может сломаться, или ему нужно скопировать много кода из нашей базы, чтобы сделать то же самое ...

Таким образом, без финальных методов мы не сможем создать надежный API-контракт.

кто-то упомянул

class Fancy {
    readonly secureFoo = (message: string) => {
        console.log(message);
    }
}

кто-то упомянул

class Fancy {
    readonly secureFoo = (message: string) => {
        console.log(message);
    }
}

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

Была ли эта страница полезной?
0 / 5 - 0 рейтинги