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 .
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.
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.
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 .
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 objetosPara 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.
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.
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 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 privadaHay 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:
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).
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).
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 .
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]
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.
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.
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]
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.
La clase Parentes el ancestro de la clase Childcuyo método se anula publicMethod.
JavaScript utiliza la herencia de prototipos.
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 */