Typescript: Soporta anular palabra clave en métodos de clase

Creado en 10 feb. 2015  ·  210Comentarios  ·  Fuente: microsoft/TypeScript

(Actualización de @RyanCavanuagh)

Consulte este comentario antes de solicitar "cualquier actualización", "agregue esto ahora", etc.


(NOTA, esto _no_ es un duplicado del número 1524. La propuesta aquí es más similar al especificador de anulación de C++, que tiene mucho más sentido para mecanografiado)

Una palabra clave anulada sería inmensamente útil en texto mecanografiado. Sería una palabra clave opcional en cualquier método que invalide un método de superclase, y similar al especificador de invalidación en C++ indicaría la intención de que "_el nombre+firma de este método siempre debe coincidir con el nombre+firma de un método de superclase_" . Esto detecta una amplia gama de problemas en bases de código más grandes que, de lo contrario, podrían pasarse por alto fácilmente.

De nuevo, similar a C++, _no es un error omitir la palabra clave anular de un método anulado_. En este caso, el compilador simplemente actúa exactamente como lo hace actualmente y omite las comprobaciones de tiempo de compilación adicionales asociadas con la palabra clave override. Esto permite los escenarios de JavaScript sin tipo más complejos donde la anulación de la clase derivada no coincide exactamente con la firma de la clase base.

Ejemplo de uso

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

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

Ejemplo de condiciones de error de compilación

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

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

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

IntelliSense

Además de la validación adicional del tiempo de compilación, la palabra clave override proporciona un mecanismo para que la inteligencia mecanografiada muestre y seleccione fácilmente los supermétodos disponibles, donde la intención es anular específicamente uno de ellos en una clase derivada. Actualmente, esto es muy complicado e implica navegar a través de la cadena de superclases, encontrar el método que desea anular y luego copiarlo y pegarlo en la clase derivada para garantizar que las firmas coincidan.

Mecanismo IntelliSense propuesto

Dentro de una declaración de clase:

  1. anular tipo
  2. Aparece un menú desplegable de autocompletar de todos los supermétodos para la clase
  3. Se selecciona un método en el menú desplegable
  4. La firma del método se emite en la declaración de clase después de la palabra clave override.
Add a Flag Revisit Suggestion

Comentario más útil

Disculpe el lloriqueo, pero sinceramente, si bien su argumento se aplica a la palabra clave public en un idioma en el que todo es público de forma predeterminada, es bastante divisorio, con elementos como abstract y un override opcional. La palabra clave

La sobreescritura es uno de los pocos aspectos restantes del lenguaje altamente sensibles a la tipografía, porque un nombre de método de sobreescritura mal escrito no es un problema obvio de tiempo de compilación. El beneficio de override es obvio, ya que le permite declarar su intención de anularlo; si el método base no existe, es un error de tiempo de compilación. Todos aclaman el sistema de tipos. ¿Por qué alguien no querría esto?

Todos 210 comentarios

Efectivamente una buena propuesta.

Sin embargo, ¿qué pasa con los siguientes ejemplos, que son anulaciones válidas para mí?

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

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

Además, agregaría un indicador del compilador para forzar que la palabra clave de anulación esté presente (o informada como una advertencia).
El motivo es detectar cuando se cambia el nombre de un método en una clase base que las clases heredadas ya implementan (pero no se supone que sea una anulación).

Ah buenos ejemplos. Hablando en términos generales, esperaría que el uso de la palabra clave override imponga la coincidencia _exacta_ de las firmas, ya que el objetivo de su uso es mantener una jerarquía de clase estricta. Entonces, para abordar sus ejemplos:

  1. Agregar un parámetro predeterminado adicional. Esto generaría un error de compilación: Snake super no define move(meters:number):void. Si bien el método derivado es funcionalmente coherente, es posible que el código del cliente que llama a Animal.move no espere que las clases derivadas también tengan en cuenta la altura (ya que la API base no la expone).
  2. Esto generaría (y siempre debería generar) un error de compilación, ya que no es funcionalmente consistente. Considere la siguiente adición al ejemplo:
class C extends A {...}
var animal : Animal = new Snake();
animal.setA(new C());
// This will have undefined run-time behavior, as C will be interpreted as type B in Snake.setA

Entonces, el ejemplo (2.) es en realidad una gran demostración de cómo una palabra clave anulada puede detectar casos sutiles en el momento de la compilación que, de lo contrario, se perderían. :)

Y volvería a enfatizar que ambos ejemplos pueden ser válidos en escenarios específicos de javascript controlados/avanzados que pueden ser necesarios... en este caso, los usuarios pueden optar por omitir la palabra clave anular.

Esto será útil. Actualmente solucionamos esto al incluir una referencia ficticia al método super:

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

Pero esto solo protege contra el segundo caso: se cambia el nombre de los supermétodos. Los cambios en la firma no desencadenan un error de compilación. Además, esto es claramente un truco.

Tampoco creo que los parámetros predeterminados y opcionales en la firma del método de clase derivada deban desencadenar un error de compilación. Eso puede ser correcto, pero va en contra de la flexibilidad inherente de JavaScript.

@rwyborn
Parece que no esperamos el mismo comportamiento.
Usaría esta palabra clave de anulación para garantizar la misma firma, mientras que yo la usaría más como una opción de lectura (por lo tanto, mi solicitud para agregar una opción de compilador para forzar su uso).
De hecho, lo que realmente esperaría es que TS detecte métodos de anulación no válidos (incluso sin el uso de anulación).
Típicamente:

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

debería generar un error, porque en realidad es una anulación de Animal.move() (comportamiento JS), pero incompatible (porque se supone que la altura no es opcional, mientras que no estará definida si se llama desde una "referencia" de Animal).
De hecho, usar override solo confirmaría (por el compilador) que este método realmente existe en la clase base (y por lo tanto con una firma compatible, pero debido al punto anterior, no debido a la palabra clave override).

@stephanedr , hablando como un solo usuario, en realidad estoy de acuerdo con usted en que el compilador siempre debe confirmar la firma, ya que personalmente me gusta imponer una tipificación estricta dentro de mis jerarquías de clase (¡incluso si javascript no lo hace!).

Sin embargo, al proponer que este comportamiento es opcional a través de la palabra clave de anulación, trato de tener en cuenta que, en última instancia, javascript no está tipificado y, por lo tanto, hacer cumplir la estricta coincidencia de firmas de forma predeterminada daría como resultado que algunos patrones de diseño de javascript ya no se puedan expresar en Typescript.

@rwyborn Me alegra que haya mencionado la implementación de C ++ porque así es exactamente como imaginé que debería funcionar antes de llegar aquí, opcionalmente. Aunque, una bandera del compilador que forzara el uso de la palabra clave anular quedaría bien en mi libro.

La palabra clave permitiría errores de tiempo de compilación para un desarrollador torpe al escribir, que es lo que más me preocupa sobre las anulaciones en su forma actual.

class Base {
    protected commitState() : void {

    }
}


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

    }
}

Actualmente (a partir de 1.4) la clase Implementation anterior simplemente declararía un nuevo método y el desarrollador no sabría nada hasta que noten que su código no funciona.

Discutido en la revisión de sugerencias.

Definitivamente entendemos los casos de uso aquí. El problema es que agregarlo en esta etapa del idioma agrega más confusión de la que elimina. Una clase con 5 métodos, de los cuales 3 están marcados override , no implicaría que los otros 2 _no_ sean anulados. Para justificar su existencia, el modificador realmente necesitaría dividir el mundo de manera más limpia que eso.

Disculpe el lloriqueo, pero sinceramente, si bien su argumento se aplica a la palabra clave public en un idioma en el que todo es público de forma predeterminada, es bastante divisorio, con elementos como abstract y un override opcional. La palabra clave

La sobreescritura es uno de los pocos aspectos restantes del lenguaje altamente sensibles a la tipografía, porque un nombre de método de sobreescritura mal escrito no es un problema obvio de tiempo de compilación. El beneficio de override es obvio, ya que le permite declarar su intención de anularlo; si el método base no existe, es un error de tiempo de compilación. Todos aclaman el sistema de tipos. ¿Por qué alguien no querría esto?

Estoy 100% de acuerdo con @hdachev , la pequeña inconsistencia referida también por @RyanCavanaugh es fácilmente superada por los beneficios de la palabra clave al llevar las verificaciones de tiempo de compilación a las anulaciones de métodos. Nuevamente señalaría que C++ usa una palabra clave de anulación opcional con éxito exactamente de la misma manera que se sugiere para mecanografiado.

No puedo enfatizar lo suficiente la diferencia que hace la verificación de anulación en una base de código a gran escala con árboles OO complejos.

Finalmente, agregaría que si la inconsistencia de una palabra clave opcional realmente es una preocupación, entonces se podría usar el enfoque de C#, es decir, el uso obligatorio de las palabras clave "nuevo" o "anular":

class Dervied extends Base {

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

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

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

Esto no es análogo a public porque se sabe que una propiedad sin un modificador de acceso es pública; un método sin override _no_ se sabe que no es anulado.

Aquí hay una solución verificada en tiempo de ejecución que usa decoradores (que viene en TS1.5) que produce buenos mensajes de error con muy poca sobrecarga:

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

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

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

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

Ejecutar este código produce un error:

Error: el método notAnOverride de Derived no anula ningún método de clase base

Dado que este código se ejecuta en el momento de la inicialización de la clase, ni siquiera necesita pruebas unitarias específicas para los métodos en cuestión; el error ocurrirá tan pronto como se cargue su código. También puede suscribirse a una versión "rápida" de override que no verifica las implementaciones de producción.

@RyanCavanaugh Así que estamos en Typescript 1.6 y los decoradores siguen siendo una característica experimental, no es algo que quiera implementar en una base de código de producción a gran escala como un truco para que la anulación funcione.

Para probar otro ángulo, todos los lenguajes tipeados populares admiten la palabra clave "anular"; Swift, ActionScript, C#, C++ y F#, por nombrar algunos. Todos estos idiomas comparten los problemas menores que ha expresado en este hilo sobre la anulación, pero claramente hay un gran grupo que ve que los beneficios de la anulación superan con creces estos problemas menores.

¿Sus objeciones se basan únicamente en el costo/beneficio? Si tuviera que seguir adelante e implementar esto en un PR, ¿sería aceptado?

No es solo una cuestión de costo/beneficio. Como explicó Ryan, el problema es que marcar un método como una anulación no implica que otro método _no_ sea una anulación. La única forma en que tendría sentido es si todas las anulaciones deben marcarse con una palabra clave override (que, si lo exigimos, sería un cambio importante).

@DanielRosenwasser Como se describió anteriormente, en C ++, la palabra clave de anulación es opcional (exactamente como se propone para Typescript), pero todos la usan sin problemas y es muy útil en bases de código grandes. Además, en Typescript tiene mucho sentido que sea opcional debido a la sobrecarga de funciones de JavaScript.

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

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

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

En cuanto a todo el argumento "no implica que otro método no sea una anulación", es exactamente análogo a "privado". Puede escribir un código base completo sin usar nunca la palabra clave privada. Solo se accederá de forma privada a algunas de las variables en ese código base y todo se compilará y funcionará bien. Sin embargo, "privado" es un azúcar sintáctico adicional que puede usar para decirle al compilador "No, realmente, error de compilación si alguien intenta acceder a esto". De la misma manera, "sobrecarga" es un azúcar sintáctico adicional para decirle al compilador "Quiero que esto coincida exactamente con la declaración de la clase base. Si no compila el error".

Saben qué, creo que ustedes están obsesionados con la interpretación literal de "anular". Realmente lo que está marcando es "exactamente_coincidencia_firma_del_método_superclase", pero eso no es tan legible :)

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

A mí también me gustaría tener disponible la palabra clave anular y hacer que el compilador genere un error si el método marcado para anular no existe o tiene una firma diferente en la clase base. Ayudaría a la legibilidad y a la refactorización.

+1, también las herramientas mejorarán mucho. Mi caso de uso es usar reaccionar. Tengo que verificar la definición cada vez que uso los métodos ComponentLifecycle :

``` C#
interfaz ComponentLifecycle

{
componenteSeMontará?(): void;
componenteHizoMontar?(): void;
componentWillReceiveProps?(nextProps: P, nextContext: any): void;
shouldComponentUpdate?(nextProps: P, nextState: S, nextContext: any): booleano;
¿componentWillUpdate?(nextProps: P, nextState: S, nextContext: any): void;
¿componentDidUpdate?(prevProps: P, prevState: S, prevContext: any): void;
componenteSeDesmontará?(): void;
}

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

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

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

    }
}

@olmobrutall parece que su caso de uso se resuelve mejor si el servicio de idiomas ofrece refactorizaciones como "implementar interfaz" u ofrece una mejor finalización, no agregando una nueva palabra clave al idioma.

No nos distraigamos :) Las características del servicio de idiomas son solo una pequeña parte del mantenimiento de las jerarquías de la interfaz en una gran base de código. De lejos, la mayor victoria es obtener errores de tiempo de compilación cuando una clase en algún lugar de su jerarquía no se ajusta. Es por eso que C ++ agregó una palabra clave de anulación opcional (cambio no disruptivo). Es por eso que Typescript debería hacer lo mismo.

La documentación de Microsoft para la anulación de C++ resume muy bien las cosas, https://msdn.microsoft.com/en-us/library/jj678987.aspx

Utilice la anulación para ayudar a evitar el comportamiento de herencia involuntario en su código. El siguiente ejemplo muestra dónde, sin utilizar la anulación, el comportamiento de la función miembro de la clase derivada puede no haberse previsto. El compilador no emite ningún error para este código.
...
Cuando usa override, el compilador genera errores en lugar de crear silenciosamente nuevas funciones miembro.

De lejos, la mayor victoria es obtener errores de tiempo de compilación cuando una clase en algún lugar de su jerarquía no se ajusta.

Tengo que estar de acuerdo. Una trampa que surge en nuestros equipos es que las personas piensan que han anulado los métodos cuando, de hecho, han escrito un poco mal o han ampliado la clase equivocada.

Los cambios de interfaz en una biblioteca base cuando se trabaja en muchos proyectos es más difícil de lo que debería ser en un lenguaje con una agenda como TypeScript.

Tenemos tantas cosas geniales, pero luego hay rarezas como esta y no hay propiedades constantes a nivel de clase.

@RyanCavanaugh es cierto, pero el servicio de idioma podría activarse después de escribir la anulación, como lo hacen muchos idiomas, sin la palabra clave es mucho más difícil determinar cuándo es el momento adecuado.

Acerca de implementar la interfaz, tenga en cuenta que la mayoría de los métodos en la interfaz son opcionales y debe anular solo los pocos que necesita, no todo el paquete. Podría abrir un cuadro de diálogo con casillas de verificación, pero aún así...

Y aunque mi problema actual es encontrar el nombre del método, en el futuro será genial recibir notificaciones con errores en tiempo de compilación si alguien cambia el nombre o la firma de un método base.

¿No se podría resolver la incoherencia simplemente agregando una advertencia cuando no se anula la escritura? Creo que mecanografiado está haciendo lo correcto al agregar pequeños cambios de ruptura razonables en lugar de preservar las malas decisiones para el final de los tiempos.

También abstracto ya está ahí, harán una pareja increíble :)

También sentí la necesidad de un especificador de 'anulación'. En proyectos medianos a grandes, esta función se vuelve esencial y, con el debido respeto, espero que el equipo de Typescript reconsidere la decisión de rechazar esta sugerencia.

Para cualquier persona interesada, hemos escrito una regla tslint personalizada que proporciona la misma funcionalidad que la palabra clave anular mediante el uso de un decorador, similar a la sugerencia de Ryan anterior, pero verificada en tiempo de compilación. Pronto lo abriremos, lo publicaré cuando esté disponible.

Sentí fuertemente la necesidad de la palabra clave 'anular' también.
En mi caso, cambié parte del nombre del método en una clase base y olvidé cambiar el nombre de algunos de los métodos anulados. Por supuesto, esto conduce a algunos errores.

Pero, si existiera tal característica, podemos encontrar estos métodos de clase fácilmente.

Como mencionó @RyanCavanaugh , si esta palabra clave es solo una palabra clave opcional, esta característica genera confusión. Entonces, ¿qué tal marcar algo en tsc para habilitar esta función?

Por favor, reconsidere esta función de nuevo....

Para mí, si la palabra clave anular va a ser útil, debe aplicarse, como en C#. Si especifica un método en C# que anula la firma de un método base, _debe_ etiquetarlo como anulado o como nuevo.

C ++ es molesto e inferior en comparación con C # en algunos lugares porque hace que demasiadas palabras clave sean opcionales y, por lo tanto, excluye la _consistencia_. Por ejemplo, si sobrecarga un método virtual, el método sobrecargado puede marcarse como virtual o no; de cualquier manera, será virtual. Prefiero este último porque ayuda a otros desarrolladores que leen el código, pero no puedo hacer que el compilador haga cumplir que se instale, lo que significa que nuestra base de código sin duda tendrá palabras clave virtuales faltantes donde realmente deberían estar. La palabra clave override es igualmente opcional. Ambos son una mierda en mi opinión. Lo que se pasa por alto aquí es que el código puede servir como documentación y mejorar la capacidad de mantenimiento al imponer la necesidad de palabras clave en lugar de un enfoque de "tómalo o déjalo". La palabra clave "lanzar" en C++ es similar.

Para lograr el objetivo anterior en TypeScript, el compilador necesita un indicador para "habilitar" este comportamiento estricto o no.

En lo que se refiere a las firmas de función de la base y la anulación, deben ser idénticas. Pero sería deseable permitir la covarianza en la especificación del tipo de retorno para hacer cumplir una verificación en tiempo de compilación.

Vengo a TS desde AS3, así que, por supuesto, también votaré aquí por una palabra clave override . Para un desarrollador que es nuevo en un código base dado, ver un override es una gran pista de lo que podría estar pasando en una clase (secundaria). Creo que esa palabra clave agrega mucho valor. Optaría por hacerlo obligatorio, pero puedo ver cómo eso sería un cambio importante y, por lo tanto, debería ser opcional. Realmente no veo ningún problema con la ambigüedad que impone, aunque soy sensible a la diferencia entre una palabra clave opcional override y la palabra clave predeterminada public .

Para todos los +1ers, ¿pueden hablar sobre lo que no es suficiente en la solución de decorador que se muestra arriba?

Para mí, se siente como una construcción artificial, no como una propiedad del lenguaje en sí. Y supongo que eso es por diseño, porque eso es lo que es. Lo que supongo que envía al desarrollador (bueno, a mí de todos modos) el mensaje de que es transitorio, no una buena práctica.

obviamente, cada idioma tiene su propio paradigma y, siendo nuevo en TypeScript, soy lento con el cambio de paradigma. Sin embargo, debo decir que override SÍ me parece una mejor práctica por varias razones. Estoy haciendo el cambio a TypeScript porque me he tragado por completo el koolaid fuertemente tipado y creo que el costo (en términos de pulsaciones de teclas y curva de aprendizaje) se ve superado en gran medida por los beneficios tanto para el código libre de errores como para la comprensión del código. override es una pieza muy importante de ese rompecabezas y comunica información muy importante sobre el papel del método de anulación.

Para mí, se trata menos de la conveniencia de IDE, aunque eso es innegablemente increíble cuando se admite correctamente, y más de cumplir con lo que creo que son los principios sobre los que ha construido este lenguaje.

@RyanCavanaugh algunos problemas que veo:

  • La sintaxis del decorador será más difícil para los IDE para completar el código (sí, supongo que podría hacerse, pero necesitarían conocimiento previo)
  • La clase derivada podría cambiar la visibilidad de un método, lo que en términos estrictos no debería permitirse.
  • Difícil para el decorador verificar la lista y los tipos de argumentos, y el tipo de devolución compatible

Sin embargo, el compilador ya está comprobando la lista de argumentos y el tipo de retorno. Y creo que incluso si override existiera como una palabra clave de primera clase, no aplicaríamos ninguna regla nueva sobre la identidad de la firma.

Y creo que incluso si anular existiera como una palabra clave de primera clase, no aplicaríamos ninguna regla nueva sobre la identidad de la firma.

@RyanCavanaugh entonces creo que podría estar en una página diferente sobre la intención de la palabra clave. El objetivo principal es para los casos en los que _quiere_ imponer la identidad de la firma. Esto es para el patrón de diseño en el que tiene una jerarquía profunda y compleja que se asienta sobre una clase base que define un contrato de métodos de interfaz, y todas las clases en la jerarquía deben coincidir _exactamente_ con esas firmas. Al agregar la palabra clave override en estos métodos en todas las clases derivadas, se le alerta sobre cualquier caso en el que la firma diverja del contrato establecido en la clase base.

Como sigo diciendo, este no es un caso de uso esotérico. Cuando se trabaja en bases de código grandes (digamos con cientos o miles de clases), esto sucede todos los días, es decir, alguien necesita cambiar la firma de la clase base (modificar el contrato) y desea que el compilador le avise de cualquier casos en toda la jerarquía que ya no coinciden.

El compilador ya advertirá sobre anulaciones ilegales. El problema con el
implementación actual es la falta de intención al declarar un invalidado
método.

El decorador mencionado anteriormente parece ir en contra de lo que el lenguaje
está tratando de lograr. No debería haber un costo de tiempo de ejecución incurrido por
algo que se puede tratar en tiempo de compilación, por pequeño que sea el costo.
Queremos detectar la mayor cantidad posible de cosas que van mal, sin necesidad de
ejecuta el código para averiguarlo.

Es posible lograr usando el decorador y personalizar un tslint personalizado
regla, pero sería mejor para el ecosistema de herramientas y la comunidad para
el idioma para tener una palabra clave oficial.

Creo que ser opcional es un enfoque, pero al igual que con algunos compiladores de C++
hay banderas que puede configurar que imponen su uso (por ejemplo, sugerir-anular,
incoherente-falta-anulación). Este parecería el mejor enfoque para evitar
cambios importantes y parece consistente con otras características nuevas que se agregan
recientemente, como tipos anulables y decoradores.

El miércoles 23 de marzo de 2016 a las 21:31, Rowan Wyborn [email protected] escribió:

Y creo que incluso si anular existiera como una palabra clave de primera clase,
no haría cumplir alguna nueva regla sobre la identidad de la firma.

@RyanCavanaugh https://github.com/RyanCavanaugh entonces creo que podrías
estar en una página diferente sobre la intención de la palabra clave. Todo el punto de
es para los casos en los que _quiere_ imponer la identidad de la firma. Esta
es para el patrón de diseño donde tienes una jerarquía profunda y compleja sentada
en una clase base que define un contrato de métodos de interfaz, y todo
las clases en la jerarquía deben coincidir _exactamente_ con esas firmas. Añadiendo
la palabra clave override en estos métodos en todas las clases derivadas, usted está
alertado de cualquier caso en que la firma se aparte del contrato establecido
en la clase básica.

Como sigo diciendo, este no es un caso de uso esotérico. Cuando se trabaja en un
grandes bases de código (digamos con cientos o miles de clases) esto es un _every
día_ ocurrencia, es decir, alguien necesita cambiar la firma de la base
clase (modificar el contrato), y desea que el compilador le avise de cualquier
casos en toda la jerarquía que ya no coinciden.


Estás recibiendo esto porque estás suscrito a este hilo.
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment-200551774

@kungfusheep fwiw el compilador solo detecta una cierta clase de anulaciones ilegales, ahí es donde hay un conflicto directo entre los parámetros declarados. _No_ detecta la adición o eliminación de parámetros, ni detecta cambios en el tipo de devolución. Estas comprobaciones adicionales son lo que activaría cualquier palabra clave de anulación.

El compilador le advierte correctamente si _agrega_ un parámetro a una función de anulación.

Sin embargo, eliminar un parámetro es _totalmente válido_:

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

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

Cambiar el tipo de devolución también es _totalmente válido_:

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

@RyanCavanaugh sí, son válidos desde una perspectiva de lenguaje estricto, pero es por eso que comencé a usar el término "contrato" arriba. Si una clase base establece una firma específica, cuando uso la anulación, declaro que quiero que mi clase derivada siga estrictamente esa firma. Si la clase base agrega parámetros adicionales o cambia el tipo de devolución, quiero saber cada punto en mi base de código que ya no coincide, ya que el contrato en el que se escribieron originalmente ahora ha cambiado de alguna manera.

La idea de tener una gran jerarquía de clases, cada una con sus propias permutaciones ligeramente diferentes de los métodos base (incluso si son válidas desde la perspectiva del lenguaje) induce a una pesadilla y me lleva de vuelta a los viejos tiempos de javascript antes de que apareciera mecanografiado :)

Si la opcionalidad es el principal problema con la palabra clave, ¿por qué no hacerla obligatoria cuando el método de la clase base se define como abstract ? De esta forma, si desea aplicar estrictamente el patrón, simplemente puede agregar una clase base abstracta. Para evitar romper el código, un interruptor del compilador podría deshabilitar la verificación.

Ahora parece que estamos hablando de dos conjuntos diferentes de expectativas. Está la situación de anulación faltante/anulación ilegal y luego está la de firma explícita. ¿Podemos todos estar de acuerdo en que lo primero es lo mínimo absoluto que esperaríamos de esta palabra clave?

Solo digo esto porque actualmente hay otras formas de hacer cumplir las firmas de métodos explícitos, como las interfaces, pero actualmente no hay forma de indicar explícitamente la intención de anular.

Ok, no hay forma de hacer cumplir firmas explícitas de métodos anulados, pero dado que el compilador exige que cualquier cambio de firma sea al menos 'seguro', entonces parece que hay una conversación separada sobre la solución a ese problema.

Sí de acuerdo. Si tuviera que elegir, la situación de anulación faltante/anulación ilegal es el problema más importante a resolver.

Llego un poco tarde a la fiesta... Creo que el objetivo de Typescript es hacer cumplir las reglas en tiempo de compilación, no en tiempo de ejecución, de lo contrario, todos estaríamos usando Javascript simple. Además, es un poco raro usar hacks/kludges para hacer cosas que son estándar en tantos idiomas.
¿Debería haber una palabra clave override en Typescript? Ciertamente lo creo. ¿Debería ser obligatorio? Por razones de compatibilidad, diría que su comportamiento podría especificarse con un argumento del compilador. ¿Debe hacer cumplir la firma exacta? Creo que esto debería ser una discusión separada, pero hasta ahora no he tenido ningún problema con el comportamiento actual.

La razón original para cerrar esto parece ser que no podemos introducir el cambio radical de que todos los métodos anulados necesitan el especificador override , lo que lo haría confuso ya que los métodos no marcados con override también podrían hecho ser anula.

¿Por qué no aplicarlo, ya sea con una opción del compilador, y/o si hay _al menos un_ método marcado override en la clase, entonces todos los métodos que se reemplazan deben marcarse como tales, de lo contrario es un error?

¿Vale la pena reabrir esto mientras está en el aire?

El viernes 8 de abril de 2016 a las 14:38, Peter Palotas [email protected] escribió:

La razón original para cerrar esto parece ser que no podemos introducir
el cambio de última hora que todos los métodos anulados necesitan la anulación
especificador, lo que lo haría confuso ya que los métodos no marcados con
'anular' también podría ser de hecho anulaciones.

¿Por qué no aplicarlo, ya sea con una opción de compilador y/o si hay al menos
menos 'un' método marcado anular en la clase, entonces todos los métodos que son
las anulaciones deben marcarse como tales, de lo contrario, ¿es un error?


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment-207434898

¡Digo, por favor, reabre! Estaría feliz con una opción de compilador.

sí, vuelva a abrir

¡Reabre esto!

¿No requiere ES7 esta anulación/sobrecarga de múltiples métodos que tienen el mismo
¿nombre?
El 8 de abril de 2016 a las 10:56 a. m., "Aram Taieb" [email protected] escribió:

¡Sí, vuelve a abrir esto!


Estás recibiendo esto porque estás suscrito a este hilo.
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment-207466464

+1: para mí, esta es la brecha más grande en la seguridad de tipos que tengo en el desarrollo diario de TypeScript.

Cada vez que implemento un método de ciclo de vida de React como "componentDidMount", busco la página de documentación de React relevante y copio/pego el nombre del método, para asegurarme de que no tengo un error tipográfico. Hago esto porque toma 20 segundos mientras que los errores de un error tipográfico son indirectos y sutiles, y pueden tomar mucho más tiempo para localizarlos.

class con 5 métodos, de los cuales 3 están marcados como anulados, no implicaría que los otros 2 no sean anulados. Para justificar su existencia, el modificador realmente necesitaría dividir el mundo de manera más limpia que eso.

Si esta es la principal preocupación, llame a la palabra clave check_override para dejar en claro que está optando por anular la verificación y, por implicación, los otros métodos _no se verifican_ en lugar de _no se anulan_.

¿Qué pasa con el uso implements. ? Siendo algo así:

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

Esta sintaxis tiene algunas ventajas:

  • Utiliza una palabra clave ya existente.
  • Después de escribir implements. el IDE tiene una excelente oportunidad para mostrar una ventana emergente de autocompletado.
  • la palabra clave aclara que se puede usar para forzar la verificación de métodos abstractos de clases base, pero también de interfaces implementadas.
  • la sintaxis es lo suficientemente ambigua como para ser utilizada también para campos, como este:
class MyComponent<MyComponentProps, MyComponentState> {
    implements.state = {/*auto-completion for MyComponentState here*/};

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

Nota: Alternativamente, podríamos usar base. , es un clasificador y más intuitivo, pero quizás más confuso (¿definición o llamada?) y el significado no es tan compatible con la implementación de la interfaz. Ejemplo:

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

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

No creo que implements cubra suficientemente todos los casos de uso
mencionado anteriormente y es un poco ambiguo. no da mas
información a un IDE que override tampoco podría, por lo que parecería
tiene sentido apegarse a la terminología que muchos otros idiomas han usado para
lograr lo mismo.
El miércoles 13 de abril de 2016 a las 19:06, Olmo [email protected] escribió:

¿Qué pasa con el uso de implementos.? Siendo algo así:

clase MyComponent extiende React.Component{
implementa.componentWillMount(){
//hacer mis cosas
}
}

Esta sintaxis tiene algunas ventajas:

  • Utiliza una palabra clave ya existente.
  • Después de escribir implementos. el IDE tiene una excelente oportunidad para mostrar
    un método de autocompletado.
  • la palabra clave aclara que se puede usar para forzar la verificación en resumen
    métodos de clases base pero también interfaces implementadas.
  • la sintaxis es lo suficientemente ambigua para ser utilizada también para campos, como
    esta:

clase MiComponente{
implements.state = {/_auto-completado para MyComponentState aquí_/};

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

}


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment-209571753

El problema con la anulación es que una vez que usa la misma palabra clave, tiene las mismas expectativas:

  • debería ser obligatorio, al menos si el método es abstract .
  • te pierdes entonces alguna palabra clave virtual para anotar métodos que están destinados a ser anulados pero tienen un comportamiento predeterminado.

Creo que el equipo de TS no quiere agregar tanto equipaje OO al idioma, y ​​creo que es una buena idea.

Al usar implements. , tiene una sintaxis liviana para obtener los principales beneficios: autocompletar y verificar el nombre en tiempo de compilación, sin inventar nuevas palabras clave ni incrementar el número de conceptos.

También tiene la ventaja de trabajar para clases e interfaces, y para métodos (en el prototipo) o campos directos.

Las formas de hacer que override sean obligatorios ya se han discutido en el hilo y las soluciones no son diferentes a otras características implementadas por el compilador.

La palabra clave virtual realmente no tiene sentido en el contexto del lenguaje y tampoco tiene un nombre que sea intuitivo para las personas que no han usado lenguajes como C++ antes. Quizás una mejor solución para proporcionar este tipo de protección, si es necesario, sería la palabra clave final .

Estoy de acuerdo en que las características del idioma no deben agregarse apresuradamente que puedan crear 'equipaje', pero override tapa un agujero legítimo en el sistema de herencia que muchos otros idiomas han considerado necesario. Su funcionalidad se logra mejor con una nueva palabra clave, ciertamente cuando los enfoques alternativos sugieren cambios de sintaxis fundamentales.

¿Qué pasa con la configuración de campos heredados frente a la redefinición de campos nuevos? Reaccionar state es un buen ejemplo.

¿O implementar métodos de interfaz opcionales?

override podría potencialmente usarse en campos, si se vuelven a declarar
en el cuerpo de la subclase. Los métodos de interfaz opcionales no se anulan, también lo son
fuera del alcance de lo que estamos hablando aquí.

El jueves 14 de abril de 2016 a las 11:58, Olmo [email protected] escribió:

¿Qué pasa con la configuración de campos heredados frente a la redefinición de campos nuevos? estado de reacción
es un buen ejemplo.

¿O implementar métodos de interfaz opcionales?


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment-209879217

Los métodos de interfaz opcionales no se anulan, por lo que están fuera del alcance de lo que estamos hablando aquí.

Los métodos de interfaz opcionales son un concepto bastante exclusivo de TypeScript hasta donde yo sé, y creo que se les debería aplicar una implementación de TypeScript de "anulaciones".

En el caso de los métodos de ciclo de vida de React como componentDidMount, estos son métodos de interfaz opcionales que no tienen implementación en la superclase.

En el caso de los métodos de ciclo de vida de React como componentDidMount, estos son métodos de interfaz opcionales que no tienen implementación en la superclase.

Exacto, para que no anulen nada. Creo que nos estamos confundiendo acerca de lo que la palabra clave override pretende proporcionar aquí, porque si solo está buscando una forma de obtener mejores sugerencias de código/intellisense, hay otras formas de lograrlo sin agregar nuevas palabras clave para el idioma.

Chicos, con el debido respeto, ¿podemos mantenernos enfocados? Creo que discutir palabras clave alternativas es contraproducente, especialmente porque el problema se inició con una solicitud específica.

¿Podemos todos estar de acuerdo en que necesitamos override y pedir amablemente al equipo de TypeScript que lo agregue o descartar la solicitud y cerrar el problema?

Creo que siempre es útil plantear la solicitud en el contexto de cuál es el problema, siempre hay diferentes formas de resolver el problema. El punto de conflicto con la anulación era si era obligatorio o no (algunos señalaron que no es obligatorio en C++). El problema al que mucha gente ha hecho referencia es obtener el nombre correcto para las funciones reemplazables (que pueden o no implementarse en la superclase): las funciones del ciclo de vida de React son el principal problema.

Si la anulación no funciona, entonces tal vez deberíamos cerrar este problema y abrir un punto más general sobre este problema. El problema general aquí es que el contrato de interfaz con una superclase no está tipificado ni verificado, y eso hace tropezar a los desarrolladores y sería genial si TS o las herramientas pudieran ayudar.

@armandn No creo que nuestra falta de cierre o la amabilidad de nuestra sugerencia sea lo que hace que se rechace la solicitud, sino las diferencias entre la semántica de C# y TS:

Anulación del método base de C#:

  • Requiere override palabra clave
  • Crea un nuevo registro en VTable
  • Comprueba el método base abstracto/virtual obligatorio
  • Cheques de firma idéntica
  • IDE: activa la función de autocompletar después de anular la escritura

Implementación de la interfaz C#:

  • No se necesita una palabra clave, pero es posible una implementación de interfaz explícita utilizando el nombre de la interfaz.
  • Cree un nuevo registro de interfaz en VTable.
  • Cheques de firma idéntica
  • IDE: QuickFix para implementar interfaz / implementar interfaz explícitamente.

Entonces, el comportamiento en C# es bastante diferente dependiendo de si pregunta sobre clases o interfaces, pero en cualquier caso obtiene los tres beneficios principales:

  1. Comprobación de firma idéntica
  2. Comprobación de que el método puede o debe anularse (falta de ortografía en el nombre del método)
  3. IDE admite escribir la función

Con una semántica ligeramente diferente, ya tenemos 1) en TS, lamentablemente faltan 2) y 3). La razón por la que se rechazó override , en mi opinión, es que una sintaxis similar asume un comportamiento similar, y esto no es deseable porque:

Una clase con 5 métodos, de los cuales 3 están marcados como anulados, no implicaría que los otros 2 no sean anulados.

Sucede que ya tenemos 1) 2) y 3) cuando escribimos _objetos literales_, pero no cuando escribimos miembros de clase que extienden otras clases o implementan interfaces.

Con esto en mente, creo que todos podemos estar de acuerdo en esta semántica:

  • La _palabra clave_ debe ser lo suficientemente ambigua para evitar el problema del 'mundo dividido'. (es decir: check o super. , o MyInterface. )
  • No verifica abstract/virtual pero verifica la existencia de miembros en la clase base/interfaces implementadas. (Beneficio 2)
  • Comprueba la compatibilidad de firmas lo suficientemente similar como lo está haciendo TS hasta ahora. (Beneficio 1)
  • IDE: activa un autocompletado después de la palabra clave. (Beneficio 3)

Además, creo que estos dos son necesarios para completar la solución *:

  • Debería funcionar para clases e interfaces , porque TS se basa principalmente en la interfaz y las clases son un atajo para la herencia prototípica. Necesario teniendo en cuenta los métodos de interfaz opcionales.
  • Debería funcionar para Métodos y Campos , porque los métodos son solo funciones que residen en un campo.

Estos dos puntos son útiles en el caso de uso muy real de la implementación del componente React:

``` C#
clase MiComponente{
implements.state = {/ completado automático para MyComponentState aquí /};

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

}
```

La solución basada en anotaciones de @RyanCavanaugh no es suficiente porque:

  • No funcionará con interfaces.
  • No funcionará con campos.
  • Suponiendo una infraestructura similar a Roslyn para ayudar con las herramientas, no hay forma de agregar un QuickFix después de escribir una lista de autocompletar después de escribir @override .

Dada la semántica, solo se trata de elegir la sintaxis correcta. Aquí algunas alternativas:

  • override componentWillMount() : Intuitivo pero engañoso.
  • check componentWillMount() : Explícito pero que consume palabras clave.
  • super componentWillMount() :
  • implements componentWillMount() :
  • super.componentWillMount() :
  • implements.componentWillMount() :
  • this.componentWillMount() :
  • ReactComponent.componentWillMount() :

¿Opiniones?

@olmobrutall buen resumen. Un par de puntos:

Una opción de palabra clave (tomada de C#) sería nueva , lo que indicaría un nuevo espacio:

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

Mi otro punto es el problema con la naturaleza obligatoria del uso de lo anterior. Como contrato entre la superclase principal y la clase derivada, tiene sentido especificar cuáles son los puntos de interfaz donde la sintaxis anterior sería válida. Estos son realmente puntos de extensión internos para la superclase, así que algo como:

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

Lo mismo se aplicaría a la interfaz también.

Gracias :)

¿Qué hay de escribir this ?

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

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

Acerca extendable , ya hay abstract en TS 1.6, y agregar virtual tal vez creará nuevamente el problema del mundo dividido.

Correcto, pensé en lo abstracto , pero puede haber una implementación en la superclase, por lo que realmente no tiene sentido. Lo mismo ocurre con virtual , ya que eso implicaría que los miembros no virtuales no son virtuales, lo cual es engañoso.

this. funciona, supongo que incluso podría tener (como una forma larga):

   this.componentWillMount = () => { }

El único problema con esto es que solo debe tener como alcance los puntos de extensión marcados, no todos los miembros de la clase base.

Que esta pasando...

TypeScript no es y nunca ha sido la versión javascript de C#. Entonces, la razón no es que la funcionalidad sugerida difiera de la semántica de C#. Los motivos del cierre declarados por Ryan y luego aclarados por Daniel R fueron

Como explicó Ryan, el problema es que marcar un método como una anulación no implica que otro método no sea una anulación. La única forma en que tendría sentido es si todas las anulaciones tuvieran que marcarse con una palabra clave de anulación (que, si lo exigiéramos, sería un cambio importante).

Sin embargo, sigues persistiendo con tus problemas relacionados con el autocompletado. Autocompletar no necesita una nueva palabra clave en el idioma para brindarle sugerencias mejoradas.

La razón por la que existe este subproceso fue para obtener errores del compilador cuando una función se anula ilegalmente o cuando un método se declaró como anulado pero en realidad no anuló una función en una clase base. Es un concepto simple y bien definido en muchos idiomas que también es compatible con la mayoría, si no más, de las funciones de lenguaje que TypeScript tiene para ofrecer. No necesita resolver todos los problemas del mundo, solo necesita ser la palabra clave de anulación, para anulaciones.

Desde entonces, más personas han mostrado interés en la propuesta original, así que ciñámonos al tema para el que se planteó el problema y planteemos nuevos problemas para nuevas ideas.

TypeScript no es y nunca ha sido la versión javascript de C#.

Lo estoy comparando con C# como lenguaje de referencia, ya que asumo que este es el comportamiento que ustedes están asumiendo.

Autocompletar no necesita una nueva palabra clave en el idioma para brindarle sugerencias mejoradas.

¿Cómo sugieres que se active entonces? Si muestra un cuadro combinado de autocompletar al escribir un nombre aleatorio en un contexto de clase, será muy molesto cuando solo queramos declarar un nuevo campo o método.

La razón por la que existe este subproceso fue para obtener errores del compilador cuando una función se anula ilegalmente o cuando un método se declaró como anulado pero en realidad no anuló una función en una clase base.

Incluí absolutamente este caso de uso, en el Beneficio 2 .

No necesita resolver todos los problemas del mundo, solo necesita ser la palabra clave de anulación, para anulaciones.

Entonces, ¿su sugerencia es corregir _un paso a la vez_ en lugar de retroceder un paso y buscar problemas similares/relacionados? Esa puede ser una buena idea para su Scrum Board pero no para diseñar lenguajes.

La razón por la que son conservadores al agregar la palabra clave es que no pueden eliminar funciones de un idioma.

Algunos errores de diseño en C# debido a la falta de finalización:

  • Covarianza/contravarianza de matriz insegura.
  • var solo funciona para tipos de variables, no para parámetros genéricos automáticos o tipos de devolución. ¿Será Mayble auto una mejor palabra clave como en C++?

Solo intenta usar React por un segundo y verás el otro lado de la imagen.

Anular un método que ya se implementó en una clase base e implementar un método de interfaz son _ dos cosas completamente diferentes _. Así que sí, lo que se sugiere es arreglar uno de esos escenarios con una palabra clave dedicada en lugar de intentar encontrar alguna palabra clave del ejército suizo.

¿De qué sirve una palabra clave que puede significar cualquiera de 3 cosas diferentes para los desarrolladores que leen un código por primera vez? Es ambiguo y confuso, especialmente si está hablando de usar una palabra clave como this que ya hace otras cosas (¡totalmente ajenas!) en el idioma; no podría ser más genérico, es casi inútil.

Si su principal preocupación es el autocompletado, los editores tienen suficiente información _ahora_ para poder sugerir métodos de clases base e interfaces implementadas 'mientras escribe'.

Anular un método que ya se implementó en una clase base e implementar un método de interfaz son dos cosas completamente diferentes.

En el caso general sí, pero no estamos hablando de implementar ningún método de interfaz. Estamos hablando de un método de interfaz opcional _donde la clase principal implementa la interfaz_. En este caso, puede decir que 1) la interfaz permite que el método se implemente como undefined , 2) la clase principal tiene una implementación indefinida y 3) la clase secundaria anula la implementación indefinida con una implementación de método.

@olmobrutall Creo que su comentario sobre el diseño de lenguajes y cómo no es un tablero de scrum es un poco egoísta. He visto unas cuatro actualizaciones de TS en menos de un año.

Si el diseño del idioma se consideró tan bien como usted insinúa que debería ser, entonces ya habría un documento de especificaciones del idioma que nos indicaría exactamente cómo deberían funcionar las anulaciones, y es posible que ni siquiera estemos teniendo esta conversación.

No hago este comentario para denigrar a los desarrolladores/diseñadores de TS, porque TS ya es excelente y temería tener que usar JS estándar en su lugar.

Sí, TS no es C# y no es C++. Pero varios lenguajes han elegido la palabra clave anular para cumplir con los objetivos discutidos aquí, por lo que parece contraproducente sugerir una sintaxis totalmente ajena.

El problema principal parece ser no querer introducir un cambio radical. La respuesta simple es una bandera del compilador, fin de la historia. Para algunas personas como yo, una palabra clave de anulación opcional es inútil. Para otros, quieren embellecer su código de forma incremental. El indicador del compilador resuelve el dilema.

Las diferencias de firma son una conversación diferente. La nueva palabra clave parece innecesaria porque JS no puede admitir varios métodos con el mismo nombre (a menos que TS cree nombres alterados derivados de firmas a la C++, lo cual es muy poco probable).

He visto unas cuatro actualizaciones de TS en menos de un año.

No quiero decir que no puedas ser rápido e iterar rápidamente. Estoy tan feliz como cualquiera de que ES6 y TS estén evolucionando rápidamente. Lo que quiero decir es que hay que intentar predecir el futuro, para evitar poner el lenguaje en un callejón sin salida.

Podría estar de acuerdo en usar la palabra clave override . Con los argumentos adecuados incluso manteniendo los campos y las interfaces fuera del alcance, pero no puedo estar de acuerdo con el argumento '_mantengamos el enfoque y resolvamos este problema en particular de la forma en que otros lenguajes lo hacen sin pensar demasiado_'.

Pero varios lenguajes han elegido la palabra clave anular para cumplir con los objetivos discutidos aquí, por lo que parece contraproducente sugerir una sintaxis totalmente ajena.

Ninguno de estos lenguajes tiene herencia prototípica o método opcional (métodos que no son ni abstractos ni virtuales, simplemente podrían _no existir_ en tiempo de ejecución), y estos son problemas relacionados que deben discutirse (y tal vez descartarse) antes de comprometerse.

Dicho de otra manera: digamos que hacemos lo que parece sugerir e implementamos la anulación sin pensar demasiado. Luego, yo, o cualquier otra persona que use TSX, agrega un problema sobre por qué override no funciona con los componentes de React. ¿Cuál es tu plan?

En el caso general sí, pero no estamos hablando de implementar ningún método de interfaz. Estamos hablando de un método de interfaz opcional donde la clase principal implementa la interfaz.

No importa dónde se configuró la interfaz, el hecho es que no son lo mismo y, por lo tanto, no deberían compartir una palabra clave porque la _intención_ del programa no está clara.

Podría, por ejemplo, anular un método que se haya implementado para el cumplimiento de la interfaz en una clase base; Si tuviéramos que poner todos nuestros huevos en una palabra clave para estas dos cosas diferentes, ¿cómo sabría alguien si esta era la declaración inicial de esa función o una anulación de una previamente definida en una clase base? No lo haría, y no sería posible saberlo sin una inspección adicional de la clase base, que incluso puede estar en un archivo .d.ts de un tercero y, por lo tanto, hacer que encontrarlo sea una pesadilla absoluta, dependiendo de qué tan profundo en la cadena de herencia, la función se implementó originalmente.

Dicho de otra manera: digamos que hacemos lo que parece sugerir e implementamos la anulación sin pensar demasiado. Luego, yo, o cualquier otra persona que use TSX, agrega un problema sobre por qué la anulación no funciona con los componentes de React. ¿Cuál es tu plan?

¿Por qué esto necesita arreglar React? Si React tiene un problema diferente al que está tratando de resolver, entonces no puedo entender por qué override necesita solucionarlo. ¿Ha intentado abrir otro problema para sugerir que se haga algo sobre la implementación de la interfaz?

No estaría de acuerdo en que no se ha pensado lo suficiente en esto. Estamos sugiriendo una técnica probada y comprobada que ha tenido éxito en todos los demás idiomas que se me ocurre que la han implementado.

el hecho es que no son lo mismo y, por lo tanto, no deberían compartir una palabra clave porque la intención del programa no está clara.

¿No son? Mira estas dos definiciones alternativas de BaseClass

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

class BaseClass extends ISomeInterface {
}

Y luego en tu código haces:

``` C#
class ClaseConcreto {
invalidar miMétodo() {
// Hacer cosas
}
}

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

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

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

El comportamiento será nuevamente idéntico si el método es abstracto o proviene de la interfaz: agregue un campo en la clase con una lambda implementándolo. Pero parece un poco extraño override un campo, ya que solo estás asignando un valor.

No vamos a verlo usando super. (mi sintaxis favorita en este momento, pero estoy abierto a alternativas).

``` C#
class ClaseConcreto {
super.myMethod() { //método en prototipo
// Hacer cosas
}

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

}
```

Ahora el concepto es conceptualmente más simple: Mi súper clase dice que hay un método o campo y ConcreteClass puede definirlo en el prototipo / asignarlo / leerlo / llamarlo.

¿Por qué esto necesita arreglar React?

No es solo reaccionar, mira angular:

Por supuesto, la mayoría de las interfaces no están pensadas para implementarse, ni todas las clases para anularse, pero una cosa está clara: en TypeScript, las interfaces son más importantes que las clases.

¿Ha intentado abrir otro problema para sugerir que se haga algo sobre la implementación de la interfaz?

¿Cómo debo llamarlo? override para interfaz y campos?

Estamos sugiriendo una técnica probada que ha tenido éxito en todos los demás idiomas que se me ocurren.

Los idiomas que tienes en mente son bastante diferentes. Tienen herencia basada en una VTable estática.

En Typescript, una clase es solo una interfaz + prototipo automático de herencia de métodos. Y los métodos son solo campos con una función dentro.

Para adaptar la función override a TS, se deben considerar estas dos diferencias fundamentales.

Por favor , @kungfusheep, haga el esfuerzo de pensar en una solución a mi problema. Si desea agregar diferentes palabras clave e implementarlas en una segunda etapa, está bien, pero tómese un segundo para imaginar cómo debería ser.

No se me ocurre otra forma de decir lo que ya he dicho. Ellos no son los mismos. Son similares, pero no iguales. Consulte este comentario de uno de los desarrolladores de TS RE: la palabra clave de solo lectura: https://github.com/Microsoft/TypeScript/pull/6532#issuecomment -179563753, que refuerza lo que digo.

Estoy de acuerdo con la idea general, pero veamos en la práctica:

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

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

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

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

Esto parece VB o Cobol, ¿no?

Parece que tiene sentido, al menos.

Considere este ejemplo, si solo hubiera la palabra clave override (o solo una).

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

Ahora implementemos nuestro propio componente usando lo que acabamos de escribir.

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

}

Una forma de saberlo sería el tipo de super :

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

¡exactamente! lo que significa que el diseño es incorrecto. no debería haber una investigación relacionada con el código de desnatado, solo debería tener sentido cuando lo lea.

esto es ambiguo. ¿Estoy implementando esto desde una interfaz o anulando un método base? No tengo forma de saberlo.

¿Cuál es la diferencia en la práctica? El objeto tiene solo un espacio de nombres y solo puede colocar una lambda en el campo do . No hay forma de implementar explícitamente una interfaz de todos modos. La implementación va a ser:

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

independientemente de lo que escribas.

No importa cuál sea la salida. Podría estar anulando intencionalmente o no alguna funcionalidad en una clase base, pero no hay una intención en una palabra clave que sea ambigua.

¿Qué error o comportamiento inesperado se resolverá al tener dos palabras clave?

Vamos amigo. Obviamente eres un tipo inteligente. Acabo de decir "anular involuntariamente alguna funcionalidad en una clase base"; ¿No podemos inferir de esto algún comportamiento inesperado que podría haber ocurrido potencialmente?

Para ser claros, no estoy proponiendo convertir este tema en una propuesta de dos palabras clave. Este problema es para la palabra clave override; cualquier otra cosa necesitará una nueva propuesta y su propia discusión sobre la semántica.

Para ser claros, no estoy proponiendo convertir este tema en una propuesta de dos palabras clave. Este problema es para la palabra clave override; cualquier otra cosa necesitará una nueva propuesta y su propia discusión sobre la semántica.

Realmente no importa en cuántos temas se debe discutir, o de quién proviene la idea. Propone dividir dos pensamientos muy relacionados y ni siquiera considera una sintaxis consistente.

Los argumentos sobre si necesitamos 1, 2 o 3 palabras clave pertenecen a este hilo y no están terminados (... pero se vuelven repetitivos). Entonces tal vez podamos discutir la sintaxis en otro hilo (porque la semántica será idéntica de todos modos: P).

En mi ejemplo:

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

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

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

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

No assign , implement y override hagan exactamente lo mismo: compruebe que el nombre existe (en la clase base, las interfaces implementadas, las interfaces implementadas por la base clase, etc.).

Si hay un conflicto de nombres entre las clases base y algunas interfaces implementadas, obtendrá un error de tiempo de compilación, ya sea con 1, 2 o ninguna palabra clave.

También piensa en el objeto literal:

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

Con exactamente la misma sintaxis, puedo reasignar campos o métodos de forma independiente si provienen de la clase base, implementaciones de interfaz directa o interfaces implementadas en la clase base.

No asignar, implementar y anular hacen exactamente lo mismo: verificar que el nombre exista (en la clase base, las interfaces implementadas, las interfaces implementadas por la clase base, etc.).

Acabas de describir 3 escenarios diferentes allí, así que obviamente no son lo mismo. Tengo la sensación de que podría describir por qué son diferentes para ti todo el día y todavía estarías sentado discutiendo que no lo son, así que me retiraré de esta línea particular de discusión por ahora. No hay nada que decir que los muchachos de TS todavía están considerando esto en este momento de todos modos.

Con el cierre de #6118, creo que hay motivos para discutir si los problemas de allí y los problemas de aquí pueden abordarse simultáneamente.

No estaba al tanto de https://github.com/Microsoft/TypeScript/pull/6118. La idea parece una posible alternativa a agregar override .

Si entendí el problema correctamente, el problema es que puede tener más de una clase/interfaz base con declaraciones de miembros compatibles pero no idénticas, y deben unificarse cuando se inicializan en la clase secundaria sin un tipo.

Sin tener una buena idea de las consecuencias, sería feliz si:

  • el miembro producirá un error en tiempo de compilación que requiere una declaración de tipo explícita en la clase secundaria
  • el miembro tomará la intersección de todos los tipos posibles.

En mi opinión, lo más importante sería tener alguna forma de activar la finalización automática al escribir miembros de la clase (Ctrl+Espacio). Cuando el cursor está directamente dentro de una clase, podría estar definiendo nuevos miembros o redefiniendo los heredados, por lo que la finalización automática no puede ser demasiado agresiva, pero con la activación manual, el comportamiento debería estar bien.

Con respecto a los comentarios de @RyanCavanaugh :

Definitivamente entendemos los casos de uso aquí. El problema es que agregarlo en esta etapa del idioma agrega más confusión de la que elimina. Una clase con 5 métodos, de los cuales 3 están marcados como anulados, no implicaría que los otros 2 no sean anulados. Para justificar su existencia, el modificador realmente necesitaría dividir el mundo de manera más limpia que eso.

Escribir una variable como any no implica que otra variable sea o no sea también any . Pero, hay un compilador plano --no-implicit-any para hacer cumplir que lo declaramos explícitamente. Podríamos tener igualmente un --no-implicit-override , que, por mi parte, activaría si estuviera disponible.

Tener una palabra clave override que se usa brinda a los desarrolladores una gran cantidad de información al leer código con el que no están familiarizados, y la capacidad de aplicarlo les daría un control adicional del tiempo de compilación.

Para todos los +1ers, ¿pueden hablar sobre lo que no es suficiente en la solución de decorador que se muestra arriba?

¿Hay alguna forma en que un decorador sea una mejor solución que una palabra clave de anulación? Hay muchas razones por las que es peor: 1) Agrega una sobrecarga de tiempo de ejecución por pequeña que sea; 2) No es una verificación de tiempo de compilación; 3) Tengo que incluir este código en cada una de mis bibliotecas; 4) No hay forma de que esto atrape funciones a las que les falta la palabra clave anular.

Demos un ejemplo. Tengo una biblioteca con tres clases ChildA , ChildB , Base . Agregué algún método doSomething() a ChildA y ChildB . Después de algunas refactorizaciones, agregué algo de lógica adicional, optimicé y moví doSomething() a la clase Base . Mientras tanto, tengo otro proyecto que depende de mi biblioteca. Ahí tengo ChildC con doSomething() . Cuando actualizo mis dependencias, no hay forma de descubrir que ChildC ahora anula implícitamente doSomething() , pero de una manera no optimizada que también faltan algunos controles. Es por eso que un decorador @overrides nunca será suficiente.

Lo que necesitamos es una palabra clave override y un indicador del compilador --no-implicit-override .

Una palabra clave override me ayudaría mucho ya que utilizo en mi proyecto una jerarquía simple de clases base para crear todos mis componentes. Mi problema radica en el hecho de que estos componentes pueden necesitar declarar un método para usar en otro lugar, y este método puede o no estar definido en una clase principal y puede o no hacer las cosas que necesito.

Por ejemplo, digamos que una función validate toma como parámetro una clase con un método getValue() . Para construir esta clase, puedo heredar otra clase que ya podría definir este método getValue() , pero realmente no puedo saber esto a menos que mire su código fuente. Lo que instintivamente haría es implementar este método en mi propia clase, y mientras la firma sea correcta, nadie me dirá nada.

Pero tal vez no debería haber hecho eso. Las siguientes posibilidades suponen que hice una anulación implícita:

  1. La clase base ya definió este método como lo hice yo, así que escribí una función para nada. No es un gran problema.
  2. Reescribí incorrectamente la función, la mayoría de las veces porque olvidé hacer algunas cosas no obvias que ya manejaba la clase base. Lo que realmente necesitaba hacer aquí es llamar a super en mi anulación, pero nadie me sugirió que lo hiciera.

Tener una palabra clave de anulación obligatoria me hubiera dicho "oye, estás anulando, así que tal vez deberías comprobar cómo se ve el método original antes de hacer tus cosas". Eso mejoraría mucho mi experiencia con la herencia.

Por supuesto, como se sugirió, debe colocarse bajo una bandera --no-implicit-override ya que sería un cambio importante y que a la mayoría de la gente no le importa mucho todo esto.

Me gusta la comparación que hizo @eggers con any y --no-implicit-any porque es el mismo tipo de anotación y funcionaría exactamente de la misma manera.

@olmobrutall Estaba viendo algo de su charla sobre la anulación de métodos abstractos y métodos de interfaz.

Si mi opinión, override implica la existencia de un super. Ni los métodos abstractos ni los métodos definidos en una interfaz pueden llamarse a través de una llamada super. , y por lo tanto no deberían anularse ( no hay nada que anular). En cambio, si hacemos algo más explícito para esos casos, debería ser una palabra clave implements . Pero, eso sería una discusión de característica separada.

Aquí están los problemas como yo los veo, clasificados de más importantes a menos. ¿Yo me perdí algo?

Fallo en anular

Es fácil _pensar_ que está anulando un método de clase base cuando no es así:

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

Este es probablemente el mayor problema.

No implementar

Esto está muy relacionado, especialmente cuando la interfaz implementada tiene propiedades opcionales:

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

Este es un problema menos importante que _failure to override_, pero sigue siendo malo.

Anulación accidental

Es posible anular un método de clase base sin darse cuenta

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

Esto debería ser relativamente raro solo porque el espacio de posibles nombres de métodos es muy grande, y las probabilidades de que también escriba una firma compatible _y_ no tenga la intención de haber estado haciendo esto en primer lugar son bajas.

any accidentales

Es fácil pensar que los métodos de anulación obtendrán los tipos de parámetros de su base:

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

Intentamos escribir estos parámetros automáticamente pero tuvimos problemas. Si hasFilename se marcara explícitamente como override , podríamos resolver esos problemas más fácilmente.

Legibilidad

No está claro de inmediato cuándo un método anula un método de clase base y cuándo no. Esto parece un problema menor porque hay soluciones alternativas razonables disponibles en la actualidad (comentarios, decoradores).

Experiencia del editor

Debe escribir manualmente los nombres de los métodos base al escribir métodos de anulación de clases derivadas, lo cual es molesto. Podríamos solucionar esto fácilmente al proporcionar siempre esos nombres en la lista de finalización (creo que en realidad ya tenemos un error en esto), por lo que realmente no necesitamos una función de idioma para solucionar este problema.

no-problemas

Enumerarlos como cosas que pertenecen a un número separado o simplemente no van a suceder:

  • Coincidencia exacta de firmas: esto no es algo que deba mezclarse con override , y realmente tiene una semántica indeseable en muchos buenos escenarios
  • Sintaxis insuficientemente exótica (por ejemplo implements.foo = ... ). No hay necesidad de bikeshed aquí

@RyanCavanaugh También estoy de acuerdo con todo lo anterior en ese orden. Solo como comentario, no creo que la palabra clave override deba resolver el problema de "falla en la implementación". Como dije anteriormente, creo que ese problema debería resolverse con una palabra clave diferente (por ejemplo implements ) como parte de un ticket diferente. ( override debería implicar un método super. )

@RyanCavanaugh Estoy de acuerdo con todos los puntos, pero no veo ninguna referencia al problema también muy relacionado de los campos inicializados declarados en un tipo principal sin anular el tipo (y luego perder la verificación de tipo). Creo que la función no debe limitarse a declaraciones de métodos en un lenguaje donde los métodos son solo funciones en campos de objetos.

@eggers , incluso si al final se necesitan dos palabras clave, la semántica será muy similar y creo que las características deberían discutirse juntas.

override debe implicar un super. método

En C#, override puede (y debe) usarse también para métodos abstractos (sin .super). En Java @override es un atributo. Por supuesto, son lenguajes diferentes, pero con palabras clave similares esperas comportamientos similares.

Estoy de acuerdo con los puntos de @RyanCavanaugh , aunque diría que el problema de "anulación accidental" podría ser un poco más común de lo que él cree (especialmente cuando se trata de extender una clase que ya extiende algo más, como un componente React que ya definir un método componentWillMount en la primera extensión).

Sin embargo, creo, como dijo @eggers , que el problema de "falla en la implementación" es algo bastante diferente, y se sentiría extraño usar una palabra clave override para un método que no existe en una clase principal. Sé que estos son lenguajes diferentes con diferentes problemáticas, pero en C# override no se usa para interfaces. Sugeriría, si pudiéramos obtener un indicador --no-implicit-override , los métodos de interfaz tendrían que tener el prefijo del nombre de la interfaz (que se parecería mucho a C# y, por lo tanto, se sentiría más natural).

Algo como

interface IBase {
    method1?(): void
}

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

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

Creo que @RyanCavanaugh enumera los problemas fundamentales. Lo complementaría con "¿Cuáles son los desacuerdos?":

  • ¿Declarar un método para que coincida con una interfaz se considera override ? Por ejemplo:
    interface IDrawable
    {
        draw?( centerPoint: Point ): void;
    }

    class Square implements IDrawable
    {
        draw( centerPoint: Point ): void; // is this an override?
    }
  • ¿Declarar una propiedad para que coincida con una interfaz se considera override ?
    interface IPoint2
    {
        x?: number;
        y?: number;
    }

    class Circle implements IPoint2
    {
        x: number; // is this an override?
        y: number; // is this an override?
        radius: number;
    }
  • ¿Existe la necesidad de algún tipo de palabra clave implements , para manejar los casos anteriores en lugar de una palabra clave override , y la forma que tomaría?

@ kevmeister68 gracias por declarar explícitamente los desacuerdos.

Una cosa: debe hacer que los miembros de la interfaz sean opcionales para mostrar realmente el problema, así el compilador de mecanografiado se quejará cuando un campo o método no esté implementado, resolviendo "Error en la implementación".

@olmobrutall Gracias por eso. Nunca he declarado que un método sea opcional. ¿Puede ser (provengo de un fondo de C# y no existe tal concepto)? Actualicé los ejemplos que enumeré anteriormente para cambiar las variables miembro a opcionales, ¿es eso mejor?

@kevmeister68 también el método draw en IDrawable :)

@RyanCavanaugh

Sintaxis poco exótica (por ejemplo, implements.foo = ...). No hay necesidad de bikeshed aquí

Gracias por enseñarme una nueva expresión . No veo muchos problemas en la semántica de la función:

  • Todos queremos errores en tiempo de compilación si el método no existe
  • Todos queremos errores en tiempo de compilación cuyo tipo no coincida.
  • También queremos que los parámetros se escriban implícitamente en el principal, como lo hacen las expresiones lambda.
  • Queremos ayuda del IDE para escribir el método correctamente.

La mayoría de los problemas se basan en la sintaxis y los comportamientos que implican:

  • Miembros opcionales en interfaz frente a miembros en clases base
  • Métodos vs campos (no sé dónde estarán las lambdas)

Comparemos las sintaxis más prometedoras del árbol:

anular / implementos

class Person{
    dateOfBirth: Date;

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

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


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

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

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

ventajas:

  • Es natural para los programadores con experiencia en OO.
  • Se siente bien cuando se compara con extends / implements .

Desventajas:

  • No proporciona una solución al problema del campo, ninguno override o implements se siente bien. Quizás super. , pero entonces, ¿qué hacemos con las interfaces?
  • Si los métodos se anulan usando una lambda (útil para capturar esto) de un function() suceden los mismos problemas.

anular / InterfaceName.

class Person{
    dateOfBirth: Date;

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

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


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

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

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

ventajas:

  • También es natural para los programadores con experiencia en OO.
  • Resuelve el problema del campo para las interfaces, pero no para las clases, pero podríamos usar super. para ellas.

Desventajas:

  • Los nombres de las interfaces pueden ser largos, especialmente con los genéricos, y mostrar la lista de competencia automática también será problemático, ya que requerirá algo de Alt+Espacio para hacerlo explícito.
  • Hace que parezca que puede implementar dos miembros con el mismo nombre desde diferentes interfaces de forma independiente. Esto funciona en C# pero no funcionará en Javascript.

this.

class Person{
    dateOfBirth: Date;

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

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


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

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

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

ventajas:

  • Resuelve clases, interfaces, métodos y campos.
  • Utiliza (¿abusa?) solo una palabra clave preexistente.
  • Usar this. para activar la finalización automática se siente natural.
  • Deja en claro que el objeto es solo un espacio de nombres, y solo puede tener una cosa para cada nombre.

Desventajas:

  • Parece una expresión, no una declaración, por lo que es difícil de analizar para ojos no entrenados.

Si bien no implementar un método de interfaz no opcional provocará un error del compilador, si la interfaz cambia en algún momento en el futuro (digamos que se elimina un método), no habrá ninguna advertencia para todas las clases que implementaron ese método. Puede que esto no sea un problema tan grande como los opcionales, pero creo que es justo decir que el alcance de esto va más allá de ellos.

Sin embargo, sigo creyendo que override es algo claramente diferente y, potencialmente, tener dos palabras clave en lugar de una transmitiría mejor la intención del programa.

Por ejemplo, considere un escenario en el que un desarrollador _cree_ que está implementando un método de interfaz cuando, de hecho, está anulando el método que ya se ha declarado en una clase base. Con dos palabras clave, el compilador puede prevenir este tipo de error y arrojar un error con el tono de method already implemented in a base class .

interface IDelegate {
    execute?() : void;
}

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

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

No sé si esto es relevante en JS, pero se supone que los campos de clase son privados en OO, por lo que no creo que debamos anular campos explícitamente, ya que el hecho de que sean privados ya nos impide anularlos por completo. .

Sin embargo, los métodos de instancia son campos y, por lo que sé, mucha gente los usa en lugar de métodos prototipo. Sobre eso,

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

es un error del compilador, porque walk es un método prototipo en Person y un método de instancia en SuperMan .

De todos modos, no estoy seguro de que override encaje aquí, porque bueno, no anulamos los campos. De nuevo, parecería C#, pero prefiero usar la palabra clave new en lugar de override aquí. Porque no es lo mismo que una anulación de método (puedo llamar a super.myMethod en una anulación, no aquí).

Mi solución preferida sería algo como (suponiendo que estamos en modo de anulación estricta):

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

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

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

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

Sin embargo, mi principal preocupación es sobre las interfaces. No deberíamos tener que escribir implements para miembros de interfaz no opcionales, porque el compilador ya nos atrapará. Entonces, si lo hacemos, habrá una confusión entre lo que es de una interfaz y lo que no, ya que no todos los miembros de la interfaz tendrán el prefijo implements . Y esa palabra es un bocado. No estoy listo para escribir algo como esto:

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

Propuse poner el prefijo con el nombre de la interfaz antes, pero @olmobrutall me mostró que era una idea peor.

De todos modos, estoy bastante convencido de que necesitaremos 3 palabras clave nuevas diferentes para abordar esto adecuadamente.

Tampoco creo que sea necesario escribir implícitamente las anulaciones ya que el compilador ya nos impedirá escribir algo que no sea compatible, especialmente porque aparentemente es difícil hacerlo bien.

¿No es new , override y implement demasiada sobrecarga de palabras clave para esencialmente la misma semántica JS? Incluso si en C# son cosas diferentes, prácticamente no hay diferencia en JS/TS.

También es bastante ambiguo:

  • ¿Por qué los campos son new para clases pero implements para interfaces?
  • Si está cambiando un método de una clase base que se implementó con una interfaz, ¿qué debe usar? Puedo ver cuatro posibilidades: override , implements , ¿alguna de las dos o ambas al mismo tiempo?

También piensa en esto:

class Animal {
}

class Human extends Animal {
}

class Habitat {
    owner: Animal;
}

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

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

La pregunta es:

¿ owner = new Human(); redefine el campo con un tipo nuevo (pero compatible), o simplemente asigna un valor?

Creo que, en general, redefinió el campo, excepto si usa la _palabra clave mágica_. Mi preferencia es por this. este momento.

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

@olmobrutall Eso podría ser un poco de sobrecarga de palabras clave, pero los 3 son cosas _diferentes_.

  • override se aplicaría a los métodos (definidos en el prototipo), lo que significa que _no_ borra el método original, al que aún se puede acceder detrás super .
  • new se aplicaría a los campos y significaría que el campo original ha sido borrado por el nuevo.
  • implements se aplicaría a cualquier cosa desde una interfaz, y significa que no borra ni reemplaza nada que ya existiera en una clase principal, porque una interfaz "no existe".

Si está cambiando un método de una clase base que se implementó con una interfaz, ¿qué debe usar? Puedo ver cuatro posibilidades: override, implements, cualquiera de los dos o ambos al mismo tiempo?

Me parece bastante lógico que sería override ya que eso es lo que efectivamente estás haciendo aquí. implements significaría que está implementando algo de la interfaz que no existe de otra manera. En cierto sentido, override y new tendrían prioridad sobre implements .

Y sobre su ejemplo de anulación de campo, nada debería cambiar sobre cómo funciona hoy. No estoy seguro de lo que sucede aquí, pero sea lo que sea, no debería cambiar (o tal vez debería, pero eso no es lo que estamos discutiendo aquí).

Siento que override sería adecuado tanto para métodos básicos como para propiedades, incluidos los abstractos (explicados a continuación).

new es una idea interesante en concepto, pero esta palabra clave en particular puede confundirse con la creación de instancias de clase, por lo que puede dar la falsa impresión de que solo funcionaría para clases, a pesar de que las propiedades pueden tener una interfaz primitiva, unión o incluso tipos de funciones. Tal vez una palabra clave diferente como reassign podría funcionar mejor allí, pero tiene el problema de que puede dar una idea falsa en el caso de que el valor solo se vuelva a declarar pero en realidad no se asigne con nada en la clase derivada ( new también puede tener ese problema). Creo que redefine también es interesante, pero podría llevar a la expectativa errónea de que la propiedad 'redefinida' también podría tener un tipo diferente al de la base, así que no estoy seguro... (_editar: en realidad revisé y podría tener un tipo diferente siempre que el nuevo sea un subtipo del base, así que esto puede no ser tan malo_).

implement (prefiero esta forma verbal en particular por consistencia con override ) parece funcionar para las interfaces. Creo que técnicamente también podría funcionar para métodos básicos abstractos, pero usar override podría parecer más consistente, a pesar de la semántica ligeramente diferente. Otra razón es que sería un poco inconveniente cambiar un método de abstracto a no abstracto, ya que uno necesitaría ir a todas las clases derivadas y cambiar override a implement .

Podría haber mejores ideas, pero eso es todo lo que tengo en este momento...

Propuse la palabra clave new porque es la que usa C# para esta característica exacta, pero estoy de acuerdo en que no es la mejor. implement es de hecho una mejor opción que implements . No creo que debamos usarlo para métodos abstractos ya que son parte de una clase base y no de una interfaz. override + new -> clase base, implement -> interfaz. Eso parece más claro.

@JabX

Acerca de anular prototipo con campo de instancia

He comprobado y tienes razón:

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

Falla con el error del compilador: Class 'Person' defines instance member function 'walk', but extended class 'SuperMan' defines it as instance member property.

Realmente no veo la razón para fallar en este caso, ya que el contrato de tipo principal se cumple y puede escribir esto de todos modos:

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

O incluso

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

Alrededor override

override se aplicaría a los métodos (definidos en el prototipo), lo que significa que no borra el método original, al que aún se puede acceder detrás de super.

Eso es en realidad un argumento. Hay menos _alguna_ diferencia observable entre override y implements ... pero no del todo.

Tanto override como implements están implementados en prototype , poder usar super es independiente, porque llamas a super.walk() no solo a super() , y está disponible en todos los métodos (anulaciones, implementos, noticias y definiciones normales).

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

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

Y poder usar super.walk() también funciona si asigna a la instancia

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

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

Ya existe una forma clara de diferenciar los campos prototype de los campos de instancia y es el token = .

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

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

Con la sintaxis this. el problema se resuelve de manera más elegante:

  • this. significa verificar mi tipo (clases base e interfaces implementadas).
  • = significa asignar a la instancia en lugar del prototipo.

Alrededor New

new se aplicaría a los campos y significaría que el campo original ha sido borrado por el nuevo.

Tenga en cuenta que no puede cambiar el campo o el método con un tipo diferente porque romperá el sistema de tipos. En C# o Java, cuando hace new el nuevo método se usará solo en llamadas enviadas estáticamente al nuevo tipo. En Javascript, todas las llamadas serán dinámicas y si cambia el tipo, es probable que el código se rompa en tiempo de ejecución (sin embargo, la coerción del tipo podría permitirse con fines prácticos, pero no la ampliación).

class Person {
    walk() { }

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

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

Entonces, más de new la palabra clave debería ser assign , porque esta es la única cosa segura que puede hacer:

class Person{
    dateOfBirth: Date;
}

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

Tenga en cuenta que reassign no tiene sentido, porque Person solo declara el campo pero no establece ningún valor en él.

Escribir assign parece redundante, sin embargo, está claro que estamos asignando porque tenemos = en el otro lado.

Nuevamente, la sintaxis this. resuelve el problema con gracia:

class Person{
    dateOfBirth: Date;
}

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

No estoy seguro de cómo aplica esto a los campos de una manera que tenga sentido.

Estamos hablando de agregar una palabra clave que solo se aplica en el cuerpo de la clase, pero los campos se pueden reasignar en _cualquier momento_ durante la vigencia de las instancias.

Typescript ya le permite inicializar campos en el cuerpo de la clase:

class Person {
    dateOfBirth : new Date();
}

También le permite reinicializar los campos declarados en la clase principal, pero la situación es mala. Por supuesto, podría escribir el nombre incorrectamente (problema similar a override ) pero el tipo también se borra. Esto significa que:

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

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

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

Este código falla con: La propiedad 'apellido' no existe en el tipo '{ nombre: cadena;

Estamos hablando de agregar una palabra clave que solo se aplica en el cuerpo de la clase, pero los campos se pueden reasignar en cualquier momento durante la vigencia de las instancias.

También métodos:

class Person {
    talk() { }
}

class SuperMan extends Person {

    talk() { }

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

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

@kungfusheep Estoy de acuerdo, los campos se pueden reasignar, pero también los métodos en el prototipo.
La razón principal por la que necesitaríamos una palabra clave para "anular" los campos es porque los métodos de instancia _son_ campos y se usan ampliamente en lugar de los métodos de prototipo adecuados porque son lambdas y, por lo tanto, se vinculan automáticamente al contexto.

@olmobrutall

Acerca de la anulación de métodos con campos

Está prohibido y creo que por una buena razón (no es lo mismo, por lo que no debería ser compatible), pero tiene razón en que es posible hacerlo manipulando directamente la instancia de la clase. No sé exactamente por qué está permitido, pero no estamos aquí para cambiar el funcionamiento del lenguaje, así que creo que no deberíamos interferir aquí.

Acerca de las interfaces

Las interfaces de clase describen el lado de la instancia de una clase, es decir, los campos y los métodos del prototipo. Ni siquiera creo que haya una diferencia entre los métodos de instancia y prototipo en una interfaz, creo que puede describir un método como uno u otro e implementarlo como uno u otro. Entonces implement podría (y debería) aplicarse también a los campos.

Acerca de nuevo

new debe tener la misma semántica que override , por lo que obviamente no puede cambiar el tipo a algo que no sea compatible. Quiero decir, de nuevo, no estamos cambiando cómo funciona el lenguaje. Solo quería una palabra clave separada porque no es el mismo tipo de redefinición. Pero estoy de acuerdo en que, como me mostró, ya podemos diferenciar los campos y los métodos mediante el uso de = , por lo que tal vez se podría usar la misma palabra clave/sintaxis sin ambigüedad.

Sobre esto.

Sin embargo, la sintaxis de anulación de campo/método unificado no debería ser this.method() o this.field porque se parece mucho a algo que podría ser Javascript válido, mientras que no lo es. Agregar una palabra clave antes del miembro sería más claro en cuanto al hecho de que, de hecho, es una cosa de Typescript.
Sin embargo, this es una buena idea, así que tal vez podríamos dejar el punto y escribir algo menos ambiguo como

class Superman {
    this walk() { }
}

Pero aún así, se ve un poco raro. Y mezclarlo con implement parecería un poco fuera de lugar (¿porque estamos de acuerdo con el hecho de que esta sintaxis this no se usaría con la interfaz?), y me gusta el implement / override dúo. Un modificador override en los campos todavía me molesta, pero tal vez sea mejor que tener una tercera palabra clave ambigua new . Dado que la asignación ( = ) hace clara la diferencia entre un método y un campo, estaría bien para mí ahora (a diferencia de lo que decía hace unas horas).

Estoy de acuerdo, los campos se pueden reasignar, pero también los métodos en el prototipo.

Sí, modificar el prototipo directamente, en TypeScript, está muy en el reino de perder el tiempo bajo el capó. No es tan común como simplemente reasignar una propiedad en una instancia de algo. Muy rara vez usamos campos como funciones, en una base de código de más de 150k líneas, por lo que creo que decir que se usa ampliamente es quizás subjetivo.

Estoy de acuerdo con la mayoría de sus otros puntos, pero no estoy convencido (y por lo que parece, usted tampoco...) sobre el uso de la palabra clave new en este contexto.

Muy rara vez usamos campos como funciones, en una base de código de más de 150k líneas, por lo que creo que decir que se usa ampliamente es quizás subjetivo.

Yo tampoco, pero tengo que recurrir a un decorador @autobind para vincular correctamente this en mis métodos, lo cual es más engorroso que simplemente escribir una lambda. Y siempre que no use la herencia, usar un campo funciona como espera. Tenía la impresión de que, al menos en el mundo JS/React, la mayoría de las personas que usaban clases ES6 usaban funciones de flecha como métodos.

Y los métodos de interfaz se pueden implementar con un método adecuado o con un método de instancia, lo que no ayuda a aclarar la diferencia.

Creo que los campos tienen los mismos problemas que los métodos en lo que respecta a las anulaciones.

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

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

Una posible solución aquí es asignar el campo en el constructor

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

pero eso no es aplicable para las interfaces. Es por eso que yo (y otros aquí) creemos que necesitamos algo para verificar la preexistencia de un campo cuando lo declaramos directamente en el cuerpo de la clase. Y no es un override . Ahora que expongo algunas ideas más aquí, creo que el problema con los campos es exactamente el mismo que el problema con los miembros de la interfaz. Diría que necesitamos una palabra clave override para los métodos que ya tienen una implementación en una clase principal (métodos de prototipo, y tal vez también métodos de instancia), y otra para cosas que están definidas pero sin implementación (no función campos incluidos).

Mi nueva propuesta sería, y usar una palabra clave tentativa member (probablemente no sea la mejor opción):

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

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

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

Tenga en cuenta que elegiría la palabra clave member para un método abstracto ya que dije que override sería para cosas con una implementación, lo que indica que estamos reemplazando un comportamiento existente. Los métodos de instancia necesitarían usar override en virtud de ser un tipo especial de campo (que el compilador ya reconoce como tal, ya que las firmas de métodos se pueden implementar con él).

Creo que Typescript debería ser una versión comprobada de JavaScript en tiempo de compilación, y al aprender TS también deberías aprender JS, al igual que al aprender C # obtienes una buena intuición de CLR.

La herencia prototípica es un concepto genial de JS, y bastante central, y TS no debería tratar de ocultarlo con conceptos de otros lenguajes que no tienen mucho sentido aquí.

La herencia prototípica no hace ninguna diferencia si hereda métodos o campos.

Por ejemplo:

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

Rectangle.prototype.color = "black";

Aquí estamos configurando un campo simple en el objeto prototipo, por lo que todos los rectángulos serán negros de forma predeterminada sin tener que tener un campo de instancia para ello.

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

Además, la palabra clave member pone celosos a los demás miembros de la clase.

Lo que necesitamos es una sintaxis que permita en un contexto de declaración de clase el mismo tipo de comportamiento (tiempo de compilación verificado/completado automático/cambio de nombre) que ya tenemos en expresiones de miembros o literales de objetos.

Tal vez una mejor alternativa a this. es solo . :

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

   someNewMethod(){}
   someNewField = 3;
}

En conclusión, no creo que Typesystem deba rastrear qué valores provienen del prototipo y cuáles de la instancia, porque este es un problema difícil una vez que puede acceder al prototipo de manera imperativa, y porque no solucionará ningún problema mientras lo haga. limitar la expresividad de JS.

Por lo tanto, no hay diferencia entre un campo de función, un campo de función de flecha y un método, ni en el sistema de tipo, ni en el código generado (si no se usa this ).

Hola chicos, esto es algo interesante, pero está bastante fuera de la tangente en términos de override específicamente. Me encantaría tener un nuevo problema de discusión para este tipo de cosas, a menos que podamos vincular estos comentarios a la sugerencia original de manera más concreta.

Mucho de lo que está diciendo aquí tampoco es específico de TypeScript, por lo que iniciar un hilo ESDiscuss también puede ser apropiado. Ciertamente, han pensado tanto en este tipo de cosas (en términos de lo que sucede en el prototipo frente a la instancia).

@olmobrutall
Las clases de ES6 ya están ocultando las cosas del prototipo y, como dijo @kungfusheep , jugar directamente con él no es exactamente algo normal.

Así que no hay diferencia entre un campo de función, un campo de función de flecha y un método, ni en el sistema de tipos, ni en el código generado (si no se usa).

Bueno, en el código generado, los métodos de clase se colocan en el prototipo y todo lo demás (sin el prefijo static ) se coloca en la instancia, lo que marca la diferencia con la herencia.

De todos modos, ahora estoy de acuerdo en que no tenemos que tener una sintaxis separada para ambos tipos de métodos, pero si tuviéramos que usar la palabra clave override , debería limitarse a los métodos y tendríamos que encontrar otra cosa para el resto. Tener una palabra clave única para todo podría ser bueno, pero debería ser muy claro acerca de su significado. override está claro, pero solo para métodos. Tu sintaxis de puntos, esto como this. todavía está demasiado cerca de JS existente para mi gusto.

@RyanCavanaugh

Está bastante fuera de la tangente en términos de override

Mi problema con la anulación es que no es lo suficientemente general para los métodos y campos de la interfaz. Veo tres opciones:

  • use override solo para métodos de clase base.
  • use override para métodos y campos de interfaz también
  • considere sintaxis alternativas ( member , implement , this. , . , etc...)
  • no hacer nada (eso sera triste)

Creo que esta decisión debe tomarse aquí.

Mucho de lo que dices no es específico de Typescript

¡Absolutamente! todos estamos contentos con el estado actual de la transpilación, pero no con la verificación de tipos/herramientas.

Cualquiera que sea la palabra clave, será solo mecanografiado (igual que abstracto)

@JabX

Tienes razón sobre el código generado, por supuesto. Mi punto era que puedes escribir algo como esto:

class Person {
    name: string = "John"; 

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

Declaramos una clase, name irá a la instancia mientras que saySomething al prototipo. Todavía puedo escribir esto:

Person.prototype.name = "Unknown"; 

Porque el tipo de Person.prototype es la persona completa. Typescript no realiza un seguimiento de lo que va en su sistema de tipos por simplicidad.

Tener una palabra clave única para todo podría ser bueno, pero debería ser muy claro acerca de su significado.

Lo que creo que es más importante y todos podríamos estar de acuerdo es la semántica:

El XXX modificado verifica que el miembro ya esté declarado en la clase base o en las interfaces implementadas, que generalmente se usan para anular funciones de la clase base.

Dado que . o this. parecen demasiado extraños, y member es redundante, creo que probablemente la mejor opción es abusar de override en todos los casos . Establecer campos o implementar métodos de interfaz es de todos modos una característica secundaria.

Hay muchos precedentes:

  • En C#, un static class ya no es realmente una _clase de objetos_.
  • No hay nada _estático_ en los campos static .
  • Los métodos virtual son bastante _concretos_ (VB usa Overrideable ).

Se verá así:

class Person{
    dateOfBirth: Date;

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

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


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

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

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

¿Podemos conformarnos con eso?

Prefiero ver una palabra clave separada implement para cualquier cosa que provenga de las interfaces (con prioridad a override si está en ambas), porque es lo que es: una implementación, no una anulación.
De lo contrario, estoy de acuerdo en que sería mejor abusar de override para todo, desde la clase principal.

Pero no hay diferencia semántica entre implement y override .

Ambos tendrán autocompletado/mensajes de error/transpilación similares a JS... es solo una diferencia filosófica.

Realmente vale la pena explicar dos palabras clave y que un compilador pedante te dice: Error you should use 'override' instead of 'implement' .

¿Qué pasa con este caso:

interface IComparable {
     compare(): number;
} 

class BaseClass implements IComparable {
    implement compare(); 
}

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

La pregunta es... ¿a quién le importa?.

También está el problema implement / implements .

Abusemos de override . Un concepto una palabra clave, esto es lo importante.

Bueno, ya propuse que override debería tener prioridad sobre implement , pero probablemente no fui lo suficientemente claro.
Todavía no creo que esto sea lo mismo. Otro ejemplo: para un miembro de interfaz obligatorio, una falla en la implementación es un error del compilador, mientras que una falla en la anulación no es un problema (a menos que el miembro base sea abstracto, es decir...).

Simplemente no creo que override en algo que no está declarado en una clase principal tenga sentido. Pero tal vez soy el único que quiere la distinción. En cualquier caso, cualquiera que sea la palabra clave que terminemos usando para esto, solo espero que podamos establecer algo y proporcionar un modo estricto para hacer cumplir su uso.

Bueno, ya propuse que anular debería tener prioridad sobre implementar, pero probablemente no fui lo suficientemente claro.

Claro, solo quería decir que hay cuatro casos. Clase base, interfaz, interfaz en clase base, interfaz de reimplementación en clase base.

@JabX

Simplemente no creo que anular algo que no está declarado en una clase principal tenga sentido. Pero tal vez soy el único que quiere la distinción .

Tu no eres. Son dos cosas fundamentalmente diferentes y es beneficioso tener esa separación a nivel de idioma.

Creo que si las interfaces deben ser compatibles con esta nueva funcionalidad, entonces no puede ser una solución a medias que se haya agregado a override . Por la misma razón extends existe como una entidad separada de implements a nivel de clase, override debe combinarse con algo similar a implement para para solucionar este problema. Es _reemplazar la funcionalidad_ frente a _definir la funcionalidad_.

@olmobrutall

Realmente vale la pena explicar dos palabras clave y que un compilador pedante le dice: Error, debe usar 'anular' en lugar de 'implementar'.

Siento que he hecho esta distinción unas 4 veces, pero esto no es pedante; es información muy importante!

Considere su propio ejemplo

interface IComparable {
     compare(): number;
} 

class BaseClass implements IComparable {
    implement compare(); 
}

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

'o implementos...' es una suposición totalmente incorrecta en este caso. El hecho de que le haya dicho al compilador que desea implementar la misma interfaz que la clase base que está extendiendo no cambia el hecho de que ya implementó compare .
Si tuviera que escribir implement en ChildClass en lugar de override entonces debería estar agradecido de que el compilador le informaría sobre su suposición incorrecta, porque es un gran problema que estaba a punto de borrar sin saberlo. un método implementado previamente!

En las bases de código de las que soy responsable esto sería un gran problema sin duda; ¡así que doy la bienvenida a cualquier característica del compilador que pueda evitar tales errores de desarrollador!

@kungfusheep

Si escribiera implement en ChildClass en lugar de anular, debería estar agradecido de que el compilador le informaría sobre su suposición incorrecta, porque es un gran problema que sin saberlo estaba a punto de eliminar un método implementado previamente.

Si anular un método ya implementado es una decisión tan importante, entonces implement también debe usarse para métodos abstractos. Si cambia un método de abstract a virtual o al revés, debe verificar (y cambiar) todas las versiones implementadas para considerar llamar a super o simplemente eliminar el método.

En la práctica, esto nunca ha sido un problema en C#.

Estoy de acuerdo, override para métodos implementados y implements para métodos no implementados (resumen/interfaz).

Podría usarse para resumen, sí.

En C # no existía el escenario de interfaz 'opcional', por lo que tal vez no se consideró un gran problema.

Pero mi punto es que en C# nosotros override implementamos y no implementamos métodos y nunca escuché que alguien se quejara.

No creo que sea realmente un problema que merezca hacer que explicar la función sea al menos dos veces más difícil.

Bueno, la única razón por la que necesitamos una palabra clave implement es porque los miembros de la interfaz pueden ser opcionales, mientras que no es posible en C# (y probablemente también en la mayoría de los lenguajes OO). No hay una palabra clave para eso porque no se puede _no_ implementar una interfaz por completo, así que no hay problema. No base demasiado su argumento en "es lo mismo en C#", porque tenemos problemas diferentes aquí.

override se usa para métodos de _clase base_, ya sean abstractos o no. Supongo que deberíamos hacer lo mismo ( override en lugar de implement para métodos abstractos) aquí porque creo que la distinción debería estar en el origen del método (clase o interfaz) en lugar de en la existencia de la implementación. Y en el futuro, si decide dar una implementación predeterminada a su método abstracto, no tendrá que revisar su código (o peor aún, el código de otros que usan su clase) para reemplazar las palabras clave.

Mi pregunta principal ahora es, ¿deberíamos tener un indicador estricto override / implement (lo quiero totalmente), ¿deberíamos forzar la palabra clave implement en los miembros de interfaz obligatorios? Porque no ayuda mucho (no puede dejar de implementarlos porque de lo contrario no se compilará) y podría generar mucha verbosidad innecesaria. Pero, por otro lado, puede ser engañoso tener implement en algunos miembros de la interfaz pero no en todos.

@JabX Personalmente, quisiera una advertencia si una clase base agregara una implementación de un método abstracto.

Sin embargo, no estoy 100% convencido de la idea de una palabra clave de implementos en primer lugar. El compilador ya te avisará si no implementaste algo. El único lugar donde es realmente útil es para métodos opcionales.

Y de todos modos, no tiene nada que ver con por qué estoy en este hilo. Solo estaba buscando si había una forma de especificar una función como anulación de una clase principal.

Mi pregunta principal ahora es, ¿deberíamos tener un indicador de anulación/implementación estricto (totalmente quiero esto), deberíamos forzar la palabra clave de implementación en los miembros de interfaz obligatorios?

Creo que no escribirlo debería ser una advertencia.

El único lugar donde es realmente útil es para métodos opcionales.

Sí, ese es un poco el punto aquí.

Sin esto, es un error muy común en el método de error tipográfico que le gustaría anular, pero de hecho está creando un nuevo método. La introducción de dicha palabra clave es solo una forma de señalar la intención de anular la función.

Eso podría ser una advertencia o lo que sea, pero actualmente es muy doloroso.

Estoy agregando una anotación de anulación a la vista de clase UML en alm.tools :rose:

image

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

También agregué un indicador de medianera para los miembros de la clase que reemplazan a un miembro de la clase base en alm .

overrides

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

Aceptar PR para una solución de alcance que creemos que logra el mayor valor con la menor complejidad:

  • La nueva palabra clave override es válida en el método de clase y las declaraciones de propiedades (incluido get/set)

    • Todas las firmas (incluida la implementación) deben tener override si una tiene

    • Tanto get como set deben estar marcados como override si alguno lo es.

  • Es un error tener override si no hay una propiedad/método de clase base con este nombre
  • El nuevo interruptor de línea de comandos --noImplicitOverride (siéntase libre de cambiar el nombre aquí) hace que override sea obligatorio para las cosas que se anulan

    • Este cambio no tiene efecto en contextos ambientales (es decir declare class o archivos .d.ts)

Fuera de alcance en este momento:

  • Heredar firmas
  • Tipificación contextual de parámetros o inicializadores (intenté esto antes y fue un desastre)
  • Palabra clave correspondiente para indicar "Estoy implementando un miembro de interfaz con esta declaración"

@RyanCavanaugh
Actualmente estoy tratando de implementar esto (acabo de comenzar, y agradecería cualquier ayuda al respecto, especialmente para las pruebas), pero no estoy seguro de entender lo que hay detrás.

Todas las firmas (incluida la implementación) deben tener override si una tiene

Dado que no puede tener varias firmas de un método en una clase, ¿está hablando del contexto de herencia? ¿Quiere decir eso (sin la bandera que impone el uso de override , de lo contrario, obviamente es un error)

class A {
    method() {}
}

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

class C extends B {
    method() {} 
}

debería generar un error en la clase C porque no escribí override antes method ?

Si ese es el caso, no estoy seguro de por qué querríamos hacer cumplir eso. ¿Y debería funcionar también al revés? ¿Sale un error en la clase B si especifiqué override en C y no en B?

Sí, creo que se aplica a las firmas del árbol de herencia.
Y no creo que funcione al revés, debería comenzar a aplicar la regla de anulación una vez que se use en un nivel de la jerarquía. En algunos casos, la decisión de aplicar la anulación se puede tomar en una clase derivada de una clase que no es propiedad del desarrollador. Entonces, una vez que se usa, debería aplicarse solo a todas las clases derivadas.

tengo otra pregunta sobre

Tanto get como set deben estar marcados como override si alguno lo es.

¿Qué sucede allí si mi clase principal solo define un getter y quiero definir en mi clase derivada el setter? Si sigo esta regla, eso significa que mi setter tendría que definirse como una anulación, pero no hay un setter en la clase principal, y debería ser un error anular algo que no existe. Bueno, en la práctica (en mi implementación ingenua actual), no hay contradicción ya que getter y setter tienen la misma propiedad.
Incluso podría escribir algo como:

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

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

Lo cual me parece completamente incorrecto.

Dado que no puede tener varias firmas de un método en una clase

Puede tener varias firmas de sobrecarga para el mismo método:

class Base { bar(): { } }

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

Creo que tu intuición sobre los otros casos es correcta.

Con respecto a getters y setters, creo que override solo se aplica a la ranura de propiedad. Escribir un setter anulado sin un captador correspondiente es claramente extremadamente sospechoso, pero no es más o menos sospechoso en presencia de override . Dado que no siempre sabemos cómo se implementó la clase base, creo que la regla es simplemente que un getter o setter override debe tener _alguna_ propiedad de clase base correspondiente, y si dice override get { , cualquier declaración set también debe tener override (y viceversa)

Ah, bueno, no sabía que podíamos escribir firmas de métodos en clases como esa, eso es bastante bueno.

Creo que virtual también tendría sentido.
Todas las palabras clave: virtual , override , final en realidad marcarían una gran diferencia, especialmente al refactorizar clases.
Estoy usando mucho la herencia, ahora es muy fácil anular el método "por error".
virtual también mejoraría la inteligencia, ya que podría proponer solo métodos abstractos/virtuales después de anular la palabra clave.

Por favor, por favor, dé prioridad a estos. ¡Gracias!

Estoy de acuerdo con @pankleks : estoy usando mucho la herencia, y se siente mal anular una función sin especificar nada directamente.

"virtual", "anular" y "final" serían perfectos.

Este es un tema importante para mí.

@RyanCavanaugh

Tipificación contextual de parámetros o inicializadores (intenté esto antes y fue un desastre)

¿Puedes dar más detalles sobre esto? De hecho, encontré este problema al tratar de averiguar por qué TS no infiere el tipo de mis parámetros en las anulaciones de métodos, lo que me sorprendió. No veo cuándo sería correcta una lista de parámetros no idénticos al anular, o un tipo de retorno que no es compatible.

@pankleks

Creo que virtual también tendría sentido.

Por su naturaleza, todo es "virtual" en JS. El soporte para final sería suficiente en mi humilde opinión: si tiene un método (o propiedad) que no debe anularse, puede marcarlo como tal para desencadenar un error del compilador cuando se viole. No hay necesidad de virtual entonces, ¿verdad? Pero esto se rastrea en el número 1534.

@avonwyss hay dos problemas aquí.

Cuando intentamos escribir contextualmente _property initializers_, nos encontramos con algunos problemas que se describen en https://github.com/Microsoft/TypeScript/pull/6118#issuecomment -216595207. Creemos que tenemos un nuevo enfoque más exitoso en #10570

Las declaraciones de métodos son una bola de cera diferente y ahí tenemos otras cosas que resolver, como lo que debería suceder aquí:

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

Podemos decidir que solo los métodos de firma única obtienen tipos de parámetros de la base. Pero esto no va a hacer felices a todos y realmente no resuelve el problema frecuente de "Quiero que mi método de clase derivada tenga las mismas _firmas_ pero solo proporcione una _implementación_ diferente".

Bueno, el tipo x sería string | number en ese caso, pero entiendo que puede ser bastante difícil encontrarlo de manera consistente.

Pero probablemente no querrías que el tipo _visto externamente_ de x fuera string | number - asumiendo que Derived tiene la misma semántica que Base , no sería No es legal invocar con (3) o ('foo', 3)

Hum, sí, me perdí eso.

Veo que sugirió restringirlo a métodos de firma única, pero creo que podría expandirse "fácilmente" a métodos de "firma coincidente", como en su ejemplo:

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

porque solo hay una firma en la clase base que coincide.

Eso sería mucho mejor que lo que tenemos (nada), y no creo que sea _tan_ difícil de hacer. (todavía fuera del alcance de este problema y probablemente más trabajo)

@RyanCavanaugh Gracias por la respuesta. Como se señaló, mi esperanza para override es que solucione el problema del parámetro (y el tipo de devolución). Pero no veo que su ejemplo sea problemático, ya que las sobrecargas siempre deben tener una sola implementación "privada" que sea compatible con todas las sobrecargas (por lo que la sugerencia de @JabX no funcionaría conceptualmente).
Entonces, tendríamos algo como esto:

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

Esta lógica ya existe y la anulación obviamente solo estaría disponible para el método de implementación "privado", no para las anulaciones distintas (del mismo modo que no puede implementar explícitamente una sola sobrecarga de todos modos). Entonces, no veo un problema aquí, ¿o me perdí algo?

Los métodos diseñados para ser anulados generalmente no tienen sobrecargas de todos modos, por lo que incluso tomar la salida simple y no inferir el parámetro y los tipos de retorno si existen sobrecargas sería perfectamente correcto y útil. Entonces, incluso si el comportamiento fuera tal como está al anular los métodos sobrecargados, obtendría un error al ejecutar en modo --no-implicit-any y tendría que especificar tipos compatibles, eso parecería un enfoque perfectamente correcto.

Probablemente me esté perdiendo algo, pero en Javascript (y, por lo tanto, Typescript) no puede tener 2 métodos con el mismo nombre, incluso si tienen firmas diferentes.

En C#, podrías tener

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

Pero aquí nunca puedes tener 2 funciones con el mismo nombre, no importa lo que hagas con las firmas... así que ninguno de tus ejemplos sería posible de todos modos, ¿verdad? A menos que estemos hablando de múltiples extensiones en la misma clase... pero eso no es lo que muestran sus ejemplos, ¿entonces probablemente no? ¿Es esto un problema?

En este momento estoy usando herencia, y es muy frustrante... Tengo que poner comentarios sobre funciones para indicar que son virtuales (es decir, las estoy anulando en algún lugar) o anuladas (es decir, que esta función anula una función subyacente). Súper molesto y desordenado! ¡E inseguro!

@ sam-s4s Sí, es posible declarar múltiples firmas de sobrecarga lógica para un método, pero todas terminan en la misma implementación única (que luego tiene que averiguar por los parámetros pasados ​​a qué "sobrecarga" se llama). Esto es muy utilizado por muchos marcos JS, por ejemplo, jQuery donde $(function) agrega una función lista, pero $(string) busca el DOM por selector.

Consulte aquí los documentos: https://www.typescriptlang.org/docs/handbook/functions.html#overloads

@avonwyss Ah, sí, declaraciones, pero no implementaciones, eso tiene sentido :) Gracias

Esto ha durado demasiado, y me resulta molesto tener que leer todo este hilo inflado para comenzar a descubrir por qué TS todavía no tiene esta afirmación básica de tiempo de compilación.

¿Dónde estamos en (a) una palabra clave override o (b) una anotación jsdoc @override que informa al compilador que "DEBE existir un método con el mismo nombre y firma de tipo en una superclase". ?

Más específicamente, ¿el comentario en https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -224776519 (8 de junio de 2016) de @RyanCavanaugh sugiere que el siguiente paso es una RP (comunitaria)?

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

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

Más específicamente, ¿el comentario en el n.º 2000 (comentario) (8 de junio de 2016) de @RyanCavanaugh sugiere que el siguiente paso es una RP (comunitaria)?

@pcj Correcto

Empecé un PR en esto en #13217. Cualquier otra persona interesada en esta funcionalidad es bienvenida a participar y/o brindar sugerencias. ¡Gracias!

Preferiría lo mismo que para Java con un decorador.

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

@ Chris2011 De acuerdo, esto parece familiar, pero esto implicaría como decorador que @Override se evaluaría en tiempo de ejecución, no en tiempo de compilación. Estoy planeando javadoc /** <strong i="7">@override</strong> */ en un PR separado una vez que la palabra clave de anulación #13217 ( public override toString() ) avanza en la revisión.

Intentando seguir la conversación de este hilo. ¿Esto incluye funciones estáticas? No veo ninguna razón por la que no podamos permitir anular funciones estáticas en clases derivadas con firmas completamente diferentes. Esto es compatible con ES6. Si tenía class A { } ; A.create = function (){} entonces class B extends A { } ; B.create = function (x,y) { } , llamar a A.create() no llamará a B.create() ni causará problemas. Por esta razón (y para crear la capacidad de usar el mismo nombre de función en los tipos como funciones de fábrica), también debe permitir anular la firma de las funciones estáticas base. No rompe nada y agrega la capacidad de hacer algunas cosas geniales (especialmente para marcos de motores de juegos, donde cualquier cosa 'nueva' es realmente un mal si se usa todo el tiempo sin extraer objetos de un caché para reducir las congelaciones de GC). Dado que los constructores invocables no son compatibles con ES6, la única otra opción es crear una convención de nomenclatura común para métodos en tipos; sin embargo, hacerlo actualmente requiere que la función estática derivada muestre una sobrecarga de la firma de la función estática base junto con la suya propia, lo que no es útil para una función de fábrica en un tipo que solo se ocupa de ESE tipo. :/ Mientras tanto, la única opción que tengo es obligar a los usuarios de mi marco a duplicar todas las firmas de funciones estáticas de la jerarquía base (como SomeType.create() ) en sus tipos derivados, y así sucesivamente, lo que se vuelve realmente tonto .

Aquí hay un ejemplo sobre la "tontería" de la que estoy hablando (que funciona, pero no es algo de lo que estaría orgulloso en un marco extensible):

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

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

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

(https://goo.gl/G01Aku)

Esto hubiera sido mucho mejor:

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

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

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

@rjamesnw , es posible que le interese "esto" polimórfico para miembros estáticos que tienen un contrato para funciones de fábrica que se analiza como un caso de uso principal a lo largo de los comentarios.

por lo tanto, reafirmando nuevamente un sentimiento que @pcj expresó ( comentario ) hace más de un año, es confuso tener que leer tantos PR y comentarios aquí y en otros lugares para determinar dónde se encuentra esta solicitud de función.

parece que # 13217 estuvo tan cerca, luego @DanielRosenwasser y compañía lo rechazaron nuevamente como una característica que puede no encajar en el idioma, aparentemente volviendo a entrar en la conversación circular aquí sobre este tema sobre si debería hacerse o no. Tal vez esto de los decoradores de 'tiempo de diseño' (# 2900) lo resuelva, ¿tal vez no? Sería bueno saberlo.

Aquí hay algunas deficiencias más del enfoque del decorador en tiempo de ejecución publicado hace unos años que no vi mencionado:

  • No funcionará con propiedades ya que no son parte del prototipo.
  • Las propiedades y los accesores pueden (más o menos...) anularse entre sí, lo que tampoco funcionará
  • Ahora tenemos clases abstractas, pero dado que las cosas abstractas en realidad no existen en tiempo de ejecución, es imposible que funcione con ellas.

Si la única advertencia fuera que las comprobaciones se produjeron en la inicialización de la clase, entonces probablemente podría aceptarlo como una medida temporal, pero tal como está, hay demasiadas limitaciones.

Francamente, no puedo creer que esta característica siga siendo tan controvertida durante 3 años desde que registré el problema por primera vez. Todos los lenguajes orientados a objetos "serios" admiten esta palabra clave, C#, F#, C++... lo que sea.

Puede argumentar hipótesis todo el día sobre por qué javascript es diferente y necesita un enfoque diferente. Pero desde una perspectiva práctica trabajando diariamente en una base de código de TypeScript muy grande, puedo decirle que la anulación marcaría una gran diferencia en la legibilidad y el mantenimiento del código. También eliminaría toda una clase de errores debido a que las clases derivadas anulan accidentalmente los métodos de la clase base con firmas compatibles pero sutilmente diferentes.

Realmente me encantaría ver una implementación adecuada de virtual/override/final. Lo usaría todo el tiempo y haría que el código fuera mucho más legible y menos propenso a errores. Siento que esta es una característica importante.

Acordado. Es frustrante ver cómo se agregan características relativamente oscuras / edge-case, mientras que algo tan... fundamental se rechaza. ¿Hay algo que podamos hacer para impulsar esto?

¡Vamos gente! Si hay tantos comentarios y más de tres años, ¿por qué no se implementó ya?

Vamos :joy_cat:

Sea constructivo y específico; no necesitamos docenas de comentarios POR FAVOR HAZLO YA

Pero chicos, sean específicos también de su lado.

Obviamente, la comunidad está muy interesada en esa función, y el equipo de TS no nos da detalles sobre el futuro de esta.

Personalmente, estoy completamente de acuerdo con @armandn , los lanzamientos recientes de TS ofrecen características que se usan con relativa poca frecuencia mientras se lleva a cabo algo como esto.

Si no tienes pensado hacerlo, solo dínoslo. De lo contrario, háganos saber cómo puede ayudar la comunidad.

Solo una línea de tiempo aquí, ya que hay más comentarios de los que GitHub está dispuesto a mostrar al cargar:

No es que no estemos tomando consideración aquí. Esto es lo más cerca que están las funciones de entrar, y hemos rechazado nuestras propias ideas de funciones por motivos similares; consulte el n.º 24423 para ver un ejemplo reciente.

Realmente queremos hacer crecer el lenguaje intencionalmente. Esto lleva tiempo, y debe esperar tener que ser paciente. En comparación, C++ es más antiguo que mucha gente en este hilo ; TypeScript aún no tiene la edad suficiente para estar en casa sin la supervisión de un adulto. Una vez que agregamos algo al idioma, no podemos quitarlo de nuevo, por lo que cada adición debe sopesarse cuidadosamente en función de sus pros y sus contras. Hablo por experiencia que las características por las que la gente estaba GRITANDO (métodos de extensión, te estoy mirando) habrían destruido nuestra capacidad de continuar evolucionando el lenguaje (sin tipos de intersección, sin tipos de unión, sin tipos condicionales) si los hubiéramos implementado solo porque hubo muchos comentarios de GitHub. No digo que override sea algo peligroso de agregar, solo que siempre nos acercamos a esto con la máxima precaución.

Si tuviera que resumir los problemas principales con override ahora mismo:

  • No te permite hacer nada que no puedas hacer hoy con un decorador (por lo que "no es nuevo" en términos de "cosas que puedes lograr")
  • La semántica no puede alinearse con precisión con override en otros idiomas (aumenta la carga cognitiva)
  • No se "ilumina" al 100 % sin un nuevo indicador de línea de comandos, y sería un indicador que probablemente querríamos tener bajo strict pero, de manera realista, no podría porque sería demasiado grande. un cambio de última hora (disminuye el valor entregado). Y cualquier cosa que implique una nueva marca de línea de comandos es otra duplicación del espacio de configuración que debemos sopesar seriamente, porque solo puede duplicar algo tantas veces antes de que consuma todo su presupuesto mental al tomar decisiones futuras.
  • Un desacuerdo interno bastante fuerte aquí sobre si override se puede aplicar a la implementación de un miembro de la interfaz (disminuye el valor percibido porque las expectativas no coincidirán con la realidad para una cierta proporción de personas)

La ventaja total aquí es que puede a) indicar que está anulando algo (lo que podría hacer hoy con un comentario), b) detectar un error ortográfico con el que el autocompletado debería haberlo ayudado de todos modos, y c) con una nueva bandera, encuentre lugares donde "olvidó" poner una palabra clave y ahora necesita hacerlo (una simple tarea mecánica que es poco probable que encuentre errores reales). Entiendo que b) es extremadamente frustrante , pero nuevamente, necesitamos cumplir con la barra de complejidad aquí.

Al final del día, si cree que una palabra clave override ayudaría enormemente a las clases de JS, entonces defender una propuesta de TC39 para agregarlo al tiempo de ejecución principal sería un buen punto de partida.

No te permite hacer nada que no puedas hacer hoy con un decorador (por lo que "no es nuevo" en términos de "cosas que puedes lograr")

Tal vez no lo entienda bien, pero hay cosas muy importantes que un decorador no puede hacer y que la palabra clave sí podría. Mencioné algunos en https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -389393397. Definitivamente, corrígeme si esas cosas son posibles, porque me gustaría usar el enfoque de decorador, pero los métodos no abstractos son solo una pequeña parte de mi caso de uso para esta característica.

Otra ventaja que no se menciona es que una palabra clave de anulación haría mucho más factible cambiar algo en una clase base. Cambiar un nombre o dividir algo en dos partes, etc. es difícil sabiendo que cualquier clase derivada se compilará bien sin actualizarse para que coincida, pero probablemente fallará en el tiempo de ejecución. Además, teniendo en cuenta que no hay palabras clave finales/selladas/internas disponibles, casi cualquier clase exportada podría derivarse en algún lugar, lo que hace que cambiar casi cualquier cosa que no sea privada sea mucho más riesgoso de lo que sería con la anulación disponible.

Mi impresión es que una regla TSLint sería, con mucho, el camino más fácil a seguir aquí. @kungfusheep mencionó brevemente la idea en https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -192502734 , pero no veo ningún seguimiento. ¿Alguien más conoce una implementación TSLint de esta función? Si no, probablemente empezaré a hackear uno cuando tenga la oportunidad. 😄

Mi pensamiento actual es que solo será un comentario, // @override , al igual que // @ts-ignore y varias otras directivas basadas en comentarios que he visto. Personalmente, preferiría alejarme de los decoradores ya que (en su forma actual) tienen semántica de tiempo de ejecución y ya que todavía están en la etapa 2 y están deshabilitados de forma predeterminada.

Con un poco de suerte, una regla TSLint personalizada sería una solución del 90 %, solo que realmente carecería de estética y, por supuesto, podría avanzar sin tener que comprometerse con ningún detalle del lenguaje. Con las reglas de reconocimiento de tipos, el servicio de lenguaje tslint y la corrección automática, realmente no hay mucha diferencia entre un error TSLint y un error TS incorporado desde la perspectiva del desarrollador. Mi esperanza es que un complemento TSLint (o varios complementos de la competencia) le brinde a la comunidad algo de experiencia y la oportunidad de llegar a un acuerdo sobre la mejor semántica. Entonces, tal vez podría agregarse como una regla básica de TSLint, y tal vez eso proporcionaría suficiente claridad y motivación para justificar el beneficio estético de una palabra clave override en el idioma principal.

Solo pensando fuera de la caja aquí. Tenemos dos escenarios diferentes aquí. C# (solo como ejemplo) usa virtual y override porque los métodos NO son virtuales de forma predeterminada. En JavaScript, todas las funciones en una clase son virtual por defecto (por naturaleza). ¿No tiene más sentido entonces invertir el proceso y tener un modificador de tipo nooverride en su lugar? Por supuesto, la gente aún podría forzarlo, pero al menos está ahí para ayudar a impulsar una convención de que algunas funciones en las clases base no deben tocarse. Nuevamente, solo estoy pensando fuera de la norma aquí. ;) También puede ser un cambio menos importante.

¿No tiene más sentido invertir el proceso y tener un modificador de tipo nooverride en su lugar?

Me gusta tu forma de pensar, y creo que lo que buscas es definitivo .

Sin embargo, ¿qué pasa con readonly ? Creo que anular un método en JS en realidad significa reemplazar el padre con el hijo durante la creación de instancias (cuando se aplica la cadena de prototipo). En ese caso, tener un método readonly que signifique "Está ahí para que todos lo vean, pero no quiero que nadie lo cambie, ya sea por herencia o dinámicamente después de la creación de instancias" tiene mucho sentido. Ya está implementado para los miembros, ¿por qué no hacerlo también para los métodos?

¿Ya hay una propuesta para ello? Si no, podría valer la pena investigarlo como una alternativa para anular...

_edit:_ resulta que puede anular un miembro de solo lectura, por lo que todo este argumento colapsa.

¿Quizás permitir funciones de clase private es otra opción?

Editar: estaba pensando "en lugar de marcarlo como final", pero debo haber estado medio dormido (obviamente, "final" significa público pero no puede anularse), LOL; no importa.

@rjamesnw ya puede definir funciones de clase como públicas, protegidas, privadas.

No creo que solo tener "final" sea una solución. El problema es que las personas pueden crear accidentalmente una nueva función en una clase que hereda de una clase base con ese nombre que ya está en uso, y luego estará rompiendo cosas en silencio sin saber por qué. Me ha pasado algunas veces, muy frustrante, ya que a menudo no obtienes errores, y cosas extrañas simplemente suceden (o no)...

Así que creo que, realmente, estamos viendo una nueva entrada tsconfig.json que forzará al compilador a arrojar un error cuando algo se anula sin que se marque como virtual (y la anulación se marque como anulada o final).

Creo que sin override , TypeScript está decepcionando a sus usuarios con su promesa [percibida] de seguridad en tiempo de compilación y seguridad de tipos.
El argumento "usar un comando IDE para anular el método" se rompe a medida que el código evoluciona (como han señalado otras personas).
También parece que todos los principales idiomas comparables han agregado override de alguna forma.
Para no convertirlo en un cambio importante, podría ser una regla tslint (similar a Java).
Por cierto, ¿por qué no permitir cambios importantes entre versiones principales de idioma, por ejemplo, 2.x -> 3.x? No queremos quedarnos estancados, ¿verdad?
Si resulta que algo anda mal con ciertos detalles de override , y requiere algunos cambios de ruptura en 3.x, que así sea. Creo que la mayoría de la gente entenderá y apreciará el equilibrio entre velocidad de evolución y compatibilidad. Realmente sin override no puedo recomendar TS como algo más práctico que Java o C#...

Si la gente quiere ser estricta, debería haber alguna forma de override obligatorios (podría ser una regla tslint opcional). Sin embargo, el valor de override obligatorio es mucho más bajo, porque la probabilidad de anular accidentalmente un método de la superclase parece mucho menor que la de no anular accidentalmente.

Realmente este número que está abierto desde 2015 es un balde de agua fría en mi entusiasmo y evangelización de TypeScript...

Esto no maneja los métodos anulados de grand*-parents:

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

También es triste y decepcionante que No one assigned en GH aquí sobre este tema. ¿Tal vez sea hora de una bifurcación de TypeScript? ;) CompileTimeSafeScript...

Todo este hilo parece un gran antipatrón de "Parálisis por análisis". ¿Por qué no simplemente hacer el "valor de los casos del 80% con el 20% del trabajo" y probarlo en la práctica y, si es necesario, modificarlo en 3.x?

Los casos de uso simples, frecuentes y muy valiosos son "rehenes" de algunos casos de esquina de los que la mayoría de la gente nunca tendrá que preocuparse.

Por lo tanto, es posible verificar el tipo en tiempo de compilación a través de decoradores, aunque no sin un poco de ayuda:

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

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

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

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

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

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

No estoy seguro de si es posible obtener una referencia al supertipo sin pasarlo explícitamente. Esto incurre en un pequeño costo de tiempo de ejecución, pero la verificación es todo tiempo de compilación.

@alangpierce

Mi impresión es que una regla TSLint sería, con mucho, el camino más fácil a seguir aquí.

[...]

Entonces, tal vez podría agregarse como una regla TSLint central, y tal vez eso proporcionaría suficiente claridad y motivación para justificar el beneficio estético de una palabra clave anulada en el idioma principal.

100% de acuerdo. ¡Empecemos ya!

Hola, un poco más tarde de lo planeado, pero aquí está nuestra regla tslint implementada con un decorador.

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

Hemos estado usando esto en nuestra base de código TypeScript de ~1mloc durante un par de años y lo hemos actualizado recientemente para usar el servicio de lenguaje, lo que lo hace mucho más rápido de ejecutar y más factible para abrir el código (la iteración anterior requería un pase completo del proyecto para recoger la información patrimonial).

Para nuestro caso de uso, ha sido invaluable, aunque todavía somos de la opinión de que, en última instancia, la responsabilidad debe recaer en el compilador y, como tal, la regla de pelusa debe verse como una solución provisional.

Gracias

Estoy de acuerdo. También creo que una solución que involucre el compilador TS sería mucho mejor que los decoradores y las reglas de pelusa.

¿Cuál es el estado de esta característica? ¿Ya se ha revisado como una característica del compilador?

Por lo tanto, es posible verificar el tipo en tiempo de compilación a través de decoradores, aunque no sin un poco de ayuda:

Algunas mejoras:

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

class Foo {

    bar( a : number ) {
        return a 
    }

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

}

class Foo2 {

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

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

    xxx() { return '777' }

}

class Foo3 extends Foo2 {

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

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

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

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

}

Patio de recreo

Ok, descubrí una solución de anulación que evita el error de compilación.
Ejemplo con flujo legible por nodo:

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

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

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

La única desventaja de esta solución es la falta de escrituras al llamar a super.parentMethod (pero gracias a interface ReadableObjStream tiene todas las escrituras cuando usa la instancia de ReadableObjStream.

@nin-jin @bioball Gracias por las contribuciones con la verificación en tiempo de compilación del decorador.

Desafortunadamente, no parece funcionar con miembros protected , solo con public .

(ejemplo de error en mi fork of nin-jin's playground

El especificador de anulación fue una característica excelente en c ++ 11.
Ayuda y protege mucho el código de refactorización.
Definitivamente me gustaría tener esto en el soporte base de TS sin ningún obstáculo (¡VOTA!)

Definitivamente hemos mostrado un gran apoyo para esta función, pero parece que todavía no planean agregar esta función en el corto plazo.

Otra idea:

class Obj { 

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

}

class Foo extends Obj {

    bar(a: number) {
        return 0
    }

    bar2(a: number) {
        return 0
    }

    foo = 1

}

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

    foo = 2

    bar( a : number ) {
        return 1
    }

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

    bar3( a : number ) {
        return 1
    }

}

declare const Protected: unique symbol

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

Enlace del patio de recreo

Añadiendo mis dos centavos aquí.
Tenemos un componente angular típico, que maneja formularios y necesita darse de baja de valueChanges y demás. No queríamos repetir el código por todas partes (una especie de estado actual), así que creé un "TypicalFormBaseComponent" que (entre otras cosas) implementa métodos angulares OnInit y OnDestroy. El problema es que ahora, si realmente lo usa y agrega su propio método OnDestroy (que es algo bastante estándar), oculta el original y rompe el sistema. llamando a super.OnInit lo arregla, pero actualmente no hay ningún mecanismo para obligar a los niños a hacer eso.

Si lo haces con el constructor, te obliga a llamar a super()... Estaba buscando algo similar, encontré este hilo y, sinceramente, estoy un poco desanimado.
Implementar "nuevo" y "anular" en el ts podría ser un cambio importante, pero solucionarlo sería increíblemente simple básicamente en cualquier base de código (simplemente agregando "anular" en todas partes donde grita). También podría ser sólo una advertencia .

De todos modos, ¿hay alguna otra forma de forzar la súper llamada en los niños? TSLint regla que impide ocultar, o algo por el estilo?

PD: No estoy de acuerdo con la política de "sin comentarios". Evita que la gente arroje ejemplos aquí. Tal vez no implementará 'anular', pero implementará algo más, que solucione sus problemas... es decir, si realmente puede leerlos.

@GonziHere En otros lenguajes como C # o Java, la anulación no implica el requisito de llamar al supermétodo; de hecho, a menudo no es el caso. Los constructores son "métodos virtuales" especiales y no normales.

La palabra clave override se usaría para especificar que el método ya debe estar definido con una firma compatible en la clase base, y el compilador podría afirmar esto.

Sí, pero la necesidad de anularlo lo obliga a notar que el método existe.

@GonziHere Parece que lo que realmente necesita es crear clases base abstractas con funciones abstractas. Quizás pueda crear métodos privados _onInit y _onDestroy los que pueda confiar más, luego cree funciones abstractas protegidas onInit y onDestroy (o funciones regulares si son no requerido). Las funciones privadas llamarán a las demás y luego se completarán normalmente.

@GonziHere @rjamesnw Lo más seguro es hacer cumplir de alguna manera que onInit y onDestroy son _final_, y definir métodos de plantilla abstractos protegidos vacíos a los que llaman los métodos finales. Esto garantiza que nadie pueda anular accidentalmente los métodos, y tratar de hacerlo (suponiendo que haya una forma de hacer cumplir los métodos finales) indicaría inmediatamente a los usuarios la forma correcta de implementar lo que desean.

@shicks , @rjamesnw , estoy en la misma situación que @GonziHere. No quiero que la clase base tenga métodos abstractos porque hay una funcionalidad que quiero ejecutar para cada uno de esos enlaces de ciclo de vida. El problema es que no quiero que esa funcionalidad base se reemplace accidentalmente cuando alguien agrega ngOnInit o ngOnDestroy en la clase secundaria sin llamar a super() . Requerir override llamaría la atención del desarrollador de que esos métodos existen en la clase base y que deberían elegir si necesitan llamar a super() ;

Desde mi experiencia escribiendo Java, @Override es tan común que se convierte en ruido. En muchos casos, el método anulado es abstracto o está vacío (donde super() _no_ debe_ llamarse), lo que hace que la presencia de override no sea una señal particularmente clara de que se necesita super() .

Desde mi experiencia escribiendo Java, @Override es tan común que se convierte en ruido. En muchos casos, el método anulado es abstracto o está vacío (donde super() _no_ debe_ llamarse), lo que hace que la presencia de override no sea una señal particularmente clara de que se necesita super() .

Eso no tiene nada que ver con el tema que nos ocupa. También podría decir que use la composición sobre la herencia para evitar el problema.

Para mí, la parte más útil de override es obtener un error al refactorizar .
En este momento, es demasiado fácil cambiar el nombre de algún método en una clase base y olvidarse de cambiar el nombre de los que lo anulan.
Recordar llamar a super no es tan importante. Incluso es correcto no llamarlo en algunos casos.

Creo que hay un malentendido sobre por qué la _anulación_ sería importante. No obligar a alguien a llamar al método _super()_, sino hacerle saber que existe y permitirle decidir conscientemente si quiere suprimir su comportamiento o extenderlo.

El patrón que uso para garantizar la corrección es usar Parameters y ReturnType mientras me refiero muy explícitamente a la clase base, algo como esto:

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

Este tipo de patrón es la base de mi sugerencia de agregar una palabra clave inherit . . Esto garantiza que cualquier actualización de la clase base se propague automáticamente y, si no es válida en la clase derivada, se da un error, o si cambia el nombre de la clase base, también se da un error, también con la idea inherit no está obligado a ofrecer solo una firma idéntica a la clase base, sino que puede extenderla intuitivamente.

También creo que hay un malentendido acerca de por qué override sería importante, pero realmente no entiendo todos estos problemas con "si se necesita super() ".

Para hacernos eco de lo que han dicho @lorenzodallavecchia y el autor original del problema, marcar una función como override es un mecanismo para evitar errores que ocurren cuando se refactoriza la superclase, específicamente cuando el nombre y/o la firma de la función anulada cambia o la función se elimina.

Tenga en cuenta que la persona que cambia la firma de la función anulada puede no saber que existen anulaciones. Si (por ejemplo, en C++) las anulaciones no se marcan explícitamente como anulaciones, cambiar el nombre/firma de la función anulada no introduce un error de compilación, simplemente hace que esas anulaciones dejen de ser anulaciones. (y ... probablemente ya no sea útil, ya no sea llamado por el código que solía llamarlos, e introduzca un montón de errores nuevos porque las cosas que se supone que deben llamar a las anulaciones ahora están llamando a la implementación base)

La palabra clave override, como se implementa en C++, evita que la persona que cambia la implementación base tenga estos problemas, porque inmediatamente después de su refactorización, los errores de compilación le indicarán que existen un montón de overrides (que reemplazan una implementación base que ahora no existe) y le darán una pista. en el hecho de que probablemente también necesiten refactorizar las anulaciones.

También hay beneficios secundarios de usabilidad del IDE al tener el modificador override (también mencionado por el autor original), a saber, que al escribir la palabra override el IDE puede mostrarle un montón de posibilidades de lo que puede ser anulado.

Junto con la palabra clave override , sería bueno introducir también una bandera que, cuando está habilitada, requiere que todos los métodos que reemplazan a otro usen la palabra clave override , para evitar casos en los que se crea una en una subclase y, en el futuro, la clase base (que podría estar en una biblioteca de terceros) crea un método con el mismo nombre que el método que creó en la subclase (lo que podría causar errores ahora, porque la subclase había anulado ese método ).

Obteniendo un error de compilación en este caso, diciendo que necesita agregar la palabra clave override en este método (incluso si en realidad no puede anular el método, simplemente cambie su nombre para no anular el método recién creado en la clase base), sería mucho mejor y evitaría posibles errores de tiempo de ejecución.

Desde mi experiencia escribiendo Java, @Override es tan común que se convierte en ruido.

Después de muchos años de Java, no estoy de acuerdo con la declaración anterior. La anulación es una forma de comunicación, como las excepciones comprobadas. No se trata de gustar o disgustar, sino de calidad y expectativas.

Y por lo tanto, un gran +1 a @lucasbasquotto , pero desde otro punto de vista: si introduzco un método en una subclase, quiero tener una semántica clara de anular o no anular. Por ejemplo, si quiero anular un método, entonces quiero tener una forma de decirlo explícitamente. Si hay algún problema con mi implementación, por ejemplo, un error tipográfico o copiar y pegar incorrecto, quiero recibir comentarios al respecto. O de otra manera: si no espero anular, también quiero comentarios, si la anulación ocurre ocasionalmente.

Comentarios de otro desarrollador de Java... @override es la única característica que realmente me falta en Typescript.

Tengo ~ 15 de experiencia en Java y 1-2 años o Typescript, así que me siento bastante cómodo con ambos.

El zen de @override es que puede eliminar un método de una interfaz y la compilación se interrumpirá.

Es como el reverso de la vinculación estricta para los nuevos métodos. Te permite eliminar los viejos.

Si implementa una interfaz y AGREGAR un método, la compilación fallará, pero no hay una inversa de esto.

Si elimina un método, terminará con un código muerto.

(aunque tal vez eso se puede arreglar con un linter)

Para ser claros, cuando dije que @Override era ruido, me refería específicamente al comentario de que es una señal de que necesitas llamar a super . La verificación que proporciona es valiosa, pero para cualquier método anulado dado, es un completo error si es necesario o no tiene sentido llamar a super . Dado que una fracción tan grande de métodos anulados _no debería_, es ineficaz para ese propósito.

Todo mi punto es que si desea asegurarse de que las subclases vuelvan a llamar a sus métodos anulables, la única forma efectiva de hacerlo es hacer que el método final (ver #33446, entre otros) y hacer que llame a un Método de plantilla vacía con un nombre diferente que se puede anular de forma segura sin la llamada super . Simplemente no hay otra forma razonable de obtener ese invariante. Estoy completamente a favor de override , pero como alguien que ha sido quemado por usuarios que subclasifican incorrectamente mis API (y estoy en el gancho para no romperlas), creo que vale la pena cambiar la atención a final como la solución correcta a este problema, en lugar de override como se sugirió en el subproceso.

Estoy un poco confundido en cuanto a por qué la gente sigue sugiriendo uno de final o override sobre el otro. Seguro que queremos las dos , ya que resuelven diferentes problemas.

Anular
Evita que las personas que están extendiendo una clase sobrescriban accidentalmente una función con la suya propia, rompiendo cosas en el proceso sin siquiera saberlo. Tener una palabra clave de anulación le permite al desarrollador saber que, de hecho, está anulando una función existente, y luego puede elegir cambiar el nombre de su función o usar la anulación (y luego puede averiguar si necesita llamar a super).

Final
Evite que las personas que amplían una clase sobrescriban una función por completo, de modo que el creador original de la clase pueda garantizar un control total.

@sam-s4s También queremos la definición :-)

Ejemplo de problema..

Inicial

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

Clase base refactorizada

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

Aquí tenemos una sobrecarga accidental.

Caso con la palabra clave define

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

Debería ser un error: redefinición accidental.

Solución alternativa con decoradores

@nin-jin no es define lo mismo que _no_ usando override ?

@sam-s4s También queremos la definición :-)

Oh, no he visto a nadie aquí mencionar una palabra clave define , pero por lo que describes, supongo que te refieres a la palabra virtual en C#.

(También encontré su ejemplo un poco confuso, ya que sus clases Base y Entity no están relacionadas. ¿Quiso decir que Entity extendiera Base ?)

Supongo que hay 2 escenarios...
a) escribe su clase base, asumiendo que todo sin final puede anularse.
b) escribe su clase base asumiendo que todo sin virtual no se puede anular.

@ sam-s4s La entidad extiende la base, por supuesto. :-) He arreglado mi mensaje. virtual se trata de otra cosa.
@lorenzodallavecchia que no usa override es define | override para el compilador. Usar define es solo define y el compilador puede verificar esto estrictamente.

@nin-jin En su modelo, esto significa que no usar la palabra clave override sigue siendo legal, ¿verdad? Por ejemplo:

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

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

Idealmente, el ejemplo anterior produce un error a menos que use la palabra clave override . Aunque, me imagino que habría un indicador del compilador para cancelar la verificación de anulación, donde la exclusión voluntaria es lo mismo que override | define para todos los métodos

@bioball sí, es necesario para la compatibilidad con una gran cantidad de código existente.

@ sam-s4s La entidad extiende la base, por supuesto. :-) He arreglado mi mensaje. virtual se trata de otra cosa.

Todavía no estoy seguro de lo que quiere decir con definir... esta es la definición de virtual en C#:

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

Tal vez quiera decir lo contrario, donde, en lugar de tener que marcar la función como virtual para anularla, puede marcar la función como define para que luego tenga que usar el override palabra clave para anularla?

(¿Por qué define ? No he visto esa palabra clave en otro idioma)

Revisé este artículo en un par de minutos, pero no he visto a nadie proponer el uso de anulación como una forma de evitar la especificación duplicada de parámetros y valores de retorno. Por ejemplo:

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

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

En el ejemplo anterior, move tendría el mismo tipo de retorno requerido ( void ) y meters también tendría el mismo tipo, pero no sería necesario especificarlo. La gran empresa para la que trabajo está tratando de migrar todo nuestro javascript a mecanografiado, lejos del tipeo del compilador Closure. Sin embargo, mientras estamos en Closure solo podemos usar @override y todos los tipos se deducirán de la superclase. Esto es muy útil para reducir la posibilidad de desajustes y reducir la duplicación. Uno podría incluso imaginar implementar esto de tal manera que la extensión del método con parámetros adicionales aún sea posible, incluso sin especificar tipos para los parámetros que se especifican en la superclase. Por ejemplo:

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

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

La motivación para venir aquí y escribir un comentario es que nuestra empresa ahora requiere que especifiquemos todos los tipos, incluso para los métodos anulados, porque TypeScript no tiene esta funcionalidad (y estamos en una migración larga). Es bastante molesto.

FWIW, si bien es más fácil de escribir, la omisión de tipos de parámetros (y otras instancias de inferencia de tipos de archivos cruzados) dificulta la legibilidad, ya que requiere que alguien que no esté familiarizado con el código investigue para encontrar dónde se define la superclase para saber qué tipo meters es (asumiendo que lo está leyendo fuera de un IDE, lo cual es bastante común para proyectos desconocidos en los que aún no tiene un IDE configurado). Y el código se lee mucho más a menudo de lo que se escribe.

@shicks Podría decir eso literalmente sobre cualquier variable o clase importada. Duplicar toda la información que pueda necesitar en un solo archivo anula el propósito de la abstracción y la modularidad. Forzar la duplicación de tipos viola el principio DRY.

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