¿Deberían todos los métodos públicos en una clase abstracta ser marcados como virtuales?

12

Recientemente tuve que actualizar una clase base abstracta en algunos OSS que estaba usando para que fuera más verificable al hacerlos virtuales (no podía usar una interfaz ya que combinaba dos). Esto me hizo pensar si debería marcar todos los métodos que necesitaba virtuales, o si debería marcar todos los métodos / propiedades públicas virtuales. En general, estoy de acuerdo con Roy Osherove en que cada método debe hacerse virtual, pero me encontré con este artículo que me hizo pensar si esto era necesario o no . Sin embargo, voy a limitar esto a clases abstractas por simplicidad (si todos los métodos públicos concretos deberían ser virtuales es especialmente discutible, estoy seguro).

Pude ver dónde podría permitir que una subclase use un método, pero no querer anular la implementación. Sin embargo, siempre y cuando confíes en que se seguirá el Principio de sustitución de Liskov , ¿por qué no permitirías que se anule? Al marcarlo como abstracto, de todos modos está forzando un cierto aumento al presupuesto, por lo que me parece que todos los métodos públicos dentro de una clase abstracta deberían marcarse como virtuales.

Sin embargo, quería preguntar en caso de que hubiera algo en lo que no estaría pensando. ¿Deberían todos los métodos públicos dentro de una clase abstracta hacerse virtuales?

Justin Pihony
fuente
Esto puede ayudar: stackoverflow.com/questions/391483/…
NoChance
1
Tal vez debería investigar por qué en C # el valor predeterminado no es virtual, pero en Java sí lo es. La razón por la que probablemente le daría una idea de la respuesta.
Daniel Little

Respuestas:

9

Sin embargo, siempre y cuando confíes en que se seguirá el Principio de sustitución de Liskov, ¿por qué no permitirías que se anule?

Por ejemplo, porque quiero que la implementación del esqueleto de un algoritmo sea reparada, y solo permita que partes específicas sean (re) definidas por subclases. Esto es ampliamente conocido como el patrón del Método de la plantilla (énfasis a continuación por mí):

El método de plantilla maneja así la imagen más amplia de la semántica de tareas y detalles de implementación más refinados de selección y secuencia de métodos. Esta imagen más amplia llama a métodos abstractos y no abstractos para la tarea en cuestión. Los métodos no abstractos están completamente controlados por el método de plantilla, pero los métodos abstractos, implementados en subclases, proporcionan el poder expresivo y el grado de libertad del patrón. Algunos o todos los métodos abstractos pueden especializarse en una subclase, lo que permite al escritor de la subclase proporcionar un comportamiento particular con modificaciones mínimas a la semántica más grande. El método de plantilla (que no es abstracto) permanece sin cambios en este patrón, asegurando que los métodos subordinados no abstractos y los métodos abstractos se invoquen en la secuencia originalmente prevista.

Actualizar

Algunos ejemplos concretos de proyectos en los que he estado trabajando:

  1. comunicarse con un sistema mainframe heredado a través de varias "pantallas". Cada pantalla tiene un montón de campos, de nombre fijo, posición y longitud, que contienen bits de datos específicos. Una solicitud llena ciertos campos con datos específicos. Una respuesta devuelve datos en uno o más campos. Cada transacción sigue la misma lógica básica, pero los detalles son diferentes en cada pantalla. Utilizamos el Método de plantilla en varios proyectos diferentes para implementar el esqueleto fijo de la lógica de manejo de pantalla, al tiempo que permitimos que las subclases definan los detalles específicos de la pantalla.
  2. exportando / importando datos de configuración en tablas DB a / desde archivos Excel. Nuevamente, el esquema básico de procesar un archivo de Excel e insertar / actualizar registros de base de datos, o volcar los registros a Excel es el mismo para cada tabla, pero los detalles de cada tabla son diferentes. Por lo tanto, el Método de plantilla es una opción muy obvia para eliminar las duplicaciones de código y hacer que el código sea más fácil de entender y mantener.
  3. Generando documentos PDF. Cada documento tiene la misma estructura, pero su contenido es diferente cada vez, dependiendo de muchos factores. Una vez más, el Método de plantilla facilita la separación del esqueleto fijo del algoritmo de generación de los detalles modificables específicos del caso. De hecho. incluso se aplica en múltiples niveles aquí, ya que el documento consta de varias secciones , cada una de las cuales consta de cero o más campos . El método de plantilla se aplica en 3 niveles distintos aquí.

En los primeros dos casos, la implementación heredada original usó la Estrategia , lo que resultó en una gran cantidad de código duplicado, que por supuesto a lo largo de los años creció diferencias sutiles aquí y allá, contenía muchos errores duplicados o ligeramente diferentes, y fue muy difícil de mantener. Refactorizar al método de plantilla (y algunas otras mejoras, como el uso de anotaciones Java) redujo el tamaño del código en aproximadamente un 40-70%.

Estos son solo los ejemplos más recientes que me vienen a la mente. Podría citar más casos de casi todos los proyectos en los que he estado trabajando hasta ahora.

Péter Török
fuente
Simplemente citar el GoF no es una respuesta. Tendría que dar una razón real para hacer tal cosa.
DeadMG
@DeadMG, he estado usando el Método de plantilla regularmente durante mi carrera, por lo que pensé que era obvio que es un patrón muy práctico y útil (como la mayoría de los patrones de GoF son ... estos no son ejercicios académicos teóricos, pero recopilados de la experiencia del mundo real). Pero aparentemente no todos están de acuerdo ... así que agrego un par de ejemplos concretos a mi respuesta.
Péter Török
2

Es perfectamente razonable, y a veces deseable tener métodos no virtuales en una clase base abstracta; el hecho de que sea una clase abstracta no significa necesariamente que cada parte de la misma deba ser utilizable polimórficamente.

Por ejemplo, es posible que desee utilizar el idioma 'Polimorfismo no virtual', por el cual una función se llama polimórficamente desde una función miembro no virtual, para garantizar que se cumplan ciertas condiciones previas o posteriores antes de que se llame a la función virtual

class MyAbstractBaseClass
{
protected:
    virtual void OverrideMe() = 0;
public:
    void CallMeFirst();

    void CallMe()
    {
        CallMeFirst();
        OverrideMe();
    }
};
Ben Cottrell
fuente
Yo diría que, siempre y cuando el comportamiento general siga siendo el mismo, esto podría hacerse virtual. Puede llenar las condiciones previas / posteriores utilizando una implementación diferente (base de datos vs en memoria). De lo contrario, debería ser una función privada?
Justin Pihony
2

Es suficiente para que una clase contenga UN método virtual, para que la clase se vuelva abstracta. Es posible que desee prestar atención a los métodos que desea virtuales y cuáles no, de acuerdo con el tipo de polimorfismo que planea usar.

aplastar
fuente
1

Pregúntese cuál es el uso de un método no virtual en una clase abstracta. Tal método debería tener una implementación para que sea útil. Pero si la clase tiene una implementación, ¿se puede llamar una clase abstracta? Incluso si el lenguaje / compilador lo permite, ¿tiene sentido? Personalmente, no lo creo. Tendría una clase normal con métodos abstractos que se espera que los descendientes implementen.

Mi idioma principal es Delphi, no C #. En Delphi, si marca un resumen de método, también debe marcarlo virtual o el compilador se queja. No he seguido los últimos cambios de idioma demasiado de cerca, pero si las clases abstractas están en o vendrían a Delphi, esperaría que el compilador se quejara de cualquier método no virtual, cualquier método privado y cualquier implementación de métodos para un resumen marcado de clase a nivel de clase.

Marjan Venema
fuente
2
Creo que tiene mucho sentido tener una clase abstracta que tenga algunos métodos abstractos, algunos métodos virtuales y algunos métodos no virtuales. Creo que siempre depende de qué es exactamente lo que quieres hacer.
svick
3
Primero revisaré tus C # q's. Un método abstracto en C # es implícitamente virtual y no puede tener una implementación. En cuanto a su primer punto, una clase abstracta en C # puede y debe tener una implementación de algún tipo (de lo contrario, solo debe usar una interfaz). El punto de que una clase sea abstracta es que DEBE estar subclaseada, sin embargo, contiene lógica que (teóricamente) usarán todas las subclases. Esto reduce la duplicación de código. Lo que pregunto es si alguna de esas implementaciones debería cerrarse para que no se anule (esencialmente diciendo que la forma básica es la única).
Justin Pihony
@JustinPihony: gracias. En Delphi, cuando marca un resumen de método, el compilador se quejará si le proporciona una implementación. Es interesante cómo los diferentes idiomas implementan conceptos de manera diferente y, por lo tanto, crean diferentes expectativas en sus usuarios.
Marjan Venema
@svick: sí, tiene mucho sentido, simplemente no lo llamaría una clase abstracta, sino una clase con métodos abstractos ... Pero supongo que esa puede ser mi interpretación.
Marjan Venema
@MarjanVenema, pero esa no es la terminología que usa C #. Si marca algún método en una clase abstract, también debe marcar toda la clase abstract.
svick
0

Sin embargo, siempre y cuando confíes en que se seguirá el Principio de sustitución de Liskov, entonces ¿por qué no permitirías que se anule?

No hace que ciertos métodos sean virtuales porque no cree que sea así. Además, al hacer que ciertos métodos no sean virtuales, está indicando a los herederos qué métodos deben implementarse.

Personalmente, a menudo haré sobrecargas de métodos que existen por conveniencia, no virtuales, para que los usuarios de la clase puedan tener valores predeterminados consistentes y los implementadores ni siquiera puedan cometer el error de romper ese comportamiento implícito.

Telastyn
fuente