¿Qué significa progtwigr en una interfaz?

Sigo escuchando la statement en la mayoría de los sitios relacionados con la progtwigción:

Progtwig a una interfaz y no a una Implementación

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

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

Probablemente estés 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 hacerlo de la primera manera? 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 debe 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; } 

¡Esto también debería ser cambiado!

 public LinkedHashSet getBuddies() { return buddies; } 

Esperemos que vea, incluso con un progtwig pequeño como este tiene implicaciones de largo scope sobre lo que declara el tipo de la variable. Con los objetos yendo y viniendo tanto, definitivamente ayuda a que el progtwig 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 solo esto:

 public Set getBuddies() { return buddies; } 

También hay otro beneficio, en eso (al menos para mí) la diferencia me ayuda a diseñar un progtwig mejor. Pero espero que mis ejemplos te den una idea ... espero que ayude.

Un día, su jefe le indicó a un progtwigdor 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 progtwigdor 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 progtwigdor volvió a progtwigr. Había código para leer datos comerciales de XML salpicado en su aplicación. Reescribió todos esos fragmentos, envolviéndolos con una condición “si”:

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

Cuando se le presentó la nueva versión del software, el jefe respondió: “Eso es genial, pero ¿también puede informar datos comerciales de este servicio web?” Recordando todas esas tediosas declaraciones si tuviera que volver a escribir OTRA VEZ, el progtwigdor 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 proporcionarlo”

En ese momento, el progtwigdor se iluminó .

Mi lectura inicial de esa statement es muy diferente a cualquier respuesta que haya leído todavía. 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 statement significa para mí.

Mi opinión es que te está diciendo que escribas código que solo depende de qué interfaz (en este caso, estoy usando “interfaz” para referirme a los métodos expuestos de una clase o tipo de interfaz) que estás usando dice que lo hace en el 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() . 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 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 mirando la implementación de Draw() que internamente llama a 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.

Espero ver si alguien más comparte esta interpretación de la frase en la pregunta del OP, o si estoy completamente fuera de la base …

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 el módulo 2 buscará la información de la cuenta bancaria desde el “back-end” utilizado.

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), el Module1 podrá para 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 al proporcionar la información según la interfaz definida, pero no se preocupará de dónde se mostrará, se imprimirá o se imprimirá esta información …

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 recomstackdos / reconstruidos, pero no necesitan tener su lógica modificada en de todas formas.

Esa es la idea de una API.

Una interfaz define los métodos que un objeto está obligado a responder.

Cuando codifica en la interfaz , puede cambiar el objeto subyacente y su código seguirá funcionando (porque su código es independiente de QUIÉN realiza el trabajo o CÓMO se realiza el trabajo). Obtiene flexibilidad de esta manera.

Cuando codifica una implementación en particular, si necesita cambiar el objeto subyacente, es muy probable que su código se rompa , porque es posible que el nuevo objeto no responda a los mismos métodos.

Entonces para poner un ejemplo claro:

Si necesita sostener varios objetos, podría haber decidido usar un Vector .

Si necesita acceder al primer objeto del Vector, puede escribir:

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

Hasta aquí todo bien.

Más tarde decidió que, por “alguna” razón, necesita cambiar la implementación (digamos que el Vector crea un cuello de botella debido a una sincronización excesiva)

Se da cuenta de que necesita usar ArrayList instad.

Bueno, tu código se romperá …

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

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

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 (es decir, no hay nada especial con el Vector otro que tiene el método firstElement ()) 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 está codificando el método get de Vector , sino el método get de List .

No importa cómo realiza el método el objeto subyacente, siempre que responda al contrato de “obtener el 0 ° elemento de la colección”

De esta manera, más tarde puede cambiarlo a cualquier otra implementació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 ingenua, pero es la base sobre la que se basa la tecnología OO (incluso en aquellos idiomas que no están tipados estáticamente, como Python, Ruby, Smalltalk, Objective-C, etc.)

Un ejemplo más complejo es la forma en que funciona JDBC . 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 tienes que probar tu producto antes, pero al menos no tienes cosas como:

  statement.executeOracle9iSomething(); 

vs

 statement.executeOracle11gSomething(); 

Algo similar ocurre con Java Swing.

Lectura adicional:

Principios de diseño de patrones de diseño

Artículo efectivo de Java: hacer referencia a los objetos por sus interfaces

(Comprar este libro es una de las mejores cosas que podrías hacer en la vida, y leer si por supuesto)

En esencia, esta statement es realmente acerca de las dependencias. Si IBar mi clase Foo en una implementación ( Bar lugar de IBar ), ahora Foo depende de Bar . Pero si codigo mi clase Foo en una interfaz ( IBar lugar de Bar ), 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.

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.

Tome un bloque Lego rojo de 2×4 y conéctelo a un bloque azul Lego de 2×4 para que uno se coloque encima del otro. Ahora quite el bloque azul y reemplácelo con un bloque amarillo Lego de 2×4. 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 2×4 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.

Poder permitir que las implementaciones varíen sin cambiar el cliente o el servidor; eso es lo que significa progtwigr en las interfaces.

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

Todos los autos 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 reparador de puertas de automóviles, no le importa cómo se ve la puerta, o si se abre de una manera u otra. Su único requisito es que la puerta actúe como una puerta, como IDoor.

 class DoorRepairr { 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.

 DoorRepairr repairr = new DoorRepairr(); repairr.Repair( new RegularDoor() ); repairr.Repair( new BackwardDoor() ); repairr.Repair( new RedUkTaxiDoor() ); 

Aplique esto para las listas, tiene LinkedList, Stack, Queue, la lista normal, y si quiere la suya, MyList. Todos implementan la interfaz IList, que les exige implementar Agregar y Eliminar. Entonces, si su clase agrega o elimina 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); 

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

un problema arquitectónico fundamental de los sistemas de progtwigción orientados a objetos donde las clases base (superclases) se consideran “frágiles” porque las modificaciones aparentemente seguras a una clase base, cuando son heredadas por las clases derivadas, pueden causar el mal funcionamiento de las clases derivadas. El progtwigdor no puede determinar si un cambio de clase base es seguro simplemente examinando de forma aislada los métodos de la clase base.

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

Usted progtwig 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 progtwigr en una interfaz. Cuando utiliza 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 progtwigció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 progtwigció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.

“Progtwig 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 clase nueva Bird también necesita este servicio de impresión? Tenemos que cambiar la clase de 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 la Printer ? El progtwig a una interfaz puede ayudar, vea el código a continuación

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

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

Espero que sea útil.

Esencialmente, las interfaces son la representación un poco más concreta de los conceptos generales de interoperación: proporcionan la especificación de lo que deberían hacer todas las diversas opciones que podría interesar “conectar” para una función en particular, de modo que el código que las utiliza no sea depende de una opción particular.

Por ejemplo, muchas bibliotecas de 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 utiliza la biblioteca de DB.

En general, le permite crear un código que es más flexible, brindando a sus clientes más opciones sobre cómo lo usan y, también, potencialmente, lo que le permite reutilizar el código más fácilmente en varios lugares en lugar de tener que escribir un nuevo código especializado.

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

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 usas IEnumerable Foo(IList mylist) lugar de ArrayList Foo(ArrayList myList) por ejemplo.

Use la implementación solo cuando construya el objeto:

 IList list = new ArrayList(); 

Si ha hecho esto, más tarde puede cambiar el tipo de objeto, tal vez quiera usar LinkedList en lugar de ArrayList más adelante, esto no es problema, ya que en cualquier otro lugar lo denomina simplemente “IList”.

La progtwigción basada en la interfaz proporciona la ausencia de un fuerte acoplamiento 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, puede usarse una clase abstracta.

enter image description here

Progtwigción basada en la implementación:

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

Progtwigción basada en la interfaz:

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

Básicamente, se trata de crear un método / interfaz como este: create( 'apple' ) donde el método create(param) proviene de una fruit abstracta de clase / interfaz que luego es implementada 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 del cliente permanece inconsciente de los tipos específicos de objetos usados ​​y permanece inconsciente de las clases que implementan estos objetos. El código del cliente solo conoce la interfaz create(param) y lo usa para crear 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.