¿Qué significa "programar en una interfaz"?

814

He visto esto mencionado varias veces y no tengo claro qué significa. ¿Cuándo y por qué harías esto?

Sé lo que hacen las interfaces, pero el hecho de que no tengo claro esto me hace pensar que me estoy perdiendo el uso correcto.

¿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 pasará, excepto si se implementa IInterface. No puedo pensar con qué frecuencia necesitarías hacer eso.

Además, ¿cómo podría escribir un método que incorpore un objeto que implemente una interfaz? ¿Es eso posible?

Damien
fuente
3
Si puede recordarlo y su programa necesita ser óptimo, justo antes de la compilación, es posible que desee cambiar la declaración de la interfaz para la implementación real. Como el uso de una interfaz agrega un nivel de indirección que da un golpe de rendimiento. Sin embargo, distribuya su código programado a las interfaces ...
Ande Turner
18
@Ande Turner: ese es un mal consejo. 1) ¡"su programa necesita ser óptimo" no es una buena razón para intercambiar interfaces! Luego dice "Distribuya su código programado a las interfaces aunque ...", por lo que está informando que, dado el requisito (1), ¿libera el código subóptimo?
Mitch Wheat
74
La mayoría de las respuestas aquí no son del todo correctas. No significa ni implica "usar la palabra clave de interfaz" en absoluto. Una interfaz es una especificación de cómo usar algo, sinónimo del contrato (búscalo). Aparte de eso está la implementación, que es cómo se cumple ese contrato. Programe solo contra las garantías del método / tipo de modo que, cuando el método / tipo se cambie de una manera que aún obedezca el contrato, no se rompa el código que lo usa.
jyoungdev
2
@ apollodude217 que en realidad es la mejor respuesta en toda la página. Al menos para la pregunta en el título, ya que hay al menos 3 preguntas muy diferentes aquí ...
Andrew Spencer
44
El problema fundamental con preguntas como esta es que supone que "programar en una interfaz" significa "envolver todo en una interfaz abstracta", lo cual es una tontería si considera que el término es anterior al concepto de interfaces abstractas de estilo Java.
Jonathan Allen

Respuestas:

1635

Aquí hay algunas respuestas maravillosas a estas preguntas que incluyen todo tipo de detalles sobre interfaces y código de acoplamiento flexible, inversión de control, etc. Hay algunas discusiones bastante intensas, por lo que me gustaría aprovechar la oportunidad para desglosar un poco para comprender por qué una interfaz es útil.

Cuando comencé a exponerme a las interfaces, 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 vi las interfaces como una forma más débil de herencia y pensé, "¿por qué molestarse?" En cierto sentido, tenía razón, puedes pensar en las interfaces como una especie de herencia débil, pero más allá de eso finalmente entendí su uso como una construcción del lenguaje al pensar en ellas como un medio para clasificar los rasgos o comportamientos comunes que fueron exhibidos por potencialmente muchas clases de objetos no relacionados.

Por ejemplo, supongamos que tiene un juego SIM y tiene 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 necesita tener algún tipo de cosa aleatoria que moleste al jugador cuando cena. Esto podría ser un HouseFlyo unTelemarketer o ambas, pero ¿cómo permitir ambas con una sola función? ¿Y cómo le pides a cada tipo diferente de objeto que "haga lo molesto" de la misma manera?

La clave para darse cuenta es que tanto a Telemarketercomo HouseFlycomparten un comportamiento común interpretado libremente aunque no sean nada similares en términos de modelarlos. 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 satisfacer el contrato de IPestese contrato es simple. Solo tienes que hacerlo 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 una cantidad de comensales y varias plagas. Tenga en cuenta el uso de la interfaz. Esto significa que en nuestro pequeño mundo, un miembro de la pestsmatriz podría ser un Telemarketerobjeto o un HouseFlyobjeto.

El ServeDinnermétodo 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 tiene instrucciones de ser molesta a través de la IPestinterfaz. De esta manera, podemos tener ambas cosas fácilmente Telemarketersy HouseFlysser molestos en cada una de sus formas: solo nos importa que tengamos algo en el DiningRoomobjeto que sea una plaga, realmente no nos importa lo que es y no podrían tener nada en común con otros.

Este ejemplo de pseudocódigo muy ingenioso (que se prolongó mucho más de lo que esperaba) simplemente tiene la intención de ilustrar el tipo de cosas que finalmente me enciende la luz en términos de cuándo podríamos usar una interfaz. Pido disculpas de antemano por la tontería del ejemplo, pero espero que ayude a su comprensión. Y, para estar seguros, las otras respuestas publicadas que ha recibido aquí realmente cubren toda la gama del uso de interfaces hoy en día en patrones de diseño y metodologías de desarrollo.

Peter Meyer
fuente
3
Otra cosa a tener en cuenta es que en algunos casos puede ser útil tener una interfaz para cosas que "podrían" ser molestas, y tener una variedad de objetos implementados BeAnnoyingcomo no operativos ; esta interfaz puede existir en lugar de, o además de, la interfaz para cosas que son molestas (si ambas interfaces existen, la interfaz "cosas que son molestas" debería heredar de la interfaz "cosas que podrían ser molestas"). La desventaja de usar tales interfaces es que las implementaciones pueden verse cargadas con la implementación de un número "molesto" de métodos de código auxiliar. La ventaja es que ...
supercat
44
Los métodos no están destinados a representar métodos abstractos; su implementación es irrelevante para la pregunta que se centró en las interfaces.
Peter Meyer
33
Los comportamientos encapsulantes, como IPest, se conocen como el patrón de estrategia en caso de que alguien esté interesado en seguir con más material sobre ese tema ...
nckbrz
99
Curiosamente, no señala que debido a que los objetos en el IPest[]son referencias IPest, puede llamar BeAnnoying()porque tienen ese método, mientras que no puede llamar a otros métodos sin una conversión. Sin embargo, cada BeAnnoying()método individual de los objetos será llamado.
D. Ben Knoble
44
Muy buena explicación ... Solo necesito decirlo aquí: nunca escuché que las interfaces sean algún tipo de mecanismo de herencia suelto, pero en cambio sé que la herencia se usa como un mecanismo pobre para definir interfaces (por ejemplo, en Python regular hazlo todo el tiempo).
Carlos H Romano
283

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 vez de

ArrayList myList = new ArrayList(); // this is bad

Estos se ven exactamente iguales en un programa corto, pero si continúa utilizando myList100 veces en su programa, puede comenzar a ver la diferencia. La primera declaración asegura que solo se invocan métodos myListdefinidos por la Listinterfaz (por lo que no hay ArrayListmétodos específicos). 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ó la interfaz .

Los beneficios son aún más obvios (creo) cuando se habla de parámetros de métodos y valores de retorno. Toma esto por ejemplo:

public ArrayList doSomething(HashMap map);

Esa declaración de método lo vincula a dos implementaciones concretas ( ArrayListy HashMap). Tan pronto como se llame a ese método desde otro código, cualquier cambio en esos tipos probablemente significa que también tendrá que cambiar el código de llamada. Sería mejor programar en las interfaces.

public List doSomething(Map map);

Ahora no importa qué tipo de Listretorno, o qué tipo de Mappasa como parámetro. Los cambios que realice dentro del doSomethingmétodo no lo obligarán a cambiar el código de llamada.

Bill el lagarto
fuente
Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Yvette
Muy clara explicación. Muy útil
samuel luswata
Tengo una pregunta sobre el motivo por el que mencionó "La primera declaración garantiza que solo llame a los métodos en myList definidos por la interfaz List (por lo que no hay métodos específicos de ArrayList). Si ha programado la interfaz de esta manera, más adelante puede decidir que realmente necesita List myList = new TreeList (); y solo tiene que cambiar su código en ese lugar ". Tal vez no he entendido bien, me pregunto por qué necesita cambiar ArrayList a TreeList si desea "asegurarse de que solo llame a métodos en myList".
user3014901
1
@ user3014901 Existen varias razones por las que es posible que desee cambiar el tipo de lista que está utilizando. Uno podría tener un mejor rendimiento de búsqueda, por ejemplo. El punto es que si programa en la interfaz de Lista, le será más fácil cambiar su código a una implementación diferente más adelante.
Bill the Lizard
73

La programación en una interfaz dice: "Necesito esta funcionalidad y no me importa de dónde viene".

Considere (en Java), la Listinterfaz frente a los ArrayListy LinkedListconcretas clases. Si todo lo que me importa es que tengo una estructura de datos que contiene múltiples elementos de datos a los que debería acceder mediante la iteración, elegiría un List(y eso es el 99% del tiempo). Si sé que necesito insertar / eliminar en tiempo constante desde cualquier extremo de la lista, podría elegir la LinkedListimplementación concreta (o más probablemente, usar la interfaz de cola ). Si sé que necesito acceso aleatorio por índice, elegiría la ArrayListclase concreta.

kdgregory
fuente
1
totalmente de acuerdo, es decir, la independencia entre lo que se hace frente a cómo se hace. Al particionar un sistema a lo largo de componentes independientes, terminas con un sistema que es simple y reutilizable (ver Simple Made Easy por el tipo que creó Clojure)
beluchin
38

El uso de interfaces es un factor clave para hacer que su código sea fácilmente comprobable, además de eliminar acoplamientos innecesarios entre sus clases. Al crear una interfaz que define las operaciones en su clase, permite que las clases que quieran usar esa funcionalidad puedan 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 instancia la implementación. El resto del código no necesita cambiar porque depende de la interfaz, no de la clase implementadora.

Esto es muy útil para crear pruebas unitarias. En la clase bajo prueba, depende de la interfaz e inyecta una instancia de la interfaz en la clase (o en una fábrica que le permite construir instancias de la interfaz según sea necesario) a través del constructor o un configurador 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 la prueba de su unidad. Puede hacerlo porque su clase bajo prueba solo trata con la interfaz, no con su implementación concreta. Cualquier clase que implemente la interfaz, incluida su clase falsa o falsa, funcionará.

EDITAR: a continuación hay un enlace a un artículo donde Erich Gamma discute su cita, "Programa para una interfaz, no una implementación".

http://www.artima.com/lejava/articles/designprinciples.html

tvanfosson
fuente
44
Por favor, lea nuevamente esta entrevista: Gamma, por supuesto, estaba hablando sobre el concepto OO de interfaz, no sobre JAVA o el tipo especial de clase C # (ISomething). El problema es que la mayoría de la gente pensó que estaba hablando de la palabra clave, por lo que ahora tenemos muchas interfaces no necesarias (ISomething).
Sylvain Rodrigue
Muy buena entrevista. Por favor, tenga cuidado con los futuros lectores, hay cuatro páginas en la entrevista. Casi cerraría el navegador antes de verlo.
Anuncio Infinitum
38

La programación en 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 significa es no jugar con los elementos internos de un objeto o estructura de datos. Utilice la interfaz del programa abstracto, 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 campo sin procesar. Para C eso significa usar funciones en lugar de punteros sin formato.

EDITAR: Y con las bases de datos significa usar vistas y procedimientos almacenados en lugar de acceso directo a la tabla.

Jonathan Allen
fuente
55
La mejor respuesta. Gamma da una explicación similar aquí: artima.com/lejava/articles/designprinciples.html (ver página 2). Se está refiriendo al concepto OO, pero tiene razón: es más grande que eso.
Sylvain Rodrigue
36

Debería analizar la 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 entraría en una configuración basada en reglas en el container objeto 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 la 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 incorpore un objeto que implemente una interfaz? ¿Es eso posible?", En C # simplemente usaría el tipo de interfaz para el tipo de parámetro, de esta manera:

public void DoSomethingToAnObject(IInterface whatever) { ... }

Esto se conecta directamente a "hablar con un objeto que hace algo específico". El método definido anteriormente sabe qué esperar del objeto, que implementa todo en IInterface, pero no le importa qué tipo de objeto es, solo que se adhiere al contrato, que es lo que es una interfaz.

Por ejemplo, probablemente esté familiarizado con las calculadoras y probablemente haya usado bastantes en sus días, pero la mayoría de las veces son todas diferentes. Usted, por otro lado, sabe cómo debería funcionar una calculadora estándar, por lo que puede usarlos todos, incluso si no puede usar las características específicas que tiene cada calculadora que ninguna de las otras tiene.

Esta es la belleza de las interfaces. Puede escribir un fragmento de código, que sepa que obtendrá objetos que le pueden pasar de los que puede esperar cierto comportamiento. No importa qué tipo de objeto sea, solo que es compatible con 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 texto en cada uno. El sistema sabe cómo manejar controles básicos, como la propiedad de tipo de control que tiene un texto, y cosas básicas similares, pero para cualquier cosa básica, se queda corto.

Ahora, dado que los controles heredan de clases predefinidas sobre las que no tenemos control, podríamos hacer una de tres cosas:

  1. Cree soporte para nuestro sistema de traducción para detectar específicamente con qué tipo de control está trabajando y traduzca los bits correctos (pesadilla de mantenimiento)
  2. Construir soporte en clases base (imposible, ya que todos los controles heredan de diferentes clases predefinidas)
  3. Agregar soporte de interfaz

Entonces 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 hay un método al que puede llamar para localizar el control.

Lasse V. Karlsen
fuente
31
¿Por qué mencionar IoC al principio ya que esto solo agregaría más confusión?
Kevin Le - Khnle
1
De acuerdo, diría que programar contra interfaces es solo una técnica para hacer que IoC sea más fácil y confiable.
terjetyl
28

Código para la interfaz No la implementación no tiene NADA que ver con Java, ni su construcción de interfaz.

Este concepto se destacó en los libros de Patrones / Banda de cuatro, pero probablemente ya existía mucho antes. El concepto ciertamente existía mucho antes de que Java existiera.

La construcción de la Interfaz 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 versus 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. Constituyen 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 cómo funciona la clase, su implementación, sin romper la forma en que las personas interactúan con ella.

Y puede programar en la interfaz, no en la implementación, sin usar nunca una construcción de interfaz. Puede programar en 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 sólida siempre que interactúen a través de interfaces públicas (contratos) en lugar de invocar métodos en objetos internos de los sistemas. Se espera que las interfaces reaccionen siempre de la misma manera esperada dados los mismos parámetros de entrada; si se implementa en la interfaz y no en 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". Ellos pueden ayudar a aplicar el concepto, pero son no el concepto.

Bill Rosmus
fuente
1
La primera oración lo dice todo. Esta debería ser la respuesta aceptada.
madumlao
14

Parece que comprende cómo funcionan las interfaces, pero no está 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 cree JpegImageLoader, GifImageLoader, PngImageLoader, etc.

La mayoría de los sistemas de complementos y complementos funcionan fuera de las interfaces.

Otro uso popular es para el patrón de repositorio. Digamos que quiero cargar una lista de códigos postales de diferentes fuentes

interface IZipCodeRepository
{
    IList<ZipCode> GetZipCodes(string state);
}

entonces podría crear un XMLZipCodeRepository, SQLZipCodeRepository, CSVZipCodeRepository, etc. Para mis aplicaciones web, a menudo creo repositorios XML desde el principio para poder poner en marcha algo antes de que la Base de Datos 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 únicamente fuera de 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());
    }
}
Todd Smith
fuente
10

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 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 ocuparse de 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 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 "funciona" en todas partes y no tengo duplicación de código.

Ed S.
fuente
10

Mucha explicación por ahí, pero para hacerlo aún más simple. Tomemos por ejemplo a List. Se puede implementar una lista con como:

  1. Una matriz interna
  2. Una lista vinculada
  3. Otras implementaciones

Al construir una interfaz, diga a List. Solo codifica en cuanto a la definición de Lista o lo que Listsignifica en realidad.

Puede usar cualquier tipo de implementación internamente, digamos una arrayimplementación. Pero suponga que desea cambiar la implementación por alguna razón, digamos un error o rendimiento. Entonces solo tienes que cambiar la declaración List<String> ls = new ArrayList<String>()a List<String> ls = new LinkedList<String>().

En ninguna otra parte del código, tendrá que cambiar algo más; Porque todo lo demás se basó en la definición de List.

Jatin
fuente
8

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 se pueden escribir en este conjunto de interfaces. En teoría, elige un controlador JDBC y su aplicación simplemente funcionaría. Si descubre que hay un controlador JDBC más rápido o "mejor" o más barato o por alguna razón, puede volver a configurar en teoría su archivo de propiedades, y sin tener que hacer ningún cambio en su aplicación, su aplicación aún funcionaría.

Kevin Le - Khnle
fuente
No solo es útil en caso de que haya un mejor controlador disponible, sino que permite cambiar completamente los proveedores de bases de datos.
Ken Liu
3
JDBC es tan malo que necesita ser reemplazado. Encuentra otro ejemplo.
Joshua
JDBC es malo, pero no por ningún motivo relacionado con la interfaz frente a la implementación o los niveles de abstracción. Y así, para ilustrar el concepto en cuestión, es simplemente perfecto.
Erwin Smout
8

La programación en Interfaces es increíble, promueve el acoplamiento suelto. Como @lassevk mencionó, la Inversión de Control es un gran uso de esto.

Además, busque en los principios SOLIDOS . aquí hay una serie de videos

Pasa por un código rígido (ejemplo fuertemente acoplado), luego mira las interfaces y finalmente pasa a una herramienta IoC / DI (NInject)

dbones
fuente
7

Llegué 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 GoF (Gang of Four) Design Patterns.

Declaró, en la p. 18:

Programa para una interfaz, no una implementación

No declare variables como instancias de clases concretas particulares. En cambio, comprométase solo a 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 tiene dos ventajas únicamente en términos de la interfaz definida por las clases abstractas:

  1. Los clientes desconocen los tipos específicos de objetos que usan, siempre que los objetos se adhieran a la interfaz que los clientes esperan.
  2. Los clientes desconocen 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 quack()método para patos, y luego un bark()método para perros, porque son demasiado específicos para una implementación particular de una clase (o subclase). En su lugar, escriba el método usando nombres que sean lo suficientemente generales como para usarse en la clase base, como giveSound()o move(), para que puedan usarse para patos, perros o incluso automóviles, y luego el cliente de sus clases puede decir en .giveSound()lugar de pensando en usar quack()o bark()incluso determinar el tipo antes de emitir el mensaje correcto que se enviará al objeto.

nonopolaridad
fuente
6

Además de la respuesta ya seleccionada (y las diversas publicaciones informativas aquí), recomendaría encarecidamente tomar 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).

Whaley
fuente
5

Para agregar a las publicaciones existentes, a veces la codificación de interfaces ayuda en proyectos grandes 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.

e11s
fuente
4

También es bueno para Unit Testing, puede inyectar sus propias clases (que cumplan los requisitos de la interfaz) en una clase que depende de ella

Ricardo
fuente
4

Puede ser ventajoso programar en interfaces, incluso cuando no dependemos de abstracciones.

La programación en interfaces nos obliga a usar un subconjunto contextualmente apropiado de un objeto . Eso ayuda porque:

  1. nos impide hacer cosas contextualmente inapropiadas, y
  2. nos permite cambiar de manera segura la implementación en el futuro.

Por ejemplo, considere una Personclase que implementa el Friendy la Employeeinterfaz.

class Person implements AbstractEmployee, AbstractFriend {
}

En el contexto del cumpleaños de la persona, programamos la Friendinterfaz para evitar tratar a la persona como un Employee.

function party() {
    const friend: Friend = new Person("Kathryn");
    friend.HaveFun();
}

En el contexto del trabajo de la persona, programamos en la Employeeinterfaz para evitar difuminar los límites del lugar de trabajo.

function workplace() {
    const employee: Employee = new Person("Kathryn");
    employee.DoWork();
}

Excelente. Nos hemos comportado adecuadamente en diferentes contextos y nuestro software funciona bien.

En el futuro, si nuestro negocio cambia para trabajar con perros, podemos cambiar el software con bastante facilidad. Primero, creamos una Dogclase que implementa ambos Friendy Employee. Luego, cambiamos de manera segura 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 cierto:

  1. La función partyusa solo el Friendsubconjunto de Person.
  2. La función workplaceusa solo el Employeesubconjunto de Person.
  3. La clase Dogimplementa las interfaces Friendy Employee.

Por otro lado, si cualquiera partyo workplacedebían haber programado contra Person, habría un riesgo de ambos con Personcódigo específico de. Cambiar de Persona Dogrequeriría que revisemos el código para extirpar cualquier Personcódigo específico que Dogno sea compatible.

La moraleja : programar en interfaces ayuda a que nuestro código se comporte de manera adecuada y esté listo para el cambio. También prepara nuestro código para depender de abstracciones, lo que trae aún más ventajas.

Shaun Luttin
fuente
1
Asumiendo que no tienes interfaces excesivamente amplias, eso es.
Casey
4

Si estoy escribiendo una nueva clase Swimmerpara agregar la funcionalidad swim()y necesito usar un objeto de clase Dog, digamos , y esta Dogclase implementa una interfaz Animalque declara swim().

En la parte superior de la jerarquía ( Animal), es muy abstracto, mientras que en la parte inferior ( Dog) es muy concreto. La forma en que pienso sobre "programar en interfaces" es que, mientras escribo la Swimmerclase, quiero escribir mi código en la interfaz que está tan arriba en esa jerarquía, que en este caso es unAnimal objeto. Una interfaz está libre de detalles de implementación y, por lo tanto, hace que su código se acople libremente.

Los detalles de implementación se pueden cambiar con el tiempo, sin embargo, no afectaría el código restante ya que todo lo que está interactuando es con la interfaz y no con la implementación. No le importa cómo es la implementación ... todo lo que sabe es que habrá una clase que implementará la interfaz.

Abhishek Shrivastava
fuente
3

Entonces, solo para hacerlo 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 la implementación se da desde la clase que elija que implemente 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 sea.

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 se pueda pasar y se le diga que se mueva. Sin el método, cada uno sabe el tipo de clase que es.

Damien
fuente
3

Imagine que tiene un producto llamado 'Zebra' que se puede ampliar con complementos. Encuentra los complementos buscando DLL en algún directorio. Carga todas esas DLL y usa 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ífica; no le importa cuáles son las clases. Solo le importa que cumplan con 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á acoplado a ningún otro código específico. Puede interactuar con complementos escritos años después 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 cantidad de interfaces.

Daniel Earwicker
fuente
3

Explicación de C ++.

Piense en una interfaz como los métodos públicos de sus clases.

Luego puede crear una plantilla que 'dependa' de estos métodos públicos para llevar a cabo su propia función (hace que las 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 hace Vector Vector satisfará el 'contrato' (como alguien explicó en la respuesta original). Los algoritmos ni siquiera necesitan ser de la misma clase base; el único requisito es que las funciones / métodos de los que depende el Vector (interfaz) estén definidos en su algoritmo.

El punto de todo esto es que puede proporcionar cualquier algoritmo / clase de búsqueda diferente siempre que proporcione la interfaz de la que depende Vector (búsqueda de burbujas, 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 haciendo 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 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' cómo funcionan las cosas; a lo grande (al menos en C ++), ya que así es como funciona la mayor parte del marco de la biblioteca TEMPLATE estándar.

Por supuesto, cuando se usan clases de herencia y abstractas, 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.

Trae Barlow
fuente
3

En Java, estas clases concretas implementan la interfaz CharSequence:

CharBuffer, String, StringBuffer, StringBuilder

Estas clases concretas no tienen una clase principal común que no sea Object, por lo que no hay nada que las relacione, aparte del hecho de que cada una tiene algo que ver con conjuntos de caracteres, que representan o manipulan. Por ejemplo, los caracteres de String no se pueden cambiar una vez que se crea una instancia de un 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 Java que solían aceptar String se han revisado para que ahora acepten 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), en su lugar, puede pasar el StringBuilder mismo mientras implementa la interfaz CharSequence.

La interfaz Appendable 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 Appendable:

BufferedWriter, CharArrayWriter, CharBuffer, FileWriter, FilterWriter, LogStream, OutputStreamWriter, PipedWriter, PrintStream, PrintWriter, StringBuffer, StringBuilder, StringWriter, Writer

RogerV
fuente
Es una interfaz tan mala como la que CharSequencees tan anémica. Desearía que Java y .NET hubieran permitido que las interfaces tuvieran una implementación predeterminada, para que las personas no redujeran las interfaces con el único fin de minimizar el código repetitivo. Dada cualquier CharSequenceimplementación legítima , uno podría emular la mayoría de las funciones de Stringusar solo los cuatro métodos anteriores, pero muchas implementaciones podrían realizar esas funciones de manera mucho más eficiente de otras maneras. Desafortunadamente, incluso si una implementación particular de CharSequencecontiene todo en un solo char[]y podría realizar muchos ...
supercat
... operaciones como indexOfrápidamente, no hay forma de que una persona que llama que no esté familiarizada con una implementación particular CharSequencepueda pedirle que lo haga en lugar de tener que usar charAtpara examinar cada personaje individual.
supercat
3

Breve historia: se le pide a un cartero que vaya a casa después de casa y reciba las portadas que contiene (cartas, documentos, cheques, tarjetas de regalo, solicitud, carta de amor) con la dirección escrita para entregar.

Suponga que no hay cobertura y pídale al cartero que se vaya a casa después de casa y reciba todas las cosas y se las entregue a otras personas, el cartero puede confundirse.

Así que mejor envuélvelo con tapa (en nuestra historia es la interfaz), entonces él hará su trabajo bien.

Ahora, el trabajo del cartero es recibir y entregar las portadas únicamente (no le molestaría lo que hay dentro de la portada).

Cree un tipo de tipo interfaceno real, pero impleméntelo con el tipo real.

Crear para la interfaz significa que sus componentes se ajustan fácilmente al resto del código

Te doy un ejemplo.

tiene la interfaz AirPlane como se muestra a continuación.

interface Airplane{
    parkPlane();
    servicePlane();
}

Supongamos que tiene métodos en su clase de Controlador de Planos como

parkPlane(Airplane plane)

y

servicePlane(Airplane plane)

implementado en su programa. No romperá su código. Quiero decir, no necesita cambiar mientras acepte argumentos como AirPlane.

Debido a que aceptará cualquier avión a pesar de tipo real, flyer, highflyr, fighter, etc.

Además, en una colección:

List<Airplane> plane; // Tomará todos tus 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 AirPlanevarias 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, ya que puede hacer que sus nuevas AirPlaneinstancias de tipo sean tantas como desee y no está cambiando el código de ControlPlaneclase.

Puedes agregar una instancia ...

JumboJetPlane // implementing AirPlane interface.
AirBus        // implementing AirPlane interface.

También puede eliminar instancias de tipos creados previamente.

Sanjay Rabari
fuente
2

Una interfaz es como un contrato, donde desea que su clase de implementación implemente métodos escritos en el contrato (interfaz). Como Java no proporciona herencia múltiple, "programar para interactuar" es una buena manera de lograr herencia múltiple.

Si tiene una clase A que ya está ampliando alguna otra clase B, pero desea que esa clase A también siga ciertas pautas o implemente un determinado contrato, puede hacerlo mediante la estrategia de "programación a interfaz".

Shivam Sugandhi
fuente
2

P: - ... "¿Podría usar alguna clase que implemente una interfaz?"
A: Sí.

P: - ... "¿Cuándo necesitarías hacer eso?"
R: - Cada vez que necesita una clase (s) que implementa la interfaz (s).

Nota: No pudimos crear una instancia de una interfaz no implementada por una clase - Verdadero.

  • ¿Por qué?
  • Debido a que la interfaz solo tiene prototipos de métodos, 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á referencia de Aclass.


Nota: Ahora podríamos entender qué pasaría si Bclass y Cclass implementaran 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: los mismos prototipos de interfaz (nombres de funciones en la interfaz) y llamadas a diferentes implementaciones.

Bibliografía: prototipos - wikipedia

Meraj al Maksud
fuente
1

El programa a una interfaz permite cambiar la implementación del contrato definido por la interfaz sin problemas. Permite un acoplamiento flexible 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?

Eche un vistazo a esta pregunta SE para un buen ejemplo.

¿Por qué debería preferirse la interfaz para una clase Java?

¿El uso de una interfaz afecta el rendimiento?

si es así cuanto?

Si. Tendrá una ligera sobrecarga de rendimiento en segundos secundarios. Pero si su aplicación tiene el requisito de cambiar la implementación de la interfaz de forma dinámica, 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 la interfaz con una implementación específica, es posible que deba implementar el parche para cambiar una implementación a otra.

Un buen caso de uso: Implementación del patrón de estrategia:

Ejemplo del mundo real del patrón de estrategia

Ravindra babu
fuente
1

programa para una interfaz es un término del libro GOF. No diría directamente que tiene que ver con la interfaz de Java sino con interfaces reales. para lograr una separación de capa limpia, necesita crear cierta separación entre sistemas, por ejemplo: Digamos que tenía una base de datos concreta que desea usar, nunca "programaría en la base de datos", sino que "programaría en la interfaz de almacenamiento". Del mismo modo, nunca "programaría para un servicio web" sino que programaría para una "interfaz de cliente". Esto es para que pueda cambiar fácilmente las cosas.

Me parece que estas reglas me ayudan:

1 . Usamos una interfaz Java cuando tenemos múltiples tipos de un objeto. Si solo tengo un solo objeto, no veo el punto. si hay al menos dos implementaciones concretas de alguna idea, entonces usaría una interfaz java.

2 . si, como dije anteriormente, desea llevar el desacoplamiento de un sistema externo (sistema de almacenamiento) a su propio sistema (base de datos local), entonces también use una interfaz.

observe cómo hay dos formas de considerar cuándo usarlos. espero que esto ayude.

j2emanue
fuente
0

También veo muchas respuestas buenas y explicativas aquí, así que quiero dar mi punto de vista aquí, incluida información adicional de 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 él. Después de escribir alrededor de 50K líneas descubrí que sería realmente necesario escribir pruebas unitarias. No utilicé interfaces (o muy poco) ... y cuando hice mi primera prueba de unidad, descubrí que era complicado. ¿Por qué?

Debido a que tuve que hacer muchas instancias de clase, utilizadas para la entrada como variables de clase y / o parámetros. Entonces las pruebas se parecen más a las pruebas de integración (tener que hacer un 'marco' completo de clases ya que todo estaba unido).

Miedo a las interfaces Así que decidí usar interfaces. Mi temor era que tenía 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 para ser utilizada. Doy un ejemplo muy simple.

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 forma, no es necesario copiar el código, mientras se tiene la ventaja de utilizar un automóvil como interfaz (ICar).

Michel Keijzers
fuente
0

Comencemos 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 se definió 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 la base de datos sea del tipo de forma siempre que implemente su interfaz, en cuyo caso esto sería un subtipo .

Muchos objetos pueden ser de muchas interfaces / tipos 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 conocerá la interfaz y no la implementación.

Entonces, en esencia, programar en una interfaz implicaría hacer algún tipo de clase abstracta, como Shapecon la interfaz solo especificada draw(), es decir getCoordinates(), getArea()etc. Y luego hacer que diferentes clases concretas implementen esas interfaces, como una clase Circle, una clase Square, una clase Triangle. Por lo tanto, programe una interfaz, no una implementación.

Robert Rocha
fuente
0

"Programa para interfaz" significa que no debe proporcionar el código duro de la manera correcta, lo que significa que su código debe ampliarse sin romper la funcionalidad anterior. Solo extensiones, no editando el código anterior.

ndoty
fuente