Interfaz (programación orientada a objetos)

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 7 de diciembre de 2017; las comprobaciones requieren 27 ediciones .

Interfaz ( interfaz en inglés  ): una estructura de programa/sintaxis que define una relación con objetos que están unidos solo por algún comportamiento. Al diseñar clases, diseñar una interfaz es lo mismo que diseñar una especificación (el conjunto de métodos que debe implementar cada clase que usa una interfaz).

Las interfaces, junto con las clases y protocolos abstractos, establecen obligaciones mutuas entre los elementos de un sistema de software, lo cual es la base del concepto de programación por contrato ( Ing.  design by contract , DbC). Una interfaz define un límite de interacción entre clases o componentes al especificar una cierta abstracción que implementa un implementador.

La interfaz en OOP es un elemento estrictamente formalizado de un lenguaje orientado a objetos y se usa ampliamente en el código fuente de los programas.

Las interfaces permiten la herencia múltiple de objetos y al mismo tiempo resuelven el problema de la herencia en forma de diamante . En el lenguaje C++ se resuelve a través de la herencia de clases usando el virtual.

Descripción y uso de interfaces

La descripción de una interfaz OOP, además de los detalles de la sintaxis de lenguajes específicos, consta de dos partes: el nombre y los métodos de la interfaz.

Las interfaces se pueden utilizar de dos formas:

Por regla general, en los lenguajes de programación orientados a objetos, las interfaces, como las clases, se pueden heredar unas de otras. En este caso, la interfaz secundaria incluye todos los métodos de la interfaz principal y, opcionalmente, les agrega sus propios métodos.

Así, por un lado, una interfaz es un “contrato” que la clase que la implementa se compromete a cumplir, por otro lado, una interfaz es un tipo de datos, porque su descripción define con suficiente claridad las propiedades de los objetos para poder tipearlos . variables en igualdad de condiciones con la clase. Sin embargo, se debe enfatizar que una interfaz no es un tipo de datos completo, ya que solo define el comportamiento externo de los objetos. La estructura interna y la implementación del comportamiento especificado por la interfaz la proporciona la clase que implementa la interfaz; por eso no hay "instancias de interfaz" en su forma pura, y cualquier variable del tipo "interfaz" contiene instancias de clases concretas.

El uso de interfaces es una opción para proporcionar polimorfismo en marcos y lenguajes de objetos . Todas las clases que implementan la misma interfaz, en términos del comportamiento que definen, se comportan de la misma manera externamente. Esto le permite escribir algoritmos de procesamiento de datos generalizados que usan parámetros de interfaz como tipos y los aplican a objetos de varios tipos, obteniendo cada vez el resultado requerido.

Por ejemplo, la interfaz “ ” Cloneablepuede describir la abstracción de la clonación (creación de copias exactas) de objetos especificando un método “ Clone” que debe copiar el contenido de un objeto a otro objeto del mismo tipo. Luego, cualquier clase cuyos objetos deban copiarse debe implementar la interfaz Cloneabley proporcionar un método Clone, y en cualquier parte del programa donde se requiera la clonación de objetos, se llama al método en el objeto para este propósito Clone. Además, el código que usa este método solo necesita tener una descripción de la interfaz, es posible que no sepa nada sobre la clase real cuyos objetos se copian. Por lo tanto, las interfaces le permiten dividir un sistema de software en módulos sin dependencia de código mutuo.

Interfaces y clases abstractas

Se puede ver que una interfaz, desde un punto de vista formal, es solo una clase abstracta pura , es decir, una clase en la que no se define nada excepto métodos abstractos . Si un lenguaje de programación admite múltiples métodos abstractos y de herencia (como, por ejemplo, C++ ), entonces no hay necesidad de introducir un concepto separado de "interfaz" en la sintaxis del lenguaje. Estas entidades se describen mediante clases abstractas y las clases las heredan para implementar métodos abstractos.

Sin embargo, admitir la herencia múltiple en su totalidad es bastante complejo y causa muchos problemas, tanto en el nivel de implementación del lenguaje como en el nivel de la arquitectura de la aplicación. La introducción del concepto de interfaces es un compromiso que le permite obtener muchos de los beneficios de la herencia múltiple (en particular, la capacidad de definir convenientemente conjuntos de métodos relacionados lógicamente como entidades similares a clases que permiten la herencia y la implementación), sin implementar en su totalidad y, por lo tanto, sin encontrar la mayoría de las dificultades asociadas con él.

A nivel de ejecución, el esquema clásico de herencia múltiple provoca una serie adicional de inconvenientes:

El uso de un esquema con interfaces (en lugar de herencia múltiple) evita estos problemas, excepto por el problema de llamar a métodos de interfaz (es decir, llamadas a métodos virtuales en herencia múltiple, consulte más arriba). La solución clásica es (por ejemplo, en JVM para Java o CLR para C#) que los métodos de interfaz se llaman de una manera menos eficiente, sin la ayuda de una tabla virtual: con cada llamada, primero se determina una clase de objeto específica, y luego se busca en él el método deseado (por supuesto, con numerosas optimizaciones).

Implementaciones de interfaz y herencia múltiple

Por lo general, los lenguajes de programación permiten que una interfaz se herede de múltiples interfaces antepasadas. Todos los métodos declarados en las interfaces antecesoras pasan a formar parte de la declaración de la interfaz secundaria. A diferencia de la herencia de clases, la herencia múltiple de interfaces es mucho más fácil de implementar y no causa dificultades significativas.

Sin embargo, todavía es posible una colisión con herencia múltiple de interfaces y con la implementación de varias interfaces por una clase. Ocurre cuando dos o más interfaces heredadas por una nueva interfaz o implementadas por una clase tienen métodos con la misma firma. Los desarrolladores de lenguajes de programación se ven obligados a elegir para tales casos ciertos métodos para resolver contradicciones. Aquí hay varias opciones: una prohibición de implementación, una indicación explícita de una específica y una implementación de la interfaz o clase base.

Interfaces en lenguajes y sistemas específicos

La implementación de las interfaces está determinada en gran medida por las capacidades iniciales del lenguaje y el propósito por el cual se introducen las interfaces. Las características del uso de interfaces en Java , Object Pascal , Delphi y C++ son muy indicativas , ya que muestran tres situaciones fundamentalmente diferentes: la orientación inicial del lenguaje para usar el concepto de interfaces, su uso por compatibilidad y su emulación por clases.

Delfos

Las interfaces se introdujeron en Delphi para soportar la tecnología COM de Microsoft . Sin embargo, cuando se lanzó Kylix , las interfaces como elemento del lenguaje se desvincularon de la tecnología COM. Todas las interfaces heredan de la interfaz [1] , que en la plataforma win32 es la misma que la interfaz COM estándar del mismo nombre, al igual que todas las clases que contiene son descendientes de la clase . El uso explícito de IUnknown como ancestro está reservado para el código que usa tecnología COM. IInterface IUnknownTObject

Ejemplo de declaración de interfaz:

IMyInterface = procedimiento de interfaz DoSomething ; fin ;

Para declarar la implementación de interfaces, en la descripción de la clase, debe especificar sus nombres entre paréntesis después de la palabra clave class, después del nombre de la clase antecesora. Dado que "una interfaz es un contrato a cumplir", el programa no se compila hasta que se implementa en la clase de implementaciónprocedure DoSomething;

El enfoque mencionado anteriormente de las interfaces de Delphi en la tecnología COM ha provocado algunos inconvenientes. El hecho es que la interfaz IInterface(de la que se heredan todas las demás interfaces) ya contiene tres métodos que son obligatorios para las interfaces COM: QueryInterface, _AddRef, _Release. Por lo tanto, cualquier clase que implemente cualquier interfaz debe implementar estos métodos, incluso si, según la lógica del programa, la interfaz y la clase no tienen nada que ver con COM. Cabe señalar que estos tres métodos también se utilizan para controlar la vida útil de un objeto e implementar el mecanismo de solicitud de interfaz a través del asoperador “ ”.

Un ejemplo de una clase que implementa una interfaz:

TMyClass = clase ( TMyParentClass , IMyInterface ) procedimiento HacerAlgo ; function QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; llamada estándar ; función _AddRef : Entero ; llamada estándar ; función _Release : Integer ; llamada estándar ; fin ; implementación

El programador debe implementar adecuadamente los métodos QueryInterface, _AddRef, _Release. Para deshacerse de la necesidad de escribir métodos estándar, se proporciona una clase de biblioteca TInterfacedObject : implementa los tres métodos anteriores, y cualquier clase que herede de ella y sus descendientes recibe esta implementación. La implementación de estos métodos TInterfacedObjectasume un control automático sobre la vida útil del objeto al contar las referencias a través de los métodos _AddRefy _Release, que se llaman automáticamente al entrar y salir del alcance.

Un ejemplo de una clase - heredero TInterfacedObject:

TMyClass = class ( TInterfacedObject , IMyInterface ) procedimiento HacerAlgo ; fin ;

Al heredar una clase que implementa una interfaz de una clase sin interfaces, el programador debe implementar los métodos mencionados manualmente, determinando la presencia o ausencia de control de conteo de referencias, así como obtener la interfaz en formato QueryInterface.

Un ejemplo de una clase arbitraria sin contar referencias:

TMyClass = class ( TObject , IInterface , IMyInterface ) // Función IInterface QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; llamada estándar ; función _AddRef : Entero ; llamada estándar ; función _Release : Integer ; llamada estándar ; // Procedimiento de MiInterfaz HacerAlgo ; fin ; { TMiClase } función TMyClass . QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; comenzar si GetInterface ( IID , Obj ) entonces Resultado := 0 else Resultado := E_NOINTERFACE ; fin ; función TMyClass . _AddRef : Entero ; comenzar Resultado := - 1 ; fin ; función TMyClass . _Liberar : Entero ; comenzar Resultado := - 1 ; fin ; procedimiento TMyClass . hacer algo ; comenzar //Hacer algo terminar ;

C++

C++ admite clases de herencia múltiple y abstractas , por lo que, como se mencionó anteriormente, no se necesita una construcción sintáctica separada para las interfaces en este lenguaje. Las interfaces se definen mediante clases abstractas , y la implementación de una interfaz se realiza heredando de estas clases.

Ejemplo de definición de interfaz :

/** * interfaz.Abierto.h * */ #ifndef INTERFACE_OPENABLE_HPP #define INTERFACE_OPENABLE_HPP // clase de interfaz iOpenable. Determina si algo se puede abrir/cerrar. clase iOpenable { público : virtual ~ iOpenable (){} vacío virtual abierto () = 0 ; cierre de vacío virtual () = 0 ; }; #terminara si

Una interfaz se implementa mediante herencia (debido a la presencia de herencia múltiple , es posible implementar varias interfaces en una clase , si es necesario; en el ejemplo a continuación, la herencia no es múltiple):

/** * clase.Puerta.h * */ #include "interfaz.abierta.h" #incluir <iostream> puerta de clase : público iOpenable { público : Puerta (){ std :: cout << "Objeto de puerta creado" << std :: endl ;} puerta ~ virtual (){} //Incrementando los métodos de la interfaz iOpenable para la clase Door virtual void open (){ std :: cout << "Puerta abierta" << std :: endl ;} virtual void close (){ std :: cout << "Puerta cerrada" << std :: endl ;} //Propiedades y métodos específicos de la clase de puerta std :: string mMaterial ; std :: cadena mColor ; //... }; /** * clase.Libro.h * */ #include "interfaz.abierta.h" #incluir <iostream> Libro de clase : público iOpenable { público : Libro (){ std :: cout << "Objeto de libro creado" << std :: endl ;} ~ libro virtual (){} //Incrementando los métodos de la interfaz iOpenable para la clase Libro virtual void open (){ std :: cout << "Libro abierto" << std :: endl ;} virtual void close (){ std :: cout << "Libro cerrado" << std :: endl ;} //Propiedades y métodos específicos del libro std :: string mTitle ; std :: cadena mAuthor ; //... };

Probemos todo juntos:

/** * prueba.openable.cpp * */ #include "interfaz.abierta.h" #include "clase.Puerta.h" #include "clase.libro.h" //La función de abrir/cerrar cualquier objeto heterogéneo que implemente la interfaz iOpenable void openAndCloseSomething ( iOpenable & smth ) { algo _ abierto (); algo _ cerrar (); } int principal () { Puerta miPuerta ; BookmyBook ; _ abrirYCerrarAlgo ( miPuerta ); abrirYCerrarAlgo ( miLibro ); sistema ( "pausa" ); devolver 0 ; }

Java

A diferencia de C++, Java no le permite heredar más de una clase. Como alternativa a la herencia múltiple, existen interfaces. Cada clase en Java puede implementar cualquier conjunto de interfaces. No es posible derivar objetos de interfaces en Java.

Declaraciones de interfaz

Una declaración de interfaz es muy similar a una declaración de clase simplificada.

Comienza con un título. Los modificadores se enumeran primero . Una interfaz se puede declarar como public, en cuyo caso está disponible para uso público, o se puede omitir un modificador de acceso, en cuyo caso la interfaz solo está disponible para tipos en su . abstractNo se requiere un modificador de interfaz porque todas las interfaces son clases abstractas . Se puede especificar, pero no se recomienda hacerlo para no saturar el archivo .

A continuación, interfacese escriben la palabra clave y el nombre de la interfaz.

Esto puede ir seguido de una palabra clave extendsy una lista de interfaces de las que heredará la interfaz declarada . Puede haber muchos tipos de padres (clases y/o interfaces); lo principal es que no haya repeticiones y que la relación de herencia no forme una dependencia cíclica.

La herencia de interfaz es, de hecho, muy flexible. Entonces, si hay dos interfaces, Ay B, y Bse hereda de A, entonces la nueva interfaz Cse puede heredar de ambas. Sin embargo, es claro que al heredar de B, indicar herencia de Aes redundante, ya que todos los elementos de esta interfaz ya serán heredados a través de la interfaz B.

Luego, el cuerpo de la interfaz se escribe entre llaves.

Ejemplo de declaración de interfaz (Error si las clases Colorable y Redimensionable: el tipo Colorable y Redimensionable no puede ser una superinterfaz de Drawable; una superinterfaz debe ser una interfaz):

interfaz pública Drawable se extiende Colorable , Redimensionable { }

El cuerpo de la interfaz consiste en la declaración de elementos, es decir, campos - constantes y métodos abstractos . Todos los campos de la interfaz son automáticamente public final static, por lo que estos modificadores son opcionales e incluso indeseables para no saturar el código. Debido a que los campos son definitivos, deben inicializarse de inmediato .

Direcciones de interfaz pública { int RIGHT = 1 ; int IZQUIERDA = 2 ; int ARRIBA = 3 ; int ABAJO = 4 ; }

Todos los métodos de interfaz son public abstract, y estos modificadores también son opcionales.

interfaz pública Movible { void moveRight (); void moverIzquierda (); void mover hacia arriba (); void mover hacia abajo (); }

Como puede ver, la descripción de la interfaz es mucho más simple que la declaración de la clase.

Implementación de interfaz

Para implementar una interfaz, debe especificarse en la declaración de la clase mediante la extensión implements. Ejemplo:

interfaz I { void interfaceMethod (); } ImplementingInterface de clase pública implementa I { void interfaceMethod () { System . fuera _ println ( "Este método se implementa desde la interfaz I" ); } } public static void main ( String [] args ) { ImplementingInterface temp = new ImplementingInterface (); temperatura _ método de interfaz (); }

Cada clase puede implementar cualquier interfaz disponible. Al mismo tiempo, todos los métodos abstractos que aparecieron al heredar de las interfaces o de una clase principal deben implementarse en la clase para que la nueva clase pueda declararse no abstracta.

Si los métodos con la misma firma se heredan de diferentes fuentes , basta con describir la implementación una vez y se aplicará a todos estos métodos. Sin embargo, si tienen un valor de retorno diferente, se produce un conflicto. Ejemplo:

interfaz A { int getValue (); } interfaz B { doble getValue (); } interfaz C { int getValue (); } public class Correct implementa A , C // la clase hereda correctamente los métodos con la misma firma { int getValue () { return 5 ; } } class Wrong implementa A , B // la clase arroja un error en tiempo de compilación { int getValue () { return 5 ; } doble obtenerValor () { retornar 5.5 ; } }

C#

En C# , las interfaces pueden heredar de una o más interfaces. Los miembros de la interfaz pueden ser métodos, propiedades, eventos e indexadores:

interfaz I1 { void Method1 (); } interfaz I2 { void Method2 (); } interfaz I : I1 , I2 { método vacío (); int Cuenta { obtener ; } event EventHandler SomeEvent ; cadena este [ int index ] { get ; conjunto ; } }

Al implementar una interfaz, una clase debe implementar tanto los métodos de la propia interfaz como sus interfaces base:

clase pública C : I { método vacío público () { } public int Count { get { throw new NotImplementedException (); } } evento público EventHandler SomeEvent ; public string this [ int index ] { get { throw new NotImplementedException (); } set { lanzar una nueva NotImplementedException (); } } public void Method1 () { } Método vacío público2 () { } }

Interfaces en el UML

Las interfaces en UML se utilizan para visualizar, especificar, construir y documentar nodos de acoplamiento UML entre las partes componentes de un sistema. Los tipos y roles UML proporcionan un mecanismo para modelar el mapeo estático y dinámico de una abstracción a una interfaz en un contexto dado.

En UML, las interfaces se representan como clases con el estereotipo "interfaz", o como círculos (en este caso, las operaciones UML contenidas en la interfaz no se muestran).

Véase también

Notas

Enlaces