¿Por qué las interfaces son más útiles que las superclases para lograr un acoplamiento suelto?

15

( Para el propósito de esta pregunta, cuando digo 'interfaz' me refiero al lenguaje de construcción interface , y no una 'interfaz' en el otro sentido de la palabra, es decir, los métodos públicos que una clase ofrece al mundo exterior para comunicarse y manipularlo. )

El acoplamiento suelto se puede lograr haciendo que un objeto dependa de una abstracción en lugar de un tipo concreto.

Esto permite un acoplamiento suelto por dos razones principales: 1- es menos probable que las abstracciones cambien que los tipos concretos, lo que significa que es menos probable que el código dependiente se rompa. 2- se pueden usar diferentes tipos de hormigón en el tiempo de ejecución, porque todos se ajustan a la abstracción. También se pueden agregar nuevos tipos concretos más adelante sin necesidad de alterar el código dependiente existente.

Por ejemplo, considere una clase Car y dos subclases Volvo y Mazda .

Si su código depende de un Car , puede usar un Volvo o un Mazda durante el tiempo de ejecución. También más adelante, se podrían agregar subclases adicionales sin necesidad de cambiar el código dependiente.

Además, Car , que es una abstracción, es menos probable que cambie que Volvo o Mazda . Los autos han sido generalmente los mismos durante bastante tiempo, pero es mucho más probable que cambien Volvos y Mazdas. Es decir. Las abstracciones son más estables que los tipos concretos.

Todo esto fue para demostrar que entiendo qué es el acoplamiento flexible y cómo se logra al depender de abstracciones y no de concreciones. (Si escribí algo inexacto, por favor dígalo).

Lo que no entiendo es esto:

Las abstracciones pueden ser superclases o interfaces.

Si es así, ¿por qué se elogia específicamente a las interfaces por su capacidad para permitir el acoplamiento suelto? No veo qué tan diferente es usar una superclase.

Las únicas diferencias que veo son: 1- Las interfaces no están limitadas por una sola herencia, pero eso no tiene mucho que ver con el tema del acoplamiento suelto. 2- Las interfaces son más "abstractas" ya que no tienen ninguna lógica de implementación. Pero aún así, no veo por qué eso hace una gran diferencia.

Por favor, explícame por qué se dice que las interfaces son excelentes para permitir el acoplamiento suelto, mientras que las superclases simples no lo son.

    
pregunta Aviv Cohn 26.04.2014 - 20:27
fuente

3 respuestas

11

Terminología: me referiré al lenguaje constructivo interface como interfaz , ya la interfaz de un tipo u objeto como superficie (por falta de un mejor término).

  

El acoplamiento suelto se puede lograr haciendo que un objeto dependa de una abstracción en lugar de un tipo concreto.

Correcto.

  

Esto permite un acoplamiento suelto por dos razones principales: 1 : las abstracciones tienen menos probabilidades de cambiar que los tipos concretos, lo que significa que es menos probable que el código dependiente se rompa. 2 : se pueden utilizar diferentes tipos concretos en el tiempo de ejecución, porque todos se ajustan a la abstracción. También se pueden agregar nuevos tipos concretos más adelante sin necesidad de alterar el código dependiente existente.

No del todo correcto. Los lenguajes actuales generalmente no anticipan que una abstracción cambiará (aunque hay algunos patrones de diseño para manejar eso). Separar detalles específicos de cosas generales es abstracción. Esto generalmente se hace mediante alguna capa de abstracción . Esta capa se puede cambiar a otros específicos sin romper el código que se basa en esta abstracción: se logra un acoplamiento suelto. Ejemplo no OOP: una rutina sort puede cambiarse de Quicksort en la versión 1 a Tim Sort en la versión 2. El código que solo depende del resultado que se está ordenando (es decir, se basa en la abstracción sort ) se desacopla del real implementación de clasificación.

Lo que he denominado superficie anterior es la parte general de una abstracción. Ahora sucede en OOP que un objeto a veces debe admitir múltiples abstracciones. Un ejemplo no muy óptimo: el java.util.LinkedList de Java admite tanto la interfaz List que trata sobre la abstracción de "colección ordenada e indexable", como la interfaz Queue que (en términos generales) trata sobre la abstracción "FIFO" .

¿Cómo puede un objeto soportar múltiples abstracciones?

C ++ no tiene interfaces, pero tiene herencia múltiple, métodos virtuales y clases abstractas. Una abstracción puede definirse como una clase abstracta (es decir, una clase que no puede ser instanciada inmediatamente) que declara, pero no define métodos virtuales. Las clases que implementan las características específicas de una abstracción pueden heredar de esa clase abstracta e implementar los métodos virtuales necesarios.

El problema aquí es que la herencia múltiple puede llevar al problema de diamante , donde el orden en que se buscan las clases para una implementación de método (MRO: orden de resolución de método) puede llevar a "contradicciones". Hay dos respuestas a esto:

  1. Defina una orden sana y rechace aquellas órdenes que no pueden ser linealizadas con sensatez. El C3 MRO es bastante sensato y funciona bien. Fue publicado en 1996.

  2. Tome la ruta fácil y rechace la herencia múltiple en todo.

Java tomó la última opción y eligió la herencia de comportamiento único. Sin embargo, todavía necesitamos la capacidad de un objeto para admitir múltiples abstracciones. Por lo tanto, se deben usar interfaces que no admitan definiciones de métodos, solo declaraciones.

El resultado es que el MRO es obvio (solo mire cada superclase en orden), y que nuestro objeto puede tener múltiples superficies para cualquier número de abstracciones.

Esto resulta ser bastante insatisfactorio, porque a menudo un poco de comportamiento es parte de la superficie. Considere una interfaz Comparable :

interface Comparable<T> {
    public int cmp(T that);
    public boolean lt(T that);  // less than
    public boolean le(T that);  // less than or equal
    public boolean eq(T that);  // equal
    public boolean ne(T that);  // not equal
    public boolean ge(T that);  // greater than or equal
    public boolean gt(T that);  // greater than
}

Esto es muy fácil de usar (una API agradable con muchos métodos convenientes), pero tedioso de implementar. Nos gustaría que la interfaz solo incluya cmp , e implemente los otros métodos automáticamente en términos de ese método requerido. Mixins , pero lo más importante es los rasgos [ 1 ], [ 2 ] resuelve este problema sin caer en las trampas de la herencia múltiple .

Esto se hace definiendo una composición de rasgos para que los rasgos no terminen realmente participando en la MRO, en lugar de eso, los métodos definidos se componen de la clase implementadora.

La interfaz Comparable podría expresarse en Scala como

trait Comparable[T] {
    def cmp(that: T): Int
    def lt(that: T): Boolean = this.cmp(that) <  0
    def le(that: T): Boolean = this.cmp(that) <= 0
    ...
}

Cuando una clase usa ese rasgo, los otros métodos se agregan a la definición de la clase:

// "extends" isn't different from Java's "implements" in this case
case class Inty(val x: Int) extends Comparable[Inty] {
    override def cmp(that: Inty) = this.x - that.x
    // lt etc. get added automatically
}

Entonces, Inty(4) cmp Inty(6) sería -2 y Inty(4) lt Inty(6) sería true .

Muchos idiomas tienen algún soporte para los rasgos, y cualquier idioma que tenga un "Protocolo de Metaobjeto (MOP)" puede tener rasgos agregados. La actualización reciente de Java 8 agregó métodos predeterminados que son similares a los rasgos (los métodos en las interfaces pueden tener implementaciones alternativas, por lo que es opcional implementar clases para implementar estos métodos).

Desafortunadamente, los rasgos son un invento bastante reciente (2002) y, por lo tanto, son bastante raros en los idiomas generales más grandes.

    
respondido por el amon 26.04.2014 - 21:59
fuente
4
  

Lo que no entiendo es esto:

     

Las abstracciones pueden ser superclases o interfaces.

     

Si es así, ¿por qué se elogia específicamente a las interfaces por su capacidad para permitir el acoplamiento suelto? No veo qué tan diferente es usar una superclase.

Primero, los subtipos y la abstracción son dos cosas diferentes. Subtitular simplemente significa que puedo sustituir valores de un tipo por valores de otro tipo, ninguno de los dos debe ser abstracto.

Más importante aún, las subclases dependen directamente de los detalles de implementación de su superclase. Ese es el tipo de acoplamiento más fuerte que existe. De hecho, si la clase base no está diseñada teniendo en cuenta la herencia, los cambios en la clase base que no cambian su comportamiento aún pueden romper subclases, y no hay manera de saber a priori si la ruptura ocurrira. Esto se conoce como el problema de clase base frágil .

La implementación de una interfaz no te une a nada excepto a la interfaz en sí misma, que no contiene ningún comportamiento.

    
respondido por el Doval 27.04.2014 - 00:14
fuente
1

Existe un acoplamiento entre las clases padre e hijo, ya que el niño depende del padre.

Supongamos que tenemos una clase A, y la clase B se hereda de ella. Si entramos en la clase A y cambiamos las cosas, la clase B también se cambia.

Supongamos que tenemos una interfaz I y la clase B la implementa. Si cambiamos la interfaz I, entonces aunque la clase B podría no implementarla más, la clase B no cambia.

    
respondido por el Michael Shaw 26.04.2014 - 21:41
fuente

Lea otras preguntas en las etiquetas