2010-01-26 14 views
8

¿Cómo se trata de tener solo una herencia única en Java? Aquí está mi problema específico:Problema de diseño de herencia múltiple en Java

tengo tres clases (simplificado):

public abstract class AbstractWord{ 
    String kind; // eg noun, verb, etc 

    public String getKind(){ return kind; } 

} 

public class Word extends AbstractWord{ 
    public final String word; 

    ctor... 

    public void setKind(){ 
     // based on the variable word calculate kind.. 
    } 
} 

public class WordDescriptor extends AbstractWord{ 

    ctor.. 

    public void setKind(String kind){this.kind = kind;} 

} 

Esto es lo que considero mi aplicación más básica, pero quiero hacer otras implementaciones.

Digamos que quiero agregar una nueva variable, digamos wordLength, pero quiero agregarla usando la herencia. Lo que significa que NO quiero modificar esa clase original de AbstractWord. Es decir, algo a lo largo de las líneas de esto:

public class Length{ 
    private int length; 

    public int getLength(){return length}; 
} 

public class BetterWord extends AbstractWord AND Length{ 

    public void setLength(){ 
     // based on the variable word calculate Length.. 
    } 
} 

public class BetterWordDescriptor extends AbstractWord AND length{ 

    public void setLength(int length){this.length = length;} 
} 

sé que Java no me deja hacer esto, pero ha hecho mi código muy feo. En este momento cada vez que agrego un campo, simplemente lo agrego a AbstractWord, pero luego necesito cambiar el nombre de AbstractWord (y Word y WordDescriptor). (No puedo simplemente agregar el campo al otro debido a la compatibilidad con versiones anteriores, es igual a métodos y cosas por el estilo).

Esto parece ser un problema de diseño muy común, pero me he dado cuenta y no puedo encontrar ninguna solución hermosa.

¿Hay algún patrón de diseño que solucione esto? Tengo algunas soluciones potenciales, pero quería ver si había algo que me faltaba.

gracias, Jake

Actualización: La longitud se refiere al número de sílabas de la palabra (lo siento por la falta de claridad)

+0

¿El 'Length' tiene que ser básico una clase en lugar de una interfaz? –

+0

@Loadmaster: Length es una clase y no una interfaz, por lo que no es necesario duplicar los métodos de longitud en Word y WordDescriptor. Por longitud, estos métodos son simples pero para otras cosas podrían ser muy complejos. – sixtyfootersdude

+0

@Jake: Si otras cosas pueden ser muy complejas, definitivamente debe usar "Patrón de estrategia". –

Respuesta

9

Favor sobre la herencia.

La solución tiene en cuenta que podría haber otro tipo de palabra que pueda necesitar WordLengthSupport.

De forma similar, podrían crearse e implementarse otras interfaces y varios tipos de palabras pueden tener mezcla y coincidencia de esas interfaces.

.

public class WordLength { 
    private int length = 0; 
    public int getLength(){return length}; 
    public void setLength(int length){this.length = length}; 
} 

.

public interface WordLengthSupport { 
    public WordLength getWordLength(); 
} 

.

public class BetterWord extends AbstractWord 
     implements WordLengthSupport { 
    WordLength wordLength; 
    public WordLength getWordLength() { 
     if(wordLength==null) { 
      // each time word changes 
      // make sure to set wordLength to null 
      calculateWordLength(); 
     } 
     return wordLength; 
    } 
    private void calculateWordLength() { 
     // This method should be 
     // called in constructor 
     // or each time word changes 
     int length = // based on the variable word calculate Length.. 
     this.wordLength = new WordLength(); 
     this.wordLength.setLength(length); 
    } 
} 

.

public class BetterWordDescriptor extends AbstractWord 
     implements WordLengthSupport { 
    WordLength wordLength; 
    public WordLength getWordLength(return wordLength); 
    public void setWordLength(WordLength wordLength) { 
     // Use this to populate WordLength of respective word 
     this.wordLength = wordLength; 
    } 
} 

.

El patrón de estrategia define una familia de algoritmos, encapsula cada uno y los hace intercambiables. La estrategia permite que el algoritmo varíe independientemente de los clientes que lo usan.

Esta solución no usa el patrón de estrategia pero se puede refactorizar para el mismo.

+0

+1 Guau, esa es una solución increíble, no muy intuitiva, pero creo que debería funcionar bastante limpia. – sixtyfootersdude

+0

Su función, WordLength privado calculateWordLength(), no devuelve un valor y debe ser nulo. –

+0

Eso es correcto crosvenir. Escribí esto en el bloc de notas por lo que no hay comprobación de sintaxis. Gracias por señalarlo, lo actualizaré. –

2

Con su ejemplo específico que podría utilizar el patrón decorator en conjunción con interfaces para complementar su clase Word con funcionalidad adicional; p.ej.

// *Optional* interface, useful if we wish to reference Words along with 
// other classes that support the concept of "length". 
public interface Length { 
    int getLength(); 
} 

// Decorator class that wraps another Word and provides additional 
// functionality. Could add any additional fields here too. 
public class WordExt extends AbstractWord implements Length { 
    private final Word word; 

    public class(Word word) { 
    this.word = word; 
    } 

    public int getLength() { 
    return word.getKind().length(); 
    } 
} 

Además, cabe señalar que la falta de herencia múltiple en Java no es realmente el problema aquí; es más un caso de volver a trabajar su diseño. En general, se considera una mala práctica sobreutilizar la herencia, ya que las jerarquías de herencia profunda son difíciles de interpretar/mantener.

+0

WordExt tiene dos copias del tipo de atributo. Primero "objWordExt.kind" heredado de AbstractWord. Segundo "objWordExt.word.kind" que obtiene de la palabra variable de instancia. –

+0

Consideré usar algo así, pero en realidad no funcionará en este caso porque algunos métodos de longitud son bastante largos y complicados. Usando el decorador necesitaría implementarlos tanto en la clase Word como en la clase WordDescriptor. Gracias por la sugerencia, sin embargo. – sixtyfootersdude

+0

@GB: Buen punto. Normalmente definiría una palabra de interfaz que WordExt implementaría para obtener una copia redundante de "kind". – Adamski

0

(No puedo simplemente agregar el campo al otro debido a la compatibilidad con versiones anteriores, es igual a los métodos y cosas por el estilo).

No se interrumpirá la compatibilidad de la fuente. No, a menos que estés haciendo algo realmente loco en tus métodos iguales.

Y cambiar el nombre de sus clases generalmente no es la manera de manejar la compatibilidad binaria.

+0

Los iguales para WordDescriptor se basan en todos los campos => comparabilidad inversa rota. – sixtyfootersdude

+0

Creo que encontrará que si dos WordDescriptors son iguales, tendrán la misma longitud de palabra independientemente. Entonces no, no rompe la compatibilidad de fuente. –

3

sólo tiene que utilizar la composición en lugar de la herencia:

un BetterWordes-unAbstractWord que tiene-unLength:

public class BetterWord extends AbstractWord { 

    private Length length; 

    public void setLength(int value){ 
     length.setLength(value); 
    } 
} 

EDITAR

Si la API necesita una objeto de tipo Longitud, solo agregue un captador:

public class BetterWord extends AbstractWord { 

    private Length length; 

    public void setLength(int value){ 
     length.setLength(value); 
    } 

    public Length getLength() { 
     return length 
    } 
} 

o cambiar el nombre de la aplicación a LengthLengthImpl y definir una interfaz Length, debido a que una clase puede implementar múltiples interfaces.

+0

@Andreas, totalmente de acuerdo con el uso de "has-a" versus "is-a", pero hubiera pensado que configurar la longitud de la palabra era algo que no se modificaría para la duración del objeto de una palabra en particular. –

+0

Si hay una API que necesita un objeto de tipo Longitud, esto no funcionará. Vea mi solución a continuación. –

2

Al verlo, mi primer sentimiento es que su modelo es un poco complicado.

Una palabra tiene una cadena para describir la palabra misma que se almacena en el objeto Word junto con una clase para decir que es un sustantivo, verbo, adjetivo, etc. Otra propiedad de una palabra es la longitud de la cadena almacenada en Objeto de Word

Piense en las cosas en términos de relaciones "is-a" y "has-a" y puede eliminar una gran cantidad de complejidad.

Por ejemplo, ¿por qué necesita un WordDescriptor que extienda AbstractWord? ¿Va a cambiar una palabra de un verbo a un adjetivo? Hubiera pensado que el tipo de palabra se configuró cuando el objeto se creó y no cambiaría durante la vigencia del objeto Word. Una vez que tenía un objeto Word para la palabra "Australia", el tipo de palabra no cambiaría durante la vigencia del objeto.

Hmmm. Tal vez podría tener un objeto Word que represente la palabra "ladrar" después de crear el objeto con un tipo de "verbo" para describir el sonido que hace un perro. Entonces te das cuenta de que realmente necesitas tener el objeto Word para representar un nombre que describe la cobertura de un árbol. Posible, pero tanto el ladrido del perro como la corteza del árbol pueden existir.

Creo que el modelo que ha elegido es demasiado complicado y su pregunta se puede resolver volviendo atrás y simplificando su modelo de objeto original.

Comience haciéndose una pregunta por cada uno de los aspectos de herencia de su modelo básico.

Cuando digo que la clase B extiende la clase A, ¿puedo decir que la clase B "es-una" clase A y que estoy especializando su comportamiento?

Por ejemplo, una clase base Animal se puede ampliar para proporcionar la clase especializada de canguro. Entonces puede decir que "el canguro" es un "Animal. Usted está especializando el comportamiento."

Luego mire los atributos, un canguro tiene un atributo de Ubicación para describir dónde se encuentra. Luego puede decir un canguro "has-a" location. Un Kangaroo "is-a" no tiene sentido

De manera similar, una palabra "tiene-una" longitud. Y la declaración de una palabra "is-a" doesn 'length doesn' tiene sentido.

BTW ¡Todas las referencias australianas en este post son deliberadas para celebrar el Día de Australia que es hoy 26 de enero! composición

HTH

+0

Te deseo feliz día de Australia. Estoy de acuerdo con tus comentarios sobre WordDescriptor. Será mejor si AbstractWord "tiene-un" campo (con getter y setter) WordDescriptor. –

0

El problema no es "cómo lidiar con la herencia individual". Lo que te estás perdiendo no es realmente un patrón de diseño, sino que aprendes a diseñar el API por separado de la implementación.

Me gustaría ponerlo en práctica, así:

public interface WordDescriptor { 
    void getKind(); 
    Word getWord(); 
} 

public interface Word { 
    String getWord(); 
} 

public class SimpleWord implements Word { 
    private String word; 

    public SimpleWord(String word) { this.word = word; } 
    public String getWord() { return word; } 
} 

public class SimpleWordDescriptor implements WordDescriptor { 
    private Word word; 
    private String kind; 

    public SimpleWordDescriptor(Word word, String kind) { 
     this.word = word; 
     this.kind = kind; // even better if WordDescriptor can figure it out internally 
    } 

    public Word getWord() { return word; } 

    public String getKind() { return kind; } 
} 

Con esta configuración básica, cuando se quiere introducir una propiedad de longitud, todo lo que tiene que hacer es lo siguiente:

public interface LengthDescriptor { 
    int getLength(); 
} 

public class BetterWordDescriptor extends SimpleWordDescriptor 
            implements LengthDescriptor { 
    public BetterWordDescriptor(Word word, String kind) { 
     super(word, kind); 
    } 

    public int getLength() { getWord().length(); }   
} 

Las otras respuestas que usa la composición de las propiedades, así como el patrón Decorator, también son soluciones completamente válidas para su problema. Solo necesita identificar cuáles son sus objetos y cuán "composables" son, y cómo deben usarse, por lo tanto, primero diseñe la API.

+0

Gracias por el comentario, sin embargo, tal vez no estaba claro qué era un WordDescriptor. Un descriptor de palabras no tiene una palabra. Un WordDescriptor puede describir muchas palabras. Por ejemplo, un WordDescriptor = sustantivo puede referirse a todos los sustantivos. Por lo tanto, no tendría sentido si un WordDescriptor contiene una palabra. – sixtyfootersdude

0

/** * Primer ejemplo */

class FieldsOfClassA { 
    public int field1; 
    public char field2; 
} 

interface IClassA { 
    public FieldsOfClassA getFieldsA(); 
} 

class CClassA implements IClassA { 
    private FieldsOfClassA fields; 

    @Override 
    public FieldsOfClassA getFieldsA() { 
     return fields; 
    } 
} 

/** 
* seems ok for now 
* but let's inherit this sht 
*/ 


class FieldsOfClassB { 

    public int field3; 
    public char field4; 
} 

interface IClassB extends IClassA { 

    public FieldsOfClassA getFieldsA(); 
    public FieldsOfClassB getFieldsB(); 
} 

class CClassB implements IClassB { 

    private FieldsOfClassA fieldsA; 
    private FieldsOfClassB fieldsB; 

    @Override 
    public FieldsOfClassA getFieldsA() { 
     return fieldsA; 
    } 

    @Override 
    public FieldsOfClassB getFieldsB() { 
     return fieldsB; 
    } 
} 

/**

  • wow este monstruo se hizo más grande

  • imaginar que necesitará 4 lvl de la herencia

  • que tomaría mucho tiempo para escribir este infierno

  • que aun no estoy hablando de que el usuario de los iface de pensará

  • qué campos i tendrá fieldsA fieldsB fieldsC u otro

Así

composición no funciona aquí y sus intentos patéticos son inútiles

Cuando u reflexionar acerca de la programación orientada a oject

necesitas modelos GRANDES con 6-7 lvls de herencia múltiple

porque es una buena prueba y porque corresponde a los modelos de la vida real o los modelos matemáticos probados por la civilización durante 4 mil años.

Si sus modelos requieren 2 lvl de parada de la herencia que pretenden usar u OO

U puede implementar fácilmente con cualquier idioma, incluso un procedimiento como el lenguaje C o */