Typescript: Suportar classes finais (não subclassíveis)

Criado em 26 abr. 2016  ·  124Comentários  ·  Fonte: microsoft/TypeScript

Eu estava pensando que poderia ser útil ter uma maneira de especificar que uma classe não deveria ser subclassificada, de modo que o compilador avisasse o usuário sobre a compilação se ele vir outra classe estendendo a original.

Em Java, uma classe marcada com final não pode ser estendida, então, com a mesma palavra-chave no TypeScript, ela ficaria assim:

final class foo {
    constructor() {
    }
}

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

Comentários muito úteis

Também deve haver um modificador final para métodos:

class Foo {
  final fooIt():void{

  }
}

class Bar {
  fooIt():void {

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

Por exemplo, costumo usar o seguinte padrão, onde desejo evitar urgentemente que fooIt seja substituído:

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();    
  }
}

Todos 124 comentários

uma classe com construtor privado não é extensível. considere usar isso.

Pelo que me lembrei, tinha certeza de que o compilador não gostou da palavra-chave privada no construtor. Mas talvez eu não esteja usando a versão colar

Este é um novo recurso, será lançado no TS 2.0, mas você pode experimentá-lo usando typescript@next . consulte https://github.com/Microsoft/TypeScript/pull/6885 para obter mais detalhes.

Ok obrigado

O construtor privado também não torna uma classe não instanciada fora da classe? Não é uma resposta certa para a aula final.

Java e / ou C # usa a classe final para otimizar sua classe em tempo de execução, sabendo que ela não será especializada. Eu diria que esse é o principal valor para o suporte final. No TypeScript, não há nada que possamos oferecer para fazer seu código funcionar melhor do que sem o final.
Considere o uso de comentários para informar seus usuários sobre o uso correto da classe e / ou não expor as classes que você pretende que sejam finais e, em vez disso, exponha suas interfaces.

Eu não concordo com isso, em vez disso, concordo com duanyao. Private não resolve esse problema, porque eu também quero que as classes finais possam ser instanciadas por meio de um construtor. Além disso, não expô-los ao usuário me forçaria a escrever fábricas adicionais para eles. Para mim, o principal valor do suporte final é evitar que os usuários cometam erros.
Argumentando assim: O que o TypeScript oferece para fazer meu código rodar mais rápido, quando eu uso tipos em assinaturas de função? Não é apenas para evitar que os usuários cometam erros? Eu poderia escrever comentários descrevendo quais tipos de valores um usuário deve passar como parâmetro. É uma pena que extensões como uma palavra-chave final sejam simplesmente afastadas, porque, na minha opinião, elas colidem com a intenção original do texto digitado: tornar o JavaScript mais seguro adicionando um nível de compilação, que executa tantas verificações quanto possível para evitar tantos erros o mais adiantado possível. Ou eu entendi mal a intenção do TypeScript?

Também deve haver um modificador final para métodos:

class Foo {
  final fooIt():void{

  }
}

class Bar {
  fooIt():void {

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

Por exemplo, costumo usar o seguinte padrão, onde desejo evitar urgentemente que fooIt seja substituído:

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();    
  }
}

O argumento sobre custo x utilidade é bastante subjetivo. A principal preocupação é que cada novo recurso, construção ou palavra-chave adiciona complexidade à linguagem e à implementação do compilador / ferramentas. O que tentamos fazer nas reuniões de design de linguagem é entender as compensações e apenas adicionar novos recursos quando o valor agregado pesar sobre a complexidade introduzida.

O problema não está bloqueado para permitir que os membros da comunidade continuem adicionando feedback. Com feedback suficiente e casos de uso atraentes, os problemas podem ser reabertos.

Na verdade, o final é um conceito muito simples, não adiciona nenhuma complexidade à linguagem e deve ser adicionado. Pelo menos para métodos. Ele agrega valor, quando muitas pessoas trabalham em um grande projeto, é valioso não permitir que alguém sobrescreva métodos, que não deveriam ser sobrescritos.

No TypeScript, não há nada que possamos oferecer para fazer seu código funcionar melhor do que sem o final.

Uau, encolhe-se! Os tipos estáticos também não fazem seu código funcionar melhor, mas segurança é uma coisa boa de se ter.

Final (lacrado) está bem ali com substituição como recursos que eu gostaria de ver para tornar as personalizações de classe um pouco mais seguras. Eu não me importo com desempenho.

Os tipos estáticos também não fazem seu código funcionar melhor, mas segurança é uma coisa boa de se ter.

Exatamente. Assim como private impede que outros chamem o método, final limita outros de sobrescrever o método.

Ambos fazem parte da interface OO da classe com o mundo exterior.

Concordo totalmente com @pauldraper e @mindarelus. Por favor, implemente isso, faria muito sentido, eu realmente sinto falta atualmente.

Não acho que o final seja benéfico apenas para o desempenho, também é benéfico para o design, mas não acho que faça sentido no TypeScript. Acho que isso é melhor resolvido rastreando os efeitos de mutabilidade de Object.freeze e Object.seal .

@aluanhaddad Você pode explicar isso com mais detalhes? Por que você acha que "não faz sentido no TypeScript"?
Congelar ou lacrar objeto significa proibir a adição de novas propriedades a um objeto, mas não impede a adição de propriedades a um objeto derivado, então mesmo se eu selasse a classe base, eu ainda poderia substituir o método em uma classe filha, que estende essa classe base . Além disso, não pude adicionar nenhuma propriedade à classe base em tempo de execução.

A ideia de usar final em uma classe ou método de classe em java tem mais a ver com a minimização da mutabilidade do objeto para segurança de thread, na minha opinião. (Item 15. Joshua Bloch, Java Efetivo)

Não sei se esses princípios são transportados para o javascript, visto que tudo em JS é mutável (corrija-me se estiver errado). Mas Typescript não é Javascript, certo?

Eu realmente gostaria de ver isso implementado. Acho que vai ajudar a criar um código mais robusto. Agora ... Como isso se traduz em JS, honestamente provavelmente não precisa. Ele pode apenas ficar do lado da cerca datilografada, onde está o resto de nossa verificação em tempo de compilação.

Claro que posso viver sem isso, mas isso é parte do que é datilografado, certo? Verificando nossos erros negligenciados?

Para mim, final desempenharia o mesmo papel no texto datilografado que private ou tipificações, isto é, contrato de código. Eles podem ser usados ​​para garantir que seu contrato de código não seja quebrado. Eu gostaria muito disso.

@ hk0i também é mencionado no item 17 (2ª edição) de maneira semelhante ao que foi ecoado aqui:

Mas e as classes concretas comuns? Tradicionalmente, eles não são finais nem projetados e documentados para subclasses, mas esse estado de coisas é perigoso. Cada vez que uma alteração é feita em uma classe, há uma chance de que as classes de cliente que estendem a classe sejam interrompidas. Este não é apenas um problema teórico. Não é incomum receber relatórios de bugs relacionados a subclasses após modificar as partes internas de uma classe concreta não final que não foi projetada e documentada para herança.

A melhor solução para esse problema é proibir a criação de subclasses em classes que não foram projetadas e documentadas para serem subclasses com segurança. Existem duas maneiras de proibir a subclasse. O mais fácil dos dois é declarar a classe final. A alternativa é tornar todos os construtores privados ou privados do pacote e adicionar fábricas estáticas públicas no lugar dos construtores.

Eu diria que isso não aumenta a complexidade cognitiva da linguagem, uma vez que a palavra-chave abstrata já existe. No entanto, não posso falar sobre o impacto da implementação / desempenho disso e respeito absolutamente a proteção da linguagem desse ângulo. Acho que separar essas preocupações seria útil para decidir se implementamos ou não esse recurso.

Eu acredito que final seria um excelente acréscimo para fechar uma aula. Um caso de uso é que você pode ter muitos métodos públicos em sua classe, mas expor, por meio de uma interface, apenas um subconjunto deles. Você pode testar a unidade da implementação rapidamente, pois ela tem todos esses métodos públicos enquanto o consumidor real usa a interface para limitar o acesso a eles. Ser capaz de selar a implementação garantiria que ninguém estendesse a implementação ou mudasse os métodos públicos.

Você também pode garantir que ninguém herde sua classe. O TypeScript deve estar presente para impor essas regras, e a sugestão de comentar parece ser uma abordagem preguiçosa para resolver esse caso de uso. A outra resposta que li é sobre o uso de privado, que só é adequado para uma situação particular, mas não a que expliquei acima.

Como muitas pessoas neste tópico, eu votaria para poder selar a classe.

@mhegazy Construtores privados e classes seladas / finais têm semânticas muito diferentes. Claro, posso evitar a extensão definindo um construtor privado, mas também não posso chamar o construtor de fora da classe, o que significa que preciso definir uma função estática para permitir a criação de instâncias.

Pessoalmente, eu defendo que as verificações do compilador selado / final garantam que as classes e métodos marcados com selado / final não possam ser estendidos ou substituídos.

No contexto do meu problema, quero ser capaz de ter um construtor público para que possa instanciar o objeto, mas evitar a extensão da classe, e somente a adição de selado / final permitirá isso.

Existe uma tarefa - escrever um código que

  • opera objetos imutáveis
  • é livre de herança
  • métodos de reflexão grátis

E a palavra-chave final é necessária aqui, IMHO.

Vejo final como uma ótima maneira de lembrar a você e aos usuários do seu código quais métodos protegidos eles devem substituir e quais não devem. Como nota lateral, também sou um proponente de declarar explicitamente que você está substituindo, parcialmente para que o leitor saiba, mas também para que o transpiler reclame se você digitar o nome do método.

@zigszigsdk Visto que os métodos nunca são substituídos, como isso funcionaria?

Por final :
Da mesma forma que funciona agora, exceto que o transpiler reclamaria se alguém esconder o método de um super -que foi declarado final- do contexto this .

Por override :
Da mesma forma que funciona agora, exceto que o transpiler reclamaria se alguém declarar um método override , e seu super não tem um método com esse nome, ou tem um, mas é declarado como final .
Também poderia estar avisando se você ocultar o método de um super do contexto this e não substituir o estado.

Java e / ou C # usa a classe final para otimizar sua classe em tempo de execução, sabendo que ela não será especializada. Eu diria que esse é o principal valor para o suporte final.

@mhegazy Não exatamente! Outra função importante é ser explícito sobre quais partes da classe esperam ser substituídas.

Por exemplo, consulte o item 17 em "Java eficaz": "Projete e documente para herança ou proíba":

Não é incomum receber relatórios de bugs relacionados a subclasses após modificar as partes internas de uma classe concreta não final que não foi projetada e documentada para herança. A melhor solução para esse problema é proibir a criação de subclasses em classes que não foram projetadas e documentadas para serem subclasses com segurança. Existem duas maneiras de proibir a subclasse. O mais fácil dos dois é declarar a classe final.

O mesmo vale para os métodos finais, na minha opinião. É muito raro que uma classe seja projetada para dar suporte à substituição de qualquer um de seus métodos públicos. Isso exigiria um design muito complexo e uma grande quantidade de testes, porque você teria que pensar em todas as combinações possíveis de estados que podem resultar da combinação de comportamento não anulado e de anulação. Portanto, em vez disso, um programador pode declarar todos os métodos públicos finais, exceto um ou dois, o que diminuiria o número de tais combinações drasticamente. E na maioria das vezes, um ou dois métodos substituíveis são exatamente o que é necessário.

Eu acho que final classes e métodos são muito importantes. Espero que você o implemente.

Outro caso de usuário atraente @mhegazy . Hoje eu aprendi o Template Method Pattern e descobri que final é necessário para proibir as subclasses de alterar o método do template, como diz o Template Method Pattern na Wikipedia . Mas não posso fazer isso no meu adorável TypeScript. Que pena!

O padrão de modelo permite que as subclasses redefinam certas etapas de um algoritmo sem alterar a estrutura do algoritmo

Para mim, é tão simples quanto tentar impor a composição em casos de herança. Sem final, você não pode fazer isso.

Por mais que eu também queira ver final digitados, acho que a mensagem subjacente que a Microsoft está tentando nos enviar é que se quisermos final , devemos usar uma linguagem que suporta isso.

Eu gostaria de ver este problema reaberto e implementado.

Ao construir uma biblioteca que você vai usar internamente ou compartilhar publicamente (como no npm), você não quer que aqueles que a usam tenham que navegar pelo código e pesquisar comentários ou documentos para ver se a classe pode / pode não ser sobrescrito. Se eles o sobrescreverem, ocorre um erro.

Em meu código, tenho uma classe que é estendida e, quando ocorre algum tipo de evento, ela aciona um método. Se a subclasse definir o método, ela o fará, caso contrário, ele voltará ao padrão. No entanto, também existem outros métodos na classe que não são métodos "substitutos", eles são mais métodos auxiliares, como adicionar um item ao DOM, caso em que não quero que esses métodos sejam substituídos.

Meus 5 centavos sobre o assunto são que isso deve ser reaberto e implementado.

Eu o usaria para forçar clientes de bibliotecas a fazer suas próprias classes concretas e não estender de alguma outra já existente. Como disse @lucasyvas , favorece a composição. Ou apenas imponha uma nova implementação de classe concreta para fornecer à injeção de dependência no angular, por exemplo.

Eu também voto pela reabertura, com suporte para aulas e métodos

Não digo que não deveríamos. Só não tenho certeza de como o padrão da palavra-chave final funcionaria no contexto do texto datilografado?

a palavra-chave final não impediria os usuários / programador de substituir / herdar da referida classe / método?

Minha maneira de ver o Final é que ele "Congela" os dados, o que significa que não pode mais ser alterado. Quer dizer, isso pode ser bom de usar, embora possa ser realmente um incômodo para pessoas que querem consertar um "bug" ou mudar algum comportamento.

@niokasgami Freeze está relacionado a [dados] (https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/freeze)
e já disponível como parte do ES6. Final se aplica a classes e métodos (a variável pode ser somente leitura e, portanto, atuar como "final", pois não podemos substituir uma supervariável).

A palavra-chave "Final" é usada em JAVA, onde selado está em C # .

Como o texto digitado é fortemente inspirado no C #, deve haver uma probabilidade maior de que "selado" seja o escolhido. Agora, podemos possivelmente ter confusão com o selo ES6 que basicamente sela o objeto em tempo de execução?

@Xample , acho que você também pode usar final em variáveis ​​locais em Java. Lembro-me de ter que fazer isso quando estava escrevendo um aplicativo Android. Acho que isso exigiu que eu fizesse isso com um evento, mas não estou 100% certo.

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

@TheColorRed Bem, a variável local final em Java significa constante, não é?

@EternalPhane Acho que sim, sempre usei const em um escopo global como ... Nunca pensei em usá-lo dentro de uma função ou método ... Acho que o comentário anterior é vazio então ...

@Xample Sim, vejo seu ponto de selado a partir do C # não está funcionando da mesma maneira. Honestamente, na minha opinião AMBAS a palavra-chave selo e final não puderam realmente entrar na categoria, pois ambos podem ser confusos (selado por selo etc) e final, não temos certeza como aplicá-lo, pois em Java é usado como uma constante: /

Em java você pode final qualquer variável, método ou classe. Para variáveis, isso significa que as referências não podem ser reatribuídas (para tipos sem referência, isso significa que você também não pode alterar seus valores). Para classes, significa sem extensões ( extends ... palavra-chave). Para métodos, significa não substituir em subclasses.

Principais razões para final em java:

  • Reduza a imutabilidade das variáveis: para a segurança do thread
  • Finalize as classes para que não possam ser herdadas: força a composição sobre a herança porque a herança é inerentemente complicada quando suas subclasses começam inadvertidamente chamando super métodos
  • Evita erros simples de programação: se você não tem a intenção de alterar um valor, boas notícias, não é mais possível
  • Usado para o que a maioria das pessoas pensa quando imagina constantes: valores reutilizáveis ​​que você verá em todos os lugares, como em public static final String KEY = "someStringKeyPathHere" , útil para reduzir erros, mas tem um benefício adicional de tempo de compilação de não recriar vários objetos de string com o mesmo valor

Posso ter omitido alguns casos de uso, mas estava tentando mantê-los bem gerais e dar alguns exemplos.

@TheColorRed Android requer isso quando você declara uma variável fora de um retorno de chamada (classe anônima) e, em seguida, faz referência a ela dentro. Acho que isso se alinha com os problemas de segurança de thread novamente. Acho que eles estão tentando impedi-lo de reatribuir a variável de outro encadeamento.

Apesar de sua utilidade, não acho que veremos esse recurso chegando tão cedo. Pode ser um bom momento para olhar outras soluções ou, como no meu caso, eliminar todos os textos digitados e lidar com a negligência em JS. Javascript tem muitos "benefícios" por não ter nenhuma sugestão desse tipo ou qualquer coisa e, no final das contas, seu código será convertido para JS de qualquer maneira. Se você quiser compatibilidade com o navegador, pode escrever ES6 e usar o Babel para convertê-lo.

Eu acho que realisticamente, embora você possa dar um tapa no pulso por alguns desses benefícios, você não obtém nenhum dos benefícios reais do java-esque final uma vez que ele foi convertido para js.

@ hk0i Acho que entendi seu ponto, mas no geral concordo e meio que discordo ao mesmo tempo sobre sua opinião.

A maioria dos códigos em datilografado são, eu diria, para fins de depuração / rastreamento para ajudar o programador a fazer um código "limpo" (por favor, não me cite que eu sei que eles têm algum código espaguete datilografado aqui hahah)

a maior parte do recurso de texto digitado não é transposto na maioria das vezes quando convertido para JS porque eles simplesmente não existem em JS.

Embora quando você executa / escreve seu código, é útil ver erros em seu código e rastreá-lo mais facilmente do que com JS (e HECK JS pode ser difícil de rastrear bugs lol)

Então eu acho que o uso da palavra-chave final pode ser um bloqueador para o usuário da API saber: Ok, essa classe não deve ser estendida, pois sua extensão pode provocar um comportamento estranho, etc ...

Datilografado como uma boa linguagem, fico triste ao descobrir que falta algum recurso importante, como a palavra-chave "verdadeira" de classe estática, que impede as pessoas de chamar o construtor de classe, mas ainda assim serem capazes de acessá-lo.

@ hk0i Também não concordo, o propósito do

Contar com as soluções ES6 atuais adicionando uma verificação de mutabilidade em tempo de execução (dizendo usando Object.seal ou Object.freeze) faz com que você perca o benefício de ter erros diretamente ao digitar seu código (apesar de poder ser feito facilmente usando este decorador ).

@TheColorRed sim… e no

  • readonly para uma variável de classe
  • const : para qualquer outra variável

Nenhum deles seria preciso para método ou classe. Substituir um método não significa que o estamos redefinindo (pense em métodos virtuais em C ++), apenas definimos que um método será chamado antes do super (que podemos chamar usando super ).

@niokasgami talvez a nomenclatura não seja realmente um problema, pois ainda entenderemos que Object.seal se aplicava a ... um objeto enquanto selava uma classe e um método à própria estrutura.

TL; DR:

  • preventExtensions seria um bom nome explícito para evitar a extensão de classes
  • preventOverrides seria um bom nome de candidato para evitar a substituição de classes
    MAS: seal é amplamente utilizado em C #, é mais curto e tem o mesmo comportamento esperado.

A propósito, esqueci de mencionar que também há Object.preventExtensions()
As diferenças podem ser vistas aqui

lendo

  • sempre possível ... quem quer uma variável somente de gravação?

atualizando e excluindo

  • evita a redefinição de uma exclusão de variável: somente leitura para um membro da classe, const (para qualquer outro)
  • impeça a redefinição de um método: nada impede que você faça (dentro de um método de classe) this.aMethod = ()=>{} . Os métodos também deveriam ser readonly para prevenir tais hacks? o mesmo para delete this.aMethod

criando

  • só se aplica ao objeto ou, como estamos falando aqui, sobre estrutura: class como a classe definiu os membros do objeto, o texto tipificado implicitamente já impede a criação de novas propriedades em tempo real ... por exemplo this.unKnownProperty = "something" ts irá gerar um erro de tempo de compilação TS2339 conforme o esperado.

estendendo

  • só se aplica a classes, não estamos fazendo nada no próprio objeto ainda ... (ao contrário da criação que está em tempo de execução). É aqui que final ou seal seriam relevantes. A questão é como se manter consistente com o congelamento, lacre e preventExtensions do ES6? (se for necessário).
    Estender uma classe significa que evitamos criar outra classe desta classe. Podemos então lê-lo, mas não excluí-lo (quem exclui a classe de qualquer maneira). A questão não é a coluna "atualização". Uma classe final atualizável? O que é uma classe atualizável? Meu palpite é que sim ... mas então a atualização seria implementar essa classe (entendendo a interface da classe)? Implementar outra classe sempre deve ser possível ... mas não está relacionado à atualização da classe inicial. Bem ... talvez eles simplesmente tenham chegado às mesmas questões escrevendo c # e decidido que seal era a boa palavra-chave.

prevalecente

  • evita substituir o método atual em uma classe de extensão. Novamente, não estamos sobrescrevendo nada ainda, pois estamos falando sobre uma definição (método dentro de uma classe). Portanto, evite criar, permitir leitura, permitir atualização (poderíamos evitar a atualização do método ou excluí-lo usando somente leitura se também fosse aplicável a métodos). Melhor nomenclatura? preventOverrides ? ou novamente seal como é usado em C #

BÔNUS: Como selar um método geralmente evita que as pessoas substituam o supercódigo obrigatório, fiz a proposta de forçar as pessoas a chamar o super método (o mesmo comportamento do construtor, mas para qualquer método), não hesite em votar se você acho que seria útil.

Não me entenda mal, não sou contra isso. Eu sou muito a favor. Só estou tentando entender a mentalidade paradoxal da Microsoft de por que não deveria ser um recurso, o que basicamente quer dizer que, por não ser um recurso JavaScript, não pode ser um recurso TypeScript.

... Se estou entendendo bem.

@ hk0i Genéricos, somente leitura e muitos outros recursos não são recursos do JavaScript, mas ainda são adicionados ao TypeScript, então não acho que seja esse o motivo ...

O TypeScript apenas se esquiva de recursos que requerem a geração de código de uma maneira não direta. Genéricos, somente leitura, etc. são puramente noções de tempo de compilação que podem ser simplesmente apagadas para produzir a saída gerada, portanto, são um jogo justo.

final pode ser "apagado" em tempo de compilação, então por que isso não o torna um "jogo justo"?

🤷‍♂️

@TheColorRed acho isso mais ou menos isso ou eles têm outra prioridade para integrar.
Ou eles não têm certeza de quão previsível essa nova palavra-chave poderia funcionar em tempo de compilação / codificação. se você pensar bem, o design da palavra-chave final pode ser extremamente vago. final pode ser usado para muitos propósitos. Como você pode ver nas documentações Jsdoc, você pode usar a tag "@final" que informa ao interpretador jsdoc que essa variável está congelada, portanto, somente leitura.

aqui um choque de design. Readonly é aplicável na variável e getter, se bem me lembro qual "congelou" a variável e torná-la somente leitura e final torná-la final, portanto, somente leitura também.

isso pode causar confusão ao programador, que não tem certeza se deve marcar suas variáveis ​​como final ou somente leitura.

A MENOS que reservemos essa palavra-chave exclusivamente para declarações de classe e método, acho que o comportamento ou final entraria em conflito com somente leitura.

Eu acho que em vez de selado (que pode colidir com object.seal) ou final (que esta palavra-chave readonly de confronto de design de propósito) poderíamos ir com uma maneira mais "direta" de nomeá-lo e projetá-lo.

anotou isso meramente uma "ideia" de um comportamento que é semelhante, mas ao mesmo tempo diferente do comportamento do C #? Eu me inspirei em como o C # normalmente não pode ser invertido e, em seguida, você deve dizer que esse método é virtual.

`exemplo de namespace {

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{}

} `

Edit: Eu não incluí Variable and get desde readonly já existe.

@mhegazy Considere reabrir este problema. A resposta da comunidade parece ser esmagadoramente ao lado de adicionar uma palavra-chave final , como Java ( final ), C # ( sealed , readonly ), PHP ( final ), Scala ( final , sealed ) e C ++ ( final , virtual ) têm. Não consigo pensar em uma linguagem OOP com tipagem estática que não tenha esse recurso, e não ouvi um argumento convincente para explicar por que o TS não precisa dele.

@bcherny @mhegazy Eu concordo totalmente com o acima. Não consigo ver nenhuma razão bem argumentada pela qual final não pode ser apenas outra restrição de tempo de compilação, como a maioria do açúcar sintático que vemos no TypeScript - vamos enfrentá-lo, tipagem estática, genéricos, modificadores de acesso, aliases de tipo , interfaces ... TODO AÇÚCAR! Eu entendo que a equipe do TypeScript provavelmente quer se concentrar em levar a linguagem em outras direções, mas, falando sério, isso é OOP 101 ... isso deveria ter sido pensado há muito tempo, quando o TypeScript ainda era muito jovem!

@mhegazy, posso encaminhá-lo para um comentário anterior que você fez sobre esse problema.

uma classe com construtor privado não é extensível. considere usar isso.

No momento em que este comentário foi escrito, este comentário foi votado contra 21 vezes.
É claro que NÃO É

uau, recebi muitos comentários sobre isso. Não consigo entender porque não há interesse em implementar

Muitas reclamações realmente improdutivas acontecendo aqui. Por favor, não apareça apenas para expressar frustração - nós podemos tomar decisões aqui e a comunidade pode dar seu feedback, mas apenas ranger de dentes não vai mudar a opinião de ninguém. Por favor, seja específico e acionável; não seremos convencidos por pessoas que simplesmente aparecem para dizer FAÇA ESTE RECURSO AGORA .

Observe que esse problema é sobre classes finais / seladas . Tem havido uma discussão fora do tópico de métodos finais / selados

Alguns pontos foram considerados quando revisamos esta última vez

  • AS AULAS SÃO UM RECURSO JAVASCRIPT, NÃO UM RECURSO TYPESCRIPT . Se você realmente acha que as aulas são completamente inúteis sem final , isso é algo para discutir com o comitê TC39 ou discutir. Se ninguém quiser ou for capaz de convencer TC39 de que final é um recurso obrigatório, podemos considerar adicioná-lo ao TypeScript.
  • Se for legal invocar new em algo, isso tem uma correspondência de tempo de execução um-para-um com a herança disso. A noção de algo que pode ser new 'd, mas não super ' d simplesmente não existe em JavaScript
  • Estamos fazendo o possível para evitar transformar o TypeScript em uma sopa de palavras-chave OOP. Existem muitos mecanismos de composição melhores do que herança em JavaScript e não precisamos ter todas as palavras-chave que C # e Java têm.
  • Você pode escrever trivialmente uma verificação de tempo de execução para detectar a herança de uma classe "deveria ser final", o que você realmente deveria fazer de qualquer maneira se estiver "preocupado" com alguém acidentalmente herdando de sua classe, uma vez que nem todos os seus consumidores podem estar usando TypeScript .
  • Você pode escrever uma regra de lint para impor uma convenção estilística de que certas classes não são herdadas
  • O cenário de que alguém herdaria "acidentalmente" de sua classe deveria ser selada é meio estranho. Eu também argumentaria que a avaliação individual de alguém sobre se é ou não "possível" herdar de uma determinada classe é provavelmente bastante suspeita.
  • Existem basicamente três motivos para selar uma classe: segurança, desempenho e intenção. Dois deles não fazem sentido no TypeScript, então temos que considerar a complexidade versus o ganho de algo que está fazendo apenas 1/3 do trabalho que faz em outros tempos de execução.

Você está livre para discordar de tudo isso, mas traga alguns comentários acionáveis ​​e específicos sobre como a falta de final está prejudicando sua capacidade de usar classes JavaScript de maneira eficaz.

não ouvi um argumento convincente sobre por que o TS não precisa dele

Apenas um lembrete de que não é assim que funciona. Todos os recursos começam em -100 . Daquele post

Em algumas dessas perguntas, as perguntas perguntam por que “retiramos” ou “deixamos de fora” um recurso específico. Essa formulação implica que começamos com uma linguagem existente (C ++ e Java são as escolhas populares aqui) e, em seguida, começamos a remover recursos até chegarmos a um ponto que gostaríamos. E, embora possa ser difícil para alguns acreditar, não foi assim que a linguagem foi projetada.

Temos um orçamento de complexidade finita. Tudo o que fazemos torna as próximas coisas 0,1% mais lentas. Quando você solicita um recurso, está solicitando que todos os outros recursos se tornem um pouco mais difíceis, um pouco mais lentos, um pouco menos possíveis. Nada atrapalha aqui porque o Java o tem ou porque o C # o tem ou porque seriam apenas algumas linhas de código ou você pode adicionar uma opção de linha de comando . Os recursos precisam pagar por si próprios e por todo o seu fardo no futuro.

@RyanCavanaugh Embora eu não tenha certeza se concordo com a decisão, agradeço a resposta realmente atenciosa e detalhada e por você ter

@RyanCavanaugh

Não acho que o ponto aqui, neste fórum, seja .. "usar classes JavaScript de maneira eficaz."

Desculpe, não resisto, e apenas acrescento às "reclamações improdutivas que acontecem aqui": a palavra-chave final é útil por todos os motivos explicados, em detalhes, e com argumentos de outros.

Estamos fazendo o possível para evitar transformar o TypeScript em uma sopa de palavras-chave OOP. Existem muitos mecanismos de composição melhores do que herança em JavaScript ...

Como quando você usa final para impor a composição sobre a herança em (insira o nome da linguagem não datilografada aqui) 😈
(Desculpe, não pude resistir)

Mas, indo direto ao ponto, parece que o TS pretende ser principalmente uma camada de compatibilidade para apresentar o "JavaScript de hoje" ou algo nesse sentido (a la Babel), com a exceção de que adiciona alguma verificação de tipo adicional.

Acho que a essência do contra-argumento para implementar isso é que, se você quiser os recursos de outra linguagem, use a outra linguagem. Se você quiser JavaScript, use JavaScript. Se você quiser TypeScript, use-o.

Eu sei que o Kotlin pode compilar para JS e tem muitos recursos que eu acho que as pessoas aqui iriam gostar, então isso é algo que pode ser útil de se olhar. Eu acho que adiciona alguma biblioteca JS como uma dependência embora (eu não tentei sozinho).

A principal lição é que, se você não está satisfeito com o TypeScript, não o use.

@RyanCavanaugh

Nos pontos que você levantou acima ...

AS AULAS SÃO UM RECURSO JAVASCRIPT, NÃO UM RECURSO TYPESCRIPT . Se você realmente acha que as aulas são completamente inúteis sem final , isso é algo para discutir com o comitê TC39 ou discutir. Se ninguém quiser ou for capaz de convencer TC39 de que final é um recurso obrigatório, podemos considerar adicioná-lo ao TypeScript.

Tenho seguido o TypeScript desde 0.7 e, naquela época, o IIRC tinha como alvo o ES3-ES5. As aulas eram definitivamente um recurso do TypeScript naquela época. O mesmo argumento se aplica aos decoradores - eles estão nos cartões para ES, mas já estão aqui no TypeScript como um recurso experimental.

Como uma comunidade, não estamos argumentando que o compilador TypeScript deve de alguma forma emitir uma classe JavaScript que é final . Uma verificação em tempo de compilação seria suficiente, da mesma forma que verificações em tempo de compilação para modificadores de acesso são suficientes; afinal, eles também não são recursos de JavaScript, mas o TypeScript ainda os torna possíveis.

Se final se tornou um recurso ES, então presumivelmente você poderia refatorar aquele pedaço do emissor ao direcionar ES * para produzir final apropriadamente (da mesma forma que você fez para as classes, quando elas foram introduzidas em ES6)?

Se for legal invocar new em algo, isso tem uma correspondência de tempo de execução um-para-um com a herança disso. A noção de algo que pode ser new 'd, mas não super ' d simplesmente não existe em JavaScript

Novamente, eu não acho que a comunidade está esperando que você invente algo aqui para impor final em tempo de execução. Como você afirmou com precisão, "não existe no JavaScript", mas como mencionado, uma restrição de tempo de compilação seria suficiente.

Estamos fazendo o possível para evitar transformar o TypeScript em uma sopa de palavras-chave OOP. Existem muitos mecanismos de composição melhores do que herança em JavaScript e não precisamos ter todas as palavras-chave que C # e Java têm.

É uma frase bastante conhecida na comunidade de desenvolvimento de software como um todo: "Favorecer a composição em vez da herança", no entanto, "favorecer" não significa não usar a herança como uma declaração geral. Claro, você não _precisa_ de todas as palavras-chave que C # e Java possuem, mas deve ter uma justificativa para permitir abstract classes (que, a propósito, "não existe em JavaScript"), então por que não final classes?

Você pode escrever trivialmente uma verificação de tempo de execução para detectar a herança de uma classe "deveria ser final", o que você realmente deveria fazer de qualquer maneira se estiver "preocupado" com alguém acidentalmente herdando de sua classe, uma vez que nem todos os seus consumidores podem estar usando TypeScript .

O mesmo poderia ser dito para C # e Java, mas não preciso de verificações de tempo de execução porque tenho sealed e final para fazer isso por mim no nível do compilador.

Se nem todos os meus consumidores estivessem usando TypeScript, eu teria muito mais com que me preocupar do que apenas herdar de uma classe final ; por exemplo, acesso a private ou protected membros, nenhuma verificação de tipo estático ou a capacidade de construir uma classe abstract .

Nosso código realmente deveria estar repleto de verificações de tempo de execução para todas essas coisas (algumas das quais nem mesmo são possíveis, devo acrescentar) apenas para garantir que nossos consumidores não-TypeScript sejam os mais seguros possíveis?

Você pode escrever uma regra de lint para impor uma convenção estilística de que certas classes não são herdadas

Isso implicaria que quem está consumindo seu código está executando o linter, então ainda não é uma solução adequada; é uma solução alternativa.

O cenário de que alguém herdaria "acidentalmente" de sua classe deveria ser selada é meio estranho. Eu também argumentaria que a avaliação individual de alguém sobre se é ou não "possível" herdar de uma determinada classe é provavelmente bastante suspeita.

Bons / ótimos desenvolvedores pensam sobre sua lógica para fazer algo, como estender uma aula. Qualquer coisa menos que isso e você acaba com uma mentalidade de "mas funciona", sem realmente entender por quê.

Fazer uma pergunta que começa com posso geralmente tende a ter uma resposta mais definitiva do que perguntas que começam com deveria .

Existem basicamente três motivos para selar uma classe: segurança, desempenho e intenção. Dois deles não fazem sentido no TypeScript, então temos que considerar a complexidade versus o ganho de algo que está fazendo apenas 1/3 do trabalho que faz em outros tempos de execução.

Sem dúvida, eu diria que o TypeScript realmente atenderia a dois desses motivos: segurança e intenção, no entanto, se isso foi implementado puramente como uma verificação de tempo de compilação, então ele poderia fazer isso no nível do compilador, em vez de, como você corretamente apontou , no nível de tempo de execução.

Não acho que não ter final esteja prejudicando minha capacidade de usar classes JavaScript de maneira eficaz, nem as classes JavaScript não podem ser utilizadas sem ele. Acho que final é mais um recurso "bom ter" do que um recurso "necessário", mas o mesmo pode ser dito para muitos dos recursos do TypeScript.

Acho que a principal queixa aqui é que o TypeScript nos permite criar classes abstract e _open_, mas em termos de herança e polimorfismo não nos permite pintar um quadro completo de uma forma elegante e expressiva.

Ocultar o construtor não é o que a comunidade deseja porque isso tira o valor semântico e expressivo das implementações de classe e, mais uma vez, porque os modificadores de acesso são "legais de se ter", isso não pode nem mesmo ser aplicado em tempo de execução; essencialmente uma situação "não existe em JavaScript".

Como desenvolvedor, tenho muitos chapéus; no momento eu tenho um chapéu C #, um chapéu Kotlin e um chapéu TypeScript.

  • Com meu chapéu Kotlin colocado, não preciso me preocupar com final porque as aulas são finais por padrão.
  • Com meu chapéu C # colocado, aplico sealed como uma questão de hábito, forçando meus consumidores a perguntar posso em vez de perguntar devo . Na maioria das vezes, as classes C # deveriam ser sealed e costumam ser esquecidas.
  • Com meu chapéu do TypeScript colocado, tenho que esconder o construtor como uma solução alternativa, porque na minha opinião (e na comunidade), a linguagem carece de algo útil.

Sem interesse, quando a equipe do TypeScript sentou-se ao redor de uma mesa e discutiu as classes abstract , alguém levantou a mão e disse "mas e quanto a final "?

@RyanCavanaugh Hum, eu entendo seus pontos de vista

Acho que na maioria das vezes foi um mal-entendido (acho que pelo que explicamos)

@ series0ne resumiu muito bem que nossas intenções não complicaram muito a saída do

Eu acho que seria bom ter esse recurso: sim, é bom ter uma maneira mais rápida de fazer alguma técnica.

É VERDADEIRAMENTE necessário? Na verdade, isso não poderia ser algo considerado secundário e não deveria ser uma prioridade se a equipe do Typescript puder implementar esse extra, então seria bom ter.

Eu vejo que a palavra-chave final / selada pode complicar MUITO alguns aspectos, especialmente: correção de bug, patch, mudança de comportamento.

se eu explicar isso, eu poderia ver um uso totalmente abusivo do final ao bloquear TODA a classe de ser estendida. Provocando a IMO uma grande dor de cabeça. Não quero usar uma API onde estou bloqueado para fazer qualquer alteração na classe de usuário por herança porque eles eram paranóicos por causa da "segurança".

Isso significaria que eu teria que usar o protótipo e depois o alias ou substituir essa classe apenas para poder alterar o comportamento dessa classe para atender às minhas necessidades.
isso seria irritante, especialmente se eu só quiser adicionar funcionalidade extra à referida classe e não quiser ir com um fluxo de trabalho destrutivo.

Depois disso, só pensei que posso entender mal as situações agora, então fique à vontade para me explicar :)

Ah, se bem me lembro, eu estava lendo muito sobre o ES4 (não tenho certeza se ainda era uma criança quando o draft estava em produção lol) e, surpreendentemente, é muito parecido com o Typescript.

e é dito no rascunho das especificações aqui:
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 } }

então, tecnicamente, a palavra-chave final foi planejada para a 4ª edição do Ecmascript. Veremos isso nas integrações futuras de JS Eu duvido, mas eles realmente integraram a classe, então isso pode ser possível, mas quem sabe?

Tenho um caso de uso que mal encontrei e que me levou a encontrar esse problema. Ele gira em torno do uso de this como um tipo de retorno:

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

Minha intenção ao escrever isso foi garantir que TileEntities sejam imutáveis ​​(e, portanto, seguras para transmissão), mas que qualquer TileEntity dado pudesse ser clonado com algumas propriedades alteradas (neste caso x e y ) sem precisar saber nada sobre a forma da subclasse. Isso é o que eu quero escrever, mas tentar escrever uma implementação (corretamente) faz com que isso seja interrompido.

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

O problema é que, sem ser capaz de declarar que _nunca_ haverá uma subclasse de TurretTileEntity, o tipo de retorno this não pode ser reduzido apenas ao tipo TurretTileEntity.

O impacto dessa deficiência no texto datilografado é mínimo, considerando que você pode converter o valor de retorno em <any> . Isso é, objetivamente, um antipadrão e uma indicação útil de que o sistema de tipos poderia ser mais expressivo. Aulas finais / seladas devem ser apoiadas.

Sem interesse, quando a equipe do TypeScript sentou-se ao redor de uma mesa e discutiu as classes abstratas, alguém levantou a mão e disse "mas e quanto ao final"?

sim. Cada vez que adicionamos uma palavra-chave, "mas então teremos que adicionar essa outra palavra-chave também!" é um ponto forte contra adicioná-lo, porque queremos desenvolver a linguagem o mais cuidadosamente possível. final em declarações de classe também está neste campo - se tivermos final classes, então "precisaremos" de final métodos ou propriedades também. Qualquer coisa que se pareça com aumento de escopo é considerado com extrema suspeita.

Acho que as aulas e os métodos finais são muito importantes. Espero que você o implemente.

Observe que este problema é sobre classes finais / seladas. Tem havido uma discussão fora do tópico de métodos finais / selados, que são um conceito completamente diferente.

mhegazy fechou https://github.com/Microsoft/TypeScript/issues/9264 em favor deste problema. Existe um problema separado que não foi resolvido como um truque para rastrear métodos finais / selados?

@crcla Ao ler alguns dos comentários de

Não posso acreditar que a proposta de final seja rotulada como Won't fix .

Mude o rótulo de volta para In Discussion e implemente-o, por favor!

No meu caso, quero escrever uma classe base abstrata para os plug-ins e quero ter certeza de que a classe do plug-in não pode sobrescrever meus métodos de classe base por engano. Nenhum dos private constructor , Object.seal pode fazer este trabalho.

Para aqueles que veem isso como um recurso crucial, viva ou morra: realmente não é. Se sua API consiste em expor uma implementação de classe que precisa ser estendida pelo consumidor, existem maneiras melhores de fazer isso. Não siga o mau exemplo de React. Basta usar funções e objetos simples e deixar esse absurdo OO onde ele pertence: no passado.

Agora traga os votos negativos 😀

Alguém poderia me explicar como essa discussão não faz distinção entre aulas finais e métodos finais? É claro que impedir que alguns métodos se sobreponham agrega um valor enorme. Quero fornecer a possibilidade de um desenvolvedor de plug-in adicionar funcionalidade criando subclasses de algumas das minhas classes de frameworks e implementando funções abstratas - mas, ao mesmo tempo, certifique-se de que ela não substitua (por que razão!) Os métodos que chamam essas funções abstratas (e certifique-se de que o tratamento / loggin / verificação de erros ocorra.

@pelotom Se você não quiser usar construções e padrões OOP, simplesmente não faça isso, ninguém está lhe dizendo para fazer o contrário, entretanto o mesmo deve se aplicar na direção oposta: se alguém quiser usar construções OOP, deixe-o.
Ao forçar os outros a adotar um padrão específico (não OOP), você está adotando o mesmo comportamento daquele pelo qual denuncia OOP. Pelo menos seja consistente :)

Adicionar uma palavra-chave final ao TypeScript não é uma alteração importante e não influenciará nenhum dos seus projetos existentes, no entanto, será um recurso vital para pessoas como @thorek , eu e muitos outros.

se alguém quiser usar construções OOP, deixe-o.

Não estamos debatendo se as pessoas devem usar construções OOP, estamos debatendo se construções OOP atualmente inexistentes devem ser adicionadas à linguagem. Fazer isso leva o tempo do desenvolvedor que poderia ser gasto em outros recursos e torna a linguagem mais complexa, o que diminui a taxa de recursos futuros (porque para cada novo recurso, deve-se considerar como ele irá interagir com cada recurso antigo). Portanto, certamente me afeta, porque prefiro gastar o orçamento de complexidade e desenvolvimento do TypeScript em coisas mais úteis!

Fwiw, provavelmente vamos incentivar nossos clientes (google) a usar @final nos comentários para que se propague para o compilador de encerramento: \

Adicionar _final_ em classes e métodos é útil quando uma equipe de P&D cria bibliotecas de TS que outras equipes podem usar. Essas palavras-chave são (uma parte importante) da estabilidade da exposição do serviço de tais ferramentas.
A palavra-chave é mais importante para grandes equipes e menos importante para pequenos projetos, imho.

@dimiVergos Este é exatamente o meu caso, e presumo o mesmo para muitos outros. Posso imaginar que pareço tendencioso devido à minha posição, mas parece o único (embora altamente) uso justificável desta palavra-chave de acordo com minha experiência.

Estou aberto a correções neste!

Eu me preocupo mais com o suporte de métodos finais (para evitar que as subclasses os substituam acidentalmente) do que com as classes finais. É o bilhete certo para votar nisso? Ticket https://github.com/Microsoft/TypeScript/issues/9264 parece sugerir isso.

Então, como eu voto em "métodos finais"? Ou eu apenas votei com este comentário? Além disso, como adicionamos "métodos" ao título? Posso apenas editá-lo? O título atual é enganosamente limitado apenas às classes, mas a discussão também inclui métodos.

Como muitos outros, gostaria de ver os métodos finais no TypeScript. Usei a seguinte abordagem como uma solução alternativa parcial que não é imune a falhas, mas pelo menos ajuda um pouco:

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 { }
}

Aqui está outro caso de uso do mundo real para métodos final . Considere a seguinte extensão de React.Component para facilitar a implementação de um Contexto simples.

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>
    );
  }
}

Sem final no método render() , nada impede que alguém sobrescreva incorretamente o método, quebrando assim tudo.

Olá, como muitos outros, gostaria da palavra-chave "final" ou "selada" pelo menos para métodos. Eu concordo totalmente que esta palavra-chave é realmente um valor agregado, pois permite que o desenvolvedor evite que seu cliente librairies estenda blocos de código cruciais (como no plugin, por exemplo).

A única solução alternativa que encontrei é usar o decorador de método como:
function sealed(target, key,descriptor){ descriptor.writable = false; // Prevent method to be overriden }

em seguida, em sua classe "base", use o decorador para evitar a substituição do método, por exemplo:

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

public OtherMethod(){}

...
}

Dessa forma, 'MySealedMethod' não poderia (facilmente) ser sobrescrito na subclasse. Exemplo:

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

na verdade, a única desvantagem dessa solução alternativa é que o "lacrado" só é visível em tempo de execução (erro no console javascript), mas NÃO em tempo de compilação (como as propriedades do método são definidas por meio da função, eu acho)

@RyanCavanaugh

sim. Cada vez que adicionamos uma palavra-chave, "mas então teremos que adicionar essa outra palavra-chave também!" é um ponto forte contra adicioná-lo, porque queremos desenvolver a linguagem o mais cuidadosamente possível.

Parece-me que você está começando este argumento com sua mente contra o recurso. Um recurso popular não deve ser contra considerar tal recurso. Deve ser uma questão de se perguntar _por que é popular_ e, então, considerar cuidadosamente.

Tenho certeza de que você discutiu isso e os argumentos foram lançados no sentido de considerá-lo ou contra ele. O argumento contra isso é apenas o fato de ser um recurso creep ou há algo mais específico? Você considera isso uma característica marginal? Em caso afirmativo, podemos fazer algo para mudar sua mente?

Deixe-me apenas deixar essas questões aqui para que todos nós reflitamos.

Existe uma maneira de selar uma classe _por projeto_ usando construções atuais?
Se o recurso fosse adicionado, isso nos permitiria fazer algo novo? (Como em algo que as construções presentes não permitem)
Se o recurso nos permitiu fazer algo novo, é algo valioso?
A adição de classes seladas aumentaria muito a complexidade do idioma?
Adicionar classes seladas aumentaria muito a complexidade do compilador?

Para o propósito de discutir isso, não estou incluindo métodos selados na discussão e também não estou incluindo verificações de tempo de execução na implementação. Todos nós entendemos que não podemos impor isso em Javascript.

Para sua informação: Eu seria incrível se o contra-ataque a esse recurso fosse um pouco mais do que apenas declarações gerais contra o recurso-creep.

@asimonf Acho que tudo o que você disse já foi abordado por este comentário (você precisará expandir a seção "mostrar mais" para vê-lo) e a seguinte.

O que mudaria nossa opinião aqui? Pessoas aparecendo com exemplos de código que não funcionaram como deveriam porque a palavra-chave sealed estava faltando .

O cenário aqui que a palavra-chave tenta abordar é aquele em que alguém herda erroneamente de uma classe. Como você herda erroneamente de uma classe? Não é um erro fácil de cometer. As classes JavaScript são inerentemente frágeis o suficiente para que qualquer subclasse deve entender os requisitos comportamentais de sua classe base, o que significa escrever alguma forma de documentação, e a primeira linha dessa documentação pode ser simplesmente "Não criar subclasses para esta classe". Ou pode haver uma verificação de tempo de execução. Ou o construtor da classe pode ser escondido atrás de um método de fábrica. Ou qualquer outra quantidade de opções.

Por que o TypeScript deve ter essa palavra-chave quando a situação já é aquela em que você deveria ter encontrado pelo menos dois roadblocks separados dizendo para você não estar lá para começar?

Eu tenho programado por aproximadamente uma eternidade anos agora e nunca tentei criar uma subclasse de algo apenas para descobrir que estava selado e eu não deveria ter tentado. Não é porque eu sou um super gênio! É um erro extremamente incomum de se cometer e, em JavaScript, essas situações já são aquelas em que você precisa primeiro fazer perguntas à classe base. Então, que problema está sendo resolvido ?

Um caso real que eu usaria o final é evitar a substituição de um método por algo que nunca chamaria o método super. Algum método público, é claro. Mas a solução real que descobri seria a capacidade de forçar qualquer sub método a chamar o super. https://github.com/Microsoft/TypeScript/issues/21388

Como não sou um designer de linguagem, não posso falar sobre as desvantagens de adicionar uma palavra-chave à linguagem. Direi que estou trabalhando atualmente em uma superclasse abstrata que tem um método que pretendo ser final, mas para minha consternação, descobri que não poderia fazê-lo. Por enquanto, irei com a abordagem nada ideal de fazer um comentário, mas os membros da equipe que estão implementando a aula podem não ler esse comentário.

Eu acho que acima há muitos exemplos excelentes de quando / onde final seria não apenas útil, mas forneceria _valor real_ e _segurança real_ para a plataforma que está sendo construída. Não é diferente para mim. Como eu disse, não sei muito sobre complexidades de design de linguagem, mas esse é claramente um recurso que as pessoas querem / usarão / Não consigo ver uma maneira de abusar dele ou que geraria um código ruim. Eu entendo o conceito de temer o aumento do escopo, mas os desenvolvedores de TS não precisam implementar tudo o que a comunidade deseja.

Apenas para adicionar minha parte ao debate, eu gostaria de colocar meu polegar no lado sim da escala porque esta questão está repleta de bons argumentos que apóiam a adição do recurso, mas os argumentos contra que vejo são:

  • Isso pode levar ao aumento do escopo (isso é gerenciável)
  • As pessoas vão pedir outros recursos (ok, mas quem se importa, isso faz parte do design de uma linguagem e não é o precedente legal que estamos estabelecendo)
  • Isso poderia reduzir a eficiência (imagino que, como a equipe de TS é cheia de valentões, você provavelmente encontrará uma maneira de tornar isso gerenciável)
  • Comentários opinativos do tipo "Não use conceitos OOP"

Nenhuma das opções acima parecem ameaças credíveis ao próprio ecossistema TS, nem ao código que é produzido a partir dele; todos eles parecem políticos (exceto talvez o de eficiência, mas tenho grande confiança em vocês, então não estou realmente preocupado com isso, além de que uma pequena diminuição de eficiência valeria a pena não explodir componentes importantes / construções de biblioteca por erros de herança involuntários) .

Espero que seja construtivo! Amo o TS e quero vê-lo melhorando!

👍

--editar--

Em resposta a @RyanCavanaugh :

So what problem is being solved?

O problema a ser resolvido, creio, foi delineado por muitos, muitos comentários acima. Não quero dizer isso como um insulto, mas essa pergunta faz parecer, para mim, que você não está realmente prestando atenção aos argumentos apresentados e que está decidido a "não". Não quero dizer desrespeito com isso, é só que, honestamente, esse tópico está cheio de exemplos de problemas que isso resolveria.

De alguma forma, perdi esse comentário , que descreve muito bem uma séria preocupação em adicionar o recurso e que faz sentido para mim. Novamente, eu não sei a complexidade de adicioná-lo, mas questões de eficiência à parte (uma vez que não cabe aos desenvolvedores se preocupar com a eficiência do TS, esse é o trabalho da equipe de TS), não parece haver nenhum bom argumento contra o final (na minha mais humilde opinião).

A maior parte da discussão recente foi centrada nos métodos finais (menos nas aulas finais). Métodos finais são certamente meu principal interesse. Acabei de notar que se você expandir o link "este comentário" no post anterior de @RyanCavanaugh , ele considera a discussão do método final fora do tópico. No entanto, @mhegazy sugeriu que este tíquete é o lugar certo (ao fechar # 9264). Portanto, como um usuário final humilde, estou perplexo sobre onde devo pedir / votar os métodos finais. A orientação seria apreciada.

@cbutterfield
A contradição é algo que também percebi, mas não apontei em meu post por medo de reclamar (na verdade encontrei ESTE tópico por meio do # 9264 e vim aqui sob a orientação de
@RyanCavanaugh
@mhegazy
De acordo com o comentário acima, onde _é_ o lugar correto para discutir os métodos finais, porque na verdade é o que estou mais interessado.

Olhando para trás em meu comentário fortemente rejeitado (certifique-se de adicionar seu 👎 se você ainda não o fez!), Fico feliz em informar que React _já não está mais definindo um mau exemplo ao forçar uma API baseada em class em seus usuários! Com a proposta do Hooks, a equipe React recobrou o juízo e adotou uma abordagem funcional radicalmente mais simples. Com isso, o último motivo para muitos desenvolvedores terem que usar classes evaporou, e está tudo pronto. Vou reiterar: tudo o que você acha que precisa das aulas pode ser feito melhor sem elas .

O futuro de JS e TS não tem classes, louvado seja!

@cbutterfield Notei isso também, e se o problema # 9264 foi fechado para ser discutido neste problema, acho que o título deveria mudar para Suggestion: Final keyword for classes and methods , caso contrário, as pessoas que procuram métodos finais (exclusivamente) podem não adicionar uma reação em a primeira postagem.

Citando @mhegazy :
Java e / ou C # usa a classe final para otimizar sua classe em tempo de execução, sabendo que ela não será especializada. Eu diria que esse é o principal valor para o suporte final. No TypeScript, não há nada que possamos oferecer para fazer seu código funcionar melhor do que sem o final.

Eu discordo fundamentalmente. Desenvolvi em Java por muitos anos em muitas equipes e nunca usamos classes ou métodos finais para otimizações de tempo de execução; na prática, é principalmente um idioma de design OOP. Às vezes, eu simplesmente não quero que minha classe ou método seja estendido / substituído por subclasses externas, seja para proteger e garantir uma função, ou para limitar o ruído da API de uma biblioteca ao essencial, ou para proteger os usuários da API de interpretar mal uma API complexa.

Sim, esses problemas podem ser resolvidos adicionando comentários ou interfaces, mas final é uma solução elegante para reduzir a API visível às funções necessárias quando tudo o que você deseja é uma classe simples com uma API extensível limpa.

Embora este problema tenha quase 3 anos, realmente deveríamos ter uma palavra-chave final pois já existe uma palavra-chave abstract .

Lembre-se de que não queremos forçar as pessoas a usá-lo e é um recurso tão útil quanto a palavra-chave abstrata. Mas aqui está outro caso de uso que mostrará fortemente os benefícios de uma palavra-chave 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

Resultado após a compilação:

toto
tata

Portanto, neste código, temos uma classe abstrata A que possui métodos e propriedades abstratos, queremos que a classe herdada os defina, mas gostaríamos de não permitir que nenhuma outra classe substituísse essas implementações.
O melhor que poderíamos fazer seria manter a palavra-chave protected , mas, como você pode ver, ainda podemos redefinir os atributos.

E daí se avançarmos no processo de compilação do Typescript e usarmos readonly para proteger nossos atributos (e fingir que nossos métodos são propriedades)?

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.
  };
}

(Observe que o código é compilado com tsc, sem erros durante a compilação ou execução)
Portanto, agora temos dois problemas:

  • Não protegemos nossos atributos B , então precisamos de uma maneira de prevenir qualquer herança inesperada.
  • Agora sabemos que podemos substituir quaisquer readonly propriedades estendendo sua classe, e SIM, Typescript sabe que é a propriedade pai que estamos alterando para usar private vez de protected na classe C apresentará um erro, pois não é o mesmo tipo de acesso / visibilidade.

No momento, poderíamos usar decoradores ( selados como mostra a documentação oficial do Typescript, ou um decorador final ), mas lembre-se de que isso só é útil em tempo de execução e que devemos evitar isso no processo de compilação.

Vou verificar se há um problema com a "violação de segurança" (se podemos chamar assim) quando queremos substituir um valor somente leitura protegido e realmente podemos e não devemos. Caso contrário, vou abrir uma questão para debater sobre a verificação da palavra-chave readonly entre as palavras-chave private , protected com herança.

tl; dr: A palavra-chave final resolveria dois problemas (embora a verificação readonly seja um bom começo para evitar qualquer reescrita não autorizada)

Três anos depois ... é tão ridículo.

Para provar a necessidade desse recurso, sugiro dar uma olhada no que foi feito em outras línguas:

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

Portanto, vemos que em todas as linguagens OOP mais populares, esse recurso existe porque vem da lógica de herança da OOP.

Também devo citar o líder Dev do TS dizendo

se tivermos classes finais, então "precisaremos" de métodos ou propriedades finais também.

Não tenho certeza se é irônico, mas em JS a palavra-chave somente leitura é usada para propriedades (e métodos, se você trapacear), então esse é um ponto muito estúpido.

E talvez não deixar um cara encerrar o problema quando ele tem +40 votos negativos, significando que a comunidade discorda totalmente dele, seria um bom conselho para evitar mais situações idiotas.

Se você não estiver interessado em reforçar o recurso principal do Typescript, diga em voz alta para que as notícias de tecnologia possam retransmitir isso no Twitter, porque caso contrário, é apenas um zumbido ruim escondido. Você está apenas ignorando sua comunidade E não tem nenhum processo para fazer com que a comunidade valide o que precisamos ou não.

talvez não deixar um cara encerrar o problema quando ele tem +40 votos negativos, o que significa que a comunidade discorda totalmente dele, seria um bom conselho para evitar outras situações idiotas

Eu não sou "um cara". Quando eu tomo decisões aqui, é o reflexo de todo o processo de tomada de decisão da equipe do TypeScript. A equipe de design analisou esse problema várias vezes e, a cada vez, chegou à mesma conclusão. Você pode discordar desse resultado, mas o processo não está em debate.

você não tem nenhum processo para fazer a comunidade validar o que precisamos ou não.

Este é o processo, bem aqui: você chamando meu resumo de nosso raciocínio de "idiota" e eu (e outras pessoas da equipe) lendo-o. Estamos ouvindo o feedback.

Eu sei que apontar outros recursos implementados como razões para considerar este é um cheiro e não realmente um argumento, mas acho irônico que um mecanismo de aliasing de tipo tenha sido considerado e adicionado (pode muito bem ser incrível, mas certamente poderia viver sem ele ), mas algo assim é rejeitado.

No final, posso viver sem esse recurso, mas o mesmo poderia ser dito sobre cerca de metade dos recursos que o TS tem. Então, qual seria um motivo apropriado para considerar a implementação disso?

O GitHub oculta inutilmente muitos comentários aqui, então estou postando várias respostas (mais algumas adições) que demos no passado. Adicionando alguns pensamentos abaixo também.


Alguns pontos foram considerados quando revisamos esta última vez

  • AS AULAS SÃO UM RECURSO JAVASCRIPT, NÃO UM RECURSO TYPESCRIPT . Se você realmente acha que as aulas são completamente inúteis sem final , isso é algo para discutir com o comitê TC39 ou discutir. Se ninguém quiser ou for capaz de convencer TC39 de que final é um recurso obrigatório, podemos considerar adicioná-lo ao TypeScript.
  • Se for legal invocar new em algo, isso tem uma correspondência de tempo de execução um-para-um com a herança disso. A noção de algo que pode ser new 'd, mas não super ' d simplesmente não existe em JavaScript
  • Estamos fazendo o possível para evitar transformar o TypeScript em uma sopa de palavras-chave OOP. Existem muitos mecanismos de composição melhores do que herança em JavaScript e não precisamos ter todas as palavras-chave que C # e Java têm.
  • Você pode escrever trivialmente uma verificação de tempo de execução para detectar a herança de uma classe "deveria ser final", o que você realmente deveria fazer de qualquer maneira se estiver "preocupado" com alguém acidentalmente herdando de sua classe, uma vez que nem todos os seus consumidores podem estar usando TypeScript .
  • Você pode escrever uma regra de lint para impor uma convenção estilística de que certas classes não são herdadas
  • O cenário de que alguém herdaria "acidentalmente" de sua classe deveria ser selada é meio estranho. Eu também argumentaria que a avaliação individual de alguém sobre se é ou não "possível" herdar de uma determinada classe é provavelmente bastante suspeita.
  • Existem basicamente três motivos para selar uma classe: segurança, desempenho e intenção. Dois deles não fazem sentido no TypeScript, então temos que considerar a complexidade versus o ganho de algo que está fazendo apenas 1/3 do trabalho que faz em outros tempos de execução.

não ouvi um argumento convincente sobre por que o TS não precisa dele

Apenas um lembrete de que não é assim que funciona. Todos os recursos começam em -100 . Daquele post

Em algumas dessas perguntas, as perguntas perguntam por que “retiramos” ou “deixamos de fora” um recurso específico. Essa formulação implica que começamos com uma linguagem existente (C ++ e Java são as escolhas populares aqui) e, em seguida, começamos a remover recursos até chegarmos a um ponto que gostaríamos. E, embora possa ser difícil para alguns acreditar, não foi assim que a linguagem foi projetada.

Temos um orçamento de complexidade finita. Tudo o que fazemos torna as próximas coisas 0,1% mais lentas. Quando você solicita um recurso, está solicitando que todos os outros recursos se tornem um pouco mais difíceis, um pouco mais lentos, um pouco menos possíveis. Nada atrapalha aqui porque o Java o tem ou porque o C # o tem ou porque seriam apenas algumas linhas de código ou você pode adicionar uma opção de linha de comando . Os recursos precisam pagar por si próprios e por todo o seu fardo no futuro.


O que mudaria nossa opinião aqui? Pessoas aparecendo com exemplos de código que não funcionaram como deveriam porque a palavra-chave final estava faltando .

O cenário aqui que a palavra-chave tenta abordar é aquele em que alguém herda erroneamente de uma classe. Como você herda erroneamente de uma classe? Não é um erro fácil de cometer. As classes JavaScript são inerentemente frágeis o suficiente para que qualquer subclasse deve entender os requisitos comportamentais de sua classe base, o que significa escrever alguma forma de documentação, e a primeira linha dessa documentação pode ser simplesmente "Não criar subclasses para esta classe". Ou pode haver uma verificação de tempo de execução. Ou o construtor da classe pode ser escondido atrás de um método de fábrica. Ou qualquer outra quantidade de opções.

Em outras palavras: o único processo correto para herdar de uma classe em JavaScript sempre começa com a leitura da documentação dessa classe . Existem muitas restrições que a classe base pode ter para simplesmente derivar e começar a codificar. Se, na primeira etapa , você já deveria saber que não deve tentar, que valor a imposição estática fornece na etapa três?

Por que o TypeScript

Eu tenho programado por aproximadamente uma eternidade anos agora e nunca tentei criar uma subclasse de algo apenas para descobrir que estava selado e eu não deveria ter tentado. Não é porque eu sou um super gênio! É um erro extremamente incomum de se cometer e, em JavaScript, essas situações já são aquelas em que você precisa primeiro fazer perguntas à classe base. Então, que problema está sendo resolvido ?

Se você não estiver interessado em reforçar o recurso principal do Typescript, diga em voz alta para que as notícias de tecnologia possam transmitir isso no Twitter

Somos muito explícitos sobre como projetamos a linguagem ; a não-meta número um responde exatamente a essa sugestão.


Minha reação pessoal depois de ler os muitos comentários aqui é que há um forte efeito de polarização devido a construções que existem em outras línguas. Se você apenas abordar isso de uma perspectiva de folha em branco, existem dezenas de comportamentos que você pode imaginar, dos quais o TypeScript tem apenas um pequeno subconjunto.

Você poderia facilmente imaginar alguma palavra-chave que foi aplicada a um método e obrigou os métodos derivados a chamá-la por meio de super . Se C # e Java tivessem essa palavra-chave, as pessoas com certeza aplicariam essa palavra-chave em lugares onde fizesse sentido. Na verdade, pode ser muito mais comumente aplicado do que final , porque é impossível aplicar uma verificação de tempo de execução e é um aspecto muito mais sutil do contrato derivado de base do que "classes derivadas podem não existir " Seria útil de várias maneiras que final não seria. Eu preferiria muito mais essa palavra-chave do que esta (embora eu ache que nenhuma delas atenda à barra de complexidade x valor).

Então, qual seria um motivo apropriado para considerar a implementação disso?

Quando olhamos para o feedback, as considerações fortes se parecem com isto:

  • Não consigo expressar adequadamente o comportamento desta biblioteca JavaScript
  • Meu código compilou, mas realmente não deveria, porque tinha este erro (sutil)
  • A intenção deste código não é comunicada de forma adequada pelo sistema de tipo

final acerta estes, mas o mesmo aconteceria com um modificador que diz "Esta função não pode ser chamada duas vezes seguidas" ou "A construção desta classe não termina realmente até o próximo tique assíncrono". Nem tudo que se pode imaginar deve existir.

Nunca vimos feedback como "Passei horas depurando porque estava tentando herdar de uma classe que não era realmente subclassível" - alguém dizendo que acionaria corretamente um 'wat' porque não é realmente imaginável. Mesmo se o modificador existisse, ele não ajudaria muito na situação, porque as bibliotecas não documentam se as classes devem ser finais - por exemplo, é fs.FSwatcher final ? Parece que mesmo os autores do nó não sabem. Então final é suficiente se os autores sabem que é final , mas isso será documentado independentemente , e a falta de final realmente não diz nada porque é frequente simplesmente não é conhecido de qualquer maneira.

Li todo o bloco e entendi a declaração da equipe sobre a escolha dos recursos, só voltarei em alguns pontos dos meus comentários

Eu não sou "um cara". Quando eu tomo decisões aqui, é o reflexo de todo o processo de tomada de decisão da equipe do TypeScript.

Desculpe, eu estava me referindo a mhegazy e ao feedback massivo que ele recebeu da comunidade que usa o Typescript.

Se você realmente acha que as aulas são completamente inúteis sem o final, isso é algo para discutir com o comitê TC39 ou discutir. Se ninguém quiser ou for capaz de convencer TC39 de que o final é um recurso obrigatório, podemos considerar adicioná-lo ao TypeScript.

Não acho que final seja o primeiro passo, pois já existe uma proposta para a palavra-chave private https://github.com/tc39/proposal-private-methods

Eu sou cético sobre You should a comment to say *dont do this* , é como dizer em um formulário Hey don't write down specific characters , você codificou por quase sempre anos, então você deve conhecer a regra n ° 1 de um desenvolvedor: Nunca confie no usuário ( e o dev que usará seu código)

Não estou dizendo que devemos parar absolutamente tudo e colocar um bilhão de dólares para implementar a palavra-chave final, porque os casos de uso são muito baixos para serem tão eficientes, também considere que dei poucos exemplos em que o limite é de TS e JS e que se as pessoas escolherem o TS, é para evitar erros em tempo de compilação, não em tempo de execução. Se quisermos que as coisas explodam em tempo de execução, podemos usar JS e parar de nos preocupar com TS, mas esse não é o ponto, porque existe um caso de uso definitivo que mostra como eu uso a palavra-chave final : Eu quero bloquear um método, não quero que ninguém o substitua.

E como o Javascript é limitado por isso, a comunidade pensou que o Typecript poderia ir além desse limite, é por isso que esse problema está em andamento há 3 anos, é por isso que as pessoas ainda estão se perguntando por que esse recurso não está aqui, e é por isso que queremos que o compilador faça a verificação manual de uma classe e / ou método.

Você não esperou que o JS tivesse métodos públicos / privados para implementá-los, embora haja na verdade propostas para incluí-los com a palavra-chave # (menos prolixo do que public / private ), Eu sei que você queria criar uma linguagem perfeita porque perfeição não é quando você não pode adicionar nada mais, é quando você não pode remover mais nada.

Se você puder encontrar uma solução para evitar que um método seja sobrescrito no processo de compilação (e não no processo de execução, já que o ponto não seria válido), fique à vontade.

Tentei mostrar a fraqueza da situação (em métodos / propriedades e não em classes), pois qualquer desenvolvedor de TS pode reescrever qualquer biblioteca que quiser, quebrando o que quiser, talvez seja essa a beleza da coisa, eu acho.

A propósito, obrigado pela resposta, sem ódio ou mau comportamento intencionado contra a equipe de desenvolvimento ou você.

Todos os pontos válidos! Mas, por favor, vamos dividir a discussão entre

a) Apoio às aulas finais
b) Métodos finais de suporte

Enquanto todos os seus argumentos têm como alvo a) - nenhum tem como alvo b.

Ter os métodos finais é crucial, como foi apontado várias vezes na discussão. A única resposta que ouvi até agora foi „não faça OOP“ - mas isso não é nada que eu (e muitos outros) aceitaria.

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

O GitHub oculta inutilmente muitos comentários aqui, então estou postando várias respostas (mais algumas adições) que demos no passado. Adicionando alguns pensamentos abaixo também.

Alguns pontos foram considerados quando revisamos esta última vez

AS AULAS SÃO UM RECURSO JAVASCRIPT, NÃO UM RECURSO TYPESCRIPT. Se você realmente acha que as aulas são completamente inúteis sem o final, isso é algo para discutir com o comitê TC39 ou discutir. Se ninguém quiser ou for capaz de convencer TC39 de que o final é um recurso obrigatório, podemos considerar adicioná-lo ao TypeScript.
Se for legal invocar algo novo, isso tem uma correspondência de tempo de execução um para um com a herança dele. A noção de algo que pode ser novo, mas não superado simplesmente não existe em JavaScript
Estamos fazendo o possível para evitar transformar o TypeScript em uma sopa de palavras-chave OOP. Existem muitos mecanismos de composição melhores do que herança em JavaScript e não precisamos ter todas as palavras-chave que C # e Java têm.
Você pode escrever trivialmente uma verificação de tempo de execução para detectar a herança de uma classe "deveria ser final", o que você realmente deveria fazer de qualquer maneira se estiver "preocupado" com alguém acidentalmente herdando de sua classe, uma vez que nem todos os seus consumidores podem estar usando TypeScript .
Você pode escrever uma regra de lint para impor uma convenção estilística de que certas classes não são herdadas
O cenário de que alguém herdaria "acidentalmente" de sua classe deveria ser selada é meio estranho. Eu também argumentaria que a avaliação individual de alguém sobre se é ou não "possível" herdar de uma determinada classe é provavelmente bastante suspeita.
Existem basicamente três motivos para selar uma classe: segurança, desempenho e intenção. Dois deles não fazem sentido no TypeScript, então temos que considerar a complexidade versus o ganho de algo que está fazendo apenas 1/3 do trabalho que faz em outros tempos de execução.
não ouvi um argumento convincente sobre por que o TS não precisa dele

Apenas um lembrete de que não é assim que funciona. Todos os recursos começam em -100 https://blogs.msdn.microsoft.com/ericgu/2004/01/12/minus-100-points/ . Daquele post

Em algumas dessas perguntas, as perguntas perguntam por que “retiramos” ou “deixamos de fora” um recurso específico. Essa formulação implica que começamos com uma linguagem existente (C ++ e Java são as escolhas populares aqui) e, em seguida, começamos a remover recursos até chegarmos a um ponto que gostaríamos. E, embora possa ser difícil para alguns acreditar, não foi assim que a linguagem foi projetada.

Temos um orçamento de complexidade finita. Tudo o que fazemos torna as próximas coisas 0,1% mais lentas. Quando você solicita um recurso, está solicitando que todos os outros recursos se tornem um pouco mais difíceis, um pouco mais lentos, um pouco menos possíveis. Nada atrapalha aqui porque o Java o tem ou porque o C # o tem ou porque seriam apenas algumas linhas de código ou você pode adicionar uma opção de linha de comando. Os recursos precisam pagar por si próprios e por todo o seu fardo no futuro.

O que mudaria nossa opinião aqui? Pessoas aparecendo com exemplos de código que não funcionou da maneira que deveria porque a palavra-chave final estava faltando.

O cenário aqui que a palavra-chave tenta abordar é aquele em que alguém herda erroneamente de uma classe. Como você herda erroneamente de uma classe? Não é um erro fácil de cometer. As classes JavaScript são inerentemente frágeis o suficiente para que qualquer subclasse deve entender os requisitos comportamentais de sua classe base, o que significa escrever alguma forma de documentação, e a primeira linha dessa documentação pode ser simplesmente "Não criar subclasses para esta classe". Ou pode haver uma verificação de tempo de execução. Ou o construtor da classe pode ser escondido atrás de um método de fábrica. Ou qualquer outra quantidade de opções.

Em outras palavras: o único processo correto para herdar de uma classe em JavaScript sempre começa com a leitura da documentação dessa classe. Existem muitas restrições que a classe base pode ter para simplesmente derivar e começar a codificar. Se, na primeira etapa, você já deveria saber que não deve tentar, que valor a imposição estática fornece na etapa três?

Por que o TypeScript deve ter essa palavra-chave quando a situação já é aquela em que você deveria ter encontrado pelo menos dois roadblocks separados dizendo para você não estar lá para começar?

Eu tenho programado por aproximadamente uma eternidade anos agora e nunca tentei criar uma subclasse de algo apenas para descobrir que estava selado e eu não deveria ter tentado. Não é porque eu sou um super gênio! É um erro extremamente incomum de se cometer e, em JavaScript, essas situações já são aquelas em que você precisa primeiro fazer perguntas à classe base. Então, que problema está sendo resolvido?

Se você não estiver interessado em reforçar o recurso principal do Typescript, diga em voz alta para que as notícias de tecnologia possam transmitir isso no Twitter

Somos muito explícitos sobre como projetamos a linguagem https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals ; a não-meta número um responde exatamente a essa sugestão.

Minha reação pessoal depois de ler os muitos comentários aqui é que há um forte efeito de polarização devido a construções que existem em outras línguas. Se você apenas abordar isso de uma perspectiva de folha em branco, existem dezenas de comportamentos que você pode imaginar, dos quais o TypeScript tem apenas um pequeno subconjunto.

Você pode facilmente imaginar alguma palavra-chave que foi aplicada a um método e obrigou os métodos derivados a chamá-la por meio de super https://github.com/Microsoft/TypeScript/issues/21388 . Se C # e Java tivessem essa palavra-chave, as pessoas com certeza aplicariam essa palavra-chave em lugares onde fizesse sentido. Na verdade, seria muito mais comumente aplicado do que final, porque é impossível aplicar uma verificação de tempo de execução e é um aspecto muito mais sutil do contrato derivado de base do que "classes derivadas podem não existir". Seria útil de várias maneiras que a final não seria. Eu preferiria muito mais essa palavra-chave do que esta (embora eu ache que nenhuma delas atenda à barra de complexidade x valor).

Então, qual seria um motivo apropriado para considerar a implementação disso?

Quando olhamos para o feedback, as considerações fortes se parecem com isto:

Não consigo expressar adequadamente o comportamento desta biblioteca JavaScript
Meu código compilou, mas realmente não deveria, porque tinha este erro (sutil)
A intenção deste código não é comunicada de forma adequada pelo sistema de tipo
o final acerta estes, mas o mesmo aconteceria com um modificador que diz "Esta função não pode ser chamada duas vezes seguidas" ou "A construção desta classe não termina realmente até o próximo tique assíncrono". Nem tudo que se pode imaginar deve existir.

Nunca vimos feedback como "Passei horas depurando porque estava tentando herdar de uma classe que não era realmente subclassível" - alguém dizendo que acionaria corretamente um 'wat' porque não é realmente imaginável. Mesmo se o modificador existisse, não ajudaria muito na situação, porque as bibliotecas não documentam se as classes devem ser finais - por exemplo, fs.FSwatcher https://nodejs.org/api/fs.html#fs_class_fs_fswatcher final ? Parece que mesmo os autores do nó não sabem. Portanto, o final é suficiente se os autores sabem que é o final, mas isso será documentado de qualquer maneira, e a falta do final não diz nada, porque muitas vezes simplesmente não é conhecido de qualquer maneira.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub https://github.com/Microsoft/TypeScript/issues/8306#issuecomment-458268725 ou desative o thread https://github.com/notifications/unsubscribe-auth/AA3NA4o9wu54CcJyK1C8WHVjXIQwfms8GIPHM8A3A3JX8A3A3A3A3A3 .

Sim, a discussão é tão difusa agora. Gostaria de ver esses problemas em um rastreador onde você possa votar, como o usado para problemas do IntelliJ / webstorm. Obtenha alguns números reais de quantas pessoas gostariam de ver final para classes e / ou métodos.

final seria muito útil

Uma palavra-chave abstrata é usada para lembrar aos programadores que eles devem sobrescrever o campo. Uma palavra-chave para lembrá-los de não substituí-la será útil de maneira semelhante.

Os métodos finais podem ser cruciais para tornar o código mais estruturado e / ou seguro.

Por exemplo, digamos que você tenha um aplicativo compatível com plug-ins de terceiros. Você pode forçar todos os plug-ins a estender uma classe abstrata que implementa uma interface para quaisquer métodos que DEVEM criar e contém alguns métodos finais que controlam a ordem das operações e permitem que você implemente ganchos entre as operações. Por causa disso, seu aplicativo não precisa estar ciente das especificações de nenhum plug-in individual e ainda estar ciente de alguns de seus estados internos.

Sem os métodos finais, eles podem interferir nas funções que controlam a ordem das operações; potencialmente causando um bug, exploração ou outro comportamento inesperado em seu aplicativo. Embora a maioria das situações tenha algum tipo de solução alternativa, essas soluções alternativas podem ser complicadas, repetitivas e, às vezes, não confiáveis; enquanto os métodos finais tornam uma solução simples quando o método é definido.

Além disso, embora este exemplo seja específico para plug-ins, ele também se aplica a outras situações. (novo dev on team, garantindo que certa lógica não fique fora do escopo, padronizando como o código é executado, garantindo que os métodos baseados em segurança não sejam alterados, etc. todos poderiam se beneficiar disso)

@RyanCavanaugh Embora haja um bom exemplo com render () querendo final, ninguém mencionou o princípio aberto-fechado (agora adicionado em segundo lugar como parte de SOLID).
Para escrever estruturas que outros usarão, isso seria útil.
Se não, qual é a abordagem preferida para adotar a abordagem aberto-fechado no Typescript?

Eu AMARIA final para métodos (e suponho que propriedades e declarações em geral). Está causando problemas reais - as pessoas continuam substituindo coisas acidentalmente, sem perceber que havia um por baixo ... quebrando coisas ...

Eu também acho que os métodos finais seriam de grande valor e, como outros apontaram, todos os argumentos contra parecem se concentrar nas classes finais. Havia um problema separado (# 9264), mas foi encerrado porque, aparentemente, esse problema o cobre, mas eu realmente não acho que cobre. Podemos reabrir esse problema ou resolvê-lo adequadamente aqui?

Minha situação é semelhante a @ 0815fox, onde tenho uma versão "interna" de um método, que é abstrata, mas quero que a versão "original" do método nunca seja substituída, pois contém um comportamento importante e não quero uma subclasse para substituir aquele.

Acho desconcertante que, não importa o apoio esmagador da comunidade em favor desse recurso, ele ainda está sendo recusado pelos desenvolvedores do TypeScript ...

talvez uma solução temporária seria adicionar um padrão ao TS-Doc onde seria aconselhado que alguém não herde uma classe particular ou substitua um método particular. Como uma anotação @final ou algo semelhante.

Bem, o que devo acrescentar ao que todos os outros, votando no "final", disseram ... Aqui está o meu voto a favor.

Concordou. Claro, você pode usar readonly com métodos se for tratá-los como uma propriedade do tipo função, mas essa solução alternativa pode ser muito confusa. Seria bom ter uma solução nativa para este problema.

Acho que final , especialmente final method é importante na verificação de tipo, pois geralmente controla o fluxo de trabalho dos métodos, que garantem a ordem do método chamado no padrão de modelo ou outros padrões de design.

final deve ser o caminho a percorrer

Gangue - como este tíquete está (a) fechado e (b) tem um título enganoso para as discussões de "método final", decidi criar um novo tíquete focado exclusivamente nos métodos finais. Esperançosamente, isso será mais difícil de ignorar e pode até estimular algum progresso. Sim, consulte https://github.com/microsoft/TypeScript/issues/33446

@RyanCavanaugh

complexidade vs barra de valor

Você poderia explicar o que há de tão complexo em final ? Parece-me que seria uma das palavras-chave mais fáceis de implementar, especialmente considerando que palavras-chave como abstract já foram implementadas e muito da lógica / código poderia ser reutilizado a partir disso.

@vinayluzrao

Apenas como um exemplo aleatório, atualmente dizemos que você pode herdar de qualquer coisa que tenha uma assinatura de construção:

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

Obviamente, uma classe final é new -able:

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

Mas agora há uma contradição: temos uma coisa que pode ser new -able, mas não é legal incluir uma cláusula extends . Portanto, um nível de indireção derrota 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);

Isso já engana as pessoas em abstract e as pessoas estão constantemente argumentando que as classes abstract deveriam ter alguma assinatura de construção implícita que existe para fins de verificação de digitação, mas não é realmente invocável ou algo assim.

Para sua informação, 90% das vezes, quando dizemos "complexidade" nessas discussões, queremos dizer complexidade conceitual conforme experimentada pelos usuários do TypeScript , não complexidade de implementação de nossa parte como desenvolvedores do produto. O último é algo que pode ser tratado (tipos condicionais e tipos mapeados são extraordinariamente complexos de uma perspectiva de implementação, por exemplo); complexidade conceitual não é algo com o qual você possa lidar colocando mais engenheiros mais inteligentes no produto. Cada vez que adicionamos algo à linguagem que causa um comportamento inesperado ou difícil de explicar, é um fardo mental para todos os usuários da linguagem.

Eu só preciso dos métodos finais, porque em uma base relativamente regular, alguém sobrescreve (sem saber) um método por baixo, portanto, meu código não funciona e as coisas quebram de maneiras muito estranhas e as pessoas não sabem o porquê, e geralmente leva muito tempo para depurar.

@RyanCavanaugh isso realmente esclarece, obrigado. Porém, eu gostaria de lançar um pensamento: parece-me que o problema no exemplo que você deu surge devido ao forte acoplamento entre new e extends , talvez esse seja o problema maior , e não final si. Eu me pergunto por que essa decisão de acoplamento foi tomada e quão difícil seria desfazê-la (de forma alguma sugerindo que deveria ser desfeita) neste ponto.

Não tenho certeza se essa pode ser uma discussão muito tangencial para se ter aqui, no entanto.

Este é um problema que encontrei. Esse exemplo de código simples não pode ser compilado.
final palavra-chave PERFEITAMENTE :

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

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

O tipo 'Filho' não pode ser atribuído ao tipo 'isso'.
'Child' pode ser atribuído à restrição do tipo 'this', mas 'this' pode ser instanciado com um subtipo diferente de restrição 'Child'.

Parque infantil

isso pode ser reaberto, por favor? é óbvio que há uma necessidade desse recurso

Já discutimos esse assunto uma dúzia de vezes em várias reuniões e todas as vezes decidimos não fazê-lo. Reabrir isso para discutir pela décima terceira vez não é um bom uso do nosso tempo.

Você pode discordar das decisões da equipe do TypeScript, o que é bom, mas não podemos passar o dia todo andando em círculos, re-re-re-re-re-re-decidindo decisões das quais as pessoas discordam. Existem literalmente milhares de outras sugestões neste repositório que merecem consideração inicial antes que este seja apresentado novamente.

Discussões construtivas como o comentário de @ nicky1038 são muito bem-vindas aqui; Solicito que as pessoas não façam ping continuamente neste tópico em busca de notícias / pedidos de reconsideração, para que não tenhamos que bloqueá-lo.

Estou aqui para oferecer suporte à adição de métodos finais também. Eu tenho uma classe base com uma função chamada _init_ que não quero que as subclasses possam substituir. A final seria a solução perfeita.

Também apoio a adição da palavra-chave final para métodos.
Hoje em dia é um padrão em programação orientada a objetos.

Adicionar final faz sentido para mim. Especialmente ao construir bibliotecas / frameworks para outros usarem, onde você não quer nem mesmo permitir o risco de sobrescrever uma função.

Se não me engano, adicionar final pode ajudar a evitar problemas com 'myObj' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'MyClass' erros ao trabalhar com genéricos porque eu poderia fazer MyClass final e, portanto, garantir isso não são quaisquer outros subtipos.
Não tenho 100% de certeza, mas acho que é assim que você pode resolver esse problema em Java.

Temos o mesmo material, estamos fazendo um componente base no angular que muitos outros pacotes podem ou precisam estender como base.
Nisso temos coisas como ngOnChanges ou mesmo ngOnInit de angular que apenas a classe base deve implementar e as subclasses não devem sobrescrever isso, mas realmente implementar o ngOnInitReal (ou como chamamos), então estamos totalmente no controle quando isso é chamado e com o que.
Como não podemos controlar isso, só podemos fazer isso por meio de documentação. Se um fabricante de componentes não ler isso, seu componente pode quebrar ou precisa copiar uma grande quantidade de código de nossa base para fazer a mesma coisa.

Portanto, sem os métodos finais, não podemos realmente fazer um contrato de API sólido como uma rocha.

alguém mencionou

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

alguém mencionou

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

Embora você possa fazer isso, existem algumas diferenças. Por exemplo, cada instância da classe terá sua própria cópia da função, ao contrário de apenas o protótipo ter a função que todos usam.

Esta página foi útil?
0 / 5 - 0 avaliações