2012-10-02 61 views
5

Estoy trabajando en una aplicación heredada de Java, que trata de "frutas" y "verduras", digamos, por el bien de la pregunta. Se tratan como cosas diferentes internamente, porque no tienen todos los métodos/propiedades en común, pero se HACEN muchas cosas muy similares a ambos.¿Cuál es el diseño adecuado para hacer frente a esto?

Por lo tanto, tenemos un montón de métodos doSomethingWithAFruit (fruta f) y doSomethingWithAVegetable (Veg v), que utilizan el correcto doOtherStuffWithAFruit (fruta f)/doOtherStuffWithAVeg (Veg v). Y esos son muy similares, excepto que los métodos que hacen cosas con frutas solo llaman a los métodos que hacen cosas con frutas, y lo mismo para las verduras.

Quiero refactorizar esto para reducir la duplicación, pero no estoy seguro de cuál es la mejor manera de lograrlo. He leído un poco sobre algunos patrones de diseño, pero no sé si me ha dejado más claro. (Puedo reconocer algunos patrones en el código que uso, pero realmente no sé cuándo debería aplicar un patrón para mejorar las cosas. Tal vez debería leer más acerca de la refactorización ...)

Estaba pensando en estas dos opciones:

1. Creando una clase que puede tener una instancia de una fruta o un vegetal y pásala a los métodos, tratando de minimizar la duplicación. Sería así:

public void doSomething(Plant p) { 
    // do the stuff that is common, and then... 
    if (p.hasFruit()) { 
     doThingWithFruit(p.getFruit()); 
    } else { 
     doThingWithVegetable(p.getVegetable()); 
    } 
} 

Esto mejoraría un poco las cosas, pero no sé ... todavía se siente mal.

2. La otra alternativa que pensé fue poner una interfaz en Fruit and Vegetable con las cosas que son comunes para ellos, y usar eso para pasarla. Siento que este es el enfoque más limpio, aunque tendré que usar instanceof y lanzarlo a Fruit/Vegetable cuando necesite algo que sea específico para ellos.

Entonces, ¿qué más puedo hacer aquí? ¿Y cuáles son las deficiencias de estos enfoques?

ACTUALIZACIÓN: Tenga en cuenta que la pregunta es un poco simplificada, estoy buscando la manera de hacer las cosas CON las "plantas", es decir, el código que en su mayoría "los utiliza" en lugar de hacerles las cosas. Una vez dicho esto, esos métodos similares I Referencia No se puede estar dentro de las "plantas" clases, y usualmente tienen otro argumento, como:

public void createSomethingUsingFruit(Something s, Fruit f); 
public void createSomethingUsingVegetable(Something s, Vegetable v); 

Es decir, esos métodos tienen otras preocupaciones, además de frutas/hortalizas, y aren' realmente apropiado para cualquier clase de Frutas/Vegetales.

ACTUALIZACIÓN 2: La mayoría del código en esos métodos sólo lee el estado de los objetos de frutas/vegetales, y crear instancias de otras clases de acuerdo con el tipo apropiado, almacenar en la base de datos y así sucesivamente - de mi respuesta a una pregunta en los comentarios que creo que es importante.

+0

¿Qué sucede si la planta no es una fruta o un vegetal? –

+0

Creo que tienes la idea correcta para dirigirte hacia una interfaz genérica. Lea sobre las colecciones también. Tal vez crees una clase de tipo definiendo cualquier método, objeto, variable que necesites para ser flexible como y luego hacer ese concreto en tu main/runtime. –

+0

Si puede implementar funciones comunes para ambos (suponiendo que se basen en los duplicados que mencionó), es mejor usar la clase abstracta ya que puede tener implementación en la clase en sí misma, lo que definitivamente reducirá el código duplicado en la subclase. – Jimmy

Respuesta

1

Otra opción que puede usar, o quizás incluir como parte de su solución, es preguntarle al consumidor si puede administrar el objeto que lo está pasando. En este punto, se convierte en responsabilidad del consumidor asegurarse de que sepa cómo manejar el objeto que lo está enviando.

Por ejemplo, si su consumo se llama Coma, que haría algo como:

Consumer e = new Eat(); 
Consumer w = new Water(); 
if(e.canProcess(myFruit)) 
    e.doSomethingWith(myFruit); 
else if (w.canProcess(myFruit)) 
    w.doSomethingWith(myFruit); 

.... etc 

Pero entonces se termina con un montón de él/clases más, así que se crea a sí mismo una fábrica que determina qué consumidor que quieres. Su fábrica básicamente realiza la bifurcación if/else para determinar qué consumidor puede manejar el objeto que pasa, y le devuelve el consumidor.

por lo que parece algo así como

public class Factory { 
    public static Consumer getConsumer(Object o){ 
    Consumer e = new Eat(); 
    Consumer w = new Water(); 
    if(e.canProcess(o)) 
     return e; 
    else if (w.canProcess(o)) 
     return w; 
    } 
} 

A continuación, el código se convierte en:

Consumer c = Factory.getConsumer(myFruit); 
c.doSomethingWith(myFruit); 

Por supuesto que en el método del consumidor canProcess, sería básicamente un instanceof o alguna otra función de su deriva para determinar si puede manejar su clase.

public class Eat implements Consumer{ 
    public boolean canProcess(Object o){ 
    return o instanceof Fruit; 
    } 
} 

Así que terminas transfiriendo la responsabilidad de tu clase a una clase de fábrica para determinar qué objetos se pueden manipular. El truco, por supuesto, es que todos los consumidores deben implementar una interfaz común.

Me doy cuenta de que mi pseudocódigo es muy básico, pero es solo para señalar la idea general. Esto puede o no funcionar en su caso y/o volverse excesivo dependiendo de cómo están estructuradas sus clases, pero si está bien diseñado, puede mejorar significativamente la legibilidad de su código, y mantener toda la lógica para cada tipo autocontenido en su propia clase sin instanceof y si/entonces se dispersa en todas partes.

+0

¡Hola! Sería un poco exagerado hacer esto por el tipo de métodos con los que estoy lidiando en este momento, pero probablemente voy a encontrar un lugar donde necesitaré algo así para lograr una buena separación. ¡Muchas gracias! – elias

2

Creo que el segundo enfoque sería mejor ... Diseñar en una interfaz siempre es una mejor manera de diseñar ... De esta manera puede cambiar fácilmente entre su implementación ...

Y si utiliza las interfaces, que no tendrá que hacer encasillado como se puede fácilmente explotar el concepto de polymorphism .. Es decir, tendrá que `base apuntando referencia de clase a un objeto subclase ..

pero si desea mantener sólo los métodos comunes a fruits y vegetables en su interfaz, y la aplicación específica en su clase de implementación .. Luego, en ese caso sería necesario typecasting ..

lo tanto, puede tener un método genérico en la interfaz nivel .. Y método más específico en el nivel de implementación.

public interface Food { 
    public void eat(Food food); 
} 

public class Fruit implements Food { 

    // Can have interface reference as parameter.. But will take Fruit object  
    public void eat(Food food) { 
     /** Fruit specific task **/ 
    } 
} 

public class Vegetable implements Food { 

    // Can have interface reference as parameter.. But will take Vegetable object 
    public void eat(Food food) { 
     /** Vegetable specific task **/ 
    } 
} 

public class Test { 
    public static void main(String args[]) { 
     Food fruit = new Fruit(); 
     fruit.eat(new Fruit()); // Invoke Fruit Version 

     Food vegetable = new Vegetable(); 
     vegetable.eat(new Vegetable()); // Invoke vegetable version 

    } 
} 

OK, he modificado un código para hacer eat() método para tomar parámetros de tipo Food .. Eso no hará mucha diferencia .. Puede pasar Vegetable objeto a Food referencia ..

+0

+1 Yo trataría de hacer que 'Food' y' doSomething' tengan nombres relacionados como la interfaz 'Iterable' y el método' iterator() 'do –

+1

@PeterLawrey .. haha ​​:) gracias por su sugerencia .. Código editado .. Ahora se ve bien :) –

+0

¡Hola!La cuestión es que el código en los métodos no son realmente preocupaciones de frutas o vegetales. He actualizado mi pregunta tratando de ser más claro. Gracias por su atención de todos modos! :) – elias

0

lo haría crear y abstraer la clase base (digamos - Comida, pero no conozco su dominio, otra cosa podría encajar mejor) y comenzar a migrar métodos a la misma una tras otra.

En caso de que vea que 'doSomethingWithVeg' y 'doSomthingWithFruit' son ligeramente diferentes, cree el método 'doSomething' en la clase base y use métodos abstractos para hacer solo las partes que sean diferentes (supongo que la lógica comercial principal puede estar unidos, y solo problemas menores como escribir en DB/archivo son diferentes).

Cuando tiene un método listo, pruébelo. Después de estar seguro de que está bien, vaya a la otra. Cuando hayas terminado, las clases de frutas y verduras no deberían tener ningún método, sino las implementaciones de las abstractas (las pequeñas diferencias entre ellas).

Espero que ayude ..

0

Esta es una pregunta básica de OOD. ya que las frutas y verduras son de tipo vegetal. que sugeriría:

interface Plant { 
    doSomething(); 
} 

class Vegetable { 
    doSomething(){ 
    .... 
    } 
} 

y lo mismo con la fruta.

me parece que los métodos doOtherStuff deben ser privados para la clase correspondiente.

0

También puede considerar hacer que ambos implementen múltiples interfaces en lugar de solo una. De esta forma, codificará contra la interfaz más significativa de acuerdo con las circunstancias que ayudarán a evitar la transmisión. Tipo de lo que Comparable<T> hace. Ayuda a los métodos (como los que ordenan objetos), donde no les importa cuáles son los objetos, el único requisito es que tengan que ser comparables. p.ej. en su caso, ambos pueden implementar algunas interfaces llamadas Edible, luego tome ambas como Edible donde se espera un Edible.

1

Si tiene una funcionalidad que es específica para frutas y verduras respectivamente y un cliente que usa ambos tipos tiene que distinguir (usando instanceof) - eso es un problema de coherencia vs. acoplamiento.

Quizás tenga en cuenta si dicha funcionalidad no está mejor ubicada cerca de frutas y vegetales en lugar de con el cliente. El cliente puede de alguna manera referirse a la funcionalidad (a través de una interfaz genérica) sin importar qué instancia está tratando. El polimorfismo se preservará al menos desde la perspectiva del cliente.

Pero eso es teórico y puede no ser práctico o estar sobrediseñado para su caso de uso. O podría terminar ocultando instanceof en otro lugar de su diseño. instanceof va a ser algo malo cuando empiece a tener más hermanos herederos junto a frutas y verduras. Entonces comenzarías a violar el Open Closed Principle.

+0

Hmm ... Gracias, respuesta muy razonable. En mi caso, es bastante seguro que no habrá otro hermano. Solo estoy tratando de reorganizar cosas. Creo que todavía voy con mi primera opción. – elias

Cuestiones relacionadas