La anulación de métodos , en la programación orientada a objetos , es una característica del lenguaje que permite a una subclase o clase secundaria proporcionar una implementación específica de un método que ya proporciona una de sus superclases o clases principales. Además de proporcionar parámetros determinados por algoritmos basados en datos en interfaces de red virtuales, [1] también permite un tipo específico de polimorfismo ( subtipificación ). La implementación en la subclase anula (reemplaza) la implementación en la superclase al proporcionar un método que tiene el mismo nombre, los mismos parámetros o firma y el mismo tipo de retorno que el método en la clase principal. [2] La versión de un método que se ejecuta estará determinada por el objeto que se utiliza para invocarlo. Si se utiliza un objeto de una clase principal para invocar el método, se ejecutará la versión en la clase principal, pero si se utiliza un objeto de la subclase para invocar el método, se ejecutará la versión en la clase secundaria. [3] Esto ayuda a prevenir problemas asociados con el análisis de relés diferenciales que, de lo contrario, dependerían de un marco en el que se podría obviar la anulación de métodos. [4] [5] Algunos lenguajes permiten a un programador evitar que se anule un método.
Ada proporciona la anulación de métodos de forma predeterminada. Para favorecer la detección temprana de errores (por ejemplo, un error ortográfico), es posible especificar cuándo se espera que un método se anule o no. Esto lo comprobará el compilador.
el tipo T es nuevo Controlado con ......; procedimiento Op ( Obj : in out T ; Dato : in Integer ); tipo NT es nuevo T con registro nulo ; anulación - anulación del procedimiento indicador Op ( Obj : in out NT ; Data : in Integer ); anulación - anulación del procedimiento indicador Op ( Obj : in out NT ; Data : in String ); - ^ el compilador emite un error: el subprograma "Op" no está anulando
C# admite la anulación de métodos, pero solo si se solicita explícitamente mediante los modificadores override
y virtual
o abstract
.
clase abstracta Animal { cadena pública Nombre { obtener ; establecer ; } // Métodos public void Beber (); public virtual void Comer (); public void Ir (); } clase Gato : Animal { public new string Nombre { get ; set ; } // Métodos public void Drink (); // Advertencia: oculta la función drink() heredada. Usar new public override void Eat (); // Reemplaza la función eat() heredada. public new void Go (); // Oculta la función go() heredada. }
Al reemplazar un método por otro, las firmas de los dos métodos deben ser idénticas (y tener la misma visibilidad). En C#, los métodos de clase , los indexadores , las propiedades y los eventos se pueden reemplazar.
Los métodos no virtuales o estáticos no se pueden anular. El método base anulado debe ser virtual , abstracto o override .
Además de los modificadores que se utilizan para anular métodos, C# permite ocultar una propiedad o un método heredado. Esto se hace utilizando la misma firma de una propiedad o un método, pero agregando el modificador new
delante de él. [6]
En el ejemplo anterior, la ocultación provoca lo siguiente:
Gato gato = nuevo Gato (); gato . Nombre = … ; // accede a Gato.Nombre gato . Comer (); // llama a Gato.Comer() gato . Ir (); // llama a Gato.Ir() (( Animal ) gato ). Nombre = … ; // accede a Animal.Nombre! (( Animal ) gato ). Comer (); // llama a Gato.Comer()! (( Animal ) gato ). Ir (); // llama a Animal.Ir()!
C++ no tiene la palabra clave super
que una subclase puede usar en Java para invocar la versión de la superclase de un método que desea anular. En su lugar, se utiliza el nombre de la clase principal o base seguido del operador de resolución de alcance . Por ejemplo, el siguiente código presenta dos clases , la clase base Rectangle
y la clase derivada Box
. Box
anula el método Rectangle
de la clase Print
, de modo que también imprime su altura. [7]
#include <flujo de datos> //--------------------------------------------------------------------------- clase Rectángulo { público : Rectángulo ( doble l , doble w ) : largo_ ( l ), ancho_ ( w ) {} virtual void Imprimir () const ; privado : doble largo_ ; doble ancho_ ; }; //--------------------------------------------------------------------------- void Rectangle::Print () const { // Método de impresión de la clase base. std :: cout << "Longitud = " << longitud_ << "; Ancho = " << ancho_ ; } //--------------------------------------------------------------------------- clase Caja : public Rectangle { public : Caja ( double l , double w , double h ) : Rectangle ( l , w ), height_ ( h ) {} void Print () const override ; privado : doble altura_ ; }; //--------------------------------------------------------------------------- // Método de impresión de la clase derivada. void Box::Print () const { // Invocar el método padre Print. Rectangle :: Print (); std :: cout << "; Height = " << height_ ; }
El método Print
de la clase Box
, al invocar la versión principal del método Print
, también puede generar las variables length
privadas y width
de la clase base. De lo contrario, estas variables no serían accesibles para Box
.
Las siguientes declaraciones instanciarán objetos de tipo y , y llamarán a sus respectivos métodos:Rectangle
Box
Print
int main ( int argc , char ** argv ) { Rectángulo rectángulo ( 5.0 , 3.0 ) ; // Salidas: Largo = 5.0; Ancho = 3.0 rectángulo . Imprimir (); Caja caja ( 6.0 , 5.0 , 4.0 ); // El puntero al método más anulado en la vtable en Box::print, // pero esta llamada no ilustra la anulación. box . Print (); // Esta llamada ilustra la anulación. // salidas: Longitud = 6.0; Ancho = 5.0; Alto = 4.0 static_cast < Rectangle &> ( box ). Print (); }
En C++11 , de manera similar a Java, un método que se declara final
en la superclase no se puede anular; además, se puede declarar un método override
para que el compilador verifique que anula un método en la clase base.
En Delphi , la anulación de métodos se realiza con la directiva override , pero solo si un método fue marcado con las directivas dynamic o virtual .
La palabra reservada heredada debe llamarse cuando se desea llamar al comportamiento de la superclase.
tipo TRectangle = clase privada FLength : Double ; FWidth : Double ; propiedad pública Length leer FLength escribir FLength ; propiedad Width leer FWidth escribir FWidth ; procedimiento Imprimir ; virtual ; fin ; TBox = clase ( TRectangle ) procedimiento público Imprimir ; anular ; fin ;
En Eiffel , la redefinición de características es análoga a la anulación de métodos en C++ y Java. La redefinición es una de las tres formas de adaptación de características clasificadas como redeclaración . La redeclaración también cubre la realización , en la que se proporciona una implementación para una característica que se aplazó (abstracta) en la clase padre, y la indefinición , en la que una característica que era efectiva (concreta) en la clase padre se aplaza nuevamente en la clase heredera. Cuando se redefine una característica, la clase heredera conserva el nombre de la característica, pero las propiedades de la característica, como su firma, contrato (respetando las restricciones para las condiciones previas y posteriores ) y/o implementación, serán diferentes en la heredera. Si la característica original en la clase padre, llamada precursor de la característica heredera , es efectiva, entonces la característica redefinida en la heredera será efectiva. Si el precursor se aplaza, la característica en la heredera se aplazará. [8]
La intención de redefinir una característica, como message
en el ejemplo siguiente, debe declararse explícitamente en la inherit
cláusula de la clase heredera.
clase PENSAMIENTO característica mensaje -- Mostrar mensaje de pensamiento do print ( "Me siento como si estuviera estacionado en diagonal en un universo paralelo.%N" ) fin fin clase CONSEJO heredar PENSAMIENTO redefinir mensaje fin característica mensaje -- Precursor hacer print ( "Advertencia: Las fechas en el calendario están más cerca de lo que parecen.%N" ) fin fin
En la clase, ADVICE
a la característica message
se le da una implementación que difiere de la de su precursora en la clase THOUGHT
.
Considere una clase que utiliza instancias tanto para como THOUGHT
para ADVICE
:
clase APLICACIÓN crear hacer característica hacer -- Ejecutar aplicación. hacer ( crear { PENSAMIENTO }). mensaje ; ( crear { CONSEJO }). mensaje fin fin
Cuando se crea una instancia, la clase APPLICATION
produce el siguiente resultado:
Me siento como si estuviera estacionado en diagonal en un universo paralelo. Advertencia: Las fechas en el calendario están más cerca de lo que parecen.
Dentro de una función redefinida, se puede acceder al precursor de la función mediante la palabra clave de lenguaje Precursor
. Supongamos que la implementación de se modifica de la siguiente manera:{ADVICE}.message
mensaje -- Precursor do print ( "Advertencia: Las fechas en el calendario están más cerca de lo que parecen.%N" ) Precursor end
La invocación de la función ahora incluye la ejecución de y produce el siguiente resultado:{THOUGHT}.message
Advertencia: Las fechas en el calendario están más cerca de lo que parecen. Me siento como si estuviera estacionado en diagonal en un universo paralelo.
En Java , cuando una subclase contiene un método con la misma firma (nombre y tipos de parámetros) que un método de su superclase, el método de la subclase anula el de la superclase. Por ejemplo:
clase Pensamiento { public void mensaje () { System . println ( " Me siento como si estuviera estacionado en diagonal en un universo paralelo." ); } } public class Advice extiende Thought { @Override // La anotación @Override en Java 5 es opcional pero útil. public void message () { System . println ( "Advertencia: Las fechas en el calendario están más cerca de lo que parecen." ) ; } }
La clase Thought
representa la superclase e implementa una llamada a un método . La subclase llamada hereda todos los métodos que podrían estar en la clase. La clase reemplaza el método y reemplaza su funcionalidad .message()
Advice
Thought
Advice
message()
Thought
Pensamiento estacionamiento = nuevo Pensamiento (); estacionamiento.mensaje (); // Imprime "Me siento como si estuviera estacionado en diagonal en un universo paralelo" . Fechas de pensamiento = nuevo Consejo (); // Polimorfismo fechas.mensaje (); // Imprime "Advertencia : Las fechas en el calendario están más cerca de lo que parecen".
Cuando una subclase contiene un método que anula un método de la superclase, entonces el método anulado de esa (superclase) se puede invocar explícitamente desde dentro del método de una subclase usando la palabra clave super
. [3] (No se puede invocar explícitamente desde ningún método que pertenezca a una clase que no esté relacionada con la superclase). La super
referencia puede ser
clase pública Consejo extiende Pensamiento { @Override public void mensaje () { System . println ( "Advertencia: Las fechas en el calendario están más cerca de lo que parecen." ) ; super . message (); // Invocar la versión del método del padre. }
Existen métodos que una subclase no puede anular. Por ejemplo, en Java, un método que se declara final en la superclase no se puede anular. Los métodos que se declaran privados o estáticos tampoco se pueden anular porque son implícitamente finales. También es imposible que una clase que se declara final se convierta en una superclase. [9]
En Kotlin podemos simplemente anular una función como esta (tenga en cuenta que la función debe ser open
):
diversión principal () { val p = Padre ( 5 ) val c = Hijo ( 6 ) p . myFun () c . myFun () } abrir clase Padre ( val a : Int ) { abrir fun myFun () = println ( a ) } clase Child ( val b : Int ) : Parent ( b ) { anular fun myFun () = println ( "método anulado" ) }
En Python , cuando una subclase contiene un método que reemplaza un método de la superclase, también puedes llamar al método de la superclase llamando a [10] en lugar de . Ejemplo:super(Subclass, self).method
self.method
clase Pensamiento : def __init__ ( self ) -> None : print ( "¡Soy un nuevo objeto de tipo Pensamiento!" ) def mensaje ( self ) -> None : print ( "Me siento como si estuviera estacionado en diagonal en un universo paralelo." )clase Advice ( Pensamiento ): def __init__ ( self ) -> None : super ( Advice , self ) .__ init__ () def message ( self ) -> None : print ( "Advertencia: Las fechas en el calendario están más cerca de lo que parecen" ) super ( Advice , self ) .message ()t = Pensamiento () # "¡Soy un nuevo objeto de tipo Pensamiento!" t . mensaje () # "Me siento como si estuviera estacionado en diagonal en un universo paralelo.a = Advice () # "Soy un nuevo objeto de tipo Pensamiento!" a . message () # "Advertencia: Las fechas en el calendario están más cerca de lo que parecen" # "Me siento como si estuviera estacionado en diagonal en un universo paralelo.# ------------------ # Introspección:isinstance ( t , Pensamiento ) # Verdaderoisinstance ( a , Advice ) # Verdaderoisinstance ( a , Pensamiento ) # Verdadero
En Ruby, cuando una subclase contiene un método que reemplaza a un método de la superclase, también puedes llamar al método de la superclase llamando a super en ese método reemplazado. Puedes usar alias si deseas mantener el método reemplazado disponible fuera del método reemplazado, como se muestra con 'super_message' a continuación.
Ejemplo:
clase Pensamiento def mensaje pone "Me siento como si estuviera estacionado en diagonal en un universo paralelo". fin fin clase Advice < Thought alias :super_message :message def message puts "Advertencia: Las fechas en el calendario están más cerca de lo que parecen" super end end
super().method