Herencia (programación)

Herencia (ing. herencia ) - el concepto de programación orientada a objetos , según el cual un tipo de datos abstracto puede heredar los datos y la funcionalidad de algún tipo existente, lo que facilita la reutilización de componentes de software .

Terminología

En la programación orientada a objetos , desde Simula 67 , los tipos de datos abstractos se denominan clases .

Superclase ( ing.  super class ), clase principal ( ing.  parent class ), ancestro, padre o superclase: una clase que produce herencia en subclases, es decir, una clase de la que heredan otras clases. Una superclase puede ser una subclase, una clase base, una clase abstracta y una interfaz.

Subclase ( subclase ing.  ), clase derivada (clase derivada ing . ), clase secundaria (clase secundaria ing. ), clase descendiente, clase sucesora o clase implementadora: una clase heredada de una superclase o interfaz, es decir, una clase definida a través de la herencia de otra clase o varias clases de este tipo. Una subclase puede ser una superclase.   

Una  clase base es una clase que se encuentra en la parte superior de la jerarquía de herencia de clases y en la parte inferior del árbol de subclases, es decir, no es una subclase y no hereda de otras superclases o interfaces. La clase base puede ser una clase abstracta y una interfaz. Cualquier clase no base es una subclase.

Una  interfaz es una estructura que define una interfaz de clase pura que consta de métodos abstractos. Las interfaces participan en la jerarquía de herencia de clases e interfaces.

Una superinterfaz ( eng.  super interface ) o una interfaz antecesora es un análogo de una superclase en la jerarquía de herencia, es decir, es una interfaz que hereda en subclases y subinterfaces.

Una interfaz descendiente, una interfaz derivada o una interfaz derivada  es un análogo de una subclase en la jerarquía de herencia de las interfaces, es decir, es una interfaz heredada de una o más superinterfaces.

Una interfaz base es el equivalente de una clase base en la jerarquía de herencia de las interfaces, es decir, es la interfaz en la parte superior de la jerarquía de herencia.

Una jerarquía de herencia o jerarquía de clases es un árbol cuyos elementos son clases e interfaces.

Aplicación

La herencia es un mecanismo para la reutilización de código (English code reuse ) y contribuye a la expansión independiente del software a través de clases abiertas (English public class) e interfaces (English interfaces). Establecer una relación de herencia entre clases genera una jerarquía de clases.

Herencia y polimorfismo de subtipo

La herencia a menudo se identifica con polimorfismo de subtipificación :

A pesar de la observación anterior, la herencia es un mecanismo ampliamente utilizado para establecer una relación es -un. Algunos lenguajes de programación están de acuerdo con la herencia y el polimorfismo de subtipos (principalmente lenguajes tipificados estáticamente como C++C#Java y Scala ), mientras que otros comparten los conceptos anteriores.

La herencia, incluso en los lenguajes de programación que admiten el uso de la herencia como mecanismo para el polimorfismo de subtipo , no garantiza el polimorfismo de comportamiento de subtipo; ver: "El principio de sustitución" de Barbara Liskov .

Tipos de herencia

Herencia "simple"

La herencia "simple", a veces denominada herencia única, describe la relación entre dos clases, una de las cuales hereda a la otra. Muchas clases pueden derivar de una sola clase, pero aun así, este tipo de relación sigue siendo una herencia "simple".

Clases abstractas y creación de objetos

Para algunos lenguajes de programación, el siguiente concepto es válido.

Hay clases "abstractas" (declaradas como tales arbitrariamente o por los métodos abstractos que se les asignan ); se pueden describir como que tienen campos y métodos . La creación de objetos (instancias) significa concretización , aplicable sólo a las clases no abstractas (incluidos los descendientes no abstractos de las abstractas), cuyos representantes, en consecuencia, serán los objetos creados.

Ejemplo: Sea abstracta la clase base “Empleado de la Universidad ”, de la cual se heredan las clases “ Estudiante de posgrado ” y “ Profesor ”. Los campos y funciones comunes de las clases (por ejemplo, el campo "Año de nacimiento") se pueden describir en la clase base. Y el programa creará objetos de solo clases derivadas: "Estudiante de posgrado" y "Profesor"; por lo general, no tiene sentido crear objetos de clases base.

Herencia múltiple

Con herencia múltiple, una clase puede tener más de un padre. En este caso, la clase hereda los métodos de todos los ancestros. La ventaja de este enfoque es una mayor flexibilidad.

La herencia múltiple se implementa en C++ . Otros lenguajes que brindan esta característica incluyen Python y Eiffel . La herencia múltiple es compatible con UML .

La herencia múltiple es una fuente potencial de errores que pueden surgir al tener los mismos nombres de método en los ancestros. En lenguajes que se posicionan como sucesores de C++ ( Java , C# y otros), se decidió abandonar la herencia múltiple en favor de las interfaces . Casi siempre se puede prescindir de utilizar este mecanismo. Sin embargo, si surgiera tal necesidad, entonces para resolver conflictos en el uso de métodos heredados con los mismos nombres, es posible, por ejemplo, aplicar la operación de extensión de visibilidad - "::" - para llamar a un método específico de un padre especifico.

Un intento de solucionar el problema de tener los mismos nombres de métodos en los ancestros se hizo en el lenguaje Eiffel , en el cual, al describir una nueva clase, es necesario indicar explícitamente los miembros importados de cada una de las clases heredadas y su denominación en el clase infantil.

La mayoría de los lenguajes de programación orientados a objetos modernos ( C# , Java , Delphi y otros) admiten la capacidad de heredar simultáneamente de una clase antepasada e implementar métodos de varias interfaces por la misma clase. Este mecanismo le permite reemplazar en gran medida la herencia múltiple: los métodos de interfaz deben redefinirse explícitamente, lo que elimina los errores al heredar la funcionalidad de los mismos métodos de diferentes clases de ancestros.

Clase base única

En varios lenguajes de programación, todas las clases, ya sea explícita o implícitamente, heredan de alguna clase base. Smalltalk fue uno de los primeros lenguajes en utilizar este concepto. Estos lenguajes también incluyen: Objective-C (clase NSObject), Perl ( UNIVERSAL), Eiffel ( ANY), Java ( java.lang.Object), C# ( System.Object), Delphi ( TObject), Scala ( Any).

Herencia en lenguajes de programación

C++

Herencia en C++ :

claseA { }; // clase base clase B : público A {}; // Clase de herencia pública C : protected A {}; // Clase de herencia protegida Z : private A {}; // herencia privada

Hay tres tipos de herencia en C++ : pública , protegida , privada . Los especificadores de acceso de los miembros de la clase base cambian en los descendientes de la siguiente manera:

  • Si una clase se declara como la clase base de otra clase con un especificador de acceso...
    • ... público :
      • miembros públicos de la clase base: disponibles como miembros públicos de la clase derivada;
      • miembros protegidos de la clase base: disponibles como miembros protegidos de la clase derivada;
    • … protegido :
      • los miembros públicos y protegidos de la clase base están disponibles como miembros protegidos de la clase derivada;
    • … privado :
      • los miembros públicos y protegidos de la clase base están disponibles como miembros privados de la clase derivada.

Una de las principales ventajas de la herencia pública es que un puntero a clases derivadas se puede convertir implícitamente en un puntero a la clase base, por lo que para el ejemplo anterior, puede escribir:

A * a = nuevoB ( );

Esta característica interesante abre la posibilidad de identificación dinámica de tipos (RTTI).

Delphi (Objeto Pascal)

Para usar el mecanismo de herencia en Delphi , debe especificar la clase classantecesora en la declaración de clase entre paréntesis:

Antepasado:

TAncestor = class private protected public // Procedimiento de procedimiento virtual VirtualProcedure ; virtuales ; abstracto ; procedimiento Procedimiento estático ; fin ;

Heredero:

TDescendant = class ( TAncestor ) private protected public // Procedimiento de anulación de procedimiento virtual VirtualProcedure ; anular ; procedimiento Procedimiento estático ; fin ;

Absolutamente todas las clases en Delphi son descendientes de TObject. Si no se especifica una clase antepasada, se supone que la nueva clase es descendiente directa de TObject.

En principio, la herencia múltiple en Delphi no se admite inicialmente, sin embargo, para aquellos que no pueden prescindir de ella, todavía existen tales oportunidades, por ejemplo, mediante el uso de clases auxiliares (Сlass Helpers).

Pitón

Python admite herencia única y múltiple. Al acceder a un atributo, la visualización de clases derivadas se produce en el orden de resolución de métodos  (MRO ) [1] .

clase Ancestor1 ( objeto ): # Ancestor-1 def m1 ( self ): pase class Ancestor2 ( objeto ): # Ancestor-2 def m1 ( self ): pase class Descendant ( Ancestor1 , Ancestor2 ): # Descendant def m2 ( self ): pasar d = Descendiente () # Instancia print d . __clase__ . __mro__ # Orden de resolución del método: ( < clase ' __principal__ . Descendiente '>, <clase ' __principal__ . Antepasado1 '>, <clase ' __principal__ . Antepasado2 '>, <tipo ' objeto '>)

A partir de Python 2.2, las clases "clásicas" y las clases "nuevas" coexisten en el lenguaje. Estos últimos son herederos object. Las clases "clásicas" se admitirán hasta la versión 2.6, pero se eliminarán del lenguaje en Python 3.0.

La herencia múltiple se usa en Python, en particular, para introducir clases mixtas en la clase principal . 

PHP

Para usar el mecanismo de herencia en PHPextends , es necesario especificar la palabra y el nombre de la clase antecesora después del nombre de la clase sucesora declarada en la declaración de clase :

clase Descendiente extiende Ancestro { }

Si la clase derivada se superpone a los métodos antepasados, se puede acceder a los métodos antepasados ​​mediante parent:

clase A { función ejemplo () { echo "Método A::ejemplo() llamado.<br /> \n " ; } } clase B extiende A { función ejemplo () { echo "Método B::ejemplo() llamado.<br /> \n " ; padre :: ejemplo (); } }

Es posible evitar que una clase derivada anule los métodos de un ancestro; para hacer esto, necesita especificar la palabra clave final:

clase A { ejemplo de función final () { echo "Método A::ejemplo() llamado.<br /> \n " ; } } clase B extiende A { ejemplo de función () { // lanzará un padre de error :: ejemplo (); // y nunca se ejecutará } }

Para hacer referencia al constructor de la clase principal durante la herencia, es necesario que la clase secundaria especifique en el constructor parent::__construct();[2]

Objetivo-C

@interfaz A  : NSObject - ( vacío ) ejemplo ; @final @implementation - ( vacío ) ejemplo { NSLog ( @"ClaseA" ); } @final @interfaz B  : A - ( vacío ) ejemplo ; @final @implementation - ( vacío ) ejemplo { NSLog ( @"ClaseB" ); } @final

La interfaz declara métodos que serán visibles fuera de la clase (público).

Los métodos internos se pueden implementar sin una interfaz. Para declarar propiedades adicionales, use la extensión de interfaz en el archivo de implementación.

Todos los métodos en Objective-C son virtuales.

Java

Un ejemplo de herencia de una clase y dos interfaces :

clase pública A { } interfaz pública I1 { } interfaz pública I2 { } clase pública B extiende A implementa I1 , I2 { }

Una directiva finalen una declaración de clase hace que sea imposible heredar de ella.

C#

Un ejemplo de herencia de una clase y dos interfaces :

clase pública A { } interfaz pública I1 { } interfaz pública I2 { } clase pública B : A , I1 , I2 { }

La herencia de las clases con tipo se puede hacer especificando un tipo fijo o transfiriendo una variable de tipo a una clase heredada:

clase pública A < T > { } clase pública B : A < int > { } clase pública B2 < T > : A < T > { }

También es posible heredar clases anidadas de clases que las contienen:

clase A // la clase A predeterminada es interna, no pública la clase B no puede ser pública { clase B : A { } }

Una directiva sealeden una declaración de clase hace que sea imposible heredar de ella. [3]

Rubí

padre de la clase def public_method "Método público" end privado def private_method "Método privado" end final classChild < Padre _ def public_method "Método público redefinido" end def call_private_method "Método privado del antepasado: " + private_method end final

La clase Parentes el ancestro de la clase Childcuyo método se anula public_method.

niño = niño . niño nuevo ._ public_method #=> "Método público redefinido" child . call_private_method #=> "Método privado del antepasado: método privado"

Los métodos privados de un antepasado se pueden llamar desde los descendientes.

JavaScript

clase Padre { constructor ( datos ) { este . datos = datos ; } publicMethod () { return 'Método público' ; } } class Child extends Parent { getData () { return `Data: ${ this . datos } ` ; } publicMethod () { return 'Método público redefinido' ; } } const prueba = nuevoNiño ( ' prueba' ); prueba _ obtener datos (); // => 'Datos: prueba' prueba . publicMethod (); // => Prueba de 'método público redefinido' . datos ; // => 'prueba'

La clase Parentes el ancestro de la clase Childcuyo método se anula publicMethod.

JavaScript utiliza la herencia de prototipos.

Constructores y destructores

En C++ , los constructores se llaman secuencialmente durante la herencia desde el antepasado más antiguo hasta el hijo más reciente y viceversa, los destructores se llaman desde el hijo más reciente hasta el antepasado más antiguo.

claseprimero_ _ { público : Primero () { cout << ">>Primer constructor" << endl ; } ~ Primero () { cout << ">>Primer destructor" << endl ; } }; clase Segundo : público Primero { público : Segundo () { cout << ">Segundo constructor" << endl ; } ~ Segundo () { cout << ">Segundo destructor" << endl ; } }; clase Tercera : pública Segunda { público : Tercero () { cout << "Tercer constructor" << endl ; } ~ Tercero () { cout << "Tercer destructor" << endl ; } }; // ejecución del código Third * th = new Third (); borrar th ; // resultado de salida /* >>Primer constructor >Segundo constructor Tercer constructor Tercer destructor >Segundo destructor >>Primer destructor */

Enlaces

Notas

  1. sobre el orden de resolución de métodos en Python
  2. ¿Qué es la programación orientada a objetos ? wh-db.com (30 de junio de 2015).
  3. Especificación del lenguaje C#, versión 4.0, Copyright © Microsoft Corporation 1999-2010