Typescript: Suporte a palavra-chave de substituição em métodos de classe

Criado em 10 fev. 2015  ·  210Comentários  ·  Fonte: microsoft/TypeScript

(Atualização por @RyanCavanuagh)

Por favor, veja este comentário antes de pedir "qualquer atualização", "por favor, adicione isso agora", etc.


(OBSERVAÇÃO, isso _não_ é uma duplicata da edição nº 1524. A proposta aqui é mais ao longo das linhas do especificador de substituição C++, o que faz muito mais sentido para o texto datilografado)

Uma palavra-chave override seria imensamente útil em texto datilografado. Seria uma palavra-chave opcional em qualquer método que substitua um método de superclasse e, semelhante ao especificador de substituição em C++, indicaria uma intenção de que "_o nome+assinatura deste método deve sempre corresponder ao nome+assinatura de um método de superclasse_" . Isso captura toda uma gama de problemas em bases de código maiores que, de outra forma, podem ser facilmente perdidas.

Novamente semelhante ao C++, _não é um erro omitir a palavra-chave override de um método substituído_. Nesse caso, o compilador apenas age exatamente como faz atualmente e ignora as verificações extras de tempo de compilação associadas à palavra-chave override. Isso permite os cenários de javascript não tipados mais complexos em que a substituição da classe derivada não corresponde exatamente à assinatura da classe base.

Exemplo de uso

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

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

Exemplo de condições de erro de compilação

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

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

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

IntelliSense

Além da validação de tempo de compilação adicional, a palavra-chave override fornece um mecanismo para que o typescript intellisense exiba e selecione facilmente os super métodos disponíveis, onde a intenção é substituir especificamente um deles em uma classe derivada. Atualmente, isso é muito desajeitado e envolve navegar pela cadeia de superclasses, encontrar o método que você deseja substituir e, em seguida, copiá-lo e colá-lo na classe derivada para garantir que as assinaturas correspondam.

Mecanismo IntelliSense proposto

Dentro de uma declaração de classe:

  1. substituição de tipo
  2. Uma lista suspensa de preenchimento automático aparece de todos os super métodos para a classe
  3. Um método é selecionado no menu suspenso
  4. A assinatura do método é emitida na declaração da classe após a palavra-chave override.
Add a Flag Revisit Suggestion

Comentários muito úteis

Por favor, desculpe a lamentação, mas honestamente, enquanto seu argumento se aplica à palavra-chave public em um idioma onde tudo é público por padrão, realmente dividindo o mundo, tendo coisas como abstract e um override opcional A palavra-chave

A substituição é um dos poucos aspectos da linguagem com alta sensibilidade a erros de digitação, porque um nome de método de substituição digitado incorretamente não é um problema óbvio de tempo de compilação. O benefício de override é óbvio, pois você permite que você declare sua intenção de substituir - se o método base não existir, é um erro de tempo de compilação. Todos saúdam o sistema de tipos. Por que alguém não iria querer isso?

Todos 210 comentários

De fato, uma boa proposta.

No entanto, e os exemplos a seguir, que são substituições válidas para mim:

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

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

Além disso, eu adicionaria um sinalizador do compilador para forçar a presença da palavra-chave override (ou relatada como um aviso).
O motivo é pegar ao renomear um método em uma classe base que as classes herdadas já implementam (mas não deve ser uma substituição).

Ah belos exemplos. De um modo geral, eu esperaria o uso da palavra-chave override para impor a correspondência _exata_ de assinaturas, pois o objetivo de usá-la é manter uma hierarquia de classes de tipos estrita. Então, para abordar seus exemplos:

  1. Adicionando um parâmetro padrão adicional. Isso geraria um erro de compilação: Snake super não define move(meters:number):void. Embora o método derivado seja funcionalmente consistente, o código do cliente que chama Animal.move pode não esperar que as classes derivadas também sejam fatoradas em altura (já que a API base não a expõe).
  2. Isso geraria (e sempre deveria) gerar um erro de compilação, pois não é funcionalmente consistente. Considere a seguinte adição ao exemplo:
class C extends A {...}
var animal : Animal = new Snake();
animal.setA(new C());
// This will have undefined run-time behavior, as C will be interpreted as type B in Snake.setA

Assim, o exemplo (2.) é na verdade uma ótima demonstração de como uma palavra-chave override pode capturar casos extremos sutis em tempo de compilação que, de outra forma, seriam perdidos! :)

E gostaria de enfatizar novamente que ambos os exemplos podem ser válidos em cenários específicos de javascript controlados/avançados que podem ser necessários... neste caso, os usuários podem optar por omitir a palavra-chave override.

Isso será útil. Atualmente, contornamos isso incluindo uma referência fictícia ao super método:

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

Mas isso apenas protege contra o segundo caso: super métodos sendo renomeados. Alterações na assinatura não acionam um erro de compilação. Além disso, este é claramente um hack.

Também não acho que os parâmetros padrão e opcionais na assinatura do método da classe derivada devam acionar um erro de compilação. Isso pode estar correto, mas vai contra a flexibilidade inerente do JavaScript.

@rwyborn
Parece que não esperamos o mesmo comportamento.
Você usaria essa palavra-chave override para garantir uma mesma assinatura, enquanto eu a usaria mais como uma opção de legibilidade (portanto, minha solicitação para adicionar uma opção de compilador para forçar seu uso).
Na verdade, o que eu realmente esperaria é que o TS detecte métodos de substituição inválidos (mesmo sem o uso de substituição).
Tipicamente:

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

deve gerar um erro, porque é realmente uma substituição de Animal.move() (comportamento JS), mas incompatível (porque a altura não deve ser opcional, enquanto será indefinida se chamada de uma "referência" Animal).
Na verdade, usar override apenas confirmaria (pelo compilador) que esse método realmente existe na classe base (e, portanto, com uma assinatura compatível, mas devido ao ponto anterior, não devido à palavra-chave override).

@stephanedr , falando como um único usuário, eu realmente concordo com você que o compilador deve sempre confirmar a assinatura, pois eu pessoalmente gosto de impor uma digitação estrita dentro das minhas hierarquias de classe (mesmo que o javascript não !!).

No entanto, ao propor que esse comportamento é opcional por meio da palavra-chave override, estou tentando ter em mente que, em última análise, o javascript não é tipado e, portanto, impor a correspondência estrita de assinatura por padrão resultaria em alguns padrões de design javascript não sendo mais exprimíveis no Typescript.

@rwyborn Fico feliz que você tenha mencionado a implementação do C++ porque é exatamente assim que imaginei que deveria funcionar antes de chegar aqui - opcionalmente. Embora, um sinalizador do compilador que forçasse o uso da palavra-chave override cairia bem no meu livro.

A palavra-chave permitiria erros de tempo de compilação para uma digitação desajeitada de desenvolvedores, que é o que mais me preocupa sobre substituições em sua forma atual.

class Base {
    protected commitState() : void {

    }
}


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

    }
}

Atualmente (a partir de 1.4) a classe Implementation acima apenas declararia um novo método e o desenvolvedor não seria mais sábio até perceber que seu código não está funcionando.

Discutido na revisão de sugestões.

Nós definitivamente entendemos os casos de uso aqui. O problema é que adicioná-lo neste estágio na linguagem adiciona mais confusão do que remove. Uma classe com 5 métodos, dos quais 3 são marcados override , não implicaria que os outros 2 _não são_ sobrescritos. Para justificar sua existência, o modificador realmente precisaria dividir o mundo de forma mais limpa do que isso.

Por favor, desculpe a lamentação, mas honestamente, enquanto seu argumento se aplica à palavra-chave public em um idioma onde tudo é público por padrão, realmente dividindo o mundo, tendo coisas como abstract e um override opcional A palavra-chave

A substituição é um dos poucos aspectos da linguagem com alta sensibilidade a erros de digitação, porque um nome de método de substituição digitado incorretamente não é um problema óbvio de tempo de compilação. O benefício de override é óbvio, pois você permite que você declare sua intenção de substituir - se o método base não existir, é um erro de tempo de compilação. Todos saúdam o sistema de tipos. Por que alguém não iria querer isso?

Eu concordo 100% com @hdachev , a pequena inconsistência referida também por @RyanCavanaugh é facilmente superada pelos benefícios da palavra-chave em trazer verificações de tempo de compilação para substituições de métodos. Mais uma vez, gostaria de salientar que C++ usa uma palavra-chave de substituição opcional com êxito exatamente da mesma maneira sugerida para o texto datilografado.

Eu não posso enfatizar o suficiente quanta diferença a verificação de substituição faz em uma base de código de grande escala com árvores OO complexas.

Finalmente, eu acrescentaria que, se a inconsistência de uma palavra-chave opcional for realmente uma preocupação, a abordagem C# pode ser usada, ou seja, o uso obrigatório das palavras-chave "new" ou "override":

class Dervied extends Base {

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

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

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

Isso não é análogo a public porque uma propriedade sem um modificador de acesso é conhecida por ser pública; um método sem override é _not_ conhecido por não ser sobrescrito.

Aqui está uma solução verificada em tempo de execução usando decoradores (que vem no TS1.5) que produz boas mensagens de erro com muito pouca sobrecarga:

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

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

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

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

A execução deste código produz um erro:

Erro: o método notAnOverride de Derived não substitui nenhum método de classe base

Como esse código é executado no momento da inicialização da classe, você nem precisa de testes de unidade específicos para os métodos em questão; o erro acontecerá assim que seu código for carregado. Você também pode usar uma versão "rápida" de override que não faz nenhuma verificação para implantações de produção.

@RyanCavanaugh Então, estamos no Typescript 1.6 e os decoradores ainda são um recurso experimental, não algo que eu queira implantar em uma base de código de produção em larga escala como um hack para que a substituição funcione.

Para tentar outro ângulo, todas as linguagens digitadas populares suportam a palavra-chave "override"; Swift, ActionScript, C#, C++ e F# para citar alguns. Todos esses idiomas compartilham os problemas menores que você expressou neste tópico sobre substituição, mas claramente há um grande grupo por aí que vê que os benefícios da substituição pesam muito sobre esses problemas menores.

Suas objeções são puramente baseadas em custo/benefício? Se eu realmente fosse em frente e implementasse isso em um PR, isso seria aceito?

Não é apenas uma questão de custo/benefício. Como Ryan explicou, o problema é que marcar um método como uma substituição não implica que outro método _não seja_ uma substituição. A única maneira que faria sentido seria se todas as substituições precisassem ser marcadas com uma palavra-chave override (o que, se formos obrigados, seria uma alteração importante).

@DanielRosenwasser Como descrito acima, em C++ a palavra-chave override é opcional (exatamente como proposto para Typescript), mas todos a usam sem problemas e é extremamente útil em grandes bases de código. Além disso, no Typescript, na verdade, faz muito sentido que seja opcional por causa da sobrecarga da função javascript.

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

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

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

Quanto ao argumento "não implica que outro método não seja uma substituição", é exatamente análogo ao "privado". Você pode escrever uma base de código inteira sem nunca usar a palavra-chave privada. Algumas das variáveis ​​nessa base de código só serão acessadas de forma privada e tudo será compilado e funcionará bem. No entanto, "privado" é algum açúcar sintático extra que você pode usar para dizer ao compilador "Não, realmente, erro de compilação se alguém tentar acessar isso". Da mesma forma, "sobrecarga" é um açúcar sintático extra para dizer ao compilador "quero que isso corresponda exatamente à declaração da classe base. Se não compilar erro".

Quer saber, acho que vocês estão fixados na interpretação literal de "substituir". Realmente o que está marcando é "exactly_match_signature_of_superclass_method", mas isso não é tão legível :)

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

Eu também gostaria de ter a palavra-chave override disponível e fazer com que o compilador gerasse um erro se o método marcado para substituição não existir ou tiver uma assinatura diferente na classe base. Ajudaria na legibilidade e na refatoração

+1, também o ferramental ficará muito melhor. Meu caso de uso está usando react. Eu tenho que verificar a definição toda vez que estou usando os métodos ComponentLifecycle :

``` C#
ciclo de vida do componente de interface

{
componentWillMount?(): void;
componentDidMount?(): void;
componentWillReceiveProps?(nextProps: P, nextContext: qualquer): void;
shouldComponentUpdate?(nextProps: P, nextState: S, nextContext: qualquer): boolean;
componentWillUpdate?(nextProps: P, nextState: S, nextContext: qualquer): void;
componentDidUpdate?(prevProps: P, prevState: S, prevContext: qualquer): void;
componentWillUnmount?(): void;
}

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

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

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

    }
}

@olmobrutall , parece que seu caso de uso é melhor resolvido pelo serviço de idiomas oferecendo refatorações como "interface de implementação" ou oferecendo melhor conclusão, não adicionando uma nova palavra-chave ao idioma.

Não vamos nos distrair :) Os recursos de serviço de linguagem são apenas uma pequena parte da manutenção de hierarquias de interface em uma grande base de código. De longe, a maior vitória é, na verdade, obter erros de tempo de compilação quando uma classe bem abaixo em sua hierarquia em algum lugar não está em conformidade. É por isso que o C++ adicionou uma palavra-chave de substituição opcional (mudança ininterrupta). É por isso que o Typescript deve fazer o mesmo.

A documentação da Microsoft para substituição de C++ resume bem as coisas, https://msdn.microsoft.com/en-us/library/jj678987.aspx

Use a substituição para ajudar a evitar o comportamento de herança inadvertida em seu código. O exemplo a seguir mostra onde, sem usar override, o comportamento da função de membro da classe derivada pode não ter sido pretendido. O compilador não emite nenhum erro para este código.
...
Quando você usa override, o compilador gera erros em vez de criar silenciosamente novas funções de membro.

De longe, a maior vitória é, na verdade, obter erros de tempo de compilação quando uma classe bem abaixo em sua hierarquia em algum lugar não está em conformidade.

Eu tenho que concordar. Uma armadilha que surge em nossas equipes é que as pessoas pensam que ignoraram os métodos quando, na verdade, digitaram um pouco incorretamente ou estenderam a classe errada.

Alterações de interface em uma biblioteca base ao trabalhar em muitos projetos são mais difíceis do que deveriam ser em uma linguagem com uma agenda como o TypeScript.

Temos tantas coisas boas, mas existem esquisitices como essa e nenhuma propriedade const de nível de classe.

@RyanCavanaugh true, mas o serviço de idioma pode ser acionado após a substituição de escrita, como muitos idiomas fazem, sem a palavra-chave é muito mais difícil descobrir quando é o momento certo.

Sobre a interface de implementação, observe que a maioria dos métodos na interface são opcionais e você deve substituir apenas os poucos necessários, não o pacote inteiro. Você pode abrir uma caixa de diálogo com caixas de seleção, mas ainda assim...

E embora meu problema atual seja encontrar o nome do método, no futuro será ótimo ser notificado com erros de tempo de compilação se alguém renomear ou alterar a assinatura de um método base.

A inconsistência não poderia ser resolvida apenas adicionando um aviso quando você não escreve a substituição? Eu acho que o texto datilografado está fazendo a coisa certa adicionando pequenas mudanças razoáveis ​​em vez de preservar decisões ruins para o fim dos tempos.

O abstract também já está lá, eles vão fazer um casal incrível :)

Também senti a necessidade de um especificador de 'substituição'. Em projetos de médio a grande porte, esse recurso se torna essencial e com todo respeito espero que a equipe do Typescript reconsidere a decisão de recusar essa sugestão.

Para qualquer pessoa interessada, escrevemos uma regra tslint personalizada que fornece a mesma funcionalidade que a palavra-chave override usando um decorador, semelhante à sugestão de Ryan acima, mas verificada em tempo de compilação. Em breve, abriremos o código, postarei de volta quando estiver disponível.

Eu senti fortemente a necessidade da palavra-chave 'substituir' também.
No meu caso, mudei alguns nomes de métodos em uma classe base e esqueci de renomear alguns nomes de métodos de substituição. Claro, isso leva alguns dos bugs.

Mas, se houvesse tal recurso, podemos encontrar esses métodos de classe facilmente.

Como @RyanCavanaugh mencionou, se essa palavra-chave for apenas uma palavra-chave opcional, esse recurso causará confusão. Então, que tal fazer algo sinalizar no tsc para habilitar esse recurso?

Por favor, reconsidere este recurso novamente....

Para mim, se a palavra-chave override deve ser útil, ela precisa ser aplicada, como em C#. Se você especificar um método em C# que substitui a assinatura de um método base, você _deve_ rotulá-lo como override ou new.

C++ é irritante e inferior em comparação com C# em alguns lugares porque torna muitas palavras-chave opcionais e, portanto, impede a _consistência_. Por exemplo, se você sobrecarregar um método virtual, o método sobrecarregado pode ser marcado como virtual ou não - de qualquer forma, será virtual. Prefiro o último porque ajuda outros desenvolvedores que lêem o código, mas não consigo fazer com que o compilador o imponha, o que significa que nossa base de código sem dúvida terá palavras-chave virtuais ausentes onde elas realmente deveriam estar. A palavra-chave override é igualmente opcional. Ambos são uma porcaria na minha opinião. O que é negligenciado aqui é que o código pode servir como documentação e melhorar a manutenção, reforçando a necessidade de palavras-chave em vez de uma abordagem "pegar ou largar". A palavra-chave "throw" em C++ é semelhante.

Para atingir o objetivo acima no TypeScript, o compilador precisa de um sinalizador para "habilitar" esse comportamento rigoroso ou não.

No que diz respeito às assinaturas de função da base e do override, elas devem ser idênticas. Mas seria desejável permitir a covariância na especificação do tipo de retorno para impor uma verificação em tempo de compilação.

Estou vindo para o TS do AS3, então, é claro, vou votar aqui para uma palavra-chave override também. Para um desenvolvedor que é novo em qualquer base de código, ver um override é uma grande pista sobre o que pode estar acontecendo em uma classe (filho). Acho que essa palavra-chave agrega muito valor. Eu optaria por torná-lo obrigatório, mas posso ver como isso seria uma mudança importante e, portanto, deveria ser opcional. Eu realmente não vejo um problema com a ambiguidade que impõe, embora eu seja sensível à diferença entre uma palavra-chave opcional override e a palavra-chave padrão public .

Para todos os +1ers - você pode falar sobre o que não é suficiente na solução do decorador mostrada acima?

Para mim, parece uma construção artificial, não uma propriedade da própria linguagem. E eu acho que é por design, porque é isso que é. O que eu acho que envia ao desenvolvedor (bem, eu de qualquer maneira) a mensagem de que é transitório, não uma prática recomendada.

obviamente cada linguagem tem seu próprio paradigma e, sendo novo no TypeScript, sou lento com a mudança de paradigma. Eu tenho que dizer que override parece uma prática recomendada para mim por várias razões. Estou fazendo a mudança para o TypeScript porque engoli totalmente o koolaid fortemente tipado e acredito que o custo (em termos de pressionamentos de tecla e curva de aprendizado) é fortemente superado pelos benefícios tanto do código livre de erros quanto da compreensão do código. override é uma peça muito importante desse quebra-cabeça e comunica algumas informações muito importantes sobre o papel do método de substituição.

Para mim, é menos sobre a conveniência do IDE, embora isso seja inegavelmente incrível quando suportado adequadamente, e mais sobre cumprir o que acredito serem os princípios sobre os quais você construiu essa linguagem.

@RyanCavanaugh alguns problemas que vejo:

  • A sintaxe do decorador será mais difícil para os IDEs fornecerem conclusão de código (sim, acho que poderia ser feito, mas eles precisariam de conhecimento prévio)
  • A classe derivada pode alterar a visibilidade de um método - o que em termos estritos não deve ser permitido
  • Difícil para o decorador verificar a lista e os tipos de argumentos e o tipo de retorno compatível

O compilador já está verificando a lista de argumentos e o tipo de retorno. E acho que mesmo que override existisse como uma palavra-chave de primeira classe, não imporíamos uma nova regra sobre identidade de assinatura

E acho que mesmo que override existisse como uma palavra-chave de primeira classe, não imporíamos alguma nova regra sobre identidade de assinatura.

@RyanCavanaugh , acho que você pode estar em uma página diferente sobre a intenção da palavra-chave. O ponto principal disso é para os casos em que você _quer_ forçar a identidade da assinatura. Isso é para o padrão de design em que você tem uma hierarquia complexa profunda em uma classe base que define um contrato de métodos de interface e todas as classes na hierarquia devem corresponder _exatamente_ a essas assinaturas. Ao adicionar a palavra-chave override a esses métodos em todas as classes derivadas, você é alertado sobre quaisquer casos em que a assinatura diverge do contrato estabelecido na classe base.

Como eu continuo dizendo, este não é um caso de uso esotérico. Ao trabalhar em uma base de código grande (com centenas ou milhares de classes) isso é uma ocorrência _todos os dias_, ou seja, alguém precisa alterar a assinatura da classe base (modificar o contrato), e você deseja que o compilador o alerte sobre qualquer casos em toda a hierarquia que não correspondem mais.

O compilador já avisará sobre substituições ilegais. A questão com o
implementação atual é a falta de intenção ao declarar uma substituição
método.

O decorador mencionado antes parece ir contra o que a linguagem
está tentando alcançar. Não deve haver um custo de tempo de execução para
algo que pode ser tratado em tempo de compilação, por menor que seja o custo.
Queremos pegar o máximo de coisas que estão dando errado, sem precisar
execute o código para descobrir.

É possível conseguir usando o decorador e customizar um tslint customizado
regra, mas seria melhor para o ecossistema de ferramentas e a comunidade para
o idioma para ter uma palavra-chave oficial.

Eu acho que ser opcional é uma abordagem, mas como acontece com alguns compiladores C++
existem sinalizadores que você pode definir que impõem seu uso (por exemplo, sugestão-substituição,
inconsistente-falta-substituição). Esta parece ser a melhor abordagem para evitar
quebrando mudanças e parece consistente com outros novos recursos sendo adicionados
recentemente, como tipos anuláveis ​​e decoradores.

Em quarta-feira, 23 de março de 2016 às 21:31, Rowan Wyborn [email protected] escreveu:

E acho que mesmo que override existisse como uma palavra-chave de primeira classe, nós
não imporia alguma nova regra sobre identidade de assinatura.

@RyanCavanaugh https://github.com/RyanCavanaugh então acho que você pode
estar em uma página diferente sobre a intenção da palavra-chave. Todo o ponto de
é para casos em que você _quer_ forçar a identidade de assinatura. este
é para o padrão de design onde você tem uma hierarquia complexa profunda
em uma classe base que define um contrato de métodos de interface, e todos
as classes na hierarquia devem corresponder _exatamente_ a essas assinaturas. Adicionando
a palavra-chave override nesses métodos em todas as classes derivadas, você está
alertado para quaisquer casos em que a assinatura divirja do contrato estabelecido
na classe básica.

Como eu continuo dizendo, este não é um caso de uso esotérico. Ao trabalhar em um
grandes bases de código (com, digamos, centenas ou milhares de classes) este é um _todo
day_ ocorrência, ou seja, alguém precisa alterar a assinatura da base
class (modifique o contrato) e você deseja que o compilador o alerte sobre qualquer
casos em toda a hierarquia que não correspondem mais.


Você está recebendo isso porque está inscrito neste tópico.
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -200551774

@kungfusheep fwiw o compilador captura apenas uma certa classe de substituições ilegais, é onde há um confronto direto entre os parâmetros declarados. Ele _não_ captura a adição ou remoção de parâmetros, nem captura alterações de tipo de retorno. Essas verificações extras são o que qualquer palavra-chave de substituição ativaria.

O compilador avisa corretamente se você _adicionar_ um parâmetro a uma função de substituição.

A remoção de um parâmetro, no entanto, é _totalmente válida_:

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

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

Alterar o tipo de retorno também é _totalmente válido_:

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

@RyanCavanaugh sim, eles são válidos de uma perspectiva estrita de linguagem, mas é por isso que comecei a usar o termo "contrato" acima. Se uma classe base apresenta uma assinatura específica, quando uso override, estou declarando que quero que minha classe derivada siga estritamente essa assinatura. Se a classe base adicionar parâmetros adicionais ou alterar o tipo de retorno, quero saber todos os pontos em minha base de código que não correspondem mais, pois o contrato em que foram originalmente escritos mudou de alguma forma.

A ideia de ter uma grande hierarquia de classes, cada uma com suas próprias permutações ligeiramente diferentes dos métodos básicos (mesmo que sejam válidas do ponto de vista da linguagem) é um pesadelo e me leva de volta aos velhos tempos do javascript antes do typescript aparecer :)

Se a opcionalidade é o principal problema com a palavra-chave, então por que não torná-la obrigatória quando o método da classe base é definido como abstract ? Dessa forma, se você quisesse impor o padrão estritamente, bastaria adicionar uma classe base abstrata. Para evitar quebrar o código, uma opção do compilador pode desabilitar a verificação.

Parece que estamos falando sobre dois conjuntos diferentes de expectativas agora. Há a situação de substituição ausente/substituição ilegal e, em seguida, há a assinatura explícita. Podemos todos concordar que o primeiro é o mínimo absoluto que esperaríamos dessa palavra-chave?

Eu só digo isso porque existem outras maneiras de impor assinaturas de métodos explícitos atualmente, como interfaces, mas atualmente não há como declarar explicitamente a intenção de substituir.

Ok, não há como impor assinaturas explícitas de métodos substituídos, mas dado que o compilador impõe que quaisquer alterações de assinatura sejam pelo menos 'seguras', parece que há uma conversa separada a ser realizada em torno da solução para esse problema.

Sim acordado. Se eu tivesse que escolher, a situação de substituição ausente/substituição ilegal é o problema mais importante a ser resolvido.

Estou um pouco atrasado para a festa... Acho que o objetivo do Typescript é impor regras em tempo de compilação, não em tempo de execução, caso contrário, todos estaríamos usando Javascript simples. Além disso, é um pouco estranho usar hacks/kludges para fazer coisas que são padrão em tantos idiomas.
Deve haver uma palavra-chave override no Typescript? Eu certamente acredito que sim. Deveria ser obrigatório? Por razões de compatibilidade, eu diria que seu comportamento pode ser especificado com um argumento do compilador. Deve impor a assinatura exata? Acho que isso deveria ser uma discussão separada, mas não tive nenhum problema com o comportamento atual até agora.

A razão original para fechar isso parece ser que não podemos introduzir a alteração importante de que todos os métodos substituídos precisam do especificador override , o que tornaria confuso, pois os métodos não marcados com override também poderiam em fato ser substituições.

Por que não aplicá-lo, seja com uma opção do compilador e/ou se houver _pelo menos um_ método marcado override na classe, então todos os métodos que são overrides devem ser marcados como tal, caso contrário, é um erro?

Vale a pena reabrir isso enquanto está no ar?

Em sex, 8 de abril de 2016 às 14:38, Peter Palotas [email protected] escreveu:

A razão original para fechar isto parece ser que não podemos introduzir
a mudança de quebra que todos os métodos substituídos precisam da substituição
especificador, o que tornaria confuso, pois os métodos não marcados com
'substituir' também pode ser de fato substituições.

Por que não aplicá-lo, seja com uma opção de compilador e/ou se houver pelo menos
menos 'um' método marcado override na classe, então todos os métodos que são
substituições devem ser marcadas como tal, caso contrário, é um erro?


Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -207434898

Eu digo, por favor, reabra! Eu ficaria feliz com uma opção de compilador.

sim - por favor reabra

Reabra isso!

O ES7 não requer essa substituição/sobrecarga de vários métodos com o mesmo
nome?
Em 8 de abril de 2016 10h56, "Aram Taieb" [email protected] escreveu:

Sim, por favor, reabra isso!


Você está recebendo isso porque está inscrito neste tópico.
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -207466464

+1 - para mim, essa é a maior lacuna na segurança de tipos que tenho no desenvolvimento diário de TypeScript.

Toda vez que implemento um método de ciclo de vida React como "componentDidMount", pesquiso a página de documentação do react relevante e copio/cole o nome do método, para garantir que não tenha um erro de digitação. Eu faço isso porque leva 20 segundos, enquanto os erros de um erro de digitação são indiretos e sutis, e podem levar muito mais tempo para serem rastreados.

classe com 5 métodos, dos quais 3 são marcados como substituição, não implicaria que os outros 2 não fossem substituições. Para justificar sua existência, o modificador realmente precisaria dividir o mundo de forma mais limpa do que isso.

Se essa for a principal preocupação, chame a palavra-chave check_override para deixar claro que você está optando por substituir a verificação e, por implicação, os outros métodos são _não verificados_ em vez de _não substituídos_.

Que tal usar implements. ? Sendo algo assim:

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

Esta sintaxe tem algumas vantagens:

  • Ele usa uma palavra-chave já existente.
  • Após escrever implements. o IDE tem uma excelente oportunidade de mostrar um popup de autocompletar.
  • a palavra-chave esclarece que pode ser usada para forçar a verificação de métodos abstratos de classes base, mas também de interfaces implementadas.
  • a sintaxe é ambígua o suficiente para ser usada também para campos, como este:
class MyComponent<MyComponentProps, MyComponentState> {
    implements.state = {/*auto-completion for MyComponentState here*/};

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

Nota: Alternativamente poderíamos usar base. , é classificador e mais intuitivo, mas talvez mais confuso (definindo ou chamando?) e o significado não é tão compatível com a implementação da interface. Exemplo:

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

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

Eu não acho que implements cubra suficientemente todos os casos de uso
mencionado anteriormente e é um pouco ambíguo. Não dá mais
informações para um IDE que override também não poderia, então parece que
faz sentido ficar com a terminologia que muitas outras línguas usaram para
conseguir a mesma coisa.
Em quarta-feira, 13 de abril de 2016 às 19:06, Olmo [email protected] escreveu:

Que tal usar implementos? Sendo algo assim:

classe MyComponent estende React.Component{
implements.componentWillMount(){
//faço minhas coisas
}
}

Esta sintaxe tem algumas vantagens:

  • Ele usa uma palavra-chave já existente.
  • Depois de escrever implementa. o IDE tem uma excelente oportunidade de mostrar
    um método de preenchimento automático.
  • a palavra-chave esclarece que pode ser usada para forçar a verificação no resumo
    métodos de classes base, mas também interfaces implementadas.
  • a sintaxe é ambígua o suficiente para ser usada também para campos, como
    esta:

class MeuComponente{
implements.state = {/_auto-completion for MyComponentState here_/};

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

}


Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -209571753

O problema com override é que, uma vez que você usa a mesma palavra-chave, você tem as mesmas expectativas:

  • deve ser obrigatório, pelo menos se o método for abstract .
  • você perde alguma palavra-chave virtual para anotar métodos que devem ser substituídos, mas têm comportamento padrão.

Acho que a equipe TS não quer adicionar tanta bagagem OO à linguagem, e acho que é uma boa ideia.

Ao usar implements. você tem uma sintaxe leve para obter os principais benefícios: preenchimento automático e nome verificado em tempo de compilação, sem inventar novas palavras-chave ou incrementar a contagem de conceitos.

Também tem o benefício de trabalhar para classes e interfaces, e para métodos (no protótipo) ou campos diretos.

Formas de tornar override obrigatório já foram discutidas no tópico e as soluções não são diferentes de outros recursos implementados pelo compilador.

A palavra-chave virtual não faz sentido no contexto da linguagem e também não é nomeada de forma intuitiva para pessoas que nunca usaram linguagens como C++ antes. Talvez uma solução melhor para fornecer esse tipo de guarda, se necessário, seria a palavra-chave final .

Concordo que recursos de linguagem não devem ser adicionados às pressas que poderiam criar 'bagagem', mas override tapa um buraco legítimo no sistema de herança que muitos outros idiomas consideraram necessário. Sua funcionalidade é melhor alcançada com uma nova palavra-chave, certamente quando abordagens alternativas são para sugerir mudanças fundamentais de sintaxe.

Que tal definir campos herdados versus redefinir novos? Reagir state é um bom exemplo.

Ou implementando métodos de interface opcionais?

override poderia ser usado em campos, se eles estiverem sendo declarados novamente
no corpo da subclasse. Os métodos de interface opcionais não são substituições, portanto, são
fora do escopo do que estamos falando aqui.

Em qui, 14 de abril de 2016 às 11:58, Olmo [email protected] escreveu:

Que tal definir campos herdados versus redefinir novos? Estado de reação
é um bom exemplo.

Ou implementando métodos de interface opcionais?


Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -209879217

Os métodos de interface opcionais não são substituições, portanto, estão fora do escopo do que estamos falando aqui.

Os métodos de interface opcionais são um conceito bastante exclusivo do TypeScript, tanto quanto eu sei, e acho que uma implementação do TypeScript de "substituições" deve ser aplicada a eles.

No caso de métodos de ciclo de vida React como componentDidMount, esses são métodos de interface opcionais que não têm implementação na superclasse.

No caso de métodos de ciclo de vida React como componentDidMount, esses são métodos de interface opcionais que não têm implementação na superclasse.

Exatamente, para que eles não substituam nada. Acho que estamos ficando confusos sobre o que a palavra-chave override pretende fornecer aqui, porque se você está apenas procurando uma maneira de obter dicas/intellisense de código melhores, existem outras maneiras que podem ser alcançadas sem adicionar novas palavras-chave para o idioma.

Pessoal, com todo o respeito, podemos, por favor, manter o foco. Acho que discutir palavras-chave alternativas é contraproducente, especialmente porque a edição foi iniciada com uma solicitação específica.

Podemos todos concordar que precisamos de override e pedir gentilmente à equipe do Typescript para adicioná-lo ou descartar a solicitação e deixar o problema encerrado?

Eu acho que é sempre útil colocar o pedido no contexto de qual é o problema, sempre há maneiras diferentes de resolver o problema. O ponto de discórdia com a substituição foi em torno de ser obrigatório ou não (alguns afirmam que não é obrigatório em C++). O problema que muitas pessoas têm referenciado é acertar o nome das funções substituíveis (que podem ou não ser implementadas na superclasse) - as funções de ciclo de vida do React são o principal problema.

Se a substituição não funcionar, talvez devêssemos fechar esse problema e abrir um ponto mais geral sobre esse problema. O problema geral aqui é que o contrato de interface com uma superclasse não é tipado nem verificado, e isso está atrapalhando os desenvolvedores e seria ótimo se o TS ou as ferramentas pudessem ajudar.

@armandn Eu não acho que nossa falta de fechamento ou a gentileza de nossa sugestão seja o que está mantendo a solicitação rejeitada, mas as diferenças entre a semântica C # e TS:

Substituição do método base C#:

  • Requer override palavra-chave
  • Cria um novo registro no VTable
  • Verifica se há resumo/virtual obrigatório no método base
  • Verifica se há assinatura idêntica
  • IDE: Aciona o preenchimento automático após escrever a substituição

Implementação da interface C#:

  • Nenhuma palavra-chave necessária, mas a implementação de interface explícita é possível usando o nome da interface.
  • Crie um novo registro de interface no VTable.
  • Verifica se há assinatura idêntica
  • IDE: QuickFix para implementar interface / implementar interface explicitamente.

Portanto, o comportamento em C# é bem diferente dependendo se você perguntar sobre classes ou interfaces, mas em qualquer caso, você obtém os três principais benefícios:

  1. Verificando a assinatura idêntica
  2. Verificando se o método pode/deve ser substituído (erro de ortografia no nome do método)
  3. Suporte IDE escrevendo a função

Com semânticas um pouco diferentes, já temos 1) no TS, infelizmente 2) e 3) estão faltando. A razão pela qual override foi rejeitada, na minha opinião, é que uma sintaxe semelhante assume um comportamento semelhante, e isso não é desejável porque:

Uma classe com 5 métodos, dos quais 3 são marcados como override, não implica que os outros 2 não sejam substituições.

Acontece que já temos 1) 2) e 3) ao escrever _literais de objeto_, mas não ao escrever membros de classe que estendem outras classes ou implementam interfaces.

Com isso em mente, acho que todos podemos concordar com essa semântica:

  • A _palavra-chave_ deve ser ambígua o suficiente para evitar o problema do 'mundo dividido'. (ou seja: check ou super. ou MyInterface. )
  • Nenhuma verificação para abstract/virtual mas verifica a existência de membros na classe base/interfaces implementadas. (Benefício 2)
  • Verifica a compatibilidade de assinatura semelhante o suficiente como o TS está fazendo até agora. (Benefício 1)
  • IDE: Aciona um preenchimento automático após a palavra-chave. (Benefício 3)

Além disso, acho que esses dois são necessários para a completude da solução*:

  • Deve funcionar para Classes e Interfaces , porque o TS é principalmente baseado em interface e as classes são um atalho para herança prototípica. Necessário considerando métodos de interface opcionais.
  • Deve funcionar para Methods e Fields , porque os métodos são apenas funções que residem em um campo.

Esses dois pontos são úteis no caso de uso muito real da implementação do componente React:

``` C#
class MeuComponente{
implements.state = {/ preenchimento automático para MyComponentState aqui /};

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

}
```

A solução baseada em anotação de @RyanCavanaugh não é suficiente porque:

  • Não funcionará com interfaces.
  • Não funcionará com campos.
  • Assumindo uma infraestrutura do tipo Roslyn para ajudar com as ferramentas, não há como adicionar um QuickFix depois de escrever a lista de preenchimento automático depois de escrever @override .

Dada a semântica, trata-se apenas de escolher a sintaxe certa. Aqui algumas alternativas:

  • override componentWillMount() : Intuitivo, mas enganoso.
  • check componentWillMount() : Explícito, mas que consome palavras-chave.
  • super componentWillMount() :
  • implements componentWillMount() :
  • super.componentWillMount() :
  • implements.componentWillMount() :
  • this.componentWillMount() :
  • ReactComponent.componentWillMount() :

Opiniões?

@olmobrutall bom resumo. Par de pontos:

Uma opção de palavra-chave (tirada do C#) seria nova - indicando um novo slot:

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

Meu outro ponto é a questão da natureza obrigatória do uso do acima. Como um contrato entre a superclasse pai e a classe derivada, faz sentido especificar quais são os pontos de interface onde a sintaxe acima seria válida. Estes são realmente pontos de extensão internos para a superclasse, então algo como:

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

O mesmo se aplicaria à interface também.

Obrigado :)

Que tal escrever this ?

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

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

Sobre extendable , já existe abstract no TS 1.6, e adicionar virtual talvez crie novamente o problema do mundo dividido?

Certo, pensei em abstract , mas pode haver uma implementação na superclasse, então não faz muito sentido. Da mesma forma com virtual , pois isso implicaria que membros não virtuais não são virtuais - o que é enganoso.

this. funciona, acho que você poderia até ter (como uma forma longa):

   this.componentWillMount = () => { }

O único problema com isso é que ele deve ter escopo apenas para pontos de extensão marcados, não para todos os membros da classe base.

o que está acontecendo...

TypeScript não é e nunca foi a versão javascript do C#. Portanto, o motivo não é porque a funcionalidade sugerida difere da semântica do C#. As razões para o encerramento, conforme indicado por Ryan e depois esclarecidas por Daniel R foram

Como Ryan explicou, o problema é que marcar um método como uma substituição não implica que outro método não seja uma substituição. A única maneira que faria sentido seria se todas as substituições precisassem ser marcadas com uma palavra-chave override (o que, se formos obrigados, seria uma alteração importante).

No entanto, você ainda está persistindo com seus problemas em torno do preenchimento automático. O preenchimento automático não precisa de uma nova palavra-chave no idioma para fornecer sugestões aprimoradas.

A razão pela qual esse thread existe era obter erros do compilador quando uma função é substituída ilegalmente ou quando um método foi declarado como uma substituição, mas na verdade não substituiu uma função em uma classe base. É um conceito simples e bem definido em muitos idiomas que também suportam a maioria, se não mais, os recursos de linguagem que o texto datilografado tem a oferecer. Não precisa resolver todos os problemas do mundo, só precisa ser a palavra-chave override, para overrides.

Desde então, mais pessoas demonstraram interesse na proposta original, então vamos nos ater ao tópico para o qual a questão foi levantada e levantar novas questões para novas ideias.

TypeScript não é e nunca foi a versão javascript do C#.

Estou comparando com C# como linguagem de referência, pois presumo que esse seja o comportamento que vocês estão assumindo.

O preenchimento automático não precisa de uma nova palavra-chave no idioma para fornecer sugestões aprimoradas.

Como você sugere que seja acionado então? Se você mostrar um combobox de autocompletar ao escrever um nome aleatório em um contexto de classe, será muito chato quando quisermos apenas declarar um novo campo ou método.

A razão pela qual esse thread existe era obter erros do compilador quando uma função é substituída ilegalmente ou quando um método foi declarado como uma substituição, mas na verdade não substituiu uma função em uma classe base.

Eu absolutamente incluí este caso de uso, em Benefício 2 .

Não precisa resolver todos os problemas do mundo, só precisa ser a palavra-chave override, para overrides.

Portanto, sua sugestão é corrigir _um passo de cada vez_ em vez de retroceder um passo e olhar para problemas semelhantes/relacionados?. Essa talvez seja uma boa ideia para o seu Scrum Board, mas não para projetar linguagens.

A razão pela qual eles são conservadores ao adicionar a palavra-chave é que eles não podem remover recursos de um idioma.

Alguns erros de design em C# devido à falta de conclusão:

  • Covariância/contravariância de array inseguro.
  • var só funciona para tipos de variáveis, não para parâmetros genéricos automáticos ou tipos de retorno. Talvez auto seja uma palavra-chave melhor como em C++?

Apenas tente usar o React por um segundo e você verá o outro lado da imagem.

Substituir um método que já foi implementado em uma classe base e implementar um método de interface são _ duas coisas completamente diferentes _. Então, sim, o que está sendo sugerido é corrigir um desses cenários com uma palavra-chave dedicada, em vez de tentar criar uma palavra-chave do Exército Suíço.

De que adianta uma palavra-chave que pode significar qualquer uma das 3 coisas diferentes para os desenvolvedores que estão lendo algum código pela primeira vez? É ambíguo e confuso, especialmente se você estiver falando sobre usar uma palavra-chave como this que já faz outras coisas (totalmente não relacionadas!) na linguagem - não poderia ser mais genérico, é quase inútil.

Se sua principal preocupação é autocompletar, os editores têm informações suficientes _agora_ para poder sugerir métodos de classes base e interfaces implementadas 'enquanto você digita'.

Substituir um método que já foi implementado em uma classe base e implementar um método de interface são duas coisas completamente diferentes.

No caso geral sim, mas não estamos falando de implementar nenhum método de interface. Estamos falando de um método de interface opcional _onde a classe pai implementa a interface_. Nesse caso, você pode dizer que 1) a interface permite que o método seja implementado como undefined , 2) a classe pai tem uma implementação indefinida e 3) a classe filha está substituindo a implementação indefinida por uma implementação de método.

@olmobrutall Acho que seu comentário sobre o design de linguagens e como não é um scrum board é um pouco egoísta. Eu vi cerca de quatro atualizações do TS no espaço de menos de um ano.

Se o design da linguagem fosse tão bem considerado como você sugere que deveria ser, então já haveria um documento de especificação de linguagem nos dizendo exatamente como as substituições deveriam funcionar, e talvez nem estaríamos tendo essa conversa.

Eu não faço este comentário com qualquer difamação dos desenvolvedores/designers do TS, porque o TS já é excelente e eu temeria ter que usar o JS padrão.

Sim TS não é C# e não é C++. Mas várias linguagens escolheram a palavra-chave override para atender aos objetivos discutidos aqui, então parece contraproducente sugerir uma sintaxe totalmente estranha.

A questão principal parece ser não querer introduzir uma mudança de ruptura. A resposta simples é um sinalizador do compilador, fim da história. Para algumas pessoas como eu, uma palavra-chave de substituição opcional é inútil. Para outros, eles querem embelezar seu código de forma incremental. O sinalizador do compilador resolve o dilema.

As diferenças de assinatura são uma conversa diferente. A nova palavra-chave parece desnecessária porque o JS não pode suportar vários métodos com o mesmo nome (a menos que o TS crie nomes desconfigurados derivados de assinatura a'la C++, o que é altamente improvável).

Eu vi cerca de quatro atualizações do TS no espaço de menos de um ano.

Não quero dizer que você não pode ser rápido e iterar rapidamente. Estou tão feliz quanto qualquer um que ES6 e TS estão evoluindo rápido. O que quero dizer é que você tem que tentar prever o futuro, para evitar colocar a linguagem em um beco sem saída.

Eu poderia concordar em usar a palavra-chave override . Com argumentos adequados mesmo mantendo campos e interfaces fora do escopo, mas não posso concordar com o argumento '_vamos manter o foco e resolver apenas esse problema específico da maneira que outras linguagens fazem sem pensar muito_'.

Mas várias linguagens escolheram a palavra-chave override para atender aos objetivos discutidos aqui, então parece contraproducente sugerir uma sintaxe totalmente estranha.

Nenhuma dessas linguagens tem herança prototípica ou método opcional (métodos que não são abstratos ou virtuais, eles apenas poderiam _não existir_ em tempo de execução), e isso são problemas relacionados que precisam ser discutidos (e talvez descartados) antes de fazer um compromisso.

Dito de outra forma: digamos que fazemos o que você sugere e implementamos a substituição sem pensar muito. Então eu, ou qualquer outra pessoa usando o TSX, adiciona um problema com o motivo pelo qual override não funciona com componentes React. Qual é o seu plano?

No caso geral sim, mas não estamos falando de implementar nenhum método de interface. Estamos falando de um método de interface opcional onde a classe pai implementa a interface.

Não importa onde a interface foi configurada, o fato é que eles não são a mesma coisa e, portanto, não devem compartilhar uma palavra-chave porque a _intenção_ do programa não é clara.

Você poderia, por exemplo, substituir um método que foi implementado para conformidade de interface em uma classe base; Se pudéssemos colocar todos os nossos ovos em uma palavra-chave para essas duas coisas diferentes, como alguém saberia se essa era a declaração inicial dessa função ou uma substituição a uma definida anteriormente em uma classe base? Você não saberia, e não seria possível saber sem uma inspeção adicional da classe base - que pode até estar em um arquivo .d.ts de terceiros e, portanto, tornando-se um pesadelo absoluto para encontrar, dependendo da profundidade na cadeia de herança a função foi originalmente implementada.

Dito de outra forma: digamos que fazemos o que você sugere e implementamos a substituição sem pensar muito. Então eu, ou qualquer outra pessoa usando o TSX, adiciona um problema sobre por que a substituição não funciona com componentes React. Qual é o seu plano?

Por que isso precisa corrigir o React? Se o React tem um problema diferente do que isso está tentando resolver, então eu não consigo entender por que override precisa consertá-lo? Você já tentou abrir outro problema para sugerir que algo seja feito sobre a implementação da interface?

Eu não concordaria que o suficiente pensamento não foi colocado para isso. Estamos sugerindo uma técnica testada e comprovada que tem sido bem sucedida em todas as outras linguagens que eu posso pensar que a implementou.

o fato é que eles não são a mesma coisa e, portanto, não devem compartilhar uma palavra-chave porque a intenção do programa não é clara.

Não são? Veja essas duas definições alternativas de BaseClass

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

class BaseClass extends ISomeInterface {
}

E então no seu código você faz:

``` C#
class ConcreteClass {
substituir meuMétodo() {
// Fazer coisas
}
}

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

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

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

O comportamento será novamente idêntico se o método for abstrato ou vier da interface: adicione um campo na classe com um lambda implementando-o. Mas parece um pouco estranho override um campo, já que você está apenas atribuindo um valor.

Não vamos ver usando super. (minha sintaxe favorita no momento, mas estou aberto a alternativas).

``` C#
class ConcreteClass {
super.myMethod() { //método no protótipo
// Fazer coisas
}

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

}
```

Agora o conceito é conceitualmente mais simples: minha superclasse diz que existe um método ou campo e a ConcreteClass pode defini-lo no protótipo / atribuí-lo / lê-lo / chamá-lo.

Por que isso precisa corrigir o React?

Não é apenas reagir, olhe para angular:

  • React : 58 interfaces e 3 classes.
  • Angular : 108 interfaces e 11 classes.

Claro que a maioria das interfaces não devem ser implementadas, nem todas as classes devem ser substituídas, mas uma coisa é clara: no Typescript as interfaces são mais importantes que as classes.

Você já tentou abrir outro problema para sugerir que algo seja feito sobre a implementação da interface?

Como devo chamá-lo? override para interface e campos?

Estamos sugerindo uma técnica testada e comprovada que foi bem sucedida em todos os outros idiomas que eu possa imaginar.

As linguagens que você tem em mente são bem diferentes. Eles têm herança baseada em um VTable estático.

No Typescript uma classe é apenas uma interface + herança de protótipo automática de métodos. E métodos são apenas campos com uma função dentro.

Para adaptar o recurso override ao TS, essas duas diferenças fundamentais devem ser consideradas.

Por favor , @kungfusheep faça um esforço para pensar em uma solução para o meu problema. Se você quiser adicionar palavras-chave diferentes e implementá-las em um segundo estágio, tudo bem, mas reserve um segundo para imaginar como deve ser.

Não consigo pensar em outra maneira de dizer o que já disse. Eles não são os mesmos. São semelhantes, mas não iguais. Por favor, veja este comentário de um dos TS devs RE: a palavra-chave readonly - https://github.com/Microsoft/TypeScript/pull/6532#issuecomment -179563753 - que reforça o que estou dizendo.

Concordo com a ideia geral, mas vamos ver na prática:

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

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

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

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

Isso parece VB ou Cobol, não é?

Parece que faz sentido, pelo menos.

Considere este exemplo, havia apenas a palavra-chave override (ou apenas uma).

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

Agora vamos implementar nosso próprio componente usando o que acabamos de escrever.

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

}

Uma maneira de saber seria o tipo de super :

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

exatamente! o que significa que o design está errado. não deve haver uma investigação envolvida com o código skimming, deve apenas fazer sentido quando você o lê.

isso é ambíguo. Estou implementando isso de uma interface ou substituindo um método base? Eu não tenho nenhuma maneira de saber.

Qual a diferença na prática? O objeto tem apenas um espaço de nomes e você só pode colocar um lambda no campo do . Não há como implementar explicitamente uma interface de qualquer maneira. A implementação será:

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

independentemente do que você escreve.

Não importa qual seja a saída. Você pode estar substituindo intencionalmente ou não alguma funcionalidade em uma classe base, mas não há intenção em uma palavra-chave que seja ambígua.

Que erro ou comportamento inesperado será resolvido com duas palavras-chave?

Vamos agora companheiro. Você é obviamente um cara inteligente. Acabei de dizer "substituindo involuntariamente alguma funcionalidade em uma classe base"; não podemos inferir disso qualquer comportamento inesperado que poderia ter ocorrido?

Para ser claro, não estou propondo transformar esta questão em uma proposta de duas palavras-chave. Este problema é para a palavra-chave override - qualquer outra coisa precisará de uma nova proposta e sua própria discussão sobre semântica.

Para ser claro, não estou propondo transformar esta questão em uma proposta de duas palavras-chave. Este problema é para a palavra-chave override - qualquer outra coisa precisará de uma nova proposta e sua própria discussão sobre semântica.

Realmente não importa em quantos assuntos deve ser discutido, ou de quem vem a ideia. Você propõe dividir dois pensamentos muito relacionados e nem considera uma sintaxe consistente.

Os argumentos para saber se precisamos de 1, 2 ou 3 palavras-chave pertencem a este tópico e não está finalizado (...mas se tornando repetitivo). Então talvez possamos discutir a sintaxe em outro thread (porque a semântica será idêntica de qualquer maneira :P).

No meu exemplo:

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

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

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

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

assign , implement e override não fazem exatamente a mesma coisa: Verifique se o nome existe (na classe base, as interfaces implementadas, as interfaces implementadas pela base aula, etc.).

Se houver um conflito de nomes entre as classes base e algumas interfaces implementadas, você receberá um erro em tempo de compilação, seja com 1, 2 ou nenhuma palavra-chave.

Pense também no literal do objeto:

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

Com exatamente a mesma sintaxe, posso reatribuir campos ou métodos independentemente se eles vierem da classe base, implementações diretas de interface ou interfaces implementadas na classe base.

Não atribua, implemente e substitua faça exatamente a mesma coisa: Verifique se o nome existe (na classe base, nas interfaces implementadas, nas interfaces implementadas pela classe base, etc...).

Você acabou de descrever 3 cenários diferentes, então obviamente eles não são os mesmos. Tenho a sensação de que poderia descrever por que eles são diferentes de você o dia todo e você ainda estaria sentado argumentando que não são, então vou me retirar dessa linha de discussão específica por enquanto. Não há nada a dizer que os caras da TS ainda estão considerando isso no momento.

Com o encerramento do nº 6118, acho que há motivos para discutir se os problemas lá e os problemas aqui podem ser abordados simultaneamente.

Eu não estava ciente de https://github.com/Microsoft/TypeScript/pull/6118. A ideia parece uma alternativa possível para adicionar override .

Se eu entendi o problema corretamente, o problema é que você pode ter mais de uma classe/interface base com declarações de membros compatíveis, mas não idênticas, e elas precisam ser unificadas ao serem inicializadas na classe filha sem um tipo.

Sem ter uma boa ideia das consequências, eu ficaria feliz se:

  • o membro produzirá um erro em tempo de compilação que requer declaração de tipo explícita na classe filha
  • o membro fará a interseção de todos os tipos possíveis.

O IMO mais importante seria ter alguma maneira de acionar o preenchimento automático ao escrever membros de classe (Ctrl + Espaço). Quando o cursor está diretamente dentro de uma classe, você pode estar definindo novos membros ou redefinindo os herdados, então o preenchimento automático não pode ser muito agressivo, mas com acionamento manual o comportamento deve ser OK.

Sobre os comentários de @RyanCavanaugh :

Nós definitivamente entendemos os casos de uso aqui. O problema é que adicioná-lo neste estágio na linguagem adiciona mais confusão do que remove. Uma classe com 5 métodos, dos quais 3 são marcados como override, não implica que os outros 2 não sejam substituições. Para justificar sua existência, o modificador realmente precisaria dividir o mundo de forma mais limpa do que isso.

Digitar uma variável como any não implica que alguma outra variável seja ou não também any . Mas, existe um compilador plano --no-implicit-any para forçar que nós o declaremos explicitamente. Poderíamos igualmente ter um --no-implicit-override , que eu ligaria se estivesse disponível.

Ter uma palavra-chave override que é usada dá aos desenvolvedores uma enorme quantidade de informações ao ler o código com o qual eles não estão familiarizados, e a capacidade de aplicá-la daria algum controle adicional do tempo de compilação.

Para todos os +1ers - você pode falar sobre o que não é suficiente na solução do decorador mostrada acima?

Existe alguma maneira de um decorador ser uma solução melhor do que uma palavra-chave de substituição? Há muitas razões pelas quais é pior: 1) Adiciona sobrecarga de tempo de execução, mesmo que pequena; 2) Não é uma verificação em tempo de compilação; 3) Eu tenho que incluir este código em todas as minhas bibliotecas; 4) Não há como isso capturar funções que não possuem a palavra-chave override.

Vamos dar um exemplo. Eu tenho uma biblioteca com três classes ChildA , ChildB , Base . Eu adicionei algum método doSomething() a ChildA e ChildB . Depois de alguma refatoração, adicionei alguma lógica adicional, otimizei e movi doSomething() para a classe Base . Enquanto isso, tenho outro projeto que depende da minha biblioteca. Lá eu tenho ChildC com doSomething() . Não há como atualizar minhas dependências para descobrir que ChildC agora está substituindo implicitamente doSomething() , mas de uma maneira não otimizada que também está faltando algumas verificações. É por isso que um decorador @overrides nunca será suficiente.

O que precisamos é de uma palavra-chave override e um sinalizador de compilador --no-implicit-override .

Uma palavra-chave override me ajudaria muito, pois uso em meu projeto uma hierarquia simples de classes base para criar todos os meus componentes. Meu problema está no fato de que esses componentes podem precisar declarar um método para ser usado em outro lugar, e esse método pode ou não ser definido em uma classe pai e pode ou não fazer coisas que eu preciso.

Por exemplo, digamos que uma função validate receba uma classe com um método getValue() como parâmetro. Para construir essa classe, posso herdar outra classe que já poderia definir esse método getValue() , mas não posso saber isso a menos que examine seu código-fonte. O que eu faria instintivamente é implementar esse método em minha própria classe e, desde que a assinatura esteja correta, ninguém me dirá nada.

Mas talvez eu não devesse ter feito isso. As seguintes possibilidades supõem que eu fiz uma substituição implícita:

  1. A classe base já definiu esse método assim como eu fiz, então escrevi uma função para nada. Não é um problema tão grande.
  2. Eu reescrevi a função incorretamente, na maioria das vezes porque esqueci de fazer algumas coisas não óbvias que já foram tratadas pela classe base. O que eu realmente precisava fazer aqui é chamar super na minha substituição, mas ninguém me sugeriu fazer isso.

Ter uma palavra-chave de substituição obrigatória teria me dito "ei, você está substituindo, então talvez você deva verificar como é o método original antes de fazer suas coisas". Isso melhoraria muito minha experiência com herança.

Claro, como sugerido, ele deve ser colocado sob uma bandeira --no-implicit-override , pois seria uma mudança de última hora e que a maioria das pessoas não se importa muito com tudo isso.

Eu gosto da comparação que @eggers fez com any e --no-implicit-any porque é o mesmo tipo de anotação e funcionaria exatamente da mesma maneira.

@olmobrutall Eu estava vendo algumas de suas conversas sobre substituir métodos abstratos e métodos de interface.

Se, na minha opinião, override implica a existência de um super. Nem métodos abstratos nem métodos definidos em uma interface podem ser chamados por meio de uma chamada super. e, portanto, não devem ser substituídos ( não há nada para substituir). Em vez disso, se fizermos algo mais explícito para esses casos, deve ser uma palavra-chave implements . Mas, isso seria uma discussão de recurso separada.

Aqui estão os problemas como eu os vejo, classificados do mais importante para o menos. Eu perdi alguma coisa?

Falha ao substituir

É fácil _pensar_ que você está substituindo um método de classe base quando não está:

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

Este é provavelmente o maior problema.

Falha na implementação

Isso está muito relacionado, especialmente quando a interface implementada possui propriedades opcionais:

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

Este é um problema menos importante do que _failure to override_, mas ainda é ruim.

Substituição acidental

É possível substituir um método de classe base sem perceber

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

Isso deve ser relativamente raro apenas porque o espaço de nomes de métodos possíveis é muito grande, e as chances de você também escrever uma assinatura compatível _e_ não querer fazer isso em primeiro lugar são baixas.

Acidental any s

É fácil pensar que os métodos de substituição obterão os tipos de parâmetro de sua base:

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

Tentamos digitar esses parâmetros automaticamente, mas tivemos problemas. Se hasFilename estivesse explicitamente marcado override , poderíamos resolver esses problemas mais facilmente.

Legibilidade

Não está imediatamente claro quando um método está substituindo um método de classe base e quando não está. Isso parece um problema menor porque existem soluções alternativas razoáveis ​​disponíveis hoje (comentários, decoradores).

Experiência do editor

Você precisa digitar manualmente os nomes dos métodos básicos ao escrever métodos de substituição de classe derivados, o que é irritante. Poderíamos facilmente corrigir isso sempre fornecendo esses nomes na lista de conclusão (acho que já temos um bug nisso), então não precisamos de um recurso de linguagem para corrigir esse problema.

Não-problemas

Listando-os como coisas que pertencem a um problema separado ou simplesmente não vão acontecer:

  • Correspondência exata de assinatura - isso não é algo que deve ser misturado com override , e realmente tem semântica indesejável em muitos bons cenários
  • Sintaxe insuficientemente exótica (por exemplo implements.foo = ... ). Não há necessidade de bikeshed aqui

@RyanCavanaugh Concordo com todos os itens acima sobre essa ordem também. Apenas como um comentário, não acho que a palavra-chave override deva resolver o problema de "falha na implementação". Como eu disse acima, acho que esse problema deve ser resolvido por uma palavra-chave diferente (por exemplo implements ) como parte de um ticket diferente. ( override deve implicar um método super. )

@RyanCavanaugh Concordo com todos os pontos, mas não vejo nenhuma referência ao problema também muito relacionado de campos inicializados declarados em um tipo pai sem substituir o tipo (e depois perder a verificação de tipo). Eu acho que o recurso não deve se limitar a declarações de métodos em uma linguagem onde os métodos são apenas funções em campos de objetos.

@eggers mesmo que no final sejam necessárias duas palavras-chave, a semântica será muito semelhante e acho que os recursos devem ser discutidos juntos.

substituição deve implicar um super. método

Em C#, override pode (e deve) ser usado também para métodos abstratos (sem .super). Em Java @override é um atributo. Claro que são linguagens diferentes, mas com palavras-chave semelhantes você espera comportamentos semelhantes.

Concordo com os pontos de @RyanCavanaugh , embora eu diga que o problema de "substituição acidental" pode ser um pouco mais comum do que ele acredita (especialmente quando se trata de estender uma classe que já estende outra coisa, como um componente React que já definir um método componentWillMount na primeira extensão).

Eu acredito, porém, como @eggers disse, que o problema de "falha na implementação" é algo bem diferente, e seria estranho usar uma palavra-chave override para um método que não existe em uma classe pai. Eu sei que são linguagens diferentes com problemas diferentes, mas em C# override não é usado para interfaces. Eu sugeriria, se pudéssemos obter um sinalizador --no-implicit-override , que os métodos de interface teriam que ser prefixados com o nome da interface (que se pareceria muito com C# e, portanto, seria mais natural).

Algo como

interface IBase {
    method1?(): void
}

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

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

Acho que @RyanCavanaugh lista os problemas fundamentais. Eu complementaria isso com "Quais são as discordâncias":

  • Declarar um método para corresponder a uma interface é considerado override ? Por exemplo:
    interface IDrawable
    {
        draw?( centerPoint: Point ): void;
    }

    class Square implements IDrawable
    {
        draw( centerPoint: Point ): void; // is this an override?
    }
  • está declarando uma propriedade para corresponder a uma interface considerada override ?
    interface IPoint2
    {
        x?: number;
        y?: number;
    }

    class Circle implements IPoint2
    {
        x: number; // is this an override?
        y: number; // is this an override?
        radius: number;
    }
  • Existe a necessidade de algum tipo de palavra-chave implements , para lidar com os casos acima, em vez de uma palavra-chave override e a forma que ela assumiria.

@kevmeister68 obrigado por declarar explicitamente as discordâncias.

Uma coisa: você tem que tornar os membros da interface opcionais para realmente mostrar o problema, assim o compilador datilografado reclamará quando um campo ou método não for implementado, resolvendo "Falha na implementação".

@olmobrutall Obrigado por isso. Eu nunca declarei um método opcional - pode ser (eu venho de um plano de fundo C# e não existe esse conceito)? Atualizei os exemplos listados acima para alterar as variáveis ​​de membro para opcionais - isso é melhor?

@kevmeister68 também o método draw em IDrawable :)

@Ryan Cavanaugh

Sintaxe insuficientemente exótica (por exemplo, implements.foo = ...). Não há necessidade de bikeshed aqui

Obrigado por me ensinar uma nova expressão . Não vejo muitos problemas na semântica do recurso:

  • Todos nós queremos erros em tempo de compilação se o método não existir
  • Todos nós queremos erros em tempo de compilação que o tipo não corresponde.
  • Também queremos que os parâmetros sejam digitados implicitamente no pai, como as expressões lambda fazem.
  • Queremos alguma ajuda do IDE para escrever o método corretamente.

A maioria dos problemas depende da sintaxe e dos comportamentos que eles implicam:

  • Membros opcionais na Interface vs membros nas classes Base
  • Métodos vs campos (não sei onde lambdas estarão)

Vamos comparar a árvore de sintaxes mais promissoras:

substituir / implementa

class Person{
    dateOfBirth: Date;

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

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


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

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

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

Vantagens:

  • É natural para programadores com experiência OO.
  • Parece certo ao comparar com extends / implements .

Desvantagens:

  • Não fornece uma solução para o problema de campo, nenhum override ou implements parece certo. Talvez super. , mas então o que fazemos com interfaces?
  • Se os métodos forem substituídos usando um lambda (útil para capturar isso) de um function() os mesmos problemas acontecem.

substituir / InterfaceName.

class Person{
    dateOfBirth: Date;

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

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


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

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

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

Vantagens:

  • Também é natural para programadores com experiência OO.
  • Resolve o problema de campo para interfaces, mas não para classes, mas poderíamos usar super. para elas.

Desvantagens:

  • Nomes de interface podem ser longos, especialmente com genéricos, e mostrar a lista de competição automática também será problemático, exigindo algum Alt+Space para torná-lo explícito.
  • Faz parecer que você pode implementar dois membros com o mesmo nome de interfaces diferentes de forma independente. Isso funciona em C#, mas não funcionará em Javascript.

this.

class Person{
    dateOfBirth: Date;

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

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


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

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

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

Vantagens:

  • Resolve classes, interfaces, métodos e campos.
  • Usa (abusos?) apenas uma palavra-chave pré-existente.
  • Usar this. para acionar o preenchimento automático parece natural.
  • Deixa claro que o objeto é apenas um namespace, e você só pode ter uma coisa para cada nome.

Desvantagens:

  • Parece uma expressão, não uma declaração, dificultando a análise para olhos não treinados.

Embora a falha na implementação de um método de interface não opcional causará um erro do compilador, se a interface mudar em algum momento no futuro (digamos que um método seja removido), não haverá aviso para todas as classes que implementaram esse método. Isso pode não ser um problema tão grande quanto os opcionais, mas acho justo dizer que o escopo disso vai além deles.

No entanto, ainda acredito que override é uma coisa distintamente diferente e, potencialmente, ter duas palavras-chave em vez de uma transmitiria melhor a intenção do programa.

Por exemplo, considere um cenário em que um desenvolvedor _acredita_ que está implementando um método de interface quando, na verdade, está substituindo o método que já foi declarado em uma classe base. Com duas palavras-chave o compilador pode evitar esse tipo de erro e lançar um erro ao tom de method already implemented in a base class .

interface IDelegate {
    execute?() : void;
}

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

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

Não sei se isso é relevante em JS, mas os campos de classe devem ser privados em OO, então não acho que precisamos substituir os campos explicitamente, pois o fato de serem privados já nos impede de substituir completamente .

Os métodos de instância são campos e, pelo que percebi, muitas pessoas os usam em vez de métodos de protótipo. Sobre isso,

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

é um erro do compilador, porque walk é um método de protótipo em Person e um método de instância em SuperMan .

De qualquer forma, não tenho certeza se override se encaixaria aqui, porque bem, não sobrescrevemos campos. Novamente, pareceria C#, mas prefiro usar a palavra-chave new em vez de override aqui. Porque não é a mesma coisa que uma substituição de método (eu posso chamar super.myMethod em uma substituição, não aqui).

Minha solução preferida seria algo como (supondo que estamos no modo de substituição estrito):

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

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

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

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

Minha principal preocupação é sobre interfaces embora. Não deveríamos ter que escrever implements para membros de interface não opcionais, porque já seremos capturados pelo compilador. Então, se fizermos isso, haverá uma confusão entre o que é de uma interface e o que não é, já que nem todos os membros de interfaces seriam prefixados por implements . E essa palavra é um bocado. Não estou pronto para escrever algo assim:

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

Eu propus prefixar com o nome da interface anteriormente, mas @olmobrutall me mostrou que era uma ideia pior.

De qualquer forma, estou bastante convencido de que precisaremos de 3 novas palavras-chave diferentes para resolver isso adequadamente.

Também não acho que seja necessário digitar implicitamente as substituições, pois o compilador já nos impedirá de escrever algo que não seja compatível, principalmente porque aparentemente é difícil fazer certo.

new , override e implement não é muita sobrecarga de palavras-chave para essencialmente a mesma semântica de JS? Mesmo que em C# sejam coisas diferentes, virtualmente não há diferença em JS/TS.

Também é bastante ambíguo:

  • Por que os campos são new para classes, mas implements para interfaces?
  • Se você estiver alterando um método de uma classe base que foi implementada com uma interface, o que você deve usar? Posso ver quatro possibilidades: override , implements , qualquer um dos dois ou ambos ao mesmo tempo?

Pense também nisso:

class Animal {
}

class Human extends Animal {
}

class Habitat {
    owner: Animal;
}

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

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

A questão é:

owner = new Human(); redefine o campo com um novo (mas compatível) tipo, ou apenas atribui um valor.

Acho que em geral você redefiniu o campo, exceto se estiver usando a _palavra-chave mágica_. Minha preferência é por this. agora.

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

@olmobrutall Isso pode realmente ser um pouco de sobrecarga de palavras-chave, mas todos os 3 são de fato coisas _diferentes_.

  • override se aplicaria a métodos (definidos no protótipo), o que significa que _não_ apaga o método original, que ainda está acessível por trás super .
  • new se aplicaria aos campos e significaria que o campo original foi apagado pelo novo.
  • implements se aplicaria a qualquer coisa de uma interface, e significa que não apaga ou substitui nada que já existia em uma classe pai, porque uma interface "não existe".

Se você estiver alterando um método de uma classe base que foi implementada com uma interface, o que você deve usar? Posso ver quatro possibilidades: override , implements , qualquer um dos dois ou ambos ao mesmo tempo?

Parece bastante lógico para mim que seria override desde que o que você está efetivamente fazendo aqui. implements significaria que você está implementando algo da interface que não existe de outra forma. De certa forma, override e new teriam prioridade sobre implements .

E sobre seu exemplo de substituição de campo, nada deve mudar sobre como ele funciona hoje. Não tenho certeza do que acontece aqui, mas seja o que for, não deve mudar (ou talvez devesse, mas não é isso que estamos discutindo aqui).

Eu sinto que override seria adequado para métodos e propriedades base, incluindo os abstratos (explicados abaixo).

new é uma ideia interessante no conceito, mas essa palavra-chave em particular pode ser confundida com instanciação de classe, então pode dar a falsa impressão de que funcionaria apenas para classes, apesar do fato de que as propriedades podem ter primitivas, interface, união ou até mesmo tipos de função. Talvez uma palavra-chave diferente como reassign possa funcionar melhor lá, mas tem o problema de dar uma ideia falsa no caso de o valor ser apenas declarado novamente, mas não atribuído a nada na classe derivada ( new pode ter esse problema também). Eu acho que redefine também é interessante, mas poderia levar à expectativa equivocada de que a propriedade 'redefinida' também poderia ter um tipo diferente da base, então não tenho certeza.. (_edit: na verdade eu verifiquei e pode ter um tipo diferente, desde que o novo seja um subtipo do base, então isso pode não ser tão ruim_).

implement (prefiro essa forma verbal específica para consistência com override ) parece funcionar para interfaces. Acredito que tecnicamente também poderia funcionar para métodos básicos abstratos, mas usar override pode parecer mais consistente, apesar da semântica ligeiramente diferente. Outra razão é que seria um pouco inconveniente alterar um método de abstrato para não abstrato, pois seria necessário ir para todas as classes derivadas e alterar override para implement .

Poderia haver ideias melhores, mas isso é tudo que eu tenho neste momento..

Propus a palavra-chave new porque é a que o C# usa para esse recurso exato, mas concordo que essa não é a melhor. implement é realmente uma escolha melhor do que implements . Eu não acho que devemos usá-lo para métodos abstratos, pois eles são parte de uma classe base e não de uma interface. override + new -> classe base, implement -> interface. Isso parece mais claro.

@JabX

Sobre a substituição de protótipo com campo de instância

Verifiquei e você está certo:

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

Falha com erro do compilador: Class 'Person' defines instance member function 'walk', but extended class 'SuperMan' defines it as instance member property.

Eu realmente não vejo o motivo para falhar neste caso, já que o contrato do tipo pai é cumprido e você pode escrever isso de qualquer maneira:

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

Ou mesmo

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

Cerca override

override se aplicaria aos métodos (definidos no protótipo), o que significa que não apaga o método original, que ainda é acessível por trás de super.

Isso é realmente um argumento. Há menos _alguma_ diferença observável entre override e implements ... mas não exatamente.

Tanto override quanto implements são implementados no prototype , poder usar super é independente, porque você chama super.walk() não apenas super() , e está disponível em todos os métodos ( overrides, implements, news e normal definitions).

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

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

E poder usar super.walk() também funciona se você atribuir à instância

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

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

Já existe uma maneira clara de diferenciar campos prototype de campos de instância e é o token = .

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

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

Com a sintaxe this. o problema é resolvido de forma mais elegante:

  • this. significa verificar meu tipo (classes base e interfaces implementadas).
  • = significa atribuir à instância em vez do protótipo.

Cerca New

new se aplicaria aos campos e significaria que o campo original foi apagado pelo novo.

Leve em consideração que você não pode alterar o campo ou método com um tipo diferente porque você quebrará o sistema de tipos. Em C# ou Java, quando você faz new o novo método será usado apenas em chamadas despachadas estaticamente para o novo tipo. Em Javascript, todas as chamadas serão dinâmicas e, se você alterar o tipo, o código provavelmente será interrompido em tempo de execução (coercer o tipo pode ser permitido para fins práticos, mas não ampliando).

class Person {
    walk() { }

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

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

Portanto, mais de new a palavra-chave deve ser assign , porque esta é a única coisa que você pode fazer com segurança de tipo:

class Person{
    dateOfBirth: Date;
}

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

Observe que reassign não faz sentido, porque Person apenas declara o campo, mas não define nenhum valor nele.

Escrever assign parece redundante, no entanto, é claro que estamos atribuindo porque temos um = do outro lado.

Mais uma vez, a sintaxe this. resolve o problema graciosamente:

class Person{
    dateOfBirth: Date;
}

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

Não tenho certeza de como você aplica isso aos campos de uma maneira que faça sentido.

Estamos falando de adicionar uma palavra-chave que é aplicável apenas no corpo da classe, mas os campos podem ser reatribuídos em _qualquer ponto_ durante o tempo de vida das instâncias.

O Typescript já permite inicializar campos no corpo da classe:

class Person {
    dateOfBirth : new Date();
}

Também permite reinicializar campos declarados na classe pai, mas a situação é ruim. Claro que você pode escrever o nome errado (problema semelhante a override ), mas o tipo também é apagado. Isso significa que:

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

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

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

Este código falha com: A propriedade 'lastName' não existe no tipo '{ firstName: string;

Estamos falando de adicionar uma palavra-chave que só é aplicável no corpo da classe, mas os campos podem ser reatribuídos a qualquer momento durante o tempo de vida das instâncias.

Também métodos:

class Person {
    talk() { }
}

class SuperMan extends Person {

    talk() { }

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

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

@kungfusheep Eu concordo, os campos podem ser reatribuídos, mas os métodos no protótipo também.
A principal razão pela qual precisaríamos de uma palavra-chave para campos de "substituição" é porque os métodos de instância _are_ campos e são amplamente usados ​​em vez de métodos de protótipo apropriados porque são lambdas e, portanto, vinculam automaticamente o contexto.

@olmobrutal

Sobre como substituir métodos com campos

É proibido e com razão eu acredito (não é a mesma coisa, então não deveria ser compatível), mas você está certo de que é possível fazer isso manipulando diretamente a instância da classe. Não sei exatamente por que isso é permitido, mas não estamos aqui para mudar a forma como a linguagem funciona, então acredito que não devemos interferir aqui.

Sobre interfaces

As interfaces de classe descrevem o lado da instância de uma classe, significando campos e métodos do protótipo. Eu nem acho que há diferença entre métodos de instância e protótipo em uma interface, acredito que você pode descrever um método como um ou outro e implementá-lo como um ou outro. Portanto implement pode (e deve) também se aplicar a campos.

Sobre o novo

new deve ter a mesma semântica que override , então obviamente você não pode alterar o tipo para algo que não seja compatível. Quero dizer, novamente, não estamos mudando a forma como a linguagem funciona. Eu só queria uma palavra-chave separada porque não é o mesmo tipo de redefinição. Mas, eu concordo que, como você me mostrou, já podemos diferenciar campos e métodos pelo uso de = , então talvez a mesma palavra-chave/sintaxe possa ser usada sem ambiguidade.

Sobre isso.

No entanto, a sintaxe de substituição de campo/método unificado não deve ser this.method() ou this.field porque se parece muito com algo que poderia ser Javascript válido, mas não é. Adicionar uma palavra-chave antes do membro seria mais claro para o fato de que é realmente uma coisa do Typescript.
this é uma boa ideia, então talvez pudéssemos soltar o ponto e escrever algo menos ambíguo como

class Superman {
    this walk() { }
}

Mas ainda assim, parece um mas estranho. E misturá-lo com implement ficaria um pouco fora do lugar (porque estamos bem com o fato de que essa sintaxe this não seria usada com interface?), e eu gosto do implement / override dupla. Um modificador override nos campos ainda me irrita, mas talvez seja melhor que ter uma terceira palavra-chave new ambígua. Como a atribuição ( = ) faz a diferença entre um método e um campo claro, isso seria bom para mim agora (ao contrário do que eu estava dizendo algumas horas atrás).

Concordo, os campos podem ser reatribuídos, mas também os métodos no protótipo.

Sim, modificar o protótipo diretamente, no TypeScript, está muito nos reinos de brincar sob o capô. Não é tão comum quanto simplesmente reatribuir uma propriedade em uma instância de algo. Raramente usamos campos como funções, em uma base de código de mais de 150 mil linhas, então acho que dizer que é amplamente usado talvez seja subjetivo.

Concordo com a maioria de seus outros pontos, mas não estou convencido (e pelo que parece, você também não...) sobre o uso da palavra-chave new neste contexto.

Raramente usamos campos como funções, em uma base de código de mais de 150 mil linhas, então acho que dizer que é amplamente usado talvez seja subjetivo.

Eu também não, mas tenho que recorrer a um decorador @autobind para vincular corretamente this em meus métodos, o que é mais complicado do que simplesmente escrever um lambda. E contanto que você não use herança, usar um campo funciona como você espera. Fiquei com a impressão de que, pelo menos no mundo JS/React, a maioria das pessoas usando classes ES6 estava usando funções de seta como métodos.

E os métodos de interface podem ser implementados com um método próprio ou com um método de instância, o que não ajuda a esclarecer a diferença.

Acredito que os campos tenham os mesmos problemas que os métodos no que diz respeito às substituições.

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

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

Uma possível solução aqui é atribuir o campo no construtor

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

mas isso não é aplicável para interfaces. É por isso que eu (e outros aqui) acredito que precisamos de algo para verificar a pré-existência de um campo ao declará-lo diretamente no corpo da classe. E não é um override . Agora que coloquei mais alguns pensamentos aqui, acredito que o problema com os campos é exatamente o mesmo que o problema com os membros da interface. Eu diria que precisamos de uma palavra-chave override para métodos que já possuem uma implementação em uma classe pai (métodos protótipo e talvez métodos de instância também) e outra para coisas que são definidas, mas sem implementação (não função campos incluídos).

Minha nova proposta seria usar uma palavra-chave provisória member (provavelmente não é a melhor escolha):

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

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

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

Observe que eu escolheria a palavra-chave member para um método abstrato, pois disse que override seria para coisas com uma implementação, indicando que estamos substituindo um comportamento existente. Métodos de instância precisariam usar override em virtude de ser um tipo especial de campo (que já é reconhecido como tal pelo compilador, pois assinaturas de método podem ser implementadas com ele).

Acho que o Typescript deve ser uma versão de JavaScript com verificação em tempo de compilação e, ao aprender TS, você também deve aprender JS, assim como ao aprender C#, você obtém uma boa intuição do CLR.

Herança prototípica é um conceito legal de JS, e bastante central, e o TS não deve tentar escondê-lo com conceitos de outras linguagens que não fazem muito sentido aqui.

A herança de protótipos não faz diferença se você estiver herdando métodos ou campos.

Por exemplo:

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

Rectangle.prototype.color = "black";

Aqui estamos definindo um campo simples no objeto protótipo, então todos os retângulos serão pretos por padrão sem ter que ter um campo de instância para ele.

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

Além disso, a palavra-chave member deixa os outros membros da classe com inveja.

O que precisamos é de uma sintaxe que permita em um contexto de declaração de classe o mesmo tipo de comportamento (tempo de compilação verificado/auto-completar/renomear) que já temos em literal de objeto ou expressões de membro.

Talvez uma alternativa melhor para this. seja apenas . :

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

   someNewMethod(){}
   someNewField = 3;
}

Concluindo, não acho que o Typesystem deva rastrear quais valores vêm do protótipo e quais da instância, porque este é um problema difícil uma vez que você pode acessar o protótipo de forma imperativa, e porque não vai resolver nenhum problema enquanto limitar a expressividade de JS.

Portanto, não há diferença entre um campo de função, um campo de função de seta e um método, nem no sistema de tipos, nem no código gerado (se this não for usado).

Ei pessoal, isso é uma coisa interessante, mas é bem fora da tangente em termos de override especificamente. Eu ficaria feliz em ter algum novo problema de discussão para esse tipo de coisa, a menos que possamos vincular esses comentários de volta à sugestão original de forma mais concreta.

Muito do que você está dizendo aqui também não é específico do TypeScript, portanto, iniciar um thread ESDiscuss também pode ser apropriado. Certamente eles pensaram sobre esse tipo de coisa (em termos do que acontece no protótipo versus a instância).

@olmobrutal
As classes ES6 já estão escondendo as coisas do protótipo e, como disse @kungfusheep , mexer diretamente com isso não é exatamente uma coisa normal de se fazer.

Portanto, não há diferença entre um campo de função, um campo de função de seta e um método, nem no sistema de tipos, nem no código gerado (se isso não for usado).

Bem, no código gerado, os métodos de classe são colocados no protótipo e todo o resto (não prefixado com static ) é colocado na instância, o que faz diferença na herança.

De qualquer forma, agora concordo que não precisamos ter uma sintaxe separada para ambos os tipos de métodos, mas se usarmos a palavra-chave override , ela deve ser limitada a métodos e teríamos que encontrar outra coisa para o resto. Ter uma palavra-chave exclusiva para tudo pode ser bom, mas deve ser muito claro sobre seu significado. override é claro, mas apenas para métodos. Sua sintaxe de pontos, como this. ainda está muito próxima do JS existente para o meu gosto.

@Ryan Cavanaugh

É bem fora da tangente em termos de override

Meus problemas com a substituição é que não é geral o suficiente para métodos e campos de interface. Vejo três opções:

  • use override apenas para métodos de classe base.
  • use override para métodos e campos de interface também
  • considere sintaxes alternativas ( member , implement , this. , . , etc...)
  • não faça nada (isso vai ser triste)

Acho que essa decisão deve ser tomada aqui.

Muito do que você está dizendo não é específico do Typescript

Absolutamente! estamos todos felizes com o estado atual da transpilação, mas não com a verificação de tipos/ferramentas.

Qualquer que seja a palavra-chave, ela será apenas Typescript (assim como abstrata)

@JabX

Você está certo sobre o código gerado, é claro. Meu ponto era que você pode escrever algo assim:

class Person {
    name: string = "John"; 

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

Declaramos uma classe, name irá para a instância enquanto saySomething para o protótipo. Ainda posso escrever isso:

Person.prototype.name = "Unknown"; 

Porque o tipo de Person.prototype é a pessoa inteira. O Typescript não acompanha o que vai para onde em seu sistema de tipos para simplificar.

Ter uma palavra-chave exclusiva para tudo pode ser bom, mas deve ser muito claro sobre seu significado.

O que eu acho mais importante e todos concordamos é a semântica:

Modificado XXX verifica se o membro já está declarado na classe base ou interfaces implementadas, normalmente usadas para substituir funções da classe base.

Já que . ou this. parecem muito estranhos, e member é redundante, acho que provavelmente a melhor escolha é abusar de override para todos os casos . Definir campos ou implementar métodos de interface é um recurso secundário.

Há muitos precedentes:

  • Em C# um static class não é mais uma _classe de objetos_.
  • Não há nada _static_ sobre os campos static .
  • Os métodos virtual são bastante _concretos_ (VB usa Overrideable ).

Isso parecerá assim:

class Person{
    dateOfBirth: Date;

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

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


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

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

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

Podemos resolver isso?

Prefiro ver uma palavra-chave implement separada para qualquer coisa que venha de interfaces (com prioridade para override se estiver em ambas), porque é o que é: uma implementação, não uma substituição.
Caso contrário, concordo que talvez seja melhor abusar de override para tudo da classe dos pais.

Mas não há diferença semântica entre implement e override .

Ambos terão auto-completar / mensagens de erro / transpilação semelhantes para JS ... é apenas uma diferença filosófica.

Vale muito a pena explicar duas palavras-chave e que um compilador pedante te diz: Error you should use 'override' instead of 'implement' .

Que tal este caso:

interface IComparable {
     compare(): number;
} 

class BaseClass implements IComparable {
    implement compare(); 
}

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

A questão é... quem se importa?

Também há a questão implement / implements .

Vamos abusar de override . Um conceito uma palavra-chave, esta é a coisa importante.

Bem, eu já propus que override deveria ter prioridade sobre implement , mas provavelmente não fui claro o suficiente.
Ainda não acredito que seja a mesma coisa. Outro exemplo: para um membro de interface obrigatório, uma falha na implementação é um erro do compilador, enquanto uma falha na substituição não é um problema (a menos que o membro base seja abstrato, ou seja...).

Eu só não acho que override em algo que não é declarado em uma classe pai faria algum sentido. Mas talvez eu seja o único que queira a distinção. De qualquer forma, qualquer palavra-chave que acabemos usando para isso, só espero que possamos resolver algo e fornecer um modo estrito para impor seu uso.

Bem, eu já propus que a substituição deveria ter prioridade sobre a implementação, mas provavelmente não fui claro o suficiente.

Claro, eu só queria dizer que existem quatro casos. Classe Base, Interface, Interface na Classe Base, Reimplementar Interface na Classe Base.

@JabX

Eu só não acho que substituir em algo que não é declarado em uma classe pai faria algum sentido. Mas talvez eu seja o único que queira a distinção .

Você não está. São duas coisas fundamentalmente diferentes e há vantagem em ter essa separação no nível da linguagem.

Eu acho que se as interfaces devem ser suportadas nesta nova funcionalidade, então não pode ser uma solução incompleta que foi parafusada em override . Pela mesma razão que extends existe como uma entidade separada para implements em nível de classe, override precisa ser emparelhado com algo implement para para corrigir este problema. É _substituir a funcionalidade_ vs _definir a funcionalidade_.

@olmobrutal

Vale muito a pena explicar duas palavras-chave e que um compilador pedante lhe diz: Erro você deve usar 'substituir' em vez de 'implementar'.

Eu sinto que fiz essa distinção umas 4 vezes agora, mas isso não é pedante; é uma informação muito importante!

Considere o seu próprio exemplo

interface IComparable {
     compare(): number;
} 

class BaseClass implements IComparable {
    implement compare(); 
}

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

'ou implementa...' é uma suposição totalmente incorreta neste caso. Só porque você disse ao compilador que deseja implementar a mesma interface que a classe base que está estendendo não muda o fato de que você já implementou compare .
Se você escrevesse implement em ChildClass em vez de override , então você deveria agradecer que o compilador lhe informasse sobre sua suposição incorreta, porque é um grande problema que você estava prestes a limpar sem saber um método implementado anteriormente!

Nas bases de código que sou responsável por isso seria um grande problema sem dúvida; por isso, congratulo-me com qualquer recurso do compilador que possa evitar esses erros do desenvolvedor!

@kungfusheep

Se você escrevesse implement em ChildClass em vez de override, deveria agradecer que o compilador lhe informasse sobre sua suposição incorreta, porque é um grande problema que você estava prestes a eliminar inconscientemente um método implementado anteriormente!

Se substituir um método já implementado é uma decisão tão importante, então implement deve ser usado para métodos abstratos também. Se você alterar um método de abstract para virtual ou vice-versa, você deve verificar (e alterar) todas as versões implementadas para considerar chamar super ou apenas remover o método.

Na prática, isso nunca foi um problema em C#.

Concordo, override para métodos implementados e implements para métodos não implementados (abstrato/interface).

Poderia ser usado para resumo, sim.

Em C# não havia o cenário de interface 'opcional', então talvez não fosse considerado um problema.

Mas meu ponto é que em C# nós override implementamos e não implementamos métodos e eu nunca ouvi falar de alguém reclamando.

Eu não acho que seja realmente um problema que mereça explicar o recurso pelo menos duas vezes mais difícil.

Bem, a única razão pela qual precisamos de uma palavra-chave implement é porque os membros da interface podem ser opcionais, enquanto isso não é possível em C# (e provavelmente na maioria das linguagens OO também). Não há palavra-chave para isso porque você não pode _não_ implementar uma interface completamente, então não há problema. Não baseie muito seu argumento em "é o mesmo em C#", porque temos problemas diferentes aqui.

override é usado para métodos _base class_, sejam eles abstratos ou não. Suponho que devemos fazer o mesmo ( override em vez de implement para métodos abstratos) aqui porque acredito que a distinção deve estar na origem do método (classe ou interface) em vez da existência da implementação. E no futuro, se você decidir dar uma implementação padrão ao seu método abstrato, você não terá que passar pelo seu código (ou pior, pelo código de outros usando sua classe) para substituir as palavras-chave.

Minha principal pergunta agora é: devemos ter um sinalizador override / implement estrito (eu quero isso), devemos forçar a palavra-chave implement em membros de interface obrigatórios? Porque isso não ajuda muito (você não pode deixar de implementá-los porque senão não compilará) e pode levar a muita verbosidade desnecessária. Mas, por outro lado, pode ser enganador ter o implement em alguns membros da interface, mas não em todos eles.

@JabX Eu pessoalmente gostaria de um aviso se uma classe base adicionasse uma implementação de um método abstrato.

No entanto, não estou 100% convencido da ideia de uma palavra-chave implements em primeiro lugar. O compilador já irá avisá-lo se você não implementou algo. O único lugar que é realmente útil é para métodos opcionais.

E de qualquer forma, não tem nada a ver com o motivo de eu estar neste tópico. Eu estava apenas procurando se havia uma maneira de especificar uma função como uma substituição de uma classe pai.

Minha pergunta principal agora é, devemos ter um sinalizador de substituição/implementação estrita (eu quero isso totalmente), devemos forçar a palavra-chave implement em membros de interface obrigatórios?

Acho que não escrever deveria ser um aviso.

O único lugar que é realmente útil é para métodos opcionais.

Sim, esse é o ponto principal aqui.

Sem isso, é um erro muito comum no método de digitação que você gostaria de substituir, mas na verdade você está criando um novo método. A introdução de tal palavra-chave é apenas uma maneira de apontar a intenção de substituir a função.

Isso pode ser um aviso ou qualquer outra coisa, mas atualmente é muito doloroso.

Estou adicionando uma anotação de substituição à exibição de classe UML em alm.tools :rose:

image

refs https://github.com/alm-tools/alm/issues/84

Também adicionei um indicador de medianiz para membros de classe que substituem um membro de classe base em alm .

overrides

refs https://github.com/alm-tools/alm/issues/111

Aceitar PRs para uma solução com escopo que acreditamos alcançar o maior valor com a menor complexidade:

  • A nova palavra-chave override é válida no método de classe e nas declarações de propriedade (incluindo get/set)

    • Todas as assinaturas (incluindo implementação) devem ter override se houver

    • Tanto get quanto set devem ser marcados override se algum deles for

  • É um erro ter override se não houver propriedade/método de classe base com este nome
  • Nova opção de linha de comando --noImplicitOverride (sinta-se à vontade para dar o nome aqui) torna override obrigatório para coisas que são substituições

    • Esta opção não tem efeito em contextos de ambiente (ou seja declare class ou arquivos .d.ts)

Fora do escopo neste momento:

  • Herdar assinaturas
  • Digitação contextual de parâmetros ou inicializadores (tentei isso anteriormente e foi um desastre)
  • Palavra-chave correspondente para indicar "Estou implementando um membro de interface com esta declaração"

@Ryan Cavanaugh
No momento, estou tentando implementar isso (acabei de começar e gostaria de receber ajuda sobre isso, especialmente para testes), mas não tenho certeza se entendi o que está por trás

Todas as assinaturas (incluindo implementação) devem ter override se houver

Como você não pode ter várias assinaturas de um método em uma classe, você está falando sobre o contexto de herança? Você quer dizer isso (sem o sinalizador que impõe o uso de override , caso contrário, é obviamente um erro)

class A {
    method() {}
}

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

class C extends B {
    method() {} 
}

deve gerar um erro na classe C porque não escrevi override antes method ?

Se for esse o caso, não sei por que queremos impor isso. E também deve funcionar ao contrário? Gerar um erro na classe B se eu especificar override em C e não em B?

Sim, acho que se aplica às assinaturas da árvore de herança.
E não acho que funcione ao contrário, deve começar a impor a regra de substituição assim que for usada em um nível na hierarquia. Em alguns casos, a decisão de aplicar a substituição pode ser feita em uma classe derivada de uma classe que não é de propriedade do desenvolvedor. Portanto, uma vez usado, deve ser aplicado apenas a todas as classes derivadas.

tenho outra pergunta sobre

Ambos get e set devem ser marcados override se um deles for

O que acontece lá se minha classe pai define apenas um getter e eu quero definir na minha classe derivada o setter? Se eu seguir essa regra, isso significa que meu setter teria que ser definido como um override, mas não há setter na classe pai e deve ser um erro substituir algo que não existe. Bem, na prática (na minha implementação ingênua atual), não há contradição, pois o getter e o setter são para a mesma propriedade.
Eu poderia até escrever algo como:

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

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

O que me parece totalmente errado.

Como você não pode ter várias assinaturas de um método em uma classe

Você pode ter várias assinaturas de sobrecarga para o mesmo método:

class Base { bar(): { } }

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

Acho que sua intuição sobre os outros casos está correta.

Em relação aos getters e setters, acho que override se aplica apenas ao slot de propriedade. Escrever um setter de substituição sem um getter correspondente é claramente extremamente suspeito, mas não é mais ou menos suspeito na presença de override . Como nem sempre sabemos como a classe base foi implementada, acho que a regra é simplesmente que um getter ou setter override deve ter _some_ propriedade de classe base correspondente, e que se você disser override get { , qualquer declaração set também deve ter override (e vice-versa)

Oh ok, eu não sabia que poderíamos escrever assinaturas de métodos em classes como essa, isso é muito legal.

Acho que virtual também faria sentido.
Todas as palavras-chave: virtual , override , final na verdade fariam muita diferença, especialmente ao refatorar classes.
Estou usando muito herança, é tão fácil agora substituir o método "por engano".
virtual também melhoraria o intellisense, pois você poderia propor apenas métodos abstratos/virtuais após a palavra-chave override.

Por favor, por favor, dê prioridade a estes. Obrigado!

Eu concordo com @pankleks - estou usando muito herança e parece errado substituir uma função sem especificar nada diretamente.

"virtual", "override" e "final" seriam perfeitos.

Esta é uma questão importante para mim.

@Ryan Cavanaugh

Digitação contextual de parâmetros ou inicializadores (tentei isso anteriormente e foi um desastre)

Você pode elaborar sobre isso? Na verdade, encontrei esse problema ao tentar descobrir por que o TS não infere o tipo de meus parâmetros em substituições de métodos, o que foi uma surpresa para mim. Não vejo quando uma lista de parâmetros não idêntica estaria correta ao substituir ou um tipo de retorno que não é compatível.

@pankleks

Acho que virtual também faria sentido.

Por sua natureza, tudo é "virtual" em JS. Suporte para final seria suficiente IMHO: se você tiver um método (ou propriedade) que não deve ser substituído, você pode marcá-lo como tal para disparar um erro do compilador quando isso for violado. Não há necessidade de virtual então, certo? Mas isso é rastreado na edição #1534.

@avonwyss existem dois problemas aqui.

Quando tentamos digitar contextualmente _property initializers_, encontramos alguns problemas descritos em https://github.com/Microsoft/TypeScript/pull/6118#issuecomment -216595207 . Achamos que temos uma nova abordagem mais bem-sucedida em #10570

Declarações de método são uma bola de cera diferente e temos algumas outras coisas para descobrir, como o que deve acontecer aqui:

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

Podemos apenas decidir que apenas métodos de assinatura única obtêm tipos de parâmetros da base. Mas isso não vai deixar todo mundo feliz e realmente não resolve o problema frequente de "Eu quero que minha classe derivada para o método tenha as mesmas _assinaturas_ mas apenas forneça uma _implementação_ diferente".

Bem, o tipo de x seria string | number nesse caso, mas entendo que pode ser muito difícil descobrir de forma consistente.

Mas você provavelmente não gostaria que o tipo _externamente visto_ de x fosse string | number - supondo que Derived tenha a mesma semântica que Base , não seria t ser legal invocar com (3) ou ('foo', 3)

Hum, sim, eu perdi isso.

Vejo que você sugeriu restringi-lo a métodos de assinatura única, mas acredito que poderia ser "facilmente" expandido para métodos de "assinatura correspondente", como no seu exemplo:

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

porque há apenas uma assinatura na classe base que corresponde.

Isso seria muito melhor do que o que temos (nada), e não acho que seria _tão_ difícil de fazer? (ainda fora do escopo para este problema e provavelmente mais trabalho)

@RyanCavanaugh Obrigado pela resposta. Como observado, minha esperança para override é que ele resolva o problema do parâmetro (e do tipo de retorno). Mas não vejo seu exemplo como problemático, pois as sobrecargas sempre precisam ter uma única implementação "privada" compatível com todas as sobrecargas (e é por isso que a sugestão do @JabX não funcionaria conceitualmente).
Então, teríamos algo assim:

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

Essa lógica já existe e a substituição obviamente só estaria disponível para o método de implementação "privado", não para as substituições distintas (assim como você não pode implementar explicitamente uma única sobrecarga de qualquer maneira). Então eu não vejo um problema aqui - ou eu perdi alguma coisa?

Métodos projetados para serem substituídos geralmente não têm sobrecargas de qualquer maneira, portanto, mesmo tomar o caminho mais simples e não inferir o parâmetro e os tipos de retorno se existirem sobrecargas seria perfeitamente bom e útil. Portanto, mesmo que o comportamento fosse como está ao substituir métodos sobrecarregados, você obteria um erro ao executar no modo --no-implicit-any e teria que especificar tipos compatíveis, isso pareceria uma abordagem perfeitamente adequada.

Provavelmente está faltando alguma coisa, mas em Javascript (e, portanto, Typescript) você não pode ter 2 métodos com o mesmo nome - mesmo que tenham assinaturas diferentes?

Em C#, você poderia ter

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

Mas aqui você nunca pode ter 2 funções com o mesmo nome, não importa o que você faça com as assinaturas... então nenhum dos seus exemplos seria possível de qualquer maneira, certo? A menos que estejamos falando de várias extensões na mesma classe... mas não é isso que seus exemplos estão mostrando, então provavelmente não? Isso é um não-problema?

Agora estou usando herança, e é muito frustrante... Estou tendo que colocar comentários sobre funções para indicar que elas são virtuais (ou seja, estou substituindo-as em algum lugar) ou override (ou seja, que essa função está substituindo uma função subjacente). Super chato e confuso! E inseguro!

@sam-s4s Sim, é possível declarar várias assinaturas de sobrecarga lógica para um método, mas todas elas acabam na mesma implementação única (que então precisa descobrir pelos parâmetros passados ​​qual "sobrecarga" está sendo chamada). Isso é muito usado por muitos frameworks JS, por exemplo jQuery onde $(function) adiciona uma função pronta, mas $(string) pesquisa o DOM por seletor.

Veja aqui os documentos: https://www.typescriptlang.org/docs/handbook/functions.html#overloads

@avonwyss Ah sim, declarações, mas não implementações, isso faz sentido :) Obrigado

Isso durou muito tempo, e eu acho irritante ter que ler todo esse tópico inchado para começar a descobrir por que o TS ainda não tem essa afirmação básica em tempo de compilação.

Onde estamos em (a) uma palavra-chave override ou (b) uma anotação jsdoc @override que informa ao compilador "um método com o mesmo nome e assinatura de tipo DEVE existir em uma superclasse". ?

Mais especificamente, o comentário em https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -224776519 (8 de junho de 2016) de @RyanCavanaugh sugere que o próximo passo é um PR (comunidade)?

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

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

Mais especificamente, o comentário em #2000 (comentário) (8 de junho de 2016) de @RyanCavanaugh sugere que o próximo passo é um PR (comunidade)?

@pcj Correto

Eu comecei um PR sobre isso em # 13217. Qualquer pessoa interessada nesta funcionalidade é muito bem-vinda para participar e/ou fornecer sugestões. Obrigado!

Eu preferiria o mesmo que para java com um decorador

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

@ Chris2011 Concordo que isso parece familiar, mas isso implicaria como decorador que @Override seria avaliado em tempo de execução, não em tempo de compilação. Estou planejando o javadoc /** <strong i="7">@override</strong> */ em um PR separado assim que a palavra-chave de substituição #13217 ( public override toString() ) avançar na revisão.

Tentando acompanhar a conversa deste tópico. Isso inclui funções estáticas? Não vejo nenhuma razão pela qual não poderíamos também permitir a substituição de funções estáticas em classes derivadas com assinaturas completamente diferentes. Isso é suportado no ES6. Se tivesse class A { } ; A.create = function (){} então class B extends A { } ; B.create = function (x,y) { } , chamar A.create() não vai chamar B.create() e causar problemas. Por esse motivo (e para criar a capacidade de usar o mesmo nome de função em tipos como funções de fábrica), você também deve permitir a substituição da assinatura de funções estáticas básicas. Não quebra nada e adiciona a capacidade de fazer algumas coisas legais (especialmente para estruturas de mecanismos de jogos, onde qualquer coisa 'nova' é realmente um mal se usada o tempo todo sem puxar de um cache de objetos para reduzir congelamentos de GC). Como construtores que podem ser chamados não são suportados no ES6, criar uma convenção de nomenclatura comum para métodos em tipos é a única outra opção; no entanto, fazer isso atualmente exige que a função estática derivada mostre uma sobrecarga da assinatura da função estática básica junto com sua própria, o que não é útil para uma função de fábrica em um tipo que lida apenas com esse tipo. :/ Enquanto isso, a única opção que tenho é forçar os usuários do meu framework a duplicar todas as assinaturas de funções estáticas da hierarquia de base (como SomeType.create() ) em seus tipos derivados e assim por diante, o que fica muito bobo .

Aqui está um exemplo sobre a "bobagem" de que estou falando (que funciona, mas não é algo que eu me orgulharia em uma estrutura extensível):

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

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

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

(https://goo.gl/G01Aku)

Isso teria sido muito mais legal:

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

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

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

@rjamesnw , você pode estar interessado em "this" polimórfico para membros estáticos com um contrato para funções de fábrica é discutido como um caso de uso principal ao longo dos comentários.

então, novamente reafirmando um sentimento que @pcj expressou ( comentário ) mais de um ano atrás, é confuso ter que ler tantos PRs e comentários aqui e em outros lugares para determinar onde esta solicitação de recurso está.

parece que o #13217 estava tão perto, depois foi derrubado novamente por @DanielRosenwasser e companhia como um recurso que pode não se encaixar no idioma, aparentemente re-entrando na conversa circular aqui sobre esta questão sobre se deve ou não ser feito. Talvez essa coisa de decoradores de 'tempo de design' (#2900) resolva isso, talvez não? Seria bom saber.

Aqui estão mais algumas deficiências da abordagem do decorador de tempo de execução postadas há alguns anos que não vi mencionadas:

  • Não funcionará com propriedades, pois elas não fazem parte do protótipo
  • Propriedades e acessadores podem (mais ou menos...) substituir um ao outro, o que também não funcionará
  • Temos classes abstratas agora, mas como coisas abstratas não existem em tempo de execução, não é possível trabalhar com elas

Se a única ressalva fosse que as verificações ocorreram na inicialização da classe, provavelmente eu poderia aceitá-la como uma medida temporária, mas, como há, existem muitas limitações.

Francamente, não posso acreditar que esse recurso ainda seja tão controverso por 3 anos desde que registrei o problema pela primeira vez. Toda linguagem orientada a objetos "sério" suporta esta palavra-chave, C#, F#, C++... você escolhe.

Você pode discutir hipóteses o dia todo sobre por que o javascript é diferente e precisa de uma abordagem diferente. Mas, de uma perspectiva prática , trabalhando diariamente em uma base de código Typescript muito grande, posso dizer que a substituição faria uma enorme diferença na legibilidade e manutenção do código. Também eliminaria uma classe inteira de bugs devido a classes derivadas que acidentalmente sobrescrevem métodos de classe base com assinaturas compatíveis, mas sutilmente diferentes.

Eu realmente adoraria ver uma implementação adequada de virtual / override / final. Eu o usaria o tempo todo e tornaria o código muito mais legível e menos propenso a erros. Eu sinto que esta é uma característica importante.

Acordado. É frustrante ver como recursos relativamente obscuros / de casos extremos estão sendo adicionados, enquanto algo tão... fundamental está sendo rejeitado. Há algo que possamos fazer para pressionar por isso?

Vamos lá pessoal! Se há tantos comentários e mais de três anos, por que ainda não está implementado!

Vamos lá :joy_cat:

Por favor, seja construtivo e específico; não precisamos de dezenas de comentários POR FAVOR FAÇA JÁ

Mas pessoal, por favor, sejam específicos também do seu lado.

Obviamente, a comunidade está muito interessada nesse recurso, e a equipe TS não nos dá detalhes sobre o futuro deste.

Pessoalmente, concordo completamente com @armandn , lançamentos recentes do TS trazem recursos relativamente raramente usados ​​enquanto algo assim está sendo realizado.

Se você não planeja fazê-lo, basta nos dizer. Caso contrário, por favor, deixe-nos saber como a comunidade pode ajudar.

Apenas uma linha do tempo aqui, pois há mais comentários do que o GitHub está disposto a exibir ao carregar:

Não é como se não estivéssemos levando em consideração aqui. Isso é o mais próximo que os recursos chegam, e rejeitamos nossas próprias ideias de recursos por motivos semelhantes - veja #24423 para um exemplo recente.

Nós realmente queremos crescer a linguagem intencionalmente. Isso leva tempo, e você deve esperar ter que ser paciente. Em comparação, C++ é mais antigo que muitas pessoas neste segmento ; O TypeScript ainda não tem idade suficiente para ficar em casa sem a supervisão de um adulto. Uma vez que adicionamos algo à linguagem, não podemos retirá-lo novamente, então cada adição deve ser cuidadosamente ponderada contra seus prós e contras. Falo por experiência que os recursos pelos quais as pessoas estavam GRITANDO (métodos de extensão, estou olhando para você) teriam destruído nossa capacidade de continuar evoluindo a linguagem (sem tipos de interseção, sem tipos de união, sem tipos condicionais) se os implementássemos apenas porque houve muitos comentários no GitHub. Não estou dizendo que override é uma coisa perigosa de se adicionar, apenas que estamos sempre abordando isso com o máximo de cautela.

Se eu tivesse que resumir os principais problemas com override agora:

  • Ele não permite que você faça nada que não possa fazer hoje com um decorador (portanto, "não é novo" em termos de "coisas que você pode realizar")
  • A semântica não pode se alinhar com precisão com override em outros idiomas (aumenta a carga cognitiva)
  • Ele não "acende" 100% sem um novo sinalizador de linha de comando, e seria um sinalizador que provavelmente gostaríamos de estar abaixo de strict mas realisticamente não poderíamos porque seria muito grande uma mudança de ruptura (diminui o valor entregue). E qualquer coisa que implique um novo sinalizador de linha de comando é outra duplicação do espaço de configuração que precisamos pesar seriamente, porque você só pode dobrar algo tantas vezes antes que consuma todo o seu orçamento mental ao tomar decisões futuras
  • Discordância interna bastante forte aqui sobre se override pode ser aplicado à implementação de um membro de interface (diminui o valor percebido porque as expectativas não correspondem à realidade para uma parte das pessoas)

A vantagem total aqui é que você pode a) indicar que está substituindo algo (o que você poderia fazer hoje com um comentário), b) pegar um erro de ortografia que o preenchimento automático deveria ter ajudado de qualquer maneira e c) com um novo sinalizador, pegar lugares onde você "esqueceu" de colocar uma palavra-chave e agora precisa (uma tarefa mecânica simples que dificilmente encontrará bugs reais). Entendo que b) é extremamente frustrante , mas, novamente, precisamos atender à barra de complexidade aqui.

No final das contas, se você acha que as classes JS seriam muito ajudadas por uma palavra-chave override , então defender uma proposta TC39 para adicioná-la ao tempo de execução principal seria um bom ponto de partida.

Ele não permite que você faça nada que não possa fazer hoje com um decorador (portanto, "não é novo" em termos de "coisas que você pode realizar")

Talvez eu esteja entendendo mal, mas há coisas muito importantes que um decorador não pode fazer e que a palavra-chave poderia. Mencionei alguns em https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -389393397. Definitivamente, corrija-me se essas coisas forem possíveis, porque eu gostaria de usar a abordagem do decorador, mas os métodos não abstratos são apenas uma pequena parte do meu caso de uso para esse recurso.

Outra vantagem não mencionada é que uma palavra-chave override tornaria muito mais viável alterar algo em uma classe base. Alterar um nome ou dividir algo em duas partes etc. é difícil sabendo que qualquer classe derivada irá compilar bem sem ser atualizada para corresponder, mas provavelmente falhará em tempo de execução. Além disso, considerando que não há palavras-chave finais/seladas/internas disponíveis, praticamente qualquer classe exportada pode ser derivada em algum lugar, tornando a alteração de praticamente qualquer coisa não privada muito mais arriscada do que seria com substituição disponível.

Minha impressão é que uma regra TSLint seria de longe o caminho mais suave a seguir. @kungfusheep mencionou brevemente a ideia em https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -192502734 , mas não vejo nenhum acompanhamento. Alguém mais está ciente de uma implementação TSLint desse recurso? Se não, provavelmente começarei a hackear um quando tiver uma chance. 😄

Meu pensamento atual é que será apenas um comentário, // @override , assim como // @ts-ignore e várias outras diretivas baseadas em comentários que já vi. Pessoalmente, prefiro evitar os decoradores, pois (em sua forma atual) eles têm semântica de tempo de execução e ainda estão no estágio 2 e desabilitados por padrão.

Com alguma sorte, uma regra TSLint personalizada seria uma solução de 90%, apenas realmente carente de estética e, claro, poderia avançar sem ter que se comprometer com nenhum detalhe da linguagem. Com regras de reconhecimento de tipo, serviço de linguagem tslint e correção automática, não há muita diferença entre um erro TSLint e um erro TS interno da perspectiva do desenvolvedor. Minha esperança é que um plugin TSLint (ou vários plugins concorrentes) dê à comunidade alguma experiência e uma chance de chegar a um acordo sobre a melhor semântica. Então talvez isso possa ser adicionado como uma regra TSLint central, e talvez isso forneça clareza e motivação suficientes para justificar o benefício estético de uma palavra-chave override na linguagem central.

Apenas pensando fora da caixa aqui. Temos dois cenários diferentes aqui. C# (apenas como exemplo) usa virtual e override porque os métodos NÃO são virtuais por padrão. Em JavaScript todas as funções em uma classe são virtual por padrão (por natureza). Não faz mais sentido inverter o processo e ter um modificador de tipo nooverride ? É claro que as pessoas ainda podem forçá-lo, mas pelo menos está lá para ajudar a empurrar uma convenção de que algumas funções nas classes base não devem ser tocadas. Novamente, apenas pensando fora da norma aqui. ;) Também pode ser uma mudança menor.

Não faz mais sentido inverter o processo e ter um modificador de tipo nooverride ?

Eu gosto da sua maneira de pensar, e acho que o que você está procurando é definitivo .

E quanto readonly ? Acredito que substituir um método em JS realmente significa substituir o pai pelo filho durante a instanciação (quando a cadeia de protótipos é aplicada). Nesse caso, ter um método readonly para significar "Está lá para todos verem, mas não quero que ninguém o altere, seja por herança ou dinamicamente após a instanciação" faz muito sentido. Já está implementado para membros, por que não fazer isso para métodos também?

Já existe uma proposta para isso? Se não, pode valer a pena investigar como uma alternativa para substituir ...

_edit:_ acontece que você pode substituir um membro readonly, então todo esse argumento é recolhido.

Talvez permitir funções de classe private seja outra opção?

Edit: eu estava pensando "em vez de marcar como final", mas devo estar meio adormecido (obviamente "final" significa público, mas não pode substituir), LOL; deixa pra lá.

@rjamesnw você já pode definir funções de classe como public, protected, private.

Eu não acho que apenas ter "final" seria uma solução. O problema é que as pessoas podem acidentalmente criar uma nova função em uma classe que herda de uma classe base com esse nome já em uso, e então você estará quebrando coisas silenciosamente sem saber por quê. Já aconteceu comigo algumas vezes, muito frustrante, pois muitas vezes não dá erro, e coisas estranhas simplesmente acontecem (ou não)...

Então eu acho, realmente, que estamos olhando para uma nova entrada tsconfig.json que forçará o compilador a lançar um erro quando algo for substituído sem que seja marcado como virtual (e a substituição seja marcada como substituição ou final).

Eu acho que sem override , o TypeScript está decepcionando seus usuários em sua promessa [percebida] de segurança em tempo de compilação e segurança de tipos.
O argumento "usar um comando IDE para substituir o método" é interrompido à medida que o código está evoluindo (como outras pessoas apontaram).
Também parece que todas as principais linguagens comparáveis ​​adicionaram override de alguma forma.
Para não torná-lo uma mudança importante, pode ser uma regra tslint (da mesma forma que em Java).
Btw, por que não permitir alterações entre as principais versões de idioma, por exemplo, 2.x -> 3.x. Não queremos ficar presos, não é?
Se acontecer que algo está errado com certos detalhes de override , e isso requer alguns ajustes no 3.x, que assim seja. Acho que a maioria das pessoas entenderá e apreciará a troca de velocidade de evolução versus compatibilidade. Realmente sem override não posso recomendar o TS como algo mais prático que Java ou C#...

Se as pessoas querem ser rígidas, deve haver alguma forma de override obrigatório (pode ser uma regra tslint opcional). O valor de override obrigatório é muito menor, porque a chance de substituir acidentalmente um método da superclasse parece muito menor do que acidentalmente não substituir .

Realmente esta questão aberta desde 2015 é um balde de água fria no meu entusiasmo e evangelismo TypeScript...

Isso não lida com métodos de substituição de avós*-pais:

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

Também é triste e decepcionante que No one assigned em GH aqui nesta questão. Talvez seja hora de um fork do TypeScript? ;) CompileTimeSafeScript...

Todo esse segmento parece um grande antipadrão de "Paralisia por análise". Por que não apenas fazer o "valor de 80% dos casos com 20% de trabalho" e, tentar na prática, e, se necessário, ajustá-lo em 3.x ?

Os casos de uso simples, frequentes e muito valiosos são mantidos "refém" por alguns casos de canto com os quais a maioria das pessoas nunca precisará se preocupar.

Portanto, é possível verificar o tipo em tempo de compilação por meio de decoradores, embora não sem um pouco de ajuda:

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

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

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

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

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

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

Não tenho certeza se é possível obter uma referência ao super tipo sem passá-lo explicitamente. Isso incorre em um pequeno custo de tempo de execução, mas a verificação é todo o tempo de compilação.

@alangpierce

Minha impressão é que uma regra TSLint seria de longe o caminho mais suave a seguir.

[...]

Então, talvez ele possa ser adicionado como uma regra TSLint principal, e talvez isso forneça clareza e motivação suficientes para justificar o benefício estético de uma palavra-chave de substituição na linguagem principal.

100% de acordo. Vamos começar já!

Oi - um pouco mais tarde do que o planejado, mas aqui está nossa regra tslint implementada usando um decorador.

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

Temos usado isso em nossa base de código TypeScript ~ 1mloc por alguns anos e recentemente o atualizamos para usar o serviço de linguagem, tornando-o muito mais rápido de executar e mais viável para código aberto (a iteração anterior exigia uma passagem completa do projeto para recolher a informação patrimonial).

Para nosso caso de uso, foi inestimável - embora ainda sejamos da opinião de que a responsabilidade deve ser do compilador e, como tal, a regra de linting deve ser vista como um tapa-buraco.

Obrigado

Eu concordo. Também acredito que uma solução envolvendo o compilador TS seria muito melhor do que decorators e regras de lint.

Qual é o status desse recurso? Já foi revisitado como um recurso do compilador?

Portanto, é possível verificar o tipo em tempo de compilação por meio de decoradores, embora não sem um pouco de ajuda:

Algumas melhorias:

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

class Foo {

    bar( a : number ) {
        return a 
    }

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

}

class Foo2 {

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

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

    xxx() { return '777' }

}

class Foo3 extends Foo2 {

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

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

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

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

}

Parque infantil

Ok, descobri a solução de substituição que evita erros de compilação.
Exemplo com stream Node Readable:

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

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

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

A única desvantagem desta solução é a falta de tipagens ao chamar super.parentMethod (mas graças ao interface ReadableObjStream você tem todas as tipagens ao usar a instância de ReadableObjStream.

@nin-jin @bioball Obrigado pelas contribuições com a verificação em tempo de compilação do decorador.

Infelizmente não parece funcionar com membros protected , apenas com public .

(exemplo de erro no meu fork do playground do nin-jin

O especificador de substituição era um recurso matador no c++ 11.
Isso ajuda e protege muito o código de refatoração.
Eu definitivamente gostaria de ter isso no suporte básico do TS sem obstáculos (VOTE!)

Definitivamente, mostramos suporte selvagem para esse recurso, mas parece que eles ainda não planejam adicionar esse recurso tão cedo.

Outra ideia:

class Obj { 

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

}

class Foo extends Obj {

    bar(a: number) {
        return 0
    }

    bar2(a: number) {
        return 0
    }

    foo = 1

}

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

    foo = 2

    bar( a : number ) {
        return 1
    }

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

    bar3( a : number ) {
        return 1
    }

}

declare const Protected: unique symbol

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

Link do Playground

Adicionando meus dois centavos aqui.
Temos um componente angular típico, que lida com formulários e precisa cancelar a assinatura de valueChanges e tal. Nós não queríamos repetir o código em todo o lugar (estado meio atual), então eu fiz um "TypicalFormBaseComponent" que (entre outras coisas) implementa os métodos angulares OnInit e OnDestroy. O problema é que agora, se você realmente usá-lo e adicionar seu próprio método OnDestroy (que é uma coisa bastante padrão a se fazer), você oculta o original e quebra o sistema. chamar super.OnInit corrige isso, mas atualmente não há nenhum mecanismo para eu forçar as crianças a fazer isso.

Se você fizer isso com o construtor, ele forçará você a chamar super()... Eu estava procurando algo semelhante, encontrei este tópico e, honestamente, estou meio chateado.
Implementar "new" e "override" no ts pode ser uma mudança importante, mas corrigi-lo seria incrivelmente simples em basicamente qualquer base de código (apenas adicionando 'override' em todos os lugares onde ele grita). Também pode ser apenas um aviso .

De qualquer forma, existe alguma outra maneira de eu forçar a super chamada nas crianças? Regra TSLint que impede a ocultação, ou algo assim?

PS: Não concordo com a política de "sem comentários". Isso evita que as pessoas joguem exemplos aqui. Talvez você não implemente 'override', mas implemente outra coisa, que cuide dos problemas deles... isto é, se você realmente puder lê-los.

@GonziHere Em outras linguagens, como C# ou Java, a substituição não implica a necessidade de chamar o super método - geralmente não é o caso. Construtores são "métodos virtuais" especiais e não normais.

A palavra-chave override seria usada para especificar que o método já deve estar definido com uma assinatura compatível na classe base, e o compilador poderia declarar isso.

Sim, mas a necessidade de substituição força você a perceber que o método existe.

@GonziHere Parece que o que você realmente precisa é criar classes base abstratas com funções abstratas. Talvez você possa criar métodos privados _onInit e _onDestroy nos quais você possa confiar mais, e então criar funções abstratas onInit e onDestroy protegidas (ou funções regulares se forem não é necessário). As funções privadas chamarão as outras e serão concluídas normalmente.

@GonziHere @rjamesnw A coisa mais segura a fazer é, de alguma forma, impor que onInit e onDestroy são _final_ e definir métodos de modelo abstratos protegidos vazios que os métodos finais chamam. Isso garante que ninguém possa substituir acidentalmente os métodos, e tentar fazer isso (supondo que haja uma maneira de impor métodos finais) indicaria imediatamente aos usuários o caminho certo para implementar o que eles desejam.

@shicks , @rjamesnw , estou na mesma situação que @GonziHere. Não quero que a classe base tenha métodos abstratos porque há uma funcionalidade que quero executar para cada um desses ganchos de ciclo de vida. O problema é que eu não quero que essa funcionalidade básica seja substituída acidentalmente quando alguém adiciona ngOnInit ou ngOnDestroy na classe filha sem chamar super() . Exigir override chamaria a atenção do desenvolvedor que esses métodos existem na classe base e que eles devem escolher se precisam chamar super() ;

Pela minha experiência escrevendo Java, @Override é tão comum que se torna um ruído. Em muitos casos, o método substituído é abstrato ou vazio (onde super() deve _não_ ser chamado), tornando a presença de override não um sinal particularmente claro de que super() é necessário.

Pela minha experiência escrevendo Java, @Override é tão comum que se torna um ruído. Em muitos casos, o método substituído é abstrato ou vazio (onde super() deve _não_ ser chamado), tornando a presença de override não um sinal particularmente claro de que super() é necessário.

Isso não tem nada a ver com o assunto em questão. Você também pode dizer para usar composição sobre herança para evitar o problema.

Para mim, a parte mais útil de override é obter um erro ao refatorar .
Agora é muito fácil renomear algum método em uma classe base e esquecer de renomear aqueles que o sobrescrevem.
Lembrar de chamar super não é tão importante. É até correto não chamá-lo em alguns casos.

Eu acho que há um mal-entendido sobre por que o _override_ seria importante. Não para forçar alguém a chamar o método _super()_, mas para torná-los cientes de que existe um e permitir que eles decidam conscientemente se desejam suprimir seu comportamento ou estendê-lo.

O padrão que uso para garantir a correção é usar Parameters e ReturnType enquanto me refiro muito explicitamente à classe base, algo assim:

class Base {
    public methodName(arg1: string, arg2: number): boolean {
        return false; // base behaviour, may be stub.
    }
}
class Derived extends Base {
    public methodName(...args: Parameters<Base["methodName"]>): ReturnType<Base["methodName"]> {
        const [meaningful, variableNames] = args;
        return true; // implemented behaviour here.
    }
}

Esse tipo de padrão é a base para minha sugestão de adicionar uma palavra-chave inherit . . Isso garante que quaisquer atualizações na classe base se propaguem automaticamente e se inválidas na classe derivada então um erro é dado, ou das mudanças de nome da classe base então um erro também é dado, também com a ideia inherit você não está limitado a apenas oferecer assinatura idêntica à classe base, mas pode estendê-la intuitivamente.

Eu também acho que há um mal-entendido sobre por que override seria importante, mas eu realmente não entendo todas essas questões com "se super() é necessário".

Para ecoar o que @lorenzodallavecchia e o autor original da edição disseram, marcar uma função como override é um mecanismo para evitar erros que acontecem quando a superclasse é refatorada, especificamente quando o nome e/ou assinatura de a função substituída muda ou a função é removida.

Lembre-se de que a pessoa que altera a assinatura da função substituída pode não estar ciente de que existem substituições. Se (por exemplo, em C++) as substituições não forem explicitamente marcadas como substituições, a alteração do nome/assinatura da função substituída não introduzirá um erro de compilação, simplesmente fará com que essas substituições não sejam mais substituições. (e .. provavelmente não será mais útil, não será mais chamado pelo código que costumava chamá-los e introduzirá um monte de novos bugs porque as coisas que deveriam chamar as substituições agora estão chamando a implementação base)

A palavra-chave override, conforme implementada em C++, salva a pessoa que altera a implementação base desses problemas, porque imediatamente após sua refatoração, erros de compilação indicarão a eles que existem várias substituições (que substituem uma implementação base agora inexistente) e dão uma pista para o fato de que eles provavelmente também precisam refatorar as substituições.

Existem benefícios secundários de usabilidade do IDE em ter o modificador override também (também mencionado pelo autor original), ou seja, ao digitar a palavra override o IDE pode mostrar várias possibilidades do que pode ser sobrescrito.

Juntamente com a palavra-chave override seria bom também introduzir um sinalizador que, quando ativado, exige que todos os métodos que substituem outro usem a palavra-chave override , para evitar casos em que você crie um método em uma subclasse e no futuro a classe base (que pode estar em uma biblioteca de terceiros) cria um método com o mesmo nome do método que você criou na subclasse (o que pode causar bugs agora, porque a subclasse sobrescreveu esse método ).

Obtendo um erro de compilação neste caso, dizendo que você precisa adicionar a palavra-chave override neste método (mesmo que você não possa realmente substituir o método, apenas altere seu nome para não substituir o método recém-criado em a classe base), seria muito melhor e evitaria possíveis bugs de tempo de execução.

Pela minha experiência escrevendo Java, @Override é tão comum que se torna um ruído.

Depois de muitos anos de Java, discordo totalmente da afirmação acima. A substituição é uma forma de comunicação, como exceções verificadas. Não se trata de gostar ou não gostar, mas de qualidade e expectativas.

E, portanto, um grande +1 para @lucasbasquerotto , mas de outro ponto de vista: se eu introduzir um método em uma subclasse, quero ter uma semântica clara de substituir ou não substituir. Por exemplo, se eu quiser substituir um método, quero ter uma maneira de informar isso explicitamente. Se algo estiver errado com minha implementação, por exemplo, um erro de digitação ou copiar e colar errado, quero obter feedback sobre isso. Ou de outra forma: se não espero substituir, também quero feedback, se a substituição ocorrer ocasionalmente.

Feedback de outro desenvolvedor Java... @override é o único recurso que realmente sinto falta do Typescript.

Eu tenho ~ 15 de experiência em Java e 1-2 anos ou Typescript tão confortável com ambos.

O zen do @override é que você pode remover um método de uma interface e a compilação será interrompida.

É como o inverso da vinculação apertada para novos métodos. Ele permite que você remova os antigos.

Se você implementar uma interface e adicionar um método, a compilação falhará, mas não há um inverso disso.

Se você remover um método, acabará com o código morto.

(embora talvez isso possa ser corrigido por um linter)

Para ser claro, quando eu disse que @Override era ruído, eu estava me referindo especificamente ao comentário sobre ser um sinal de que você precisa chamar super . A verificação que ele fornece é valiosa, mas para qualquer método substituído, é uma disputa completa se é necessário ou inútil chamar super . Como uma fração tão grande de métodos substituídos _não deveria_, é ineficaz para esse propósito.

Meu ponto principal é que, se você quiser garantir que as subclasses chamem de volta para seus métodos substituíveis, a única maneira eficaz de fazer isso é tornar o método final (consulte #33446, entre outros) e fazê-lo chamar em um _diferente-nomeado_ método de modelo vazio que pode ser substituído com segurança _sem_ a chamada super . Simplesmente não há outra maneira razoável de obter essa invariante. Sou completamente a favor de override , mas como alguém que foi queimado por usuários incorretamente subclassificando minhas APIs (e estou no gancho para não quebrá-las), acredito que vale a pena mudar a atenção para final como a solução correta para este problema, em vez de override como foi sugerido no upthread.

Estou um pouco confuso sobre por que as pessoas continuam sugerindo um de final ou override sobre o outro. Certamente queremos os dois , pois eles resolvem problemas diferentes.

Sobrepor
Impede que as pessoas que estão estendendo uma classe substituam acidentalmente uma função pela sua própria, quebrando coisas no processo sem nem saber. Ter uma palavra-chave override permite que o desenvolvedor saiba que ele está de fato substituindo uma função existente e, em seguida, ele pode optar por renomear sua função ou usar a substituição (e, então, eles podem descobrir se precisam chamar super).

Final
Evite que as pessoas que estão estendendo uma classe sobrescrevam uma função completamente, para que o criador original da classe possa garantir o controle completo.

@sam-s4s Queremos a definição também :-)

Exemplo de problema..

Inicial

class Base {}
class Entity extends Base {
    id() {
        return 'BUG-123' // busisess entity id
    }
}

Classe base refatorada

class Base {
    id() {
        return '84256635572' // storage object id
    }
}
class Entity extends Base {
    id() {
        return '12' // busisess entity id
    }
}

Temos sobrecarga acidental aqui.

Caso com a palavra-chave define

class Base {
    define id() {
        return '84256635572' // storage object id
    }
}
class Entity extends Base {
    define id() {
        return '12' // busisess entity id
    }
}

Deve ser erro: redefinição acidental.

Solução alternativa com decoradores

@nin-jin não é define o mesmo que _not_ usando override ?

@sam-s4s Queremos a definição também :-)

Ah, eu não vi ninguém aqui mencionar uma palavra-chave define - mas pelo que você descreve, suponho que você queira dizer a palavra virtual em C# ?

(também achei seu exemplo um pouco confuso, pois suas classes Base e Entity não estão relacionadas. Você quis dizer que Entity estende Base ?)

Acho que existem 2 cenários...
a) você escreve sua classe base, assumindo que tudo sem final pode ser substituído.
b) você escreve sua classe base assumindo que tudo sem virtual não pode ser substituído.

@sam-s4s Entity estende Base, é claro. :-) Corrigi minha mensagem. virtual é sobre outra coisa.
@lorenzodallavecchia não usar override é define | override para o compilador. Usar define é apenas define e o compilador pode verificar isso rigorosamente.

@nin-jin Em seu modelo, isso significa que não usar a palavra-chave override ainda é legal, certo? Por exemplo:

class Base {
  myMethod () { ... }
}

class Overridden extends Base {
  // this would not fail because this is interpreted as define | override.
  myMethod () { ... }
}

Idealmente, o exemplo acima produz um erro, a menos que você use a palavra-chave override . No entanto, imagino que haveria um sinalizador do compilador para desativar a verificação de substituição, onde a desativação é a mesma coisa que override | define para todos os métodos

@bioball sim, é necessário para compatibilidade com muitos códigos existentes.

@sam-s4s Entity estende Base, é claro. :-) Corrigi minha mensagem. virtual é sobre outra coisa.

Ainda não tenho certeza do que você quer dizer com definir ... esta é a definição de virtual em C#:

The virtual keyword is used to modify a method, property, indexer, or event declaration and allow for it to be overridden in a derived class.

Talvez você queira dizer o contrário, onde, em vez de precisar marcar a função como virtual para substituir, você pode marcar a função como define para significar que você deve usar o override palavra-chave para substituí-la?

(por que define ? Não vi essa palavra-chave em outro idioma)

Dei uma olhada neste tópico em alguns minutos, mas não vi ninguém propor o uso de override como forma de evitar a especificação duplicada de parâmetros e valores de retorno. Por exemplo:

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

class Snake extends Animal {
    override move(meters) {
    }
}

No exemplo acima, move teria o mesmo tipo de retorno obrigatório ( void ) e meters também teria o mesmo tipo, mas não seria necessário especificá-lo. A grande empresa para a qual trabalho está tentando migrar todo o nosso javascript para typescript, longe da digitação do compilador Closure. No entanto, enquanto em Closure podemos apenas usar @override e todos os tipos serão inferidos da superclasse. Isso é bastante útil para reduzir a possibilidade de incompatibilidade e reduzir a duplicação. Pode-se até imaginar implementar isso de forma que a extensão do método com parâmetros adicionais ainda seja possível, mesmo sem especificar tipos para os parâmetros especificados na superclasse. Por exemplo:

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

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

A motivação para eu vir aqui e escrever um comentário é que nossa empresa agora está exigindo que especifiquemos todos os tipos, mesmo para métodos substituídos, porque o typescript não tem essa funcionalidade (e estamos em uma longa migração). É bastante irritante.

FWIW, embora seja mais fácil de escrever, omitir tipos de parâmetro (e outras instâncias de inferência de tipo de arquivo cruzado) dificulta a legibilidade, pois exige que alguém não familiarizado com o código pesquise para descobrir onde a superclasse está definida para saber qual tipo meters é (supondo que você esteja lendo fora de um IDE, o que é bastante comum para projetos desconhecidos onde você ainda não tem um IDE configurado). E o código é lido com muito mais frequência do que é escrito.

@shicks Você poderia dizer isso literalmente sobre qualquer variável ou classe importada. Duplicar todas as informações necessárias em um único arquivo anula o propósito de abstração e modularidade. Forçar tipos a serem duplicados viola o princípio DRY.

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

Questões relacionadas

blendsdk picture blendsdk  ·  3Comentários

weswigham picture weswigham  ·  3Comentários

zhuravlikjb picture zhuravlikjb  ·  3Comentários

kyasbal-1994 picture kyasbal-1994  ·  3Comentários

bgrieder picture bgrieder  ·  3Comentários