2009-09-11 13 views
18

He estado escuchando la declaración en la mayoría de los sitios relacionados con la programación: Programa¿Qué significa programar en una interfaz?

a una interfaz y no a un Implementación

Sin embargo no entiendo las implicaciones?
Los ejemplos ayudarían.

EDIT: He recibido muchas buenas respuestas, incluso así podría complementarla con algunos fragmentos de código para una mejor comprensión del tema. ¡Gracias!

Respuesta

34

Usted probablemente está buscando algo como esto:

public static void main(String... args) { 
    // do this - declare the variable to be of type Set, which is an interface 
    Set buddies = new HashSet(); 

    // don't do this - you declare the variable to have a fixed type 
    HashSet buddies2 = new HashSet(); 
} 

Por qué se considera bueno para hacerlo el primer camino? Digamos más adelante que usted decide que necesita usar una estructura de datos diferente, por ejemplo, un LinkedHashSet, para aprovechar la funcionalidad de LinkedHashSet. El código tiene que ser cambiado así:

public static void main(String... args) { 
    // do this - declare the variable to be of type Set, which is an interface 
    Set buddies = new LinkedHashSet(); // <- change the constructor call 

    // don't do this - you declare the variable to have a fixed type 
    // this you have to change both the variable type and the constructor call 
    // HashSet buddies2 = new HashSet(); // old version 
    LinkedHashSet buddies2 = new LinkedHashSet(); 
} 

Esto no parece tan malo, ¿verdad? Pero, ¿y si escribes getters de la misma manera?

public HashSet getBuddies() { 
    return buddies; 
} 

¡Tendría que cambiarse también!

public LinkedHashSet getBuddies() { 
    return buddies; 
} 

Esperemos que ver, incluso con un pequeño programa como este que tienen amplias implicaciones en lo que se declara el tipo de la variable a ser. Con los objetos yendo y viniendo tanto, definitivamente ayuda a que el programa sea más fácil de codificar y mantener si solo confía en que una variable se declare como una interfaz, no como una implementación específica de esa interfaz (en este caso, declare que es una Establecer, no un LinkedHashSet o lo que sea). Puede ser simplemente esto:

public Set getBuddies() { 
    return buddies; 
} 

Hay otra ventaja también, en que (bueno, al menos para mí) la diferencia ayuda a diseñar un programa mejor. Pero espero que mis ejemplos te den una idea ... espero que ayude.

+0

¡Guau! ¡Eso está muy bien explicado! –

+1

¡Gracias! Me alegro de poder ayudar :) – weiji

+2

Solo ten en cuenta que este principio se aplica a los tipos que creas, así como a las abstracciones integradas como Set. Por ejemplo, si tiene un "Foo" en su proyecto. No solo haga un "Foo de clase", en su lugar haga "interfaz Foo" y luego implemente algo como "clase MyFoo implementa Foo". Incluso podría encontrar que su proyecto tendrá varias clases que implementan la interfaz "Foo". Al escribir constantemente el nombre de la interfaz en lugar de la implementación (excepto las invocaciones de constructor), puede cambiar rápidamente las implementaciones de Foo para que se ajusten a sus necesidades. –

3

Una interfaz es como un contrato entre usted y la persona que creó la interfaz para que su código lleve a cabo lo que solicitan. Además, desea codificar las cosas de tal manera que su solución pueda resolver el problema muchas veces. Piensa en volver a usar el código. Cuando está codificando una implementación, está pensando exclusivamente en la instancia de un problema que está tratando de resolver. Entonces, cuando bajo esta influencia, sus soluciones serán menos genéricas y más enfocadas. Eso hará que la escritura sea una solución general que se rija por una interfaz mucho más desafiante.

+0

Esta respuesta no es una declaración a favor o en contra de esta metodología, FYI. –

5

Es una forma de separar responsabilidades/dependencias entre módulos. Al definir una Interfaz particular (una API), se asegura de que los módulos en cada lado de la interfaz no se "molestarán" entre sí.

Por ejemplo, digamos que el módulo 1 se encargará de mostrar la información de la cuenta bancaria para un usuario en particular, y module2 buscará la información de la cuenta bancaria de "lo que sea" back-end.

Al definir algunos tipos y funciones, junto con los parámetros asociados, por ejemplo, una estructura que define una transacción bancaria, y algunos métodos (funciones) como GetLastTransactions (AccountNumber, NbTransactionsWanted, ArrayToReturnTheseRec) y GetBalance (AccountNumer), Module1 podrá obtener la información necesaria y no preocuparse por cómo se almacena o calcula esta información o lo que sea. Por el contrario, el Module2 simplemente responderá a la llamada de métodos proporcionando la información según la interfaz definida, pero no se preocupará de dónde se mostrará, se imprimirá esta información o lo que sea ...

Cuando se cambia un módulo , la implementación de la interfaz puede variar, pero mientras la interfaz siga siendo la misma, los módulos que usan la API pueden, en el peor de los casos, necesitar ser recompilados/reconstruidos, pero no necesitan tener su lógica modificada de todos modos.

Esa es la idea de una API.

1

Básicamente, las interfaces son la representación un poco más concreta de los conceptos generales de interoperación: proporcionan la especificación de qué deberían hacer las demás opciones para "conectar" para una función particular de forma similar para que el código que las usa no dependerá de una opción en particular. Por ejemplo, muchas bibliotecas DB actúan como interfaces ya que pueden operar con muchos DB reales diferentes (MSSQL, MySQL, PostgreSQL, SQLite, etc.) sin tener que cambiar el código que usa la biblioteca DB.

En general, le permite crear código que es más flexible, brindando a sus clientes más opciones sobre cómo lo usan y también potencialmente le permite reutilizar códigos en múltiples lugares en lugar de tener que escribir nuevos códigos especializados.

4

En esencia, esta afirmación se trata realmente de dependencias.Si codigo mi clase Foo a una implementación (Bar en lugar de IBar) entonces Foo ahora depende de Bar. Pero si codigo mi clase Foo en una interfaz (IBar en lugar de Bar), entonces la implementación puede variar y Foo ya no depende de una implementación específica. Este enfoque proporciona una base de código flexible y poco compacta que se puede reutilizar, refactorizar y probar de manera más fácil.

+1

@Downvoter: ¿Qué? – jason

1

Al programar en una interfaz, es más probable que aplique el principio de bajo acoplamiento/alta cohesión. Al programar en una interfaz, puede cambiar fácilmente la implementación de esa interfaz (la clase específica).

3

Tome un bloque Lego rojo de 2x4 y conéctelo a un bloque azul Lego de 2x4 para que uno se coloque encima del otro. Ahora quite el bloque azul y reemplácelo con un bloque amarillo Lego de 2x4. Observe que el bloque rojo no tuvo que cambiar aunque la "implementación" del bloque adjunto varió.

Ahora ve a buscar otro tipo de bloque que no comparta la "interfaz" de Lego. Intenta conectarlo al Lego 2x4 rojo. Para que esto suceda, deberá cambiar el Lego u otro bloque, tal vez cortando un poco de plástico o añadiendo plástico nuevo o pegamento. Tenga en cuenta que variando la "implementación" se ve obligado a cambiarlo o al cliente.

Ser capaz de permitir que las implementaciones varíen sin cambiar el cliente o el servidor; eso es lo que significa programar las interfaces.

+0

+1 para el uso de Lego en una analogía – Smalltown2k

+0

+1 Mejor respuesta: D –

1

Significa que sus variables, propiedades, parámetros y tipos de devolución deben tener un tipo de interfaz en lugar de una implementación concreta.

Lo que significa que usa IEnumerable<T> Foo(IList mylist) en lugar de ArrayList Foo(ArrayList myList) por ejemplo.

Uso de la aplicación sólo cuando la construcción del objeto:

IList list = new ArrayList(); 

Si ha hecho esto más adelante podrá cambiar el tipo de objeto puede que desea utilizar LinkedList en lugar de ArrayList más adelante, esto no es un problema, ya que todo el mundo de lo contrario se refieren a él como simplemente "IList"

7

Mi lectura inicial de esa afirmación es muy diferente a cualquier respuesta que haya leído hasta ahora. Estoy de acuerdo con todas las personas que dicen que usar tipos de interfaz para los parámetros de su método, etc. son muy importantes, pero eso no es lo que esta declaración significa para mí.

Mi opinión es que le está diciendo que escriba código que solo depende de qué tipo de interfaz usa la interfaz (en este caso, estoy usando "interfaz" para referirme a los métodos expuestos de clase o interfaz). lo hace en la documentación. Esto es lo contrario de escribir código que depende de los detalles de implementación de las funciones a las que llama. Debe tratar todas las llamadas a funciones como cuadros negros (puede hacer excepciones a esto si ambas funciones son métodos de la misma clase, pero idealmente se mantiene en todo momento).

Ejemplo: supongamos que hay una clase Screen que tiene métodos Draw(image) y Clear() en ella. La documentación dice algo así como "el método de dibujo dibuja la imagen especificada en la pantalla" y "el método claro borra la pantalla". Si desea mostrar las imágenes secuencialmente, la forma correcta de hacerlo sería llamar repetidamente al Clear() seguido de Draw(). Eso sería codificar a la interfaz. Si está codificando la implementación, puede hacer algo así como llamar solo al método Draw() porque sabe al mirar la implementación de Draw() que internamente llama al Clear() antes de hacer cualquier dibujo. Esto es malo porque ahora depende de los detalles de implementación que no puede conocer al mirar la interfaz expuesta.

miro adelante a ver si alguien más comparte esta interpretación de la frase en cuestión de la OP, o si estoy fuera de base por completo ...

+2

Eso es lo que yo también entiendo: programar en una interfaz significa programar la documentación, la especificación o el protocolo. Entonces, si está programando para una interfaz, una discusión sobre "does X work" no es "Lo intenté y funciona/no funciona", es más como "He leído la documentación, y permite X/prohíbe X/no menciona X/es ambiguo sobre X ". –

2

Además de las otras respuestas, agrego más:

Programa en una interfaz porque es más fácil de manejar. La interfaz encapsula el comportamiento de la clase subyacente. De esta manera, la clase es una blackbox. Toda tu vida real es programar en una interfaz. Cuando usa un televisor, un automóvil o un estéreo, está actuando en su interfaz, no en sus detalles de implementación, y asume que si la implementación cambia (por ejemplo, motor diesel o gas), la interfaz sigue siendo la misma. La programación en una interfaz le permite conservar su comportamiento cuando se modifican, optimizan o arreglan detalles no disruptivos. Esto simplifica también la tarea de documentar, aprender y usar.

Además, la programación en una interfaz le permite delinear cuál es el comportamiento de su código incluso antes de escribirlo. Esperas que una clase haga algo. Puedes probar esto incluso antes de escribir el código real que lo hace. Cuando su interfaz está limpia y lista, y le gusta interactuar con ella, puede escribir el código real que hace las cosas.

+0

Como ejemplo, he visto una clase que tenía varios métodos privados y un par de métodos públicos que llamaban internamente a estos métodos privados. En este caso, estamos exponiendo solo un subconjunto de la API. ¿Es esta una buena práctica de programación? –

5

Una interfaz define los métodos y un objeto se compromete a responder.

Cuando código para la interfaz, puede cambiar el objeto subyacente y su código se siguen trabajando (debido a que su código es independiente del de la OMS no realizar el trabajo o cómo se realiza el trabajo) Ganas flexibilidad que este camino.

Cuando se codifican a una determinada aplicación , si necesita cambiar el subyacente objeto Su código más probable ruptura, debido a que el nuevo objeto puede no responder a los mismos métodos.

lo tanto, para poner un ejemplo claro:

Si necesita llevar a cabo una serie de objetos que podría haber decidido utilizar un Vector.

Si necesita acceder al primer objeto del vector se podría escribir:

Vector items = new Vector(); 
// fill it 
Object first = items.firstElement(); 

Hasta aquí todo bien.

Más tarde se decidió que debido a "alguna" razón, tiene que cambiar la aplicación (digamos que el vector crea un cuello de botella debido a la sincronización excesiva)

Se da cuenta de que necesita utilizar un instad ArrayList.

Bueno, el código se romperá ...

ArrayList items = new ArrayList(); 
// fill it 
Object first = items.firstElement(); // compile time error. 

No se puede. Esta línea y todas aquellas líneas que usan el método firstElement() se romperán.

Si necesita un comportamiento específico y definitivamente necesita este método, podría estar bien (aunque no podrá cambiar la implementación) Pero si lo que necesita es simplemente recuperar el primer elemento (que es , no hay nada de especial con el Vector que tenga el primer método de Element()) luego usar la interfaz en lugar de la implementación le daría la flexibilidad de cambiar.

List items = new Vector(); 
// fill it 
Object first = items.get(0); // 

De esta forma no se está codificando a la get method of Vector, sino a la get method of List.

No importa cómo hacer el objeto subyacente lleva a cabo el método, siempre que responde al contrato de "sacar el elemento 0 ª de la colección"

De esta manera es posible que después cambiarlo a cualquier otra aplicación :

List items = new ArrayList(); // Or LinkedList or any other who implements List 
// fill it 
Object first = items.get(0); // Doesn't break 

Esta muestra puede parecer ingenuo, pero es la base sobre la que se basa la tecnología OO (incluso en aquellos idiomas que no son de tipos estáticos como Python, Ruby, Smalltalk, Objective-C, etc.)

Un ejemplo más complejo es t el camino JDBC funciona. Puede cambiar el controlador, pero la mayor parte de su llamada funcionará de la misma manera. Por ejemplo, puede usar el controlador estándar para las bases de datos de Oracle o puede usar uno más sofisticado como los que proporcionan Weblogic o Webpshere. Por supuesto que no es mágico todavía tiene que probar el producto antes, pero al menos usted no tiene cosas como:

statement.executeOracle9iSomething(); 

vs

statement.executeOracle11gSomething(); 

Algo similar sucede con Java Swing.

lectura adicional:

Design Principles from Design Patterns

Effective Java Item: Refer to objects by their interfaces

(La compra de este libro una de las mejores cosas que puede hacer en la vida - y leer si por supuesto -)

+0

¡Fantástico! ¡Los libros también parecen increíbles! Con respecto al código, hemos cambiado una línea de código en el programa de Elementos de lista = nuevo Vector(); a los elementos de la lista = new ArrayList(); así que eventualmente tuvimos que cambiar un código en el área del cliente así que también podríamos cambiar las líneas restantes de código relacionadas con Vector que se van a romper. Todavía no entiendo el significado. –

+1

Parece que tiene un código que espera usar un Vector, que es exactamente lo que significa "programación para una implementación". Su otro código ahora espera usar Vector. Si, en cambio, tuvieras tu código, esperaras que la estructura de datos fuera una Lista (en otras palabras, declaraste elementos para ser una Lista, así que no la trates como una subclase específica de Lista, y entonces "programa a una interfaz "), entonces su otro código no necesitaría modificaciones más adelante cuando cambie el tipo real cambiando" nuevo Vector() "a" nuevo ArrayList() ". – weiji

16

Un día , su jefe le indicó a un programador junior que escribiera una aplicación para analizar datos comerciales y condensarlo todo en informes bonitos con métricas, gráficos y todo eso. El jefe le dio un archivo XML con la observación "aquí hay algunos ejemplos de datos comerciales".

El programador comenzó a codificar. Unas semanas más tarde sintió que las métricas, los gráficos y las cosas eran lo suficientemente bonitas como para satisfacer al jefe, y presentó su trabajo. "Eso es genial", dijo el jefe, "¿pero también puede mostrar datos comerciales de esta base de datos SQL que tenemos?".

El programador volvió a la codificación. Había código para leer datos comerciales de XML salpicado en su aplicación. Él volvió a escribir todos esos fragmentos, envolviéndolos con un "si" condición:

if (dataType == "XML") 
{ 
    ... read a piece of XML data ... 
} 
else 
{ 
    .. query something from the SQL database ... 
} 

Cuando se presentó la nueva versión del software, el jefe respondió: "Eso está muy bien, pero puede también informar sobre los datos comerciales de este ¿servicio web?" Recordando todas esas tediosas declaraciones si tuviera que volver a escribir OTRA VEZ, el programador se enfureció. "Primero xml, luego SQL, ahora servicios web! ¿Cuál es la fuente REAL de datos comerciales?"

El jefe respondió: "Cualquier cosa que pueda proporcionarla"

En ese momento, el programador fue enlightened.

+0

¡Maravilloso! ¡Seguramente escrito por una persona Iluminada! –

+0

¡Er! ... ¡Pero podrías iluminarme! ...¿Qué hizo el programador? –

+6

Se dio cuenta de que podía hacer que la mayoría del código fuese independiente del tipo de fuente de datos programando contra una única abstracción de la fuente de datos; una interfaz! Agregar soporte para nuevos tipos de fuentes de datos se convierte en una cuestión de crear una nueva implementación de la interfaz. El resto del código no tiene que ser tocado. Este es el patrón de ** mapeador de datos **. –

3

Mira, no me di cuenta de que esto era para Java, y mi código se basa en C#, pero creo que proporciona el punto.

Todos los automóviles tienen puertas.

Pero no todas las puertas actúan igual, como en el Reino Unido, las puertas del taxi están al revés. Un hecho universal es que "abren" y "cierran".

interface IDoor 
{ 
    void Open(); 
    void Close(); 
} 

class BackwardDoor : IDoor 
{ 
    public void Open() 
    { 
     // code to make the door open the "wrong way". 
    } 

    public void Close() 
    { 
     // code to make the door close properly. 
    } 
} 

class RegularDoor : IDoor 
{ 
    public void Open() 
    { 
     // code to make the door open the "proper way" 
    } 

    public void Close() 
    { 
     // code to make the door close properly. 
    } 
} 

class RedUkTaxiDoor : BackwardDoor 
{ 
    public Color Color 
    { 
     get 
     { 
      return Color.Red; 
     } 
    } 
} 

Si usted es un taller de reparación de la puerta del coche, que no me importa cómo se ve la puerta, o si se abre de una manera u otra manera. Su único requisito es que la puerta actúe como una puerta, como IDoor.

class DoorRepairer 
{ 
    public void Repair(IDoor door) 
    { 
     door.Open(); 
     // Do stuff inside the car. 
     door.Close(); 
    } 
} 

El reparador puede manejar RedUkTaxiDoor, RegularDoor y BackwardDoor. Y cualquier otro tipo de puertas, como puertas de camiones, puertas de limusinas.

DoorRepairer repairer = new DoorRepairer(); 

repairer.Repair(new RegularDoor()); 
repairer.Repair(new BackwardDoor()); 
repairer.Repair(new RedUkTaxiDoor()); 

Aplicar esto para las listas, usted tiene LinkedList, Montón, cola, la lista de lo normal, y si usted quiere que su propia, MyList. Todos implementan la interfaz IList, que les exige implementar Agregar y Eliminar. Así que si la clase de añadir o eliminar elementos en cualquier lista dada ...

class ListAdder 
{ 
    public void PopulateWithSomething(IList list) 
    { 
     list.Add("one"); 
     list.Add("two"); 
    } 
} 

Stack stack = new Stack(); 
Queue queue = new Queue(); 

ListAdder la = new ListAdder() 
la.PopulateWithSomething(stack); 
la.PopulateWithSomething(queue); 
+0

¡Ningún problema! con respecto al lenguaje Java o C#, siempre que comprendamos. ¡Gracias! –

+0

En Java, la convención es usar Door y no IDoor para la interfaz. –

2

Allen Holub escribió un gran artículo para JavaWorld en 2003 sobre este tema llamado Why extends is evil.Su opinión sobre la declaración del "programa a la interfaz", como se puede deducir de su título, es que debería implementar interfaces felizmente, pero muy raramente usar la palabra clave extends en la subclase. Señala, entre otras cosas, lo que se conoce como el problema fragile base-class. De Wikipedia:

un problema de arquitectura fundamental de los sistemas de programación orientados a objetos, donde se consideran las clases base (superclases) "frágil" porque las modificaciones aparentemente seguro a una clase base, cuando se hereda por las clases derivadas, pueden hacer que la deriva clases para mal funcionamiento. El programador no puede determinar si un cambio de clase base es seguro simplemente examinando de forma aislada los métodos de la clase base.

2

"Programa a una interfaz" puede ser más flexible.

Por ejemplo, estamos escribiendo una impresora de clase que proporciona servicio de impresión. Actualmente hay 2 clases (Cat y Dog) que se deben imprimir. Así que escribimos código, como a continuación

class Printer 
{ 
    public void PrintCat(Cat cat) 
    { 
     ... 
    } 

    public void PrintDog(Dog dog) 
    { 
     ... 
    } 
    ... 
} 

¿Qué tal si hay una nueva clase Bird también necesita este servicio de impresión? Tenemos que cambiar la clase Printer para agregar un nuevo método PrintBird(). En el caso real, cuando desarrollamos la clase Impresora, es posible que no tengamos idea de quién la usará. Entonces, ¿cómo escribir Printer? Programa de una interfaz puede ayudar, ver más abajo código

class Printer 
{ 
    public void Print(Printable p) 
    { 
     Bitmap bitmap = p.GetBitmap(); 

     // print bitmap ... 
    } 
} 

Con esta nueva impresora, todo puede ser impreso, siempre que implementa la interfaz Printable. Aquí el método GetBitmap() es solo un ejemplo. La clave es exponer una interfaz, no una implementación.

Espero que sea útil.

+0

Ni siquiera puedo entender esta respuesta ... ¿de qué sirve? ¿Y en serio está diciendo que está bien escribir un código que expone 100 puntos de posible refactorización cuando podría (con bastante facilidad) exponer solo uno? –

+0

gracias @Andrew, he editado mi publicación, espero que esta vez sea mejor :). y también, el ejemplo de weiji es bueno, mi opinión anterior era incorrecta, mantener el código claro y refactorizar menos lugares siempre es una buena idea. – rockXrock

0

La programación basada en la interfaz proporciona la ausencia de un acoplamiento fuerte con un objeto específico en el tiempo de ejecución. Debido a que las variables del objeto Java son polimórficas, la referencia del objeto a la superclase puede referirse a un objeto de cualquiera de sus subclases. Los objetos que se declararon con supertipo pueden asignarse con objetos que pertenecen a cualquier implementación específica del supertipo.

Tenga en cuenta que, como interfaz, se puede utilizar una clase abstracta.

enter image description here

de programación basado en la aplicación:

Motorcycle motorcycle = new Motorcycle(); 
motorcycle.driveMoto(); 

de programación basado en la interfaz:

Vehicle vehicle; 
vehicle = new Motorcycle(); // Note that DI -framework can do it for you 
vehicle.drive(); 
0

Se trata básicamente de dónde realice un método/interfaz de la siguiente manera: create('apple') donde el método create(param) viene de una clase/interfaz abstracta fruit que es más tarde im plementado por clases concretas. Esto es diferente a la subclasificación. Está creando un contrato que las clases deben cumplir. Esto también reduce el acoplamiento y hace las cosas más flexibles donde cada clase concreta lo implementa de manera diferente.

El código de cliente no tiene conocimiento de los tipos específicos de objetos utilizados y desconoce las clases que implementan estos objetos. El código de cliente solo conoce la interfaz create(param) y la usa para hacer objetos de fruta.Es como decir: "No me importa cómo lo obtengas o lo haga, solo quiero que me lo des".

Una analogía de esto es un conjunto de botones de encendido y apagado. Esa es una interfaz on() y off(). Puede usar estos botones en varios dispositivos, un televisor, radio, luz. Todos los manejan de forma diferente, pero no nos importa eso, lo único que nos importa es encenderlo o apagarlo.

Cuestiones relacionadas