Typescript: Admite clases finales (no subclasificables)

Creado en 26 abr. 2016  ·  124Comentarios  ·  Fuente: microsoft/TypeScript

Estaba pensando que podría ser útil tener una forma de especificar que una clase no debe ser subclasificada, de modo que el compilador advierta al usuario sobre la compilación si ve que otra clase extiende la original.

En Java, una clase marcada con final no se puede extender, por lo que con la misma palabra clave en TypeScript se vería así:

final class foo {
    constructor() {
    }
}

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

Comentario más útil

También debería haber un modificador final para los métodos:

class Foo {
  final fooIt():void{

  }
}

class Bar {
  fooIt():void {

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

Por ejemplo, a menudo uso el siguiente patrón, donde quiero evitar urgentemente que fooIt sea anulado:

import Whatever ...

abstract class Foo {
  private ImportantVariable:boolean;

  protected abstract fooIt_inner:Whatever();

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

Todos 124 comentarios

una clase con constructor privado no es ampliable. considere usar esto en su lugar.

Por lo que recordé, estaba seguro de que al compilador no le gustaba la palabra clave privada en el constructor. Quizás no estoy usando la versión para pegar

Esta es una característica nueva, se lanzará en TS 2.0, pero puede probarla usando typescript@next . consulte https://github.com/Microsoft/TypeScript/pull/6885 para obtener más detalles.

Está bien, gracias

¿El constructor privado también hace que una clase no se pueda instanciar fuera de la clase? No es una respuesta correcta para la clase final.

Java y / o C # usan la clase final para optimizar su clase en tiempo de ejecución, sabiendo que no será especializada. este yo diría que es el valor principal para el apoyo final. En TypeScript no hay nada que podamos ofrecer para que su código se ejecute mejor que sin final.
Considere usar comentarios para informar a sus usuarios sobre el uso correcto de la clase y / o no exponer las clases que pretende que sean definitivas y exponer sus interfaces en su lugar.

No estoy de acuerdo con eso, en cambio estoy de acuerdo con duanyao. Private no resuelve ese problema, porque también quiero que las clases que son finales se puedan instanciar mediante un constructor. Además, no exponerlos al usuario me obligaría a escribir fábricas adicionales para ellos. Para mí, el principal valor del soporte final es que evita que los usuarios cometan errores.
Argumentando así: ¿Qué ofrece TypeScript para hacer que mi código se ejecute más rápido, cuando uso tipos en firmas de funciones? ¿No es también solo para evitar que los usuarios cometan errores? Podría escribir comentarios que describan qué tipos de valores debe pasar un usuario como parámetro. Es una lástima, que tales extensiones como una palabra clave final simplemente se eliminen, porque en mi opinión choca con la intención original de mecanografiado: hacer que JavaScript sea más seguro agregando un nivel de compilación, que realiza tantas comprobaciones como sea posible para evitar tantos errores por adelantado como sea posible. ¿O entendí mal la intención de TypeScript?

También debería haber un modificador final para los métodos:

class Foo {
  final fooIt():void{

  }
}

class Bar {
  fooIt():void {

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

Por ejemplo, a menudo uso el siguiente patrón, donde quiero evitar urgentemente que fooIt sea anulado:

import Whatever ...

abstract class Foo {
  private ImportantVariable:boolean;

  protected abstract fooIt_inner:Whatever();

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

El argumento sobre costo versus utilidad es bastante subjetivo. La principal preocupación es que cada nueva característica, construcción o palabra clave agrega complejidad al lenguaje y la implementación del compilador / herramientas. Lo que intentamos hacer en las reuniones de diseño de idiomas es comprender las compensaciones y solo agregar nuevas funciones cuando el valor agregado supera la complejidad introducida.

El problema no está bloqueado para permitir que los miembros de la comunidad continúen agregando comentarios. Con suficientes comentarios y casos prácticos convincentes, los problemas se pueden reabrir.

En realidad, el concepto final es muy simple, no agrega ninguna complejidad al lenguaje y debe agregarse. Al menos por métodos. Agrega valor, cuando mucha gente trabaja en un gran proyecto, es valioso no permitir que alguien anule métodos, que no deberían anularse.

En TypeScript no hay nada que podamos ofrecer para que su código se ejecute mejor que sin final.

¡Guau, encogete! Los tipos estáticos tampoco hacen que su código se ejecute mejor, pero la seguridad es algo bueno.

Final (sellado) está a la altura de la anulación como características que me gustaría ver para hacer que las personalizaciones de clases sean un poco más seguras. No me importa el rendimiento.

Los tipos estáticos tampoco hacen que su código se ejecute mejor, pero la seguridad es algo bueno.

Exactamente. Así como private evita que otros llamen al método, final limita que otros anulen el método.

Ambos son parte de la interfaz OO de la clase con el mundo exterior.

Totalmente de acuerdo con @pauldraper y @mindarelus. Por favor implemente esto, esto tendría mucho sentido, realmente lo extraño actualmente.

No creo que final solo sea beneficioso para el rendimiento, también es beneficioso para el diseño, pero no creo que tenga ningún sentido en TypeScript. Creo que esto se resuelve mejor rastreando los efectos de mutabilidad de Object.freeze y Object.seal .

@aluanhaddad ¿Puedes explicar eso con más detalle? ¿Por qué cree que "no tiene ningún sentido en TypeScript"?
Congelar o sellar un objeto significa no permitir agregar nuevas propiedades a un objeto, pero no evita agregar propiedades a un objeto derivado, por lo que incluso si sellaría la clase base, aún podría anular el método en una clase secundaria, lo que extiende esa clase base . Además, no pude agregar ninguna propiedad a la clase base en tiempo de ejecución.

La idea de usar final en una clase o método de clase en Java tiene más que ver con minimizar la mutabilidad del objeto para la seguridad de los subprocesos, en mi opinión. (Ítem 15. Joshua Bloch, Eficaz Java)

No sé si estos principios se trasladan a javascript, ya que todo en JS es mutable (corríjame si me equivoco). Pero TypeScript no es Javascript, ¿no?

Realmente me gustaría ver esto implementado. Creo que ayudará a crear un código más robusto. Ahora ... cómo eso se traduce en JS, honestamente, probablemente no tenga que hacerlo. Simplemente puede quedarse en el lado mecanografiado de la cerca donde está el resto de nuestra verificación en tiempo de compilación.

Claro que puedo vivir sin él, pero eso es parte de lo que es el mecanografiado, ¿verdad? ¿Comprobando dos veces nuestros errores pasados ​​por alto?

Para mí, final desempeñaría el mismo papel en mecanografiado que private o mecanografiado, es decir, contrato de código. Se pueden utilizar para garantizar que su contrato de código no se rompa. Me gustaria mucho.

@ hk0i también se menciona en el artículo 17 (segunda edición) de una manera similar a lo que se ha repetido aquí:

Pero, ¿qué pasa con las clases concretas ordinarias? Tradicionalmente, no son definitivos ni están diseñados y documentados para subclases, pero esta situación es peligrosa. Cada vez que se realiza un cambio en una clase de este tipo, existe la posibilidad de que las clases cliente que amplían la clase se rompan. Este no es solo un problema teórico. No es raro recibir informes de errores relacionados con subclases después de modificar las partes internas de una clase concreta no final que no fue diseñada y documentada para herencia.

La mejor solución a este problema es prohibir la subclasificación en clases que no están diseñadas y documentadas para ser subclasificadas de forma segura. Hay dos formas de prohibir las subclases. El más fácil de los dos es declarar final a la clase. La alternativa es hacer que todos los constructores sean privados o de paquetes privados y agregar fábricas estáticas públicas en lugar de los constructores.

Yo diría que no aumenta la complejidad cognitiva del lenguaje dado que la palabra clave abstracta ya existe. Sin embargo, no puedo hablar sobre el impacto en la implementación / rendimiento y respeto absolutamente la protección del lenguaje desde ese ángulo. Creo que separar esas preocupaciones sería útil para decidir si implementar o no esta característica.

Creo que final sería una excelente adición para sellar una clase. Un caso de uso es que puede tener muchos métodos públicos en su clase pero exponer, a través de una interfaz, solo un subconjunto de ellos. Puede realizar pruebas unitarias de la implementación rápidamente, ya que tiene todos estos métodos públicos, mientras que el consumidor real usa la interfaz para limitar el acceso a ellos. Ser capaz de sellar la implementación garantizaría que nadie extienda la implementación o cambie los métodos públicos.

También puede asegurarse de que nadie herede su clase. TypeScript debería estar ahí para hacer cumplir esas reglas, y la sugerencia de comentar parece ser un enfoque vago para resolver este caso de uso. La otra respuesta que leí es sobre el uso de privado, que solo es adecuado para una situación particular, pero no la que expliqué anteriormente.

Como muchas personas en este hilo, votaría para poder sellar la clase.

@mhegazy Los constructores privados y las clases selladas / finales tienen semánticas muy diferentes. Claro, puedo evitar la extensión definiendo un constructor privado, pero tampoco puedo llamar al constructor desde fuera de la clase, lo que significa que luego necesito definir una función estática para permitir que se creen instancias.

Personalmente, abogaría por tener comprobaciones del compilador sellado / final para garantizar que las clases y métodos marcados como sellados / final no se puedan extender o anular.

En el contexto de mi problema, quiero poder tener un constructor público para poder crear una instancia del objeto, pero evitar la extensión de la clase, y solo la adición de sellado / final lo permitirá.

Hay una tarea: escriba un código que

  • opera objetos inmutables
  • es libre de herencia
  • métodos de reflexión gratuitos

Y la palabra clave final es necesaria aquí, en mi humilde opinión.

Veo final como una excelente manera de recordarte a ti mismo y a los usuarios de tu código qué métodos protegidos deberían anular y cuáles no. Como nota al margen, también soy partidario de indicar explícitamente que está anulando, en parte para que el lector lo sepa, pero también para que el transpilador se queje si escribe el nombre del método.

@zigszigsdk Dado que los métodos nunca se anulan, ¿cómo funcionaría esto?

Por final :
De la misma manera que funciona ahora, excepto que el transpilador se quejaría si uno oculta el método de un super -que ha sido declarado final- del contexto this .

Por override :
De la misma manera que funciona ahora, excepto que el transpilador se quejaría si uno declara un método override , y su super no tiene un método con ese nombre, o tiene uno pero se declara final .
Es posible que también le advierta si oculta el método de un super del contexto this y no declara la anulación.

Java y / o C # usan la clase final para optimizar su clase en tiempo de ejecución, sabiendo que no será especializada. este yo diría que es el valor principal para el apoyo final.

@mhegazy ¡ No del todo! Otra función importante es ser explícito sobre qué partes de la clase esperan ser anuladas.

Por ejemplo, consulte el elemento 17 en "Java efectivo": "Diseñe y documente para herencia o prohíbalo":

No es raro recibir informes de errores relacionados con subclases después de modificar las partes internas de una clase concreta no final que no fue diseñada y documentada para herencia. La mejor solución a este problema es prohibir la subclasificación en clases que no están diseñadas y documentadas para ser subclasificadas de forma segura. Hay dos formas de prohibir las subclases. El más fácil de los dos es declarar final a

Lo mismo ocurre con los métodos finales, en mi opinión. Es muy raro que una clase esté diseñada para admitir la invalidación de cualquiera de sus métodos públicos. Esto requeriría un diseño muy complejo y una gran cantidad de pruebas, porque tendría que pensar en todas las combinaciones posibles de estados que podrían resultar de una combinación de comportamiento no anulado y anulado. Entonces, en cambio, un programador podría declarar todos los métodos públicos finales excepto uno o dos, lo que disminuiría drásticamente el número de tales combinaciones. Y más a menudo que no, uno o dos métodos reemplazables es precisamente lo que se necesita.

Creo que las clases y métodos final son muy importantes. Espero que lo implementes.

Otro caso de usuario convincente @mhegazy . Hoy aprendí el patrón de método de plantilla y descubrí que se requiere final para prohibir que las subclases cambien el método de plantilla como dice el patrón de método de plantilla en wikipedia . Pero no puedo hacer esto en mi adorable TypeScript. ¡Qué pena!

El patrón de plantilla permite que las subclases redefinan ciertos pasos de un algoritmo sin cambiar la estructura del algoritmo

Para mí es tan simple como tratar de imponer la composición en los casos por encima de la herencia. Sin final no puedes hacer esto.

Por mucho que también me gustaría ver que final llegue a mecanografiar, creo que el mensaje subyacente que Microsoft está tratando de enviarnos es que si queremos final , deberíamos usar un lenguaje que lo apoya.

Me gustaría ver este problema reabierto e implementado.

Al crear una biblioteca que va a usar internamente o compartir públicamente (como en npm), no desea que quienes la usan tengan que navegar por el código y buscar comentarios o documentos para ver si la clase puede / puede no se sobrescribirá. Si lo sobrescriben arroja un error.

En mi código, tengo una clase que se extiende y, cuando ocurre algún tipo de evento, se activa un método. Si la subclase define el método, lo hará; de lo contrario, volverá al predeterminado. Sin embargo, también hay otros métodos en la clase que no son métodos "alternativos", son más métodos auxiliares, como agregar un elemento al DOM, en cuyo caso no quiero que estos métodos se sobrescriban.

Mis 5 centavos sobre el tema son que esto debería reabrirse e implementarse.

Lo usaría para obligar a los clientes de bibliotecas a crear sus propias clases concretas y no se extienden a partir de otras que ya existen. Como dijo @lucasyvas , favorezca la composición. O simplemente aplique una nueva implementación de clase concreta para proporcionar a la inyección de dependencia en angular, por ejemplo.

También voto por la reapertura, con apoyo tanto para clases como para métodos.

No digo que no deberíamos, simplemente no estoy seguro de cómo funcionaría el patrón de la palabra clave final en el contexto de la escritura mecanografiada.

¿No impediría la palabra clave final a los usuarios / programadores anular / heredar de dicha clase / método?

Mi forma de ver Final es que es "Congelar" los datos, lo que significa que ya no se pueden cambiar. Quiero decir que esto puede ser agradable de usar, aunque puede ser realmente una molestia para las personas que quieren corregir "errores" o cambiar algún comportamiento.

@niokasgami Freeze está relacionado con [data] (https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/freeze)
y ya está disponible como parte de ES6. Final se aplica a clases y métodos (la variable puede ser de solo lectura y, por lo tanto, actúa como "final", ya que no podemos anular una supervariable).

La palabra clave "Final" se utiliza en JAVA, donde selladas en C # .

Como el mecanografiado está fuertemente inspirado en C #, debería haber una mayor probabilidad de que "sellado" sea el elegido. Ahora, ¿posiblemente podríamos tener confusión con el sello ES6 que básicamente sella el objeto en tiempo de ejecución?

@Xample Creo que también puedes usar final en variables locales en Java. Recuerdo tener que hacerlo cuando estaba escribiendo una aplicación para Android. Creo que me obligó a hacerlo con un evento, pero no estoy 100% seguro.

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

@TheColorRed Bueno, la variable local final en Java significa constante, ¿no es así?

@EternalPhane Supongo que sí, siempre he usado const en un alcance similar global ... Nunca pensé en usarlo dentro de una función o método ... Supongo que mi comentario anterior es nulo entonces ...

@Xample Sí, veo su punto para Sealed from the C #, no está funcionando de la misma manera. Honestamente, en mi opinión, AMBAS palabras clave seal y final realmente no podrían ingresar a la categoría, ya que ambas pueden ser confusas (selladas para sellar, etc.) y Final, no estamos seguros. cómo aplicarlo ya que en Java se usa como una constante: /

En Java puedes final cualquier variable, método o clase. Para las variables, significa que las referencias no se pueden reasignar (para los tipos que no son de referencia, esto significa que tampoco puede cambiar sus valores). Para las clases, significa que no hay extensiones ( extends ... palabra clave). Para los métodos, significa que no se anulan las subclases.

Razones principales para final en Java:

  • Reducir la inmutabilidad de las variables: para la seguridad de los hilos
  • Finalice las clases para que no se puedan heredar: fuerza la composición sobre la herencia porque la herencia es intrínsecamente complicada cuando sus subclases comienzan a llamar inadvertidamente a supermétodos
  • Evita errores de programación simples: si no tiene la intención de cambiar un valor, es una buena noticia que ya no es posible
  • Se usa para lo que la mayoría de la gente piensa cuando imagina constantes: valores reutilizables que verá en todas partes, como en public static final String KEY = "someStringKeyPathHere" , útiles para reducir errores pero tiene el beneficio adicional de tiempo de compilación de no recrear múltiples objetos de cadena por el mismo valor

Es posible que haya omitido un par de casos de uso, pero estaba tratando de mantenerlo muy general y dar algunos ejemplos.

@TheColorRed Android lo requiere cuando declara una variable fuera de una devolución de llamada (clase anónima) y luego hace referencia a ella dentro. Creo que esto se alinea nuevamente con los problemas de seguridad de los subprocesos. Creo que están tratando de evitar que reasigne la variable de otro hilo.

A pesar de su utilidad, no creo que veamos esta función en el corto plazo. Podría ser un buen momento para buscar otras soluciones o, como en mi caso, soltar el mecanografiado y lidiar con el descuido en JS. Javascript tiene muchos "beneficios" al no tener ninguna sugerencia de este tipo ni nada y, en última instancia, su código se convertirá a JS de todos modos. Si desea compatibilidad con el navegador, puede escribir ES6 y usar Babel para convertirlo.

Creo que de manera realista, aunque puedes dar una palmada en la muñeca por algunos de estos beneficios, no obtienes ninguno de los beneficios reales del final al estilo java una vez que se ha convertido a js.

@ hk0i Creo que puedo entender tu punto, pero en general estoy de acuerdo y un poco en desacuerdo al mismo tiempo sobre tu opinión.

La mayor parte del código en el mecanografiado es, diría yo, para depurar / rastrear, para ayudar al programador a hacer un código "limpio" (por favor, no me cite que sé que tienen un código espagueti mecanografiado aquí, jaja)

la mayoría de las funciones de mecanografiado no se transpilan la mayor parte del tiempo cuando se convierten a JS porque simplemente no existen en JS.

Aunque cuando ejecuta / escribe su código, es útil ver el error en su código y rastrearlo más fácilmente que con JS (y HECK JS puede ser difícil de rastrear errores jajaja)

Así que creo que el uso de la palabra clave final podría verse como un bloqueador para que el usuario de la API sepa: Oh, está bien, esta clase no debería extenderse ya que su extensión podría provocar un comportamiento extraño, etc.

Mecanografiado como buen lenguaje Me entristece descubrir que carece de alguna característica importante como la palabra clave de clase estática "verdadera" que impide que las personas llamen al constructor de clases pero aún así puedan acceder a él.

@ hk0i No estoy de acuerdo tampoco, el propósito del mecanografiado es estructurar el código y, por lo tanto, proporcionar las herramientas para hacerlo. NO TENEMOS que usar final en Java, generalmente lo usamos cuando sabemos que extender una clase o método podría llevar a la introducción de efectos secundarios (o por cualquier otra razón de seguridad).

Confiar en las soluciones ES6 actuales que agregan una verificación de mutabilidad en tiempo de ejecución (por ejemplo, usar Object.seal u Object.freeze) le hace perder el beneficio de tener errores directamente cuando escribe su código (a pesar de que se podría hacer fácilmente con este decorador ).

@TheColorRed sí ... y en el mecanografiado tenemos una distinción para variable local y de clase.

  • readonly para una variable de clase
  • const : para cualquier otra variable

Ninguno de ellos sería exacto para el método o la clase. Anular un método no significa que lo estemos redefiniendo (piense en métodos virtuales en C ++), simplemente definimos que un método será el principal que se llamará antes que el super uno (que podemos llamar usando super ).

@niokasgami quizás el nombre no sea realmente un problema ya que aún entenderemos que Object.seal se aplicó a ... un objeto mientras sellaba una clase y un método a la estructura misma.

TL; DR:

  • preventExtensions sería un buen nombre explícito para evitar la extensión de clases
  • preventOverrides sería un buen nombre candidato para evitar clases anuladas
    PERO: seal se usa ampliamente en C #, es más corto y tiene el mismo comportamiento esperado.

Por cierto, olvidé mencionar que también hay Object.preventExtensions()
Las diferencias se pueden ver aquí.

leyendo

  • siempre es posible ... ¿quién quiere una variable de solo escritura?

actualizar y eliminar

  • evitar redefinir una eliminación de una variable: solo lectura para un miembro de la clase, const (para cualquier otro)
  • evitar redefinir un método: nada le impide hacer (dentro de un método de clase) this.aMethod = ()=>{} . ¿Deberían los métodos ser readonly también para prevenir tales hackeos? lo mismo por delete this.aMethod

creando

  • solo se aplica al objeto o como estamos hablando aquí sobre la estructura: class como la clase definió los miembros del objeto, el mecanografiado implícitamente ya previene la creación de nuevas propiedades sobre la marcha ... por ejemplo this.unKnownProperty = "something" ts generará un error de tiempo de compilación TS2339 como se esperaba.

extensión

  • solo se aplica a las clases, todavía no estamos haciendo nada en el objeto en sí ... (a diferencia de la creación que está en tiempo de ejecución). Aquí es donde final o seal serían relevantes. La pregunta es ¿cómo mantener la coherencia con el congelamiento, sellado y preventExtensions de ES6? (si es necesario).
    Extender una clase significa que evitamos crear otra clase a partir de esta clase. Luego podemos leerlo pero no eliminarlo (quién elimina la clase de todos modos). La pregunta no es la columna "actualización". ¿Se puede actualizar una clase final ? ¿Qué es una clase actualizable? Supongo que sí ... ¿pero entonces la actualización sería implementar esta clase (entendiendo la interfaz de la clase)? La implementación de otra clase siempre debería ser posible ... pero no está relacionada con la actualización de la clase inicial. Bueno ... quizás simplemente llegaron a las mismas preguntas escribiendo c # y decidieron que seal era la palabra clave adecuada.

primordial

  • evitar anular el método actual en una clase de extensión. Nuevamente, no estamos sobrescribiendo nada todavía, ya que estamos hablando de una definición (método dentro de una clase). Por lo tanto, evite la creación, permita la lectura, permita la actualización (podríamos evitar la actualización del método o eliminarlo usando readonly si también fuera aplicable a los métodos). ¿Mejor nombre? preventOverrides ? o de nuevo seal como se usa en C #

BONUS: Como sellar un método generalmente es para evitar que las personas anulen el supercódigo obligatorio, hice la propuesta de obligar a las personas a llamar al supermétodo (el mismo comportamiento que para el constructor pero para cualquier método) no dude en votar a favor si creo que sería útil.

No me malinterpretes, no estoy en contra de esto. Estoy muy a favor. Solo estoy tratando de respaldar la mentalidad paradójica de Microsoft de por qué no debería ser una característica, que esencialmente dice que debido a que no es una característica de JavaScript, no puede ser una característica de TypeScript.

... Si entiendo correctamente.

@ hk0i Generics, readonly, y muchas otras características no son características de JavaScript, pero aún se agregan a TypeScript, así que no creo que esa sea la razón ...

TypeScript solo evita las funciones que requieren generar código de una manera no sencilla. Los genéricos, de solo lectura, etc. son nociones puramente en tiempo de compilación que pueden simplemente borrarse para producir la salida generada, por lo que son un juego limpio.

final se puede "borrar" en tiempo de compilación, así que ¿por qué eso no lo convierte en un "juego justo"?

🤷‍♂️

@TheColorRed Creo que esto más o menos esto o tienen otra prioridad para integrar.
O no están seguros de cuán predecible podría funcionar esta nueva palabra clave en el tiempo de compilación / codificación. si lo piensa, el diseño de la palabra clave final puede ser extremadamente vago. final se puede usar mucho para muchos propósitos. Como puede ver en la documentación de Jsdoc, puede usar la etiqueta "@final" que le dice al intérprete de jsdoc que esta variable está congelada, por lo tanto, es de solo lectura.

aquí un choque de diseño. Readonly es aplicable en variable y getter si recuerdo cuál "congeló" la variable y la hizo de solo lectura y final, la hizo final, por lo tanto, también de solo lectura.

esto puede causar confusión al programador que no está seguro de si debe etiquetar sus variables como final o readonly.

A MENOS QUE reservemos esta palabra clave exclusivamente para declaraciones de clases y métodos, creo que el comportamiento o final entrarían en conflicto con readonly.

Creo que en lugar de sellado (que puede entrar en conflicto con object.seal) o final (que este propósito de diseño choca con la palabra clave de solo lectura) podríamos optar por una forma más "directa" de nombrarlo y diseñarlo.

¿Tomar nota de esto simplemente una "idea" de un comportamiento que es similar pero al mismo tiempo diferente del comportamiento de C #? Me inspiré en cómo C # normalmente no es reemplazable y luego tienes que decir que este método es virtual.

`Ejemplo de espacio de nombres {

export class myClass {

    constructor(){}

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

    }

    public myMethodB(){

    }
}

export class MyClassB extends myClass {

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

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

    }

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

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

    constructor()

    // nonoverridable by default.
    public MyMethod(){

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

} `

Editar: no incluí Variable y obtuve ya que readonly ya existe.

@mhegazy Considere la posibilidad de reabrir este número. La respuesta de la comunidad parece estar abrumadoramente del lado de agregar una palabra clave final , como Java ( final ), C # ( sealed , readonly ), PHP ( final ), Scala ( final , sealed ) y C ++ ( final , virtual ) tienen. No puedo pensar en un lenguaje de programación orientada a objetos de tipo estático que no tenga esta característica, y no he escuchado un argumento convincente de por qué TS no lo necesita.

@bcherny @mhegazy Estoy completamente de acuerdo con lo anterior. No puedo ver ninguna razón bien argumentada por la que final no puede ser simplemente otra restricción de tiempo de compilación, como la mayoría del azúcar sintáctico que vemos en TypeScript: seamos realistas, tipado estático, genéricos, modificadores de acceso, alias de tipo , interfaces ... TODO AZÚCAR! Entiendo que el equipo de TypeScript probablemente quiera enfocarse en llevar el lenguaje en otras direcciones, pero en serio, esto es OOP 101 ... ¡esto debería haberse pensado hace mucho tiempo, cuando TypeScript aún era muy joven!

@mhegazy , ¿puedo remitirlo a un comentario anterior que hizo sobre este tema?

una clase con constructor privado no es ampliable. considere usar esto en su lugar.

En el momento de escribir este comentario, este comentario ha sido rechazado 21 veces.
¡Claramente esto NO ES LO QUE LA COMUNIDAD QUIERE COMO SOLUCIÓN!

wow, obtuve muchos comentarios sobre esto. No puedo entender por qué no me interesa implementar

Aquí hay muchas quejas realmente improductivas. Por favor, no se limite a expresar su frustración: aquí se nos permite tomar decisiones y la comunidad puede dar su opinión, pero el simple crujir de dientes no hará que nadie cambie de opinión. Sea específico y procesable; no nos va a convencer que la gente simplemente se presente para decir HAGA ESTA FUNCIÓN AHORA .

Tenga en cuenta que este problema se trata de clases finales / selladas . Ha habido una discusión fuera del tema de los métodos finales / sellados

Se consideraron algunos puntos cuando revisamos esta última vez

  • LAS CLASES SON UNA FUNCIÓN DE JAVASCRIPT, NO UNA FUNCIÓN DE TYPESCRIPT . Si realmente siente que las clases son completamente inútiles sin final , eso es algo que debe discutir con el comité TC39 o llevarlo a es-discusion. Si nadie está dispuesto o es capaz de convencer a TC39 de que final es una característica imprescindible, entonces podemos considerar agregarla a TypeScript.
  • Si es legal invocar new en algo, eso tiene una correspondencia de tiempo de ejecución uno a uno con la herencia. La noción de algo que puede ser new 'd pero no super ' d simplemente no existe en JavaScript
  • Estamos haciendo todo lo posible para evitar convertir TypeScript en una sopa de palabras clave de programación orientada a objetos. Hay muchas mejores mecánicas de composición que la herencia en JavaScript y no necesitamos tener todas las palabras clave que tienen C # y Java.
  • Puede escribir trivialmente una verificación de tiempo de ejecución para detectar la herencia de una clase "debería ser final", lo que debería hacer de todos modos si está "preocupado" por alguien que herede accidentalmente de su clase, ya que no todos sus consumidores podrían estar usando TypeScript. .
  • Puede escribir una regla de pelusa para hacer cumplir una convención estilística de la que ciertas clases no se heredan
  • El escenario que alguien heredaría "accidentalmente" de tu clase que debería ser sellada es algo extraño. También diría que la evaluación individual de alguien de si es "posible" o no heredar de una clase determinada es probablemente bastante sospechosa.
  • Básicamente, hay tres razones para sellar una clase: seguridad, desempeño e intención. Dos de ellos no tienen sentido en TypeScript, por lo que debemos considerar la complejidad frente a la ganancia de algo que solo hace 1/3 del trabajo que hace en otros tiempos de ejecución.

Usted es libre de no estar de acuerdo con todos estos, pero por favor traiga algunos comentarios prácticos y específicos sobre cómo la falta de final está dañando su capacidad para usar las clases de JavaScript de manera efectiva.

no he escuchado un argumento convincente de por qué TS no lo necesita

Solo un recordatorio de que no es así como funciona. Todas las funciones comienzan en -100 . De esa publicación

En algunas de esas preguntas, las preguntas preguntan por qué "eliminamos" o "omitimos" una característica específica. Esa redacción implica que comenzamos con un lenguaje existente (C ++ y Java son las opciones populares aquí), y luego comenzamos a eliminar funciones hasta que llegamos a un punto en el que nos gustó. Y, aunque puede ser difícil de creer para algunos, no es así como se diseñó el lenguaje.

Tenemos un presupuesto de complejidad finita. Todo lo que hacemos hace que todo lo que hagamos sea un 0,1% más lento. Cuando solicita una función, está solicitando que todas las demás funciones se vuelvan un poco más difíciles, un poco más lentas, un poco menos posibles. Nada entra por la puerta aquí porque Java lo tiene o porque C # lo tiene o porque solo serían unas pocas líneas de código o puede agregar un interruptor de línea de comandos . Las funciones deben pagarse por sí mismas y por toda su carga sobre el futuro.

@RyanCavanaugh Si bien no estoy seguro de estar de acuerdo con la decisión, aprecio la respuesta realmente reflexiva y detallada, y por tomarse el tiempo para escribirla. Entiendo que desea mantener el lenguaje sencillo; es bueno reflexionar sobre si las características que tienen otros lenguajes populares son realmente valiosas o simplemente comunes.

@RyanCavanaugh

No creo que el punto aquí, en este foro, sea ... "usar efectivamente las clases de JavaScript".

Lo siento, no puedo resistir, y solo agregue a las "quejas improductivas que están sucediendo aquí": la palabra clave final es útil por todas las razones explicadas, en detalle, y con argumentos de otros.

Estamos haciendo todo lo posible para evitar convertir TypeScript en una sopa de palabras clave de programación orientada a objetos. Hay muchas mejores mecánicas de composición que la herencia en JavaScript ...

Como cuando usa final para imponer la composición sobre la herencia en (no inserte aquí el nombre del idioma mecanografiado) 😈
(Lo siento, no pude resistir)

Pero más concretamente, parece que TS tiene la intención de ser principalmente una capa de compatibilidad para introducir "JavaScript de hoy" o algo por el estilo (a la Babel) con la excepción de que agrega alguna verificación de tipo adicional.

Creo que la esencia del argumento contrario para implementar esto es que si desea las características de otro idioma, use el otro idioma en su lugar. Si desea JavaScript, use JavaScript. Si quieres TypeScript, úsalo.

Sé que Kotlin puede compilar en JS y tiene muchas de las características que creo que la gente de aquí disfrutaría, por lo que es algo que podría ser útil para analizar. Sin embargo, creo que agrega alguna biblioteca JS como dependencia (no lo he probado yo mismo).

La conclusión principal es que si no está satisfecho con TypeScript, no lo use.

@RyanCavanaugh

Sobre los puntos que planteaste anteriormente ...

LAS CLASES SON UNA FUNCIÓN DE JAVASCRIPT, NO UNA FUNCIÓN DE TYPESCRIPT . Si realmente siente que las clases son completamente inútiles sin final , eso es algo que debe discutir con el comité TC39 o llevarlo a es-discusion. Si nadie está dispuesto o es capaz de convencer a TC39 de que final es una característica imprescindible, entonces podemos considerar agregarla a TypeScript.

He estado siguiendo TypeScript desde 0.7 y en ese entonces, IIRC apuntaba a ES3-ES5. Las clases eran definitivamente una característica de TypeScript en ese entonces. El mismo argumento se aplica a los decoradores: están en las cartas para ES, pero ya están aquí en TypeScript como una característica experimental.

Como comunidad, no estamos argumentando que el compilador de TypeScript deba emitir de alguna manera una clase de JavaScript que sea final . Una comprobación en tiempo de compilación sería suficiente, de la misma forma que las comprobaciones en tiempo de compilación para los modificadores de acceso son suficientes; después de todo, tampoco son características de JavaScript, pero TypeScript aún las hace posibles.

Si final convirtió en una característica de ES, entonces presumiblemente podría refactorizar ese bit del emisor cuando apunte a ES * para generar final adecuada (de la misma manera que hizo para las clases, cuando se introdujeron en ES6)?

Si es legal invocar new en algo, eso tiene una correspondencia de tiempo de ejecución uno a uno con la herencia. La noción de algo que puede ser new 'd pero no super ' d simplemente no existe en JavaScript

Nuevamente, no creo que la comunidad espere que hagas algo mágico aquí para hacer cumplir final en tiempo de ejecución. Como ha dicho con precisión, "no existe en JavaScript", pero como se mencionó, una restricción de tiempo de compilación sería suficiente.

Estamos haciendo todo lo posible para evitar convertir TypeScript en una sopa de palabras clave de programación orientada a objetos. Hay muchas mejores mecánicas de composición que la herencia en JavaScript y no necesitamos tener todas las palabras clave que tienen C # y Java.

Es una frase bastante conocida en la comunidad de desarrollo de software en su conjunto: "Favorecer la composición sobre la herencia", sin embargo, "favorecer" no significa no utilizar la herencia como una declaración general. Claro, usted no _necesita_ todas las palabras clave que tienen C # y Java, pero debe haber tenido un fundamento para permitir clases abstract (que por cierto "no existe en JavaScript"), así que ¿por qué no? final clases?

Puede escribir trivialmente una verificación de tiempo de ejecución para detectar la herencia de una clase "debería ser final", lo que debería hacer de todos modos si está "preocupado" por alguien que herede accidentalmente de su clase, ya que no todos sus consumidores podrían estar usando TypeScript. .

Lo mismo podría decirse de C # y Java, pero no necesito verificaciones de tiempo de ejecución porque tengo sealed y final para hacer eso por mí a nivel del compilador.

Si no todos mis consumidores estuvieran usando TypeScript, tendría mucho más de qué preocuparme que simplemente heredar de una clase final ; por ejemplo, acceso a miembros private o protected , sin verificación de tipo estático, o la capacidad de construir una clase abstract .

¿Nuestro código debería estar plagado de comprobaciones en tiempo de ejecución para todas estas cosas (algunas de las cuales ni siquiera son posibles, podría agregar) solo para asegurarnos de que nuestros consumidores que no son de TypeScript estén lo más seguros posible?

Puede escribir una regla de pelusa para hacer cumplir una convención estilística de la que ciertas clases no se heredan

Esto implicaría que quien esté consumiendo su código está ejecutando el linter, por lo que todavía no es una solución adecuada; es una solución.

El escenario que alguien heredaría "accidentalmente" de tu clase que debería ser sellada es algo extraño. También diría que la evaluación individual de alguien de si es "posible" o no heredar de una clase determinada es probablemente bastante sospechosa.

Los desarrolladores buenos / excelentes piensan en su razón de ser para hacer algo, como extender una clase. Menos que eso y terminas con una mentalidad de "pero funciona", sin entender realmente por qué.

Hacer una pregunta que comienza con can, generalmente tiende a tener una respuesta más definitiva que las preguntas que comienzan con debería .

Básicamente, hay tres razones para sellar una clase: seguridad, desempeño e intención. Dos de ellos no tienen sentido en TypeScript, por lo que debemos considerar la complejidad frente a la ganancia de algo que solo hace 1/3 del trabajo que hace en otros tiempos de ejecución.

Podría decirse que yo diría que mecanografiado en realidad cumplir con dos de estas razones: la seguridad y la intención, sin embargo, si esto se llevó a cabo puramente como un cheque en tiempo de compilación, entonces sólo podía hacer esto a nivel del compilador, en lugar de, como señalan con razón , en el nivel de tiempo de ejecución.

No creo que no tener final esté dañando mi capacidad para usar las clases de JavaScript de manera efectiva, ni las clases de JavaScript son inutilizables sin él. Siento que final está más del lado de una característica "agradable de tener" que de una característica "necesaria", pero lo mismo podría decirse de muchas de las características de TypeScript.

Creo que la queja principal aquí es que TypeScript nos permite crear clases abstract y _open_, pero en términos de herencia y polimorfismo no nos permite pintar una imagen completa de una manera elegante y expresiva.

Ocultar el constructor no es lo que la comunidad quiere porque esto quita valor semántico y expresivo de las implementaciones de clases, y una vez más, debido a que los modificadores de acceso son "agradables de tener", esto ni siquiera se puede hacer cumplir en tiempo de ejecución; esencialmente una situación de "no existe en JavaScript".

Como desarrollador tengo muchas funciones; por el momento tengo un sombrero de C #, un sombrero de Kotlin y un sombrero de TypeScript.

  • Con mi sombrero de Kotlin puesto, no tengo que preocuparme por final porque las clases son definitivas por defecto.
  • Con mi sombrero de C # puesto, aplico sealed como un hábito, lo que obliga a mis consumidores a preguntar si puedo en lugar de preguntar si debo . La mayoría de las veces, las clases de C # deberían ser sealed y, a menudo, se pasan por alto.
  • Con mi sombrero TypeScript puesto, tengo que ocultar el constructor como una solución, porque en mi opinión (y en la de las comunidades), el lenguaje carece de algo útil.

Por interés, cuando el equipo de TypeScript se sentó alrededor de una mesa y discutió las clases de abstract , ¿alguien levantó la mano y dijo "pero qué pasa con final "?

@RyanCavanaugh Hum Entiendo tus puntos

Creo que sobre todo fue un malentendido (supongo por cómo explicamos)

@ series0ne resumen muy bien que nuestras intenciones no complicaron demasiado la salida mecanografiada al pedir crear la clase final en JS (diablos, esto sería un desastre para hacerlo) sino simplemente crear reglas adicionales para decirle a la gente: No, puedes ' hacer eso. y le dice al usuario de la API que probablemente no debería meterse con esa clase (por razones de estabilidad, seguridad, etc.) TODO en los archivos / compilador mecanografiado NO en la salida.

Creo que sería bueno tener esta característica: sí, es bueno tener una forma más rápida de hacer alguna técnica.

¿Es realmente necesario? En realidad, esto no podría ser algo considerado menor y no debería ser una prioridad si el equipo de Typecript puede permitirse implementar este extra, entonces sería bueno tenerlo.

Veo que la palabra clave final / sellada podría complicar MUCHOS aspectos especialmente: corrección de errores, parche, cambio de comportamiento.

si explico esto, podría ver un uso abusivo de final bloqueando TODA la clase para que no se extienda. Provocando a la OMI un gran dolor en el culo con el que lidiar. No quiero usar una API en la que estoy bloqueado para hacer algún cambio en la clase de usuario por herencia porque estaban paranoicos debido a la "seguridad".

Esto significaría que tendría que usar el prototipo y luego el alias o anular esta clase solo por el simple hecho de poder cambiar el comportamiento de esta clase para que se ajuste a mis necesidades.
esto sería molesto, especialmente si solo quiero agregar funcionalidad adicional a dicha clase y no quiero ir con un flujo de trabajo destructivo.

Después de esto, solo mi aunque podría malinterpretar las situaciones en este momento, así que sé libre de explicármelo :)

Ah, si recuerdo que estaba leyendo mucho sobre el ES4 (no estoy seguro de que todavía era un niño cuando el borrador estaba en producciones jajaja) y sorprendentemente se ve muy similar a Typecript.

y se dice en el borrador de especificaciones aquí:
https://www.ecma-international.org/activities/Languages/Language%20overview.pdf

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

Entonces, técnicamente, la palabra clave final se planeó en la cuarta edición de Ecmascript. ¿Lo veremos en las futuras integraciones de JS? Lo dudo, pero de hecho integraron la clase, por lo que esto podría ser posible, pero ¿quién sabe?

Tengo un caso de uso con el que apenas me encontré y que me llevó a encontrar este problema. Gira en torno al uso de this como tipo de retorno:

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

Mi intención al escribir esto era asegurarme de que TileEntity sea inmutable (y por lo tanto seguro para pasar) pero que cualquier TileEntity dado podría ser clonado con algunas propiedades cambiadas (en este caso x y y ) sin tener que saber nada sobre la forma de la subclase. Eso es lo que quiero escribir, pero intentar escribir una implementación (correctamente) hace que esto se rompa.

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

El problema es que sin poder declarar que _nunca_ habrá una subclase de TurretTileEntity, el tipo de retorno this no se puede reducir al tipo TurretTileEntity.

El impacto de esta deficiencia en TypeScript es mínimo, considerando que puede emitir el valor de retorno a <any> . Esto es, objetivamente, un antipatrón y una indicación útil de que el sistema de tipos podría ser más expresivo. Se deben apoyar las clases finales / selladas.

Por interés, cuando el equipo de TypeScript se sentó alrededor de una mesa y discutió las clases abstractas, ¿alguien levantó la mano y dijo "pero qué pasa con el final"?

Si. Cada vez que agregamos una palabra clave, "¡pero luego tendremos que agregar esta otra palabra clave también!" es un punto fuerte en contra de agregarlo, porque queremos hacer crecer el idioma con el mayor cuidado posible. final en declaraciones de clase también está en este campo - si tenemos final clases, entonces "necesitaremos" final métodos o propiedades también. Cualquier cosa que parezca una fluencia de alcance se considera con extrema sospecha.

Creo que las clases y los métodos finales son muy importantes. Espero que lo implementes.

Tenga en cuenta que este problema se trata de clases finales / selladas. Ha habido una discusión fuera del tema de los métodos finales / sellados, que son un concepto completamente diferente.

mhegazy cerró https://github.com/Microsoft/TypeScript/issues/9264 a favor de este problema. ¿Existe un problema separado que no se cierra como un engaño para rastrear los métodos finales / sellados?

@crcla Después de leer algunos de los comentarios de @RyanCavanaugh anteriores, creo que la razón para cerrar el otro problema es que el soporte de clases finales / selladas abarca el soporte de métodos finales / sellados. Los dos son temas bastante relacionados.

No puedo creer que la propuesta de final esté etiquetada como Won't fix .

Cambie la etiqueta de nuevo a In Discussion e impleméntela, ¡por favor!

En mi caso, quiero escribir una clase base abstracta para los complementos y quiero asegurarme de que la clase de complemento no pueda sobrescribir mis métodos de clase base por error. Ninguno de los private constructor , Object.seal puede hacer este trabajo.

Para aquellos que ven esto como una característica crucial de vivir o morir: realmente no lo es. Si su API consiste en exponer una implementación de clase que el consumidor debe ampliar, existen mejores formas de hacerlo. No sigas el mal ejemplo de React. Simplemente use funciones y objetos simples, y deje esta tontería OO donde pertenece: en el pasado.

Ahora trae los votos negativos 😀

¿Alguien podría explicarme cómo esta discusión no distingue entre clases finales y métodos finales? Por supuesto, evitar que algunos métodos se anulen agrega un valor enorme. Quiero brindar la posibilidad de que un desarrollador de complementos agregue funcionalidad subclasificando algunas de mis clases de marcos e implementando funciones abstractas, pero al mismo tiempo me aseguro de que no anule (¡por qué nunca!) Los métodos que llaman a estas funciones abstractas (y asegúrese de que se lleve a cabo el manejo / registro / verificación de errores.

@pelotom Si no desea usar construcciones y patrones de programación
Al obligar a otros a adoptar un patrón específico (no OOP), está adoptando el mismo comportamiento por el que denuncia OOP. Al menos sea consistente :)

Agregar una palabra clave final a TypeScript no es un cambio importante y no influirá en ninguno de sus proyectos existentes de ninguna manera, sin embargo, será una característica vital para personas como @thorek , yo mismo y muchos otros.

si alguien quiere usar construcciones de programación orientada a objetos, déjelo.

No estamos debatiendo si la gente debería usar construcciones de programación orientada a objetos, estamos debatiendo si las construcciones de programación orientada a objetos que no existen actualmente deben agregarse al lenguaje. Hacerlo requiere tiempo del desarrollador que podría dedicarse a otras funciones y hace que el lenguaje sea más complejo, lo que ralentiza la velocidad de las funciones futuras (porque para cada función nueva se debe considerar cómo interactuará con todas las funciones antiguas). Así que ciertamente me afecta, ¡porque prefiero gastar el presupuesto de desarrollo y complejidad de TypeScript en cosas más útiles!

fwiw, probablemente vamos a alentar a nuestros clientes (google) a usar @final en los comentarios para que se propague al compilador de cierre: \

Agregar _final_ en clases y métodos es útil cuando un equipo de I + D crea bibliotecas de TS que otros equipos pueden usar. Estas palabras clave son (una parte importante) de la estabilidad de exposición al servicio de dichas herramientas.
La palabra clave es más importante para equipos grandes y menos importante para proyectos pequeños, en mi humilde opinión.

@dimiVergos Este es exactamente mi caso, y asumo lo mismo para muchos otros. Puedo imaginar que puedo parecer parcial debido a mi posición, pero parece el único (aunque muy) uso justificable de esta palabra clave según mi experiencia.

¡Estoy abierto a correcciones en este caso!

Me importa más admitir métodos finales (para evitar que las subclases los anulen accidentalmente) que las clases finales. ¿Es el boleto correcto para votar por eso? Ticket https://github.com/Microsoft/TypeScript/issues/9264 parece implicarlo.

Entonces, ¿cómo voto por los "métodos finales"? ¿O simplemente lo voté con este comentario? Además, ¿cómo agregamos "métodos" al título? ¿Puedo editarlo? El título actual está engañosamente limitado únicamente a las clases, pero la discusión también incluye métodos.

Como muchos otros, me gustaría ver métodos finales en TypeScript. He utilizado el siguiente enfoque como una solución parcial que no es inmune a fallas, pero al menos ayuda un poco:

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

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

Aquí hay otro caso de uso del mundo real para los métodos final . Considere la siguiente extensión de React.Component para facilitar la implementación de un Contexto simple.

interface FooStore{
  foo: string;
}

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

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

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

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

Sin final en el método render() , nada impide que uno anule incorrectamente el método, rompiendo así todo.

Hola, como muchos otros, me gustaría la palabra clave "final" o "sellada" al menos para los métodos. Estoy totalmente de acuerdo en que esta palabra clave es realmente un valor agregado, ya que permite a los desarrolladores evitar que sus clientes de bibliotecas extiendan bloques de código cruciales (como en el complemento, por ejemplo).

La única solución que encontré es usar el decorador de métodos como:
function sealed(target, key,descriptor){ descriptor.writable = false; // Prevent method to be overriden }

luego, en su clase "base", use el decorador para evitar la anulación del método, ejemplo:

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

public OtherMethod(){}

...
}

De esta manera, 'MySealedMethod' no podría (fácilmente) ser anulado en la subclase. Ejemplo:

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

en realidad, el único inconveniente de esta solución es que el "sellado" solo es visible en tiempo de ejecución (error en la consola de JavaScript) pero NO en el tiempo de compilación (ya que las propiedades del método se establecen a través de la función, supongo)

@RyanCavanaugh

Si. Cada vez que agregamos una palabra clave, "¡pero luego tendremos que agregar esta otra palabra clave también!" es un punto fuerte en contra de agregarlo, porque queremos hacer crecer el idioma con el mayor cuidado posible.

Me parece que está comenzando este argumento con su mente puesta en contra de la función. Una característica popular no debería ser un punto en contra de considerarla. Debería ser un punto para preguntarse _por qué es popular_, y luego considerarlo cuidadosamente.

Estoy seguro de que lo ha discutido y se han lanzado argumentos a favor o en contra de considerarlo. ¿El argumento en contra es solo que se trata de una característica lenta, o hay algo más específico? ¿Lo consideras una característica marginal? Si es así, ¿podríamos hacer algo para cambiar de opinión?

Permítanme dejar estas preguntas aquí para que todos las meditemos.

¿Hay alguna manera de sellar una clase _por diseño_ utilizando construcciones actuales?
Si se agregara la función, ¿nos permitiría hacer algo nuevo? (Como en, algo que las construcciones presentes no permiten)
Si la función nos permitió hacer algo nuevo, ¿es algo valioso?
¿Agregar clases selladas aumentaría demasiado la complejidad del idioma?
¿Agregar clases selladas aumentaría demasiado la complejidad del compilador?

Con el fin de discutir estos, no incluyo métodos sellados en la discusión y tampoco incluyo comprobaciones en tiempo de ejecución en la implementación. Todos entendemos que no podemos hacer cumplir esto en Javascript.

FYI: Sería increíble si el contraataque de esta característica fuera un poco más que declaraciones generales contra el deslizamiento de características.

@asimonf Creo que todo lo que ha dicho ya fue abordado por este comentario (necesitará expandir la sección "mostrar más" para verlo) y el siguiente.

¿Qué cambiaría nuestra mente aquí? Las personas aparecen con ejemplos de código que no funcionó como debería porque faltaba la palabra clave sealed .

El escenario aquí que la palabra clave intenta abordar es uno en el que alguien hereda por error de una clase. ¿Cómo se hereda por error de una clase? No es un error fácil de cometer. Las clases de JavaScript son inherentemente lo suficientemente frágiles como para que cualquier subclase deba comprender los requisitos de comportamiento de su clase base, lo que significa escribir algún tipo de documentación, y la primera línea de esa documentación puede ser simplemente "No subclases esta clase". O puede haber una verificación de tiempo de ejecución. O el constructor de la clase se puede ocultar detrás de un método de fábrica. O cualquier otra cantidad de opciones.

¿Por qué TypeScript debe tener esta palabra clave cuando la situación ya es una en la que debería haber encontrado al menos dos obstáculos separados que le dicen que no debe estar allí para empezar?

He estado programando durante aproximadamente una eternidad y no he intentado ni una vez subclasificar algo solo para descubrir que estaba sellado y no debería haberlo intentado. ¡No es porque sea un super genio! Es un error muy poco común de cometer, y en JavaScript estas situaciones ya son aquellas en las que debe hacer preguntas a la clase base en primer lugar. Entonces, ¿qué problema se está resolviendo ?

Un caso real en el que estaría usando final es evitar anular un método con algo que nunca llamaría al súper método. Algún método público, por supuesto. Pero la solución real que descubrí sería la capacidad de forzar a cualquier submétodo a llamar al super. https://github.com/Microsoft/TypeScript/issues/21388

Como no soy un diseñador de idiomas, no puedo hablar de los inconvenientes de agregar una palabra clave al idioma. Diré que actualmente estoy trabajando en una superclase abstracta que tiene un método que pretendo que sea definitivo, pero para mi consternación descubrí que no podía hacerlo. Por ahora, optaré por el enfoque menos que ideal de poner un comentario, pero los miembros del equipo que implementan la clase podrían no leer dicho comentario.

Creo que arriba hay muchos ejemplos excelentes de cuándo / dónde final no solo sería útil, sino que proporcionaría _valor real_ y _ seguridad real_ a la plataforma que se está construyendo. No es diferente para mí. Como dije, no sé mucho sobre las complejidades del diseño del lenguaje, pero esta es claramente una característica que la gente quiere / usará / realmente no veo una manera de abusar de ella o que generaría un código incorrecto. Entiendo el concepto de temer el avance del alcance, pero los desarrolladores de TS no tienen que implementar todo lo que la comunidad quiere.

Solo para agregar mi granito de arena al debate, me gustaría poner mi pulgar en el lado del sí de la escala porque este problema está repleto de buenos argumentos que respaldan la adición de la función, pero los argumentos en contra que veo son:

  • Podría dar lugar a un deslizamiento del alcance (esto es manejable)
  • La gente pedirá otras características (está bien, pero a quién le importa, eso es parte del diseño de un lenguaje y este no es un precedente legal que estamos estableciendo)
  • Podría reducir la eficiencia (imagino que dado que el equipo de TS está lleno de malos traseros, probablemente encontraría una manera de hacerlo manejable)
  • Comentarios con opiniones "No utilice conceptos de programación orientada a objetos"

Ninguno de los anteriores parece una amenaza creíble para el ecosistema de TS en sí, ni el código que se produce a partir de él; todos parecen políticos (excepto tal vez el de eficiencia, pero tengo una gran confianza en ustedes, así que no estoy realmente preocupado por eso, además de una disminución menor de eficiencia valdría la pena no hacer estallar construcciones importantes de componentes / bibliotecas por errores de herencia involuntarios) .

¡Espero que esto sea constructivo! ¡Amo TS y quiero que siga mejorando!

👍

--editar--

En respuesta a @RyanCavanaugh :

So what problem is being solved?

Creo que el problema que se está resolviendo se describe en muchos, muchos comentarios anteriores. No lo digo como un insulto, pero esa pregunta me parece que no está prestando atención a los argumentos presentados y que tiene la mente puesta en "no". No me refiero a faltarle el respeto a eso, es solo que, honestamente, este hilo está lleno de ejemplos de problemas que esto resolvería.

De alguna manera me perdí este comentario , que describe muy bien una seria preocupación por agregar la función y eso tiene sentido para mí. Una vez más, no conozco la complejidad de agregarlo, pero dejando de lado las preocupaciones de eficiencia (ya que no depende de los desarrolladores preocuparse por la eficiencia de TS, ese es el trabajo del equipo de TS), no parece haber buenos argumentos en contra de final (en mi más humilde de opiniones).

La mayor parte de la discusión reciente se ha centrado en los métodos finales (menos en las clases finales). Los métodos finales son sin duda mi principal interés. Me acabo de dar cuenta de que si expande el enlace "este comentario" en la publicación anterior de @mhegazy sugirió que este boleto es el lugar correcto (al cerrar el # 9264). Entonces, como usuario final humilde, estoy desconcertado en cuanto a dónde debería pedir / votar los métodos finales. Se agradecería orientación.

@cbutterfield
La contradicción es algo que también noté, pero que no señalé en mi publicación por temor a despotricar (en realidad encontré ESTE hilo a través del # 9264 y vine aquí en la dirección de @mhegazy ).
@RyanCavanaugh
@mhegazy
Según el comentario anterior, donde _es_ el lugar correcto para discutir los métodos finales, porque eso es lo que más me interesa.

Mirando hacia atrás en mi comentario fuertemente votado (¡asegúrese de agregar su 👎 si aún no lo ha hecho!) Me complace informar que React _ ya no_ está dando un mal ejemplo al forzar una API basada en class en sus usuarios! Con la propuesta de Hooks, el equipo de React ha recuperado el sentido y ha adoptado un enfoque funcional radicalmente más simple. Con eso, la última razón por la que muchos desarrolladores tienen que usar clases se ha evaporado, y bueno. Lo reiteraré: todo lo que crea que necesita clases se puede hacer mejor sin ellas .

El futuro de JS y TS no tiene clases, ¡alabado sea!

@cbutterfield También me di cuenta de eso, y si el problema # 9264 se cerró para discutirlo en este número, creo que el título debería cambiar a Suggestion: Final keyword for classes and methods , de lo contrario, las personas que buscan métodos finales (exclusivamente) podrían no agregar una reacción en la primera publicación.

Citando a @mhegazy :
Java y / o C # usan la clase final para optimizar su clase en tiempo de ejecución, sabiendo que no será especializada. este yo diría que es el valor principal para el apoyo final. En TypeScript no hay nada que podamos ofrecer para que su código se ejecute mejor que sin final.

Fundamentalmente no estoy de acuerdo. He desarrollado en Java durante muchos años en muchos equipos y nunca hemos usado clases o métodos finales para optimizaciones de tiempo de ejecución; en la práctica, es principalmente un lenguaje de diseño orientado a objetos. A veces simplemente no quiero que mi clase o método sea extendido / anulado por subclases externas, ya sea para proteger y garantizar una función, o para limitar el ruido de la API de una biblioteca a lo esencial, o para proteger a los usuarios de la API de malinterpretar una API compleja.

Sí, esos problemas se pueden resolver agregando comentarios o interfaces, pero final es una solución elegante para reducir la API visible a las funciones necesarias cuando todo lo que desea es una clase simple con una API ampliable limpia.

Aunque este problema tiene casi 3 años, realmente deberíamos tener una palabra clave final ya que ya existe una palabra clave abstract .

Tenga en cuenta que no queremos obligar a la gente a usarlo y es una característica tan útil como la palabra clave abstracta. Pero aquí hay otro caso de uso que mostrará claramente el beneficio de una palabra clave final :

abstract class A {
  protected abstract myVar: string;

  protected abstract myFunction(): void;
}

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

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

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

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

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

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

const myB = new B(); // toto

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

Resultado después de la compilación:

toto
tata

Entonces, en este código, tenemos una clase abstracta A que tiene métodos y propiedades abstractos, queremos que la clase heredada los defina, pero nos gustaría no permitir que ninguna otra clase anule esas implementaciones.
Lo mejor que podríamos hacer sería mantener la palabra clave protected , pero como puede ver, aún podemos redefinir los atributos.

Entonces, ¿qué pasa si vamos más allá en el proceso de compilación de TypeScript y usamos readonly para proteger nuestros atributos (y pretender que nuestros métodos son propiedades)?

class B extends A {
  [...]

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

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

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

(Tenga en cuenta que el código se compila con tsc, no se producen errores al compilarlo o ejecutarlo)
Entonces ahora tenemos dos problemas:

  • No protegemos nuestros atributos B , por lo que necesitamos una forma de evitar cualquier herencia inesperada.
  • Ahora sabemos que podemos anular cualquier propiedad readonly extendiendo su clase, y SÍ, Typecript sabe que es la propiedad principal que estamos cambiando usando private lugar de protected en C class arrojará un error ya que no es el mismo tipo de acceso / visibilidad.

Por el momento, podríamos usar decoradores ( sellados como muestra la documentación oficial de TypeScript, o un decorador final ) pero recuerde que solo es útil en tiempo de ejecución, y que debemos evitar esto en el proceso de compilación.

Veré si hay un problema sobre la "violación de seguridad" (si podemos llamar a eso) cuando queremos anular un valor de solo lectura protegido y realmente podemos y no debemos. De lo contrario, abriré un problema para debatir sobre la verificación de la palabra clave readonly entre la palabra clave private , protected con elementos de herencia.

tl; dr: La palabra clave final resolvería dos problemas (aunque la verificación readonly sería un buen comienzo para evitar cualquier reescritura no autorizada)

Tres años después ... es tan ridículo.

Para demostrar la necesidad de esta función, sugiero echar un vistazo a lo que se hizo en otros idiomas:

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

Entonces, vemos que en todos los lenguajes de programación orientados a objetos más populares existe esta característica porque proviene de la lógica de herencia de programación orientada a objetos.

También tengo que citar al líder de desarrollo de TS diciendo

si tenemos clases finales, entonces también "necesitaremos" métodos o propiedades finales.

No estoy seguro de si es irónico, pero en JS la palabra clave readonly se usa para propiedades (y métodos si haces trampa), así que ese es un punto bastante tonto.

Y tal vez no permitir que un chico cierre el problema cuando tiene +40 votos negativos, lo que significa que la comunidad no está de acuerdo con él sería un buen consejo para evitar más situaciones tontas.

Si no está interesado en reforzar la función principal de Typecript, dígalo en voz alta para que las noticias tecnológicas puedan transmitirlo en Twitter, porque de lo contrario, es solo un mal zumbido oculto. Simplemente está ignorando a su comunidad, Y no tiene un proceso para hacer que la comunidad valide lo que necesitamos o no.

tal vez no permitir que un chico cierre el problema cuando tiene +40 votos negativos, lo que significa que la comunidad no está de acuerdo con él sería un buen consejo para evitar más situaciones tontas

No soy "un chico". Cuando tomo decisiones aquí, es el reflejo del proceso de toma de decisiones de todo el equipo de TypeScript. El equipo de diseño ha examinado este problema varias veces y cada vez llega a la misma conclusión. Puede estar en desacuerdo con ese resultado, pero el proceso no está sujeto a debate.

no tiene ningún proceso para hacer que la comunidad valide lo que necesitamos o no.

Este es el proceso, aquí mismo: usted dice que mi resumen de nuestro razonamiento es "tonto" y yo (y otras personas del equipo) lo leo. Estamos escuchando los comentarios.

Sé que señalar otras características implementadas como razones para considerar que esta es un olor y no realmente un argumento, pero me parece irónico que se haya considerado y agregado un mecanismo de alias de tipo (puede ser increíble, pero ciertamente se podría vivir sin él ) pero algo como esto se rechaza.

Al final, puedo vivir sin esa característica, pero lo mismo podría decirse de aproximadamente la mitad de las características que tiene TS. Entonces, ¿cuál sería una razón apropiada para considerar implementar esto?

GitHub oculta inútilmente muchos comentarios aquí, así que estoy volviendo a publicar varias respuestas (más algunas adiciones) que hemos dado en el pasado. Añadiendo algunos pensamientos a continuación también.


Se consideraron algunos puntos cuando revisamos esta última vez

  • LAS CLASES SON UNA FUNCIÓN DE JAVASCRIPT, NO UNA FUNCIÓN DE TYPESCRIPT . Si realmente siente que las clases son completamente inútiles sin final , eso es algo que debe discutir con el comité TC39 o llevarlo a es-discusion. Si nadie está dispuesto o es capaz de convencer a TC39 de que final es una característica imprescindible, entonces podemos considerar agregarla a TypeScript.
  • Si es legal invocar new en algo, eso tiene una correspondencia de tiempo de ejecución uno a uno con la herencia. La noción de algo que puede ser new 'd pero no super ' d simplemente no existe en JavaScript
  • Estamos haciendo todo lo posible para evitar convertir TypeScript en una sopa de palabras clave de programación orientada a objetos. Hay muchas mejores mecánicas de composición que la herencia en JavaScript y no necesitamos tener todas las palabras clave que tienen C # y Java.
  • Puede escribir trivialmente una verificación de tiempo de ejecución para detectar la herencia de una clase "debería ser final", lo que debería hacer de todos modos si está "preocupado" por alguien que herede accidentalmente de su clase, ya que no todos sus consumidores podrían estar usando TypeScript. .
  • Puede escribir una regla de pelusa para hacer cumplir una convención estilística de la que ciertas clases no se heredan
  • El escenario que alguien heredaría "accidentalmente" de tu clase que debería ser sellada es algo extraño. También diría que la evaluación individual de alguien de si es "posible" o no heredar de una clase determinada es probablemente bastante sospechosa.
  • Básicamente, hay tres razones para sellar una clase: seguridad, desempeño e intención. Dos de ellos no tienen sentido en TypeScript, por lo que debemos considerar la complejidad frente a la ganancia de algo que solo hace 1/3 del trabajo que hace en otros tiempos de ejecución.

no he escuchado un argumento convincente de por qué TS no lo necesita

Solo un recordatorio de que no es así como funciona. Todas las funciones comienzan en -100 . De esa publicación

En algunas de esas preguntas, las preguntas preguntan por qué "eliminamos" o "omitimos" una característica específica. Esa redacción implica que comenzamos con un lenguaje existente (C ++ y Java son las opciones populares aquí), y luego comenzamos a eliminar funciones hasta que llegamos a un punto en el que nos gustó. Y, aunque puede ser difícil de creer para algunos, no es así como se diseñó el lenguaje.

Tenemos un presupuesto de complejidad finita. Todo lo que hacemos hace que todo lo que hagamos sea un 0,1% más lento. Cuando solicita una función, está solicitando que todas las demás funciones se vuelvan un poco más difíciles, un poco más lentas, un poco menos posibles. Nada entra por la puerta aquí porque Java lo tiene o porque C # lo tiene o porque solo serían unas pocas líneas de código o puede agregar un interruptor de línea de comandos . Las funciones deben pagarse por sí mismas y por toda su carga sobre el futuro.


¿Qué cambiaría nuestra mente aquí? Las personas aparecen con ejemplos de código que no funcionó como debería porque faltaba la palabra clave final .

El escenario aquí que la palabra clave intenta abordar es uno en el que alguien hereda por error de una clase. ¿Cómo se hereda por error de una clase? No es un error fácil de cometer. Las clases de JavaScript son inherentemente lo suficientemente frágiles como para que cualquier subclase deba comprender los requisitos de comportamiento de su clase base, lo que significa escribir algún tipo de documentación, y la primera línea de esa documentación puede ser simplemente "No subclases esta clase". O puede haber una verificación de tiempo de ejecución. O el constructor de la clase se puede ocultar detrás de un método de fábrica. O cualquier otra cantidad de opciones.

En otras palabras: el único proceso correcto para heredar de una clase en JavaScript siempre comienza con la lectura de la documentación de esa clase . Hay demasiadas restricciones que la clase base podría tener para simplemente derivar y comenzar a codificar. Si, en el primer paso , ya debería saber que no debe intentarlo, ¿qué valor proporciona la aplicación estática en el paso tres?

¿Por qué TypeScript debe tener esta palabra clave cuando la situación ya es una en la que debería haber encontrado al menos dos obstáculos separados que le dicen que no debe estar allí para empezar?

He estado programando durante aproximadamente una eternidad y no he intentado ni una vez subclasificar algo solo para descubrir que estaba sellado y no debería haberlo intentado. ¡No es porque sea un super genio! Es un error muy poco común de cometer, y en JavaScript estas situaciones ya son aquellas en las que debe hacer preguntas a la clase base en primer lugar. Entonces, ¿qué problema se está resolviendo ?

Si no está interesado en reforzar la función principal de Typecript, dígalo en voz alta para que las noticias tecnológicas puedan transmitirlo en Twitter.

Somos muy explícitos sobre cómo diseñamos el lenguaje ; El no objetivo número uno responde exactamente a esta sugerencia.


Mi reacción personal después de leer los muchos comentarios aquí es que hay un fuerte efecto de sesgo debido a las construcciones que existen en otros idiomas. Si solo aborda esto desde una perspectiva de pizarra en blanco, hay docenas de comportamientos que podría imaginar, de los cuales TypeScript tiene solo un pequeño subconjunto.

Podrías imaginar fácilmente alguna palabra clave que se aplicara a un método y se hiciera cumplir que los métodos derivados lo llamaran a través de super . Si C # y Java tuvieran esta palabra clave, la gente la aplicaría final , porque es imposible aplicar una verificación de tiempo de ejecución y es un aspecto mucho más sutil del contrato derivado de la base que "las clases derivadas pueden no existir ". Sería útil de varias formas en las que final no lo sería. Preferiría tener esa palabra clave que esta (aunque creo que ninguna cumple con la barra de complejidad vs valor).

Entonces, ¿cuál sería una razón apropiada para considerar implementar esto?

Cuando miramos los comentarios, las consideraciones importantes se ven así:

  • No puedo expresar adecuadamente el comportamiento de esta biblioteca de JavaScript
  • Mi código se compiló, pero realmente no debería haberlo hecho porque tenía este (sutil) error
  • El sistema de tipos no comunica adecuadamente la intención de este código.

final golpea, pero también lo haría un modificador que dice "Esta función no se puede llamar dos veces seguidas" o "La construcción de esta clase no termina realmente hasta el siguiente tick asincrónico". No todo lo imaginable debería existir.

Nunca hemos visto comentarios como "Pasé horas depurando porque estaba tratando de heredar de una clase que en realidad no era subclasificable", alguien que dijera que con razón desencadenaría un 'wat' porque no es realmente imaginable. Incluso si el modificador existiera, realmente no ayudaría a la situación, porque las bibliotecas no documentan si las clases están destinadas a ser definitivas, por ejemplo, ¿es fs.FSwatcher final ? Parece que ni siquiera los autores de los nodos lo saben. Entonces final es suficiente si los autores saben que es final , pero eso se documentará independientemente , y la falta de final realmente no le dice nada porque es a menudo simplemente no se sabe de ninguna manera.

Leí todo el bloque y entiendo la declaración del equipo sobre la elección de funciones, solo volveré a ciertos puntos de mis comentarios.

No soy "un chico". Cuando tomo decisiones aquí, es el reflejo del proceso de toma de decisiones de todo el equipo de TypeScript.

Siento haberme referido a mhegazy y los comentarios bastante masivos que recibió de la comunidad que usa Typecript.

Si realmente siente que las clases son completamente inútiles sin un final, eso es algo que debe discutir con el comité TC39 o llevarlo a discutir. Si nadie está dispuesto o es capaz de convencer a TC39 de que final es una característica imprescindible, entonces podemos considerar agregarla a TypeScript.

No creo que final sea ​​el primer paso, ya que ya existe una propuesta para la palabra clave private https://github.com/tc39/proposal-private-methods

Soy escéptico sobre el You should a comment to say *dont do this* , es como decir en un formulario Hey don't write down specific characters , codificó durante casi una eternidad, por lo que debe conocer la regla n ° 1 de un desarrollador: nunca confíe en el usuario ( y el desarrollador que usará su código)

No estoy diciendo que debamos detener absolutamente todo y poner mil millones de dólares para implementar la palabra clave final, porque los casos de uso son demasiado bajos para ser tan eficientes, también considere que di algunos ejemplos donde el límite es tanto de TS como de JS y que si la gente elige TS, es para evitar errores en tiempo de compilación, no en tiempo de ejecución. Si queremos que las cosas exploten en tiempo de ejecución, podemos usar JS y dejar de preocuparnos por TS, pero ese no es el punto, porque hay un caso de uso definitivo que muestra cómo uso la palabra clave final : quiero bloquear un método, no quiero que nadie lo anule.

Y como Javascript está limitado por eso, la comunidad pensó que TypeScript podría ir más allá de ese límite, es por eso que este problema ha estado activo durante 3 años, es por eso que la gente todavía se pregunta por qué esta función no está aquí, y es por eso que queremos que el compilador realizar la comprobación manual de una clase y / o método.

No esperó que JS tuviera métodos privados / públicos para implementarlos, aunque en realidad hay propuestas para incluirlos con la palabra clave # (menos detallada que public / private ), Sé que querías crear un lenguaje perfecto porque la perfección no es cuando ya no puedes agregar nada, es cuando ya no puedes eliminar nada.

Si puede encontrar una solución para evitar que un método se sobrescriba en el proceso de compilación (y no en el proceso de tiempo de ejecución, ya que el punto no sería válido), sea mi invitado.

Traté de mostrar la debilidad de la situación (en métodos / propiedades y no en clases), porque cualquier desarrollador de TS puede reescribir las bibliotecas que quiera, rompiendo lo que quiera, tal vez esa sea la belleza de la cosa, supongo.

Gracias por la respuesta, por cierto, sin odio o mal comportamiento contra el equipo de desarrollo o contra ti.

¡Todos los puntos válidos! Pero, por favor, dividamos la discusión entre

a) Apoyar las clases finales
b) Apoyar los métodos finales

Mientras que todos sus argumentos apuntan a a) - ninguno apunta b.

Tener métodos finales es crucial como se señaló muchas veces en la discusión. La única respuesta que escuché hasta ahora fue "no hagas OOP", pero eso no es nada con lo que yo (y muchos otros) iría.

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

GitHub oculta inútilmente muchos comentarios aquí, así que estoy volviendo a publicar varias respuestas (más algunas adiciones) que hemos dado en el pasado. Añadiendo algunos pensamientos a continuación también.

Se consideraron algunos puntos cuando revisamos esta última vez

LAS CLASES SON UNA FUNCIÓN DE JAVASCRIPT, NO UNA FUNCIÓN DE TYPESCRIPT. Si realmente siente que las clases son completamente inútiles sin un final, eso es algo que debe discutir con el comité TC39 o llevarlo a discutir. Si nadie está dispuesto o es capaz de convencer a TC39 de que final es una característica imprescindible, entonces podemos considerar agregarla a TypeScript.
Si es legal invocar nuevo en algo, eso tiene una correspondencia de tiempo de ejecución uno a uno con la herencia. La noción de algo que puede ser nuevo pero no superd, simplemente no existe en JavaScript
Estamos haciendo todo lo posible para evitar convertir TypeScript en una sopa de palabras clave de programación orientada a objetos. Hay muchas mejores mecánicas de composición que la herencia en JavaScript y no necesitamos tener todas las palabras clave que tienen C # y Java.
Puede escribir trivialmente una verificación de tiempo de ejecución para detectar la herencia de una clase "debería ser final", lo que debería hacer de todos modos si está "preocupado" por alguien que herede accidentalmente de su clase, ya que no todos sus consumidores podrían estar usando TypeScript. .
Puede escribir una regla de pelusa para hacer cumplir una convención estilística de la que ciertas clases no se heredan
El escenario que alguien heredaría "accidentalmente" de tu clase que debería ser sellada es algo extraño. También diría que la evaluación individual de alguien de si es "posible" o no heredar de una clase determinada es probablemente bastante sospechosa.
Básicamente, hay tres razones para sellar una clase: seguridad, desempeño e intención. Dos de ellos no tienen sentido en TypeScript, por lo que debemos considerar la complejidad frente a la ganancia de algo que solo hace 1/3 del trabajo que hace en otros tiempos de ejecución.
no he escuchado un argumento convincente de por qué TS no lo necesita

Solo un recordatorio de que no es así como funciona. Todas las funciones comienzan en -100 https://blogs.msdn.microsoft.com/ericgu/2004/01/12/minus-100-points/ . De esa publicación

En algunas de esas preguntas, las preguntas preguntan por qué "eliminamos" o "omitimos" una característica específica. Esa redacción implica que comenzamos con un lenguaje existente (C ++ y Java son las opciones populares aquí), y luego comenzamos a eliminar funciones hasta que llegamos a un punto en el que nos gustó. Y, aunque puede ser difícil de creer para algunos, no es así como se diseñó el lenguaje.

Tenemos un presupuesto de complejidad finita. Todo lo que hacemos hace que todo lo que hagamos sea un 0,1% más lento. Cuando solicita una función, está solicitando que todas las demás funciones se vuelvan un poco más difíciles, un poco más lentas, un poco menos posibles. Nada entra por la puerta aquí porque Java lo tiene o porque C # lo tiene o porque solo serían unas pocas líneas de código o puede agregar un interruptor de línea de comandos. Las funciones deben pagarse por sí mismas y por toda su carga sobre el futuro.

¿Qué cambiaría nuestra mente aquí? Las personas aparecieron con ejemplos de código que no funcionó como debería porque faltaba la palabra clave final.

El escenario aquí que la palabra clave intenta abordar es uno en el que alguien hereda por error de una clase. ¿Cómo heredas por error de una clase? No es un error fácil de cometer. Las clases de JavaScript son inherentemente lo suficientemente frágiles como para que cualquier subclase deba comprender los requisitos de comportamiento de su clase base, lo que significa escribir algún tipo de documentación, y la primera línea de esa documentación puede ser simplemente "No subclases esta clase". O puede haber una verificación de tiempo de ejecución. O el constructor de la clase se puede ocultar detrás de un método de fábrica. O cualquier otra cantidad de opciones.

En otras palabras: el único proceso correcto para heredar de una clase en JavaScript siempre comienza con la lectura de la documentación de esa clase. Hay demasiadas restricciones que la clase base podría tener para simplemente derivar y comenzar a codificar. Si, en el primer paso, ya debería saber que no debe intentarlo, ¿qué valor proporciona la aplicación estática en el paso tres?

¿Por qué TypeScript debe tener esta palabra clave cuando la situación ya es una en la que debería haber encontrado al menos dos obstáculos separados que le dicen que no debe estar allí para empezar?

He estado programando durante aproximadamente una eternidad y no he intentado ni una vez subclasificar algo solo para descubrir que estaba sellado y no debería haberlo intentado. ¡No es porque sea un super genio! Es un error muy poco común de cometer, y en JavaScript estas situaciones ya son aquellas en las que debe hacer preguntas a la clase base en primer lugar. Entonces, ¿qué problema se está resolviendo?

Si no está interesado en reforzar la función principal de Typecript, dígalo en voz alta para que las noticias tecnológicas puedan transmitirlo en Twitter.

Somos muy explícitos sobre cómo diseñamos el lenguaje https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals ; El no objetivo número uno responde exactamente a esta sugerencia.

Mi reacción personal después de leer los muchos comentarios aquí es que hay un fuerte efecto de sesgo debido a las construcciones que existen en otros idiomas. Si solo aborda esto desde una perspectiva de pizarra en blanco, hay docenas de comportamientos que podría imaginar, de los cuales TypeScript tiene solo un pequeño subconjunto.

Podrías imaginar fácilmente alguna palabra clave que se aplicó a un método y se hizo cumplir que los métodos derivados lo llamaron a través de super https://github.com/Microsoft/TypeScript/issues/21388 . Si C # y Java tuvieran esta palabra clave, la gente la aplicaría absolutamente en lugares donde tuviera sentido. De hecho, podría decirse que se aplicaría mucho más comúnmente que el final, porque es imposible aplicar una verificación de tiempo de ejecución y es un aspecto mucho más sutil del contrato derivado de la base que "las clases derivadas pueden no existir". Sería útil en una variedad de formas que la final no lo sería. Preferiría tener esa palabra clave que esta (aunque creo que ninguna cumple con la barra de complejidad vs valor).

Entonces, ¿cuál sería una razón apropiada para considerar implementar esto?

Cuando miramos los comentarios, las consideraciones importantes se ven así:

No puedo expresar adecuadamente el comportamiento de esta biblioteca de JavaScript
Mi código se compiló, pero realmente no debería haberlo hecho porque tenía este (sutil) error
El sistema de tipos no comunica adecuadamente la intención de este código.
final los golpea, pero también lo haría un modificador que dice "Esta función no se puede llamar dos veces seguidas" o "La construcción de esta clase no termina realmente hasta el siguiente tick asincrónico". No todo lo imaginable debería existir.

Nunca hemos visto comentarios como "Pasé horas depurando porque estaba tratando de heredar de una clase que en realidad no era subclasificable", alguien que dijera que con razón desencadenaría un 'wat' porque no es realmente imaginable. Incluso si el modificador existiera, realmente no ayudaría a la situación, porque las bibliotecas no documentan si las clases están destinadas a ser definitivas, por ejemplo, es fs.FSwatcher https://nodejs.org/api/fs.html#fs_class_fs_fswatcher final ? Parece que ni siquiera los autores de los nodos lo saben. Por lo tanto, final es suficiente si los autores saben que es final, pero eso se documentará independientemente, y la falta de final no le dice nada porque a menudo simplemente no se conoce de ninguna manera.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub https://github.com/Microsoft/TypeScript/issues/8306#issuecomment-458268725 , o silencie el hilo https://github.com/notifications/unsubscribe-auth/AA3NA4o9wu54CcJyK1C8WHVjXIQwfms4U8ks .

Sí, la discusión es tan difusa ahora. Me gustaría ver estos problemas en un rastreador donde pueda votar, como el que se usa para los problemas de IntelliJ / webstorm. Obtenga algunos números reales de cuántas personas les gustaría ver final para clases y / o métodos.

final sería muy útil

Se utiliza una palabra clave abstracta para recordar a los programadores que deben anular el campo. Una palabra clave para recordarles que no la anulen será útil de manera similar.

Los métodos finales pueden ser cruciales para hacer que el código sea más estructurado y / o seguro.

Por ejemplo, digamos que tiene una aplicación que admite complementos de terceros. Puede forzar a todos los complementos a extender una clase abstracta que implementa una interfaz para cualquier método que DEBEN crear y contiene algunos métodos finales que controlan el orden de las operaciones y le permiten implementar enlaces entre operaciones. Debido a esto, su aplicación no necesita conocer las especificaciones de ningún complemento individual y aún conocer algunos de su estado interno.

Sin métodos finales, podrían interferir con sus funciones controlando el orden de las operaciones; potencialmente causando un error, exploit u otro comportamiento inesperado dentro de su aplicación. Si bien la mayoría de las situaciones tendrán algún tipo de solución, esas soluciones pueden ser complicadas, repetitivas y, a veces, poco fiables; mientras que los métodos finales lo convierten en una solución simple cuando se define el método.

Además, aunque este ejemplo es específico para complementos, también es aplicable a otras situaciones. (nuevo equipo de desarrollo, garantizar que cierta lógica no se quede fuera de alcance, estandarizar cómo se ejecuta el código, asegurarse de que los métodos basados ​​en la seguridad no se modifiquen, etc., todos podrían beneficiarse de esto)

@RyanCavanaugh Si bien hay un buen ejemplo en el que render () quiere ser final, nadie ha mencionado el principio abierto-cerrado (ahora acrónimo en segundo lugar como parte de SOLID).
Para escribir marcos que otros usarán, esto sería útil.
Si no es así, ¿cuál es el enfoque preferido para adoptar el enfoque abierto-cerrado en Typecript?

Me ENCANTARÍA un final por métodos (y supongo que propiedades y declaraciones en general). Está causando verdaderos problemas: la gente sigue anulando cosas accidentalmente sin darse cuenta de que había una debajo ... rompiendo cosas ...

También creo que los métodos finales serían de gran valor y, como han señalado otros, todos los argumentos en contra parecen centrarse en las clases finales. Hubo un problema separado (# 9264) pero se cerró porque aparentemente este problema lo cubre, pero realmente no creo que lo haga. ¿Podríamos reabrir ese problema o abordarlo adecuadamente aquí?

Mi situación es similar a @ 0815fox donde tengo una versión "interna" de un método, que es abstracta, pero quiero que la versión "original" del método nunca se anule, ya que contiene un comportamiento importante y no quiero una subclase para anular esa.

Me resulta desconcertante que, sin importar el apoyo abrumador de la comunidad a favor de esta función, los desarrolladores de TypeScript sigan rechazándola ...

quizás una solución temporal sería agregar un estándar a TS-Doc donde se advertiría que alguien no herede una clase en particular o anule un método en particular. Como una anotación @final o algo similar.

Bueno, ¿qué debo agregar a lo que todos los demás, votando por "final", han dicho ... Aquí está mi voto a favor?

Acordado. Claro, puede usar readonly con métodos si lo tratará como una propiedad de la función de tipo, pero esta solución puede ser bastante confusa. Sería bueno tener una solución nativa para este problema.

Creo que final , especialmente final method es importante en la verificación de tipos, ya que a menudo controla el flujo de trabajo de los métodos, que garantizan el orden del método llamado en el patrón de plantilla u otros patrones de diseño.

final debería ser el camino a seguir

Pandilla: dado que este ticket está (a) cerrado y (b) tiene un título engañoso para las discusiones sobre el "método final", decidí crear un nuevo ticket centrado únicamente en los métodos finales. Con suerte, eso será más difícil de ignorar e incluso puede estimular algún progreso. Oh, sí, consulte https://github.com/microsoft/TypeScript/issues/33446

@RyanCavanaugh

complejidad vs barra de valor

¿Podrías explicarnos qué tiene de complejo final ? Me parece que sería una de las palabras clave más fáciles de implementar, especialmente dado que las palabras clave como abstract ya están implementadas, y gran parte de la lógica / código podría reutilizarse a partir de eso.

@vinayluzrao

Solo como un ejemplo aleatorio, actualmente decimos que puede heredar de cualquier cosa que tenga una firma de construcción:

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

Obviamente, una clase final es new -able:

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

Pero ahora hay una contradicción: tenemos algo que es new -able, pero no es legal incluir una cláusula extends . Entonces, un nivel de indirección derrota a final :

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

Esto ya la gente hace tropezar a la gente con abstract y la gente está constantemente argumentando que las clases abstract deberían tener alguna firma de construcción implícita que existe para fines de verificación de tipos pero que en realidad no se puede invocar o algo así.

Para su información, el 90% de las veces cuando decimos "complejidad" en estas discusiones, nos referimos a la complejidad conceptual que experimentan los usuarios de TypeScript , no a la complejidad de la implementación de nuestra parte como desarrolladores del producto. Esto último es algo con lo que se puede tratar (los tipos condicionales y los tipos mapeados son extraordinariamente complejos desde una perspectiva de implementación, por ejemplo); La complejidad conceptual no es algo con lo que pueda lidiar con más ingenieros y más inteligentes en el producto. Cada vez que agregamos algo al lenguaje que causa un comportamiento inesperado o difícil de explicar, es una carga mental para todos los usuarios del idioma.

Solo necesito final para los métodos, porque de manera relativamente regular, alguien anula (sin saberlo) un método subyacente, por lo tanto, mi código no se ejecuta y las cosas se rompen de maneras muy extrañas, y la gente no sabe por qué, y a menudo lleva mucho tiempo depurarlo.

@RyanCavanaugh eso realmente lo aclara, gracias. Sin embargo, me gustaría dejar un pensamiento: me parece que el problema en el ejemplo que dio surge debido al fuerte acoplamiento entre new y extends , tal vez ese sea el problema más grande y no final sí. Me pregunto por qué se tomó esta decisión de acoplamiento y qué tan difícil sería deshacerla (no implica en absoluto que deba deshacerse) en este momento.

Sin embargo, no estoy seguro de si esta podría ser una discusión demasiado tangencial para tenerla aquí.

Este es un problema con el que me encontré. Este ejemplo de código simple no se puede compilar.
final palabra clave PERFECTAMENTE :

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

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

El tipo "Niño" no se puede asignar al tipo "esto".
'Child' se puede asignar a la restricción de tipo 'this', pero 'this' podría instanciarse con un subtipo diferente de restricción 'Child'.

Patio de recreo

¿Se puede reabrir esto por favor? es obvio que esta función es necesaria

Hemos discutido este tema una docena de veces en varias reuniones y cada vez hemos decidido no hacerlo. Reabrir esto para discutir una decimotercera vez no es un buen uso de nuestro tiempo.

Puede estar en desacuerdo con las decisiones del equipo de TypeScript, lo cual está bien, pero no podemos pasar todo el día dando vueltas en círculos para volver a tomar decisiones con las que la gente no está de acuerdo. Hay literalmente miles de otras sugerencias en este repositorio que merecen una consideración inicial antes de que esta vuelva a aparecer.

La discusión constructiva como el comentario de @ nicky1038 es muy bienvenida aquí; Solicitaría que la gente no haga ping continuamente a este hilo en busca de noticias / solicitudes de reconsideración para que no tengamos que bloquearlo.

Estoy aquí para apoyar la adición de métodos finales también. Tengo una clase base con una función llamada _init_ que no quiero que las subclases puedan anular. Final sería la solución perfecta.

También apoyo la adición de la palabra clave final para los métodos.
Hoy en día es un estándar en programación orientada a objetos.

Agregar final tiene sentido para mí. Especialmente cuando se crean bibliotecas / marcos para que otros los usen, donde ni siquiera desea permitir el riesgo de anular una función.

Si no me equivoco, agregar final podría ayudar a evitar problemas con los errores de 'myObj' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'MyClass' al trabajar con genéricos porque podría ganar MyClass final y, por lo tanto, garantizarlo no hay otros subtipos.
No estoy 100% seguro, pero creo que así es como puede resolver este problema en Java.

Tenemos lo mismo, estamos haciendo un Componente base en angular que muchos otros paquetes pueden o necesitan extender como base.
En eso tenemos cosas como ngOnChanges o incluso ngOnInit de angular que solo la clase base debe implementar y las subclases no deben anularlas, sino que realmente implementan ngOnInitReal (o como lo llamemos) para que tengamos el control total cuando se llame y con que.
Como no podemos controlar esto, solo podemos hacerlo a través de la documentación. Si un fabricante de componentes no lee esto, su componente puede romperse, o necesita copiar mucho código de nuestra base para hacer lo mismo.

Entonces, sin los métodos finales, realmente no podemos hacer un contrato de API sólido como una roca.

alguien mencionó

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

alguien mencionó

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

Si bien puede hacer esto, existen algunas diferencias. Por ejemplo, cada instancia de la clase tendrá su propia copia de la función, en lugar de que solo el prototipo tenga la función que todos usan.

¿Fue útil esta página
0 / 5 - 0 calificaciones