language agnostic - ¿Qué significa "programar a una interfaz"?
language-agnostic oop (30)
P: - ... "¿Podrías usar cualquier clase que implemente interfaz?"
A: - Sí.P: -... "¿Cuándo necesitarías hacer eso?"
R: - Cada vez que necesita una (s) clase (s) que implementa interfaz (s).
Nota: no pudimos crear una instancia de una interfaz no implementada por una clase - Verdadero.
- ¿por qué?
- porque la interfaz solo tiene métodos prototipos, no definiciones (solo nombres de funciones, no su lógica)
AnIntf anInst = new Aclass ();
// podríamos hacer esto solo si Aclass implementa AnIntf.
// anInst tendrá una referencia Aclass.
Nota:
Ahora podríamos entender qué sucedería si Bclass y Cclass implementan el mismo Dintf.
Dintf bInst = new Bclass();
// now we could call all Dintf functions implemented (defined) in Bclass.
Dintf cInst = new Cclass();
// now we could call all Dintf functions implemented (defined) in Cclass.
Lo que tenemos:
prototipos de la misma interfaz (nombres de funciones en la interfaz), y llamar a diferentes implementaciones.
Bibliografía:
Prototipos - wikipedia
He visto esto mencionado varias veces y no tengo claro lo que significa. ¿Cuándo y por qué harías esto?
Sé lo que hacen las interfaces, pero el hecho de que no esté claro me hace pensar que me estoy perdiendo de usarlas correctamente.
¿Es así si tuvieras que hacer?
IInterface classRef = new ObjectWhatever()
¿Podrías usar cualquier clase que implemente IInterface
? ¿Cuándo necesitarías hacer eso? Lo único en lo que puedo pensar es si tiene un método y no está seguro de qué objeto se IInterface
. IInterface
se implemente en el IInterface
. No puedo pensar con qué frecuencia necesitarías hacer eso ... (Además, ¿cómo podrías escribir un método que incluya un objeto que implemente una interfaz? ¿Es eso posible?)
Lo siento si me perdí completamente el punto.
Además de la respuesta ya seleccionada (y las diversas publicaciones informativas aquí), recomendaría encarecidamente obtener una copia de Head First Design Patterns . Es una lectura muy fácil y responderá su pregunta directamente, explicará por qué es importante y le mostrará muchos patrones de programación que puede usar para hacer uso de ese principio (y otros).
Deberías mirar en Inversión de Control:
- Martin Fowler: contenedores de inversión de control y el patrón de inyección de dependencia
- Wikipedia: Inversión de Control
En tal escenario, no escribirías esto:
IInterface classRef = new ObjectWhatever();
Escribirías algo como esto:
IInterface classRef = container.Resolve<IInterface>();
Esto iría a una configuración basada en reglas en el objeto container
y construiría el objeto real para usted, que podría ser ObjectWhatever. Lo importante es que podría reemplazar esta regla con algo que usara otro tipo de objeto por completo, y su código aún funcionaría.
Si dejamos IoC fuera de la tabla, puede escribir código que sepa que puede hablar con un objeto que hace algo específico , pero no qué tipo de objeto o cómo lo hace.
Esto sería útil al pasar parámetros.
En cuanto a su pregunta entre paréntesis "Además, ¿cómo podría escribir un método que incluya un objeto que implemente una Interfaz? ¿Es posible?", En C # simplemente usaría el tipo de interfaz para el tipo de parámetro, como este:
public void DoSomethingToAnObject(IInterface whatever) { ... }
Esto se conecta directamente a la "conversación con un objeto que hace algo específico". El método definido anteriormente sabe qué esperar del objeto, que implementa todo en Interfaz, pero no le importa qué tipo de objeto es, solo que se adhiere al contrato, que es lo que es una interfaz.
Por ejemplo, es probable que esté familiarizado con las calculadoras y que haya usado bastantes en sus días, pero la mayoría de las veces todas son diferentes. Por otro lado, usted sabe cómo debería funcionar una calculadora estándar, por lo que puede usarlas todas, incluso si no puede usar las funciones específicas que tiene cada calculadora que ninguna otra.
Esta es la belleza de las interfaces. Puede escribir un fragmento de código, que sabe que se le pasarán objetos a los que se puede esperar cierto comportamiento. No importa qué tipo de objeto sea, solo que respalde el comportamiento necesario.
Déjame darte un ejemplo concreto.
Tenemos un sistema de traducción personalizado para formularios de Windows. Este sistema recorre los controles de un formulario y traduce el texto en cada uno. El sistema sabe cómo manejar los controles básicos, como la propiedad de tipo-de-control-que-tiene-una-Texto, y cosas básicas similares, pero para cualquier cosa básica, se queda corto.
Ahora, como los controles heredan de clases predefinidas sobre las que no tenemos control, podríamos hacer una de tres cosas:
- Cree soporte para nuestro sistema de traducción para detectar específicamente con qué tipo de control está trabajando y traducir los bits correctos (pesadilla de mantenimiento)
- Crear soporte en clases base (imposible, ya que todos los controles heredan de diferentes clases predefinidas)
- Añadir soporte de interfaz
Así que hicimos nr. 3. Todos nuestros controles implementan ILocalizable, que es una interfaz que nos brinda un método, la capacidad de traducir "a sí mismo" en un contenedor de texto / reglas de traducción. Como tal, el formulario no necesita saber qué tipo de control ha encontrado, solo que implementa la interfaz específica, y sabe que existe un método al que se puede llamar para localizar el control.
El ejemplo específico que solía dar a los estudiantes es que deberían escribir
List myList = new ArrayList(); // programming to the List interface
en lugar de
ArrayList myList = new ArrayList(); // this is bad
Se ven exactamente iguales en un programa corto, pero si va a usar myList
100 veces en su programa, puede comenzar a ver una diferencia. La primera declaración garantiza que solo llame a los métodos en myList
que están definidos por la interfaz de la List
(por lo que no hay métodos específicos de ArrayList
). Si ha programado para la interfaz de esta manera, más adelante puede decidir que realmente necesita
List myList = new TreeList();
y solo tienes que cambiar tu código en ese lugar. Ya sabe que el resto de su código no hace nada que se rompa al cambiar la implementación porque programó en la interfaz .
Los beneficios son aún más obvios (creo) cuando se habla de parámetros de método y valores de retorno. Toma esto por ejemplo:
public ArrayList doSomething(HashMap map);
Esa declaración de método lo vincula con dos implementaciones concretas ( ArrayList
y HashMap
). Tan pronto como se llame a ese método desde otro código, cualquier cambio en esos tipos probablemente signifique que también tendrá que cambiar el código de llamada. Sería mejor programar a las interfaces.
public List doSomething(Map map);
Ahora no importa qué tipo de List
devuelve, o qué tipo de Map
se pasa como parámetro. Los cambios que realice dentro del método doSomething
no lo obligarán a cambiar el código de llamada.
El uso de interfaces es un factor clave para que su código sea fácilmente comprobable, además de eliminar los acoplamientos innecesarios entre sus clases. Al crear una interfaz que define las operaciones en su clase, le permite a las clases que desean usar esa funcionalidad la capacidad de usarla sin depender directamente de su clase de implementación. Si más adelante decide cambiar y usar una implementación diferente, solo necesita cambiar la parte del código donde se implementa la instancia. El resto del código no necesita cambiar porque depende de la interfaz, no de la clase implementadora.
Esto es muy útil en la creación de pruebas unitarias. En la clase bajo prueba, depende de la interfaz e inyecte una instancia de la interfaz en la clase (o una fábrica que le permita crear instancias de la interfaz según sea necesario) a través del constructor o un conjunto de propiedades. La clase utiliza la interfaz proporcionada (o creada) en sus métodos. Cuando vaya a escribir sus pruebas, puede simular o simular la interfaz y proporcionar una interfaz que responda con los datos configurados en su prueba de unidad. Puede hacer esto porque su clase bajo prueba solo se ocupa de la interfaz, no de su implementación concreta. Cualquier clase que implemente la interfaz, incluida tu clase simulada o falsa, funcionará.
EDITAR: A continuación se muestra un enlace a un artículo en el que Erich Gamma analiza su cita, "Programa para una interfaz, no una implementación".
Entonces, solo para hacer esto bien, la ventaja de una interfaz es que puedo separar la llamada de un método de cualquier clase en particular. En lugar de crear una instancia de la interfaz, donde se da la implementación de la clase que elija que implementa esa interfaz. Por lo tanto, me permite tener muchas clases, que tienen una funcionalidad similar pero ligeramente diferente y en algunos casos (los casos relacionados con la intención de la interfaz) no me importa qué objeto es.
Por ejemplo, podría tener una interfaz de movimiento. Un método que hace que algo se "mueva" y cualquier objeto (Persona, Coche, Gato) que implemente la interfaz de movimiento podría pasarse y decirse que mueva. Sin el método, cada uno sabe el tipo de clase que es.
Hace que su código sea mucho más extensible y más fácil de mantener cuando tiene conjuntos de clases similares. Soy un programador junior, así que no soy un experto, pero acabo de terminar un proyecto que requería algo similar.
Trabajo en el software del lado del cliente que habla con un servidor que ejecuta un dispositivo médico. Estamos desarrollando una nueva versión de este dispositivo que tiene algunos componentes nuevos que el cliente debe configurar a veces. Hay dos tipos de componentes nuevos, y son diferentes, pero también son muy similares. Básicamente, tuve que crear dos formularios de configuración, dos clases de listas, dos de todo.
Decidí que sería mejor crear una clase base abstracta para cada tipo de control que contendría casi toda la lógica real, y luego los tipos derivados para resolver las diferencias entre los dos componentes. Sin embargo, las clases base no habrían podido realizar operaciones en estos componentes si tuviera que preocuparme por los tipos todo el tiempo (bueno, podrían haberlo hecho, pero habría habido una declaración "if" o un cambio en cada método) .
Definí una interfaz simple para estos componentes y todas las clases base hablan con esta interfaz. Ahora, cuando cambio algo, prácticamente ''simplemente funciona'' en todas partes y no tengo duplicación de código.
Hay algunas respuestas maravillosas aquí a estas preguntas que entran en todo tipo de gran detalle sobre las interfaces y el código de acoplamiento flexible, la inversión de control y así sucesivamente. Hay algunas discusiones bastante contundentes, así que me gustaría aprovechar la oportunidad para desglosar un poco las cosas para entender por qué una interfaz es útil.
Cuando empecé a exponerme a las interfaces, yo también estaba confundido acerca de su relevancia. No entendí por qué los necesitabas. Si estamos usando un lenguaje como Java o C #, ya tenemos herencia y yo veía las interfaces como una forma más débil de herencia y pensamiento, "¿por qué molestarse?" En cierto sentido, tenía razón, puede pensar en las interfaces como una forma de herencia débil, pero más allá de eso finalmente entendí su uso como una construcción de lenguaje al pensar en ellas como un medio para clasificar rasgos o comportamientos comunes que exhibían potencialmente muchas clases de objetos no relacionados.
Por ejemplo, digamos que tienes un juego SIM y tienes las siguientes clases:
class HouseFly inherits Insect {
void FlyAroundYourHead(){}
void LandOnThings(){}
}
class Telemarketer inherits Person {
void CallDuringDinner(){}
void ContinueTalkingWhenYouSayNo(){}
}
Claramente, estos dos objetos no tienen nada en común en términos de herencia directa. Pero, se podría decir que ambos son molestos.
Digamos que nuestro juego debe tener algún tipo de cosa aleatoria que moleste al jugador cuando cena. Esto podría ser un HouseFly
o un HouseFly
o ambos, pero ¿cómo permite ambos con una sola función? ¿Y cómo le pide a cada tipo diferente de objeto que "haga su cosa molesta" de la misma manera?
La clave es darse cuenta de que tanto Telemarketer
como HouseFly
comparten un comportamiento común interpretado a la HouseFly
aunque no se parecen en nada a los modelos. Entonces, hagamos una interfaz que ambos puedan implementar:
interface IPest {
void BeAnnoying();
}
class HouseFly inherits Insect implements IPest {
void FlyAroundYourHead(){}
void LandOnThings(){}
void BeAnnoying() {
FlyAroundYourHead();
LandOnThings();
}
}
class Telemarketer inherits Person implements IPest {
void CallDuringDinner(){}
void ContinueTalkingWhenYouSayNo(){}
void BeAnnoying() {
CallDuringDinner();
ContinueTalkingWhenYouSayNo();
}
}
Ahora tenemos dos clases que pueden ser molestas a su manera. Y no necesitan derivar de la misma clase base y compartir características inherentes comunes, simplemente necesitan cumplir el contrato de IPest
, ese contrato es simple. Solo tienes que ser BeAnnoying
. En este sentido, podemos modelar lo siguiente:
class DiningRoom {
DiningRoom(Person[] diningPeople, IPest[] pests) { ... }
void ServeDinner() {
when diningPeople are eating,
foreach pest in pests
pest.BeAnnoying();
}
}
Aquí tenemos un comedor que acepta un número de comensales y una serie de plagas, tenga en cuenta el uso de la interfaz. Esto significa que en nuestro pequeño mundo, un miembro de la matriz de pests
podría ser en realidad un objeto de Telemarketer
o un objeto de HouseFly
.
El método ServeDinner
se llama cuando se sirve la cena y se supone que nuestra gente en el comedor debe comer. En nuestro pequeño juego, es cuando nuestras plagas hacen su trabajo: cada plaga es instruida para que sea molesta a través de la interfaz IPest
. De esta manera, podemos hacer que tanto los HouseFlys
por HouseFlys
como HouseFlys
sean molestos en sus propias maneras: solo nos importa que tengamos algo en el objeto DiningRoom
que sea una plaga, realmente no nos importa lo que sea y podrían No tienen nada en común con otros.
Este ejemplo muy artificial de pseudocódigo (que se prolongó mucho más de lo que anticipé) simplemente pretende ilustrar el tipo de cosa que finalmente se encendió para mí en términos de cuándo podríamos usar una interfaz. Me disculpo de antemano por la estupidez del ejemplo, pero espero que ayude en su comprensión. Y, sin duda, las otras respuestas publicadas que ha recibido aquí cubren realmente la gama del uso de interfaces hoy en día en patrones de diseño y metodologías de desarrollo.
Hay muchas explicaciones por ahí, pero para hacerlo aún más simple. Tomemos, por ejemplo, una List
. Uno puede implementar una lista con como:
- una matriz interna
- Una lista enlazada
- otra implementación
Al construir una interfaz, diga una List
. Usted solo codifica en cuanto a la definición de Lista o lo que List
significa en realidad.
Podría usar cualquier tipo de implementación internamente, digamos una implementación de array
. Pero suponga que desea cambiar la implementación por algún motivo, por ejemplo, un error o rendimiento. Luego, solo tiene que cambiar la declaración List<String> ls = new ArrayList<String>()
para List<String> ls = new LinkedList<String>()
.
En ningún otro lugar del código, tendrás que cambiar algo más; Porque todo lo demás fue construido en la definición de la List
.
Imagina que tienes un producto llamado ''Zebra'' que puede extenderse mediante complementos. Encuentra los complementos buscando DLL en algún directorio. Carga todas esas DLL y utiliza la reflexión para encontrar cualquier clase que implemente IZebraPlugin
, y luego llama a los métodos de esa interfaz para comunicarse con los complementos.
Esto lo hace completamente independiente de cualquier clase de complemento específico, no importa cuáles son las clases. Sólo importa que cumplan la especificación de la interfaz.
Las interfaces son una forma de definir puntos de extensibilidad como este.El código que habla con una interfaz está más débilmente acoplado, de hecho, no está asociado en absoluto a ningún otro código específico. Puede interoperar con complementos escritos años más tarde por personas que nunca han conocido al desarrollador original.
En su lugar, podría usar una clase base con funciones virtuales: todos los complementos se derivarían de la clase base. Pero esto es mucho más limitante porque una clase solo puede tener una clase base, mientras que puede implementar cualquier número de interfaces.
La programación a Interfaces es impresionante, promueve el acoplamiento suelto. Como mencionó @lassevk, la inversión de control es un gran uso de esto.
Además, mira en los directores de SOLID . aquí hay una serie de videos
Pasa a través de un código duro (ejemplo fuertemente acoplado) y luego mira las interfaces, finalmente progresando a una herramienta IoC / DI (NInject)
Llego tarde a esta pregunta, pero quiero mencionar aquí que la línea "Programa para una interfaz, no una implementación" tuvo una buena discusión en el libro de Patrones de diseño de GoF (Gang of Four).
Afirmó, en la p. 18:
Programa para una interfaz, no una implementación
No declare las variables como instancias de clases concretas particulares. En su lugar, comprometerse solo con una interfaz definida por una clase abstracta. Encontrará que este es un tema común de los patrones de diseño en este libro.
y encima de eso, comenzó con:
La manipulación de objetos solo tiene dos ventajas en términos de la interfaz definida por las clases abstractas:
- Los clientes no son conscientes de los tipos específicos de objetos que utilizan, siempre que los objetos se adhieran a la interfaz que los clientes esperan.
- Los clientes no conocen las clases que implementan estos objetos. Los clientes solo conocen las clases abstractas que definen la interfaz.
En otras palabras, no lo escriba en sus clases para que tenga un método quack()
para patos, y luego un método de bark()
para perros, porque son demasiado específicos para una implementación particular de una clase (o subclase) . En su lugar, escriba el método utilizando nombres que sean lo suficientemente generales para ser utilizados en la clase base, como por ejemplo, giveSound()
o move()
, de modo que puedan usarse para patos, perros o incluso autos, y luego el cliente de su las clases solo pueden decir .giveSound()
lugar de pensar en usar quack()
o bark()
o incluso determinar el tipo antes de emitir el mensaje correcto que se enviará al objeto.
Para agregar a las publicaciones existentes, a veces la codificación de interfaces ayuda en grandes proyectos cuando los desarrolladores trabajan en componentes separados simultáneamente. Todo lo que necesita es definir interfaces por adelantado y escribir código en ellas mientras otros desarrolladores escriben código en la interfaz que está implementando.
Parece que entiendes cómo funcionan las interfaces pero no estás seguro de cuándo usarlas y qué ventajas ofrecen. Aquí hay algunos ejemplos de cuándo una interfaz tendría sentido:
// if I want to add search capabilities to my application and support multiple search
// engines such as google, yahoo, live, etc.
interface ISearchProvider
{
string Search(string keywords);
}
entonces podría crear GoogleSearchProvider, YahooSearchProvider, LiveSearchProvider etc.
// if I want to support multiple downloads using different protocols
// HTTP, HTTPS, FTP, FTPS, etc.
interface IUrlDownload
{
void Download(string url)
}
// how about an image loader for different kinds of images JPG, GIF, PNG, etc.
interface IImageLoader
{
Bitmap LoadImage(string filename)
}
luego crea JpegImageLoader, GifImageLoader, PngImageLoader, etc.
La mayoría de los complementos y sistemas de complementos funcionan fuera de las interfaces.
Otro uso popular es para el patrón Repositorio. Digamos que quiero cargar una lista de códigos postales de diferentes fuentes
interface IZipCodeRepository
{
IList<ZipCode> GetZipCodes(string state);
}
luego podría crear un XMLZipCodeRepository, SQLZipCodeRepository, CSVZipCodeRepository, etc. Para mis aplicaciones web, a menudo creo repositorios XML desde el principio para que pueda poner en marcha algo antes de que la base de datos de SQL esté lista. Una vez que la base de datos está lista, escribo un SQLRepository para reemplazar la versión XML. El resto de mi código permanece sin cambios ya que se ejecuta solo en las interfaces.
Los métodos pueden aceptar interfaces como:
PrintZipCodes(IZipCodeRepository zipCodeRepository, string state)
{
foreach (ZipCode zipCode in zipCodeRepository.GetZipCodes(state))
{
Console.WriteLine(zipCode.ToString());
}
}
Programar a una interfaz no tiene absolutamente nada que ver con interfaces abstractas como las que vemos en Java o .NET. Ni siquiera es un concepto OOP.
Lo que realmente significa es que no te metas con las partes internas de un objeto o estructura de datos. Utilice la interfaz abstracta del programa, o API, para interactuar con sus datos. En Java o C #, eso significa usar propiedades y métodos públicos en lugar de acceso a campos sin procesar. Para C, eso significa usar funciones en lugar de punteros en bruto.
EDIT: Y con bases de datos significa usar vistas y procedimientos almacenados en lugar de acceso directo a la tabla.
Programar en una interfaz está diciendo: "Necesito esta funcionalidad y no me importa de dónde viene".
Considere (en Java), la interfaz de la List
frente a las clases concretas ArrayList
y LinkedList
. Si lo único que me importa es que tengo una estructura de datos que contiene varios elementos de datos a los que debo acceder a través de la iteración, elegiría una List
(y eso es el 99% del tiempo). Si sé que necesito insertar / eliminar constantemente desde cualquiera de los extremos de la lista, podría elegir la implementación concreta de LinkedList
(o más probablemente, usar la interfaz de la Queue ). Si sé que necesito acceso aleatorio por índice, elegiría la clase concreta ArrayList
.
Si programa en Java, JDBC es un buen ejemplo. JDBC define un conjunto de interfaces pero no dice nada sobre la implementación. Sus aplicaciones pueden escribirse contra este conjunto de interfaces. En teoría, elige algún controlador JDBC y su aplicación simplemente funcionará. Si descubre que hay un controlador JDBC más rápido o "mejor" o más barato o por cualquier motivo, en teoría puede volver a configurar su archivo de propiedades y, sin tener que realizar ningún cambio en su aplicación, su aplicación aún funcionará.
También es bueno para la prueba unitaria, puede inyectar sus propias clases (que cumplan con los requisitos de la interfaz) en una clase que dependa de ella
Código para la interfaz No la implementación tiene NADA que ver con Java, ni su estructura de interfaz.
Este concepto se destacó en los libros de Patrones / Pandillas de los Cuatro, pero probablemente fue mucho antes. El concepto ciertamente existió mucho antes de que Java existiera.
La construcción de la Interfaz de Java se creó para ayudar en esta idea (entre otras cosas), y las personas se han centrado demasiado en la construcción como el centro del significado en lugar de la intención original. Sin embargo, es la razón por la que tenemos métodos y atributos públicos y privados en Java, C ++, C #, etc.
Significa simplemente interactuar con un objeto o la interfaz pública del sistema. No se preocupe ni anticipe cómo hace lo que hace internamente. No te preocupes por cómo se implementa. En el código orientado a objetos, es por eso que tenemos métodos / atributos públicos frente a privados. Estamos destinados a usar los métodos públicos porque los métodos privados están ahí solo para uso interno, dentro de la clase. Forman la implementación de la clase y se pueden cambiar según sea necesario sin cambiar la interfaz pública. Suponga que con respecto a la funcionalidad, un método en una clase realizará la misma operación con el mismo resultado esperado cada vez que lo llame con los mismos parámetros. Permite al autor cambiar la forma en que funciona la clase, su implementación, sin romper la forma en que las personas interactúan con ella.
Y puede programar para la interfaz, no la implementación sin utilizar nunca una construcción de interfaz. Puede programar la interfaz, no la implementación en C ++, que no tiene una construcción de interfaz. Puede integrar dos sistemas empresariales masivos de manera mucho más robusta siempre que interactúen a través de interfaces públicas (contratos) en lugar de llamar a métodos en objetos internos de los sistemas. Se espera que las interfaces siempre reaccionen de la misma manera esperada dados los mismos parámetros de entrada; Si se implementa a la interfaz y no a la implementación. El concepto funciona en muchos lugares.
Sacuda la idea de que las Interfaces Java tienen algo que ver con el concepto de ''Programa para la interfaz, no la implementación''. Pueden ayudar a aplicar el concepto, pero no son el concepto.
El programa a una interfaz permite cambiar la implementación del contrato definido por la interfaz sin problemas. Permite el acoplamiento suelto entre el contrato y las implementaciones específicas.
IInterface classRef = new ObjectWhatever()
¿Podrías usar cualquier clase que implemente IInterface? ¿Cuándo necesitarías hacer eso?
Echa un vistazo a esta pregunta SE para un buen ejemplo.
¿Por qué debería preferirse la interfaz para una clase Java?
¿Usando un rendimiento de éxito de interfaz?
si es asi cuanto?
Sí.Tendrá una ligera sobrecarga de rendimiento en segundos. Pero si su aplicación tiene el requisito de cambiar la implementación de la interfaz dinámicamente, no se preocupe por el impacto en el rendimiento.
¿Cómo puedes evitarlo sin tener que mantener dos bits de código?
No intente evitar múltiples implementaciones de interfaz si su aplicación las necesita. En ausencia de un acoplamiento estrecho de interfaz con una implementación específica, es posible que tenga que implementar el parche para cambiar una implementación a otra implementación.
Un buen caso de uso: Implementación del patrón de estrategia:
En lenguaje sencillo...Si estoy escribiendo una nueva clase Swimmer para agregar la funcionalidad swim () y necesito usar un objeto de clase, por ejemplo Dog, y esta clase Dog implementa la interfaz Animal que declara swim () [Para entender mejor ... puede dibujar un diagrama de lo que estoy hablando]. En la parte superior de la jerarquía (Animal) es muy abstracto, mientras que en la parte inferior (Perro) es muy concreto. La forma en que pienso acerca de la "programación para interfaces" es que, mientras escribo la clase Swimmer, quiero escribir mi código en la interfaz que está tan arriba en esa jerarquía, que en este caso es el objeto Animal. Una interfaz está libre de detalles de implementación y, por lo tanto, hace que su código se acople de forma flexible. Los detalles de la implementación se pueden cambiar con el tiempo, sin embargo, no afectará el código restante, ya que todo lo que está interactuando es con la interfaz y no con la implementación.No te importa cómo sea la implementación ... todo lo que sabes es que habrá una clase que implementará la interfaz.
C ++ explicación.
Piense en una interfaz como sus clases de métodos públicos.
Luego, podría crear una plantilla que ''dependa'' de estos métodos públicos para llevar a cabo su propia función (realiza llamadas a funciones definidas en la interfaz pública de las clases). Digamos que esta plantilla es un contenedor, como una clase Vector, y la interfaz de la que depende es un algoritmo de búsqueda.
Cualquier clase de algoritmo que defina las funciones / interfaz a las que Vector Vector satisfará el ''contrato'' (como alguien explicó en la respuesta original). Los algoritmos ni siquiera tienen que ser de la misma clase base; el único requisito es que las funciones / métodos de los que depende el Vector (interfaz) estén definidas en su algoritmo.
El punto de todo esto es que puede proporcionar cualquier clase / algoritmo de búsqueda diferente siempre que proporcione la interfaz de la que depende Vector (búsqueda de burbuja, búsqueda secuencial, búsqueda rápida).
También es posible que desee diseñar otros contenedores (listas, colas) que aprovechen el mismo algoritmo de búsqueda que Vector al hacer que cumplan con la interfaz / contrato del que dependen sus algoritmos de búsqueda.
Esto ahorra tiempo (principio de OOP, ''reutilización de código''), ya que puede escribir un algoritmo una vez en lugar de una y otra vez, una y otra vez, específico para cada nuevo objeto que cree sin complicar demasiado el problema con un árbol de herencia demasiado grande.
En cuanto a ''perderse'' en cómo funcionan las cosas; en grande (al menos en C ++), ya que así es como funciona la mayor parte del marco de la Biblioteca de PLANTILLA Estándar.
Por supuesto, cuando se usan clases abstractas y de herencia, la metodología de programación para una interfaz cambia; pero el principio es el mismo, sus funciones / métodos públicos son su interfaz de clases.
Este es un tema enorme y uno de los principios fundamentales de los patrones de diseño.
El programa de interfaz significa que no proporcione códigos duros de la manera correcta, significa que sus códigos deben extenderse sin romper la funcionalidad anterior ... solo extensiones que no editan los códigos anteriores
Empecemos con algunas definiciones primero:
Interfaz n. El conjunto de todas las firmas definidas por las operaciones de un objeto se denomina interfaz con el objeto.
Escriba n. Una interfaz particular
Un ejemplo simple de una interfaz como se define anteriormente sería todos los métodos de objeto PDO tales como query()
, commit()
, close()
etc., en su conjunto, no por separado. Estos métodos, es decir, su interfaz definen el conjunto completo de mensajes, solicitudes que pueden enviarse al objeto.
Un tipo como el definido anteriormente es una interfaz particular. Voy a utilizar la interfaz de forma maquillada para demostrar: draw()
, getArea()
, getPerimeter()
etc ..
Si un objeto es del tipo Base de datos, queremos decir que acepta mensajes / solicitudes de la interfaz de la base de datos query()
, commit()
etc. Los objetos pueden ser de muchos tipos. Puede hacer que un objeto de base de datos sea del tipo de forma siempre y cuando implemente su interfaz, en cuyo caso esto sería un subtipo .
Muchos objetos pueden ser de muchos tipos / interfaces diferentes e implementar esa interfaz de manera diferente. Esto nos permite sustituir objetos, permitiéndonos elegir cuál usar. También conocido como polimorfismo.
El cliente solo será consciente de la interfaz y no de la implementación.
Entonces, en esencia, la programación a una interfaz implicaría hacer algún tipo de clase abstracta, como Shape
con la interfaz solo especificada draw()
, es decir getCoordinates()
, getArea()
etc., y luego hacer que diferentes clases concretas implementen esas interfaces como una clase Círculo, una clase Cuadrada, una clase Triángulo. De ahí el programa para una interfaz no una implementación.
En Java, todas estas clases concretas implementan la interfaz CharSequence:
CharBuffer, String, StringBuffer, StringBuilder
Estas clases concretas no tienen una clase padre común que no sea el Objeto, por lo que no hay nada que las relacione, excepto el hecho de que cada una tiene algo que ver con matrices de caracteres, representando tales o manipulando tales. Por ejemplo, los caracteres de String no se pueden cambiar una vez que se crea una instancia del objeto String, mientras que los caracteres de StringBuffer o StringBuilder se pueden editar.
Sin embargo, cada una de estas clases es capaz de implementar adecuadamente los métodos de interfaz CharSequence:
char charAt(int index)
int length()
CharSequence subSequence(int start, int end)
String toString()
En algunos casos, las clases de la biblioteca de clases de Java que solían aceptar cadenas se han revisado para aceptar ahora la interfaz CharSequence. Entonces, si tiene una instancia de StringBuilder, en lugar de extraer un objeto String (lo que significa crear una instancia de un nuevo objeto), puede pasar el StringBuilder como implementa la interfaz de CharSequence.
La interfaz anexable que implementan algunas clases tiene el mismo tipo de beneficio para cualquier situación en la que los caracteres se pueden agregar a una instancia de la instancia de objeto de clase concreta subyacente. Todas estas clases concretas implementan la interfaz anexable:
BufferedWriter, CharArrayWriter, CharBuffer, FileWriter, FilterWriter, LogStream, OutputStreamWriter, PipedWriter, PrintStream, PrintWriter, StringBuffer, StringBuilder, StringWriter, Escritor
Interfaz es como un contrato en el que desea que su clase de implementación implemente métodos escritos en el contrato (Interfaz). Desde que Java no proporciona herencia múltiple, la programación a la interfaz es una buena manera de lograr el propósito de la herencia múltiple. Si tiene una clase A Ya extendiendo alguna otra clase B, pero desea que la clase A también siga ciertas pautas o implemente cierto contrato, entonces puede hacerlo programando la estrategia de interfaz.
Puede ser ventajoso programar a interfaces, incluso cuando no dependemos de abstracciones.
La programación a interfaces nos obliga a usar un subconjunto de un objeto apropiado al contexto. Eso ayuda porque:
- nos impide hacer cosas contextualmente inapropiadas, y
- Nos permite cambiar de forma segura la implementación en el futuro.
Por ejemplo, considere una Person
clase que implementa el Friend
y la Employee
interfaz.
class Person implements AbstractEmployee, AbstractFriend {
}
En el contexto del cumpleaños de la persona, programamos a la Friend
interfaz, para evitar tratar a la persona como a Employee
.
function party() {
const friend: Friend = new Person("Kathryn");
friend.HaveFun();
}
En el contexto del trabajo de la persona, programamos la Employee
interfaz para evitar que los límites del lugar de trabajo se vean borrosos.
function workplace() {
const employee: Employee = new Person("Kathryn");
employee.DoWork();
}
Genial. Nos hemos comportado adecuadamente en diferentes contextos, y nuestro software está funcionando bien.
En el futuro, si nuestro negocio cambia para funcionar con perros, podemos cambiar el software con bastante facilidad. Primero, creamos una Dog
clase que implementa tanto Friend
y como Employee
. Entonces, con seguridad nos cambiamos new Person()
a new Dog()
. Incluso si ambas funciones tienen miles de líneas de código, esa edición simple funcionará porque sabemos que lo siguiente es verdadero:
- La función
party
usa solo elFriend
subconjunto dePerson
. - La función
workplace
usa solo elEmployee
subconjunto dePerson
. - La clase
Dog
implementa las interfacesFriend
yEmployee
.
Por otro lado, si cualquiera party
o workplace
fuera programado en contra Person
, habría un riesgo de que ambos tuvieran un Person
código específico. Cambiar de Person
a Dog
requeriría que revisemos el código para extirpar cualquier Person
código específico que Dog
no sea compatible.
La moraleja : la programación a interfaces ayuda a nuestro código a comportarse de manera apropiada y estar listo para el cambio. También prepara nuestro código para depender de las abstracciones, lo que brinda aún más ventajas.
También veo muchas respuestas buenas y explicativas aquí, por lo que quiero dar mi punto de vista aquí, incluida información adicional sobre lo que noté al usar este método.
Examen de la unidad
Durante los últimos dos años, he escrito un proyecto de pasatiempo y no escribí pruebas unitarias para ello. Después de escribir alrededor de 50K líneas, descubrí que sería realmente necesario escribir pruebas unitarias. No usé interfaces (o con mucha moderación) ... y cuando hice mi primera prueba de unidad, descubrí que era complicado. ¿Por qué?
Debido a que tuve que crear muchas instancias de clase, se usaron para ingresar como variables de clase y / o parámetros. Así que las pruebas se parecen más a las pruebas de integración (tener que hacer un ''marco'' completo de clases ya que todas se unieron)
Miedo a las interfaces Así que decidí usar interfaces. Mi temor era tener que implementar todas las funciones en todas partes (en todas las clases usadas) varias veces. De alguna manera esto es cierto, sin embargo, al usar la herencia se puede reducir mucho.
Combinación de interfaces y herencia Descubrí que la combinación es muy buena de usar. Te doy un ejemplo muy sencillo.
public interface IPricable
{
int Price { get; }
}
public interface ICar : IPricable
public abstract class Article
{
public int Price { get { return ... } }
}
public class Car : Article, ICar
{
// Price does not need to be defined here
}
De esta manera, no es necesario copiar el código, mientras se tiene la ventaja de usar un automóvil como interfaz (ICar).
historia corta: Se le pide al cartero que se vaya a casa por casa y reciba las portadas que contiene (cartas, documentos, cheques, tarjetas de regalo, solicitud, carta de amor) con la dirección escrita para entregar.
Supongamos que no hay cobertura y pida al encargado de correos que vaya a su casa por casa y reciba todas las cosas y entregue a otra persona que el cartero pueda confundir,
así que mejor envuélvalo con una cubierta (en nuestra historia es la interfaz) entonces él hará su trabajo bien.
Ahora el trabajo del cartero es recibir y entregar las portadas solo ... (no le molestó lo que hay dentro de la portada).
Cree el tipo de tipo interface
no real, pero implemente con el tipo real.
Crear para la interfaz significa que sus componentes se adaptan fácilmente al resto del código
Te doy ejemplo.
tienes la interfaz de AirPlane como se muestra abajo.
interface Airplane{
parkPlane();
servicePlane();
}
Supongamos que tiene métodos en su clase Controlador de Planos como
parkPlane(Airplane plane)
y
servicePlane(Airplane plane)
implementado en su programa. No romperá su código. Quiero decir, no tiene que cambiar mientras acepte argumentos como AirPlane
.
Debido a que aceptará cualquier avión a pesar de tipo presencial, flyer
, highflyr
, fighter
, etc.
Además, en una colección:
List<Airplane> plane;
// Tomará todos sus aviones.
El siguiente ejemplo aclarará su comprensión.
Tienes un avión de combate que lo implementa, así que
public class Fighter implements Airplane {
public void parkPlane(){
// Specific implementations for fighter plane to park
}
public void servicePlane(){
// Specific implementatoins for fighter plane to service.
}
}
Lo mismo para HighFlyer y otras clases:
public class HighFlyer implements Airplane {
public void parkPlane(){
// Specific implementations for HighFlyer plane to park
}
public void servicePlane(){
// specific implementatoins for HighFlyer plane to service.
}
}
Ahora piense que sus clases de controlador usan AirPlane
varias veces,
Supongamos que su clase de controlador es ControlPlane como a continuación,
public Class ControlPlane{
AirPlane plane;
// so much method with AirPlane reference are used here...
}
aquí viene la magia como
puede crear sus nuevas AirPlane
instancias de tipo tantas como desee y no está cambiando
Código de ControlPlane
clase.
puede agregar instancia ..
JumboJetPlane // implementing AirPlane interface.
AirBus // implementing AirPlane interface.
También puede eliminar instancias ... de tipos creados previamente.
No conservo interface
s son lo más importante en un idioma: se usa más comúnmente para heredar la clase. ¡Pero de todos modos son importantes!
Por ejemplo (esto es Java
código, pero puede adaptarse simplemente a C#
muchos otros idiomas):
interface Convertable<T> {
T convert();
}
public class NumerableText implements Convertable<Integer> {
private String text = "";
public NumerableText() { }
public NumerableText(String text) {
this.text = text;
}
public String getText() {
return this.text;
}
public void setText(String text) {
this.text = text;
}
public Integer convert() {
return this.text.hashCode();
}
}
public class NumerableTextArray implements Convertable<Integer> {
private String[] textArray = "";
public NumerableTextArray() { }
public NumerableTextArray(String[] textArray) {
this.textArray = textArray;
}
public String[] getTextArray() {
return this.textArray;
}
public void setTextArray(String[] text) {
this.textArray = textArray;
}
public Integer convert() {
Integer value = 0;
for (String text : textArray)
value += text.hashCode();
return value;
}
}
public class Foo {
public static void main() {
Convertable<Integer> num1 = new NumerableText("hello");
Convertable<Integer> num2 = new NumerableTextArray(new String[] { "test n°1", "test n°2" });
System.out.println(String.valueOf(num1.convert()));
System.out.println(String.valueOf(num2.convert()));
//Here are you two numbers generated from two classes of different type, but both with the method convert(), which allows you to get that number.
}
}