2012-10-02 1 views
5

Ich arbeite an einer Legacy-Java-Anwendung, die sich mit "Obst" und "Gemüse" beschäftigt, sagen wir mal, um der Frage willen. Sie werden intern als verschiedene Dinge behandelt, weil sie nicht alle Methoden/Eigenschaften gemeinsam haben, aber viele Dinge sind DONE sehr ähnlich zu beiden.Was ist das richtige Design, um damit umzugehen?

Also, wir haben eine Menge von Methoden doSomethingWithAFruit (Frucht f) und doSomethingWithAVegetable (Veg v), die die ordnungsgemäße Verwendung doOtherStuffWithAFruit (Fruit f)/doOtherStuffWithAVeg (Veg v). Und diese sind sehr ähnlich, außer dass Methoden, die Dinge mit Früchten tun, nur die Methoden nennen, die Dinge mit Früchten tun, und das Gleiche für Gemüse.

Ich möchte dies umgestalten, um die Duplizierung zu reduzieren, aber ich bin mir nicht sicher, was der beste Weg ist, das zu erreichen. Ich habe ein bisschen über Designmuster gelesen, aber ich weiß nicht, ob es mir klarer geworden ist. (Ich kann einige Muster in dem Code erkennen, den ich benutze, aber ich weiß nicht, wann ich ein Muster anwenden sollte, um die Dinge zu verbessern. Vielleicht sollte ich mehr über das Refactoring selbst lesen ...)

Ich war Denken Sie an diese zwei Optionen:

1. Erstellen einer Klasse, die eine Instanz von entweder eine Frucht oder ein Gemüse haben kann und es an die Methoden weitergeben, versuchen, die Duplizierung zu minimieren. Es würde so gehen:

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

Diese Dinge ein bisschen besser werden würde, aber ich weiß nicht ... es immer noch falsch anfühlt.

2. Die andere Alternative, die ich dachte, war, eine Schnittstelle in Frucht und Gemüse mit dem Zeug zu setzen, das ihnen gemein ist, und dieses zu verwenden, um es herumzugeben. Ich denke, das ist die sauberere Herangehensweise, obwohl ich instanceof verwenden und zu Obst/Gemüse umwandeln muss, wenn es Sachen braucht, die für sie spezifisch sind.

Also, was kann ich hier noch tun? Und welche Mängel haben diese Ansätze?

UPDATE: Beachten Sie, dass die Frage ein wenig vereinfacht ist, ich suche nach Möglichkeiten, Dinge mit den "Pflanzen" zu tun, das heißt, Code, der sie meistens "nutzt", anstatt ihnen Dinge zu tun. Having said that, beziehen sich diese ähnliche Methoden, die ich nicht in den „Pflanzen“ Klassen sein können und sie in der Regel ein weiteres Argument haben, wie:

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

nämlich diese Methoden haben andere Bedenken neben Obst/Gemüse und aren‘ t wirklich geeignet, um in irgendeiner Obst-/Gemüseklasse zu sein.

UPDATE 2: Die meisten Code in diesem Verfahren lesen nur Zustand von den Obst/Gemüse-Objekten und in der Datenbank in dem entsprechenden Typ, Speicher nach Instanzen anderer Klassen erstellen und so weiter - aus meiner Antwort auf eine Frage in den Kommentaren, die ich denke, dass es wichtig ist.

+0

Was ist, wenn die Pflanze keine Frucht oder ein Gemüse ist? –

+0

Ich denke, Sie haben die richtige Idee, auf eine generische Schnittstelle zuzugehen. Lesen Sie auch über Sammlungen nach. Vielleicht erstellen Sie eine Klasse des Typs Definieren Sie alle Methoden, Objekte, Variablen, die Sie flexibel sein müssen, als dann machen Sie das in Ihrem Haupt/Laufzeit. –

+0

Wenn Sie gemeinsame Funktionen für beide implementieren können (ausgehend von den von Ihnen erwähnten Duplikaten), ist es besser, die abstrakte Klasse zu verwenden, da Sie die Implementierung auf der Klasse selbst durchführen können, was den doppelten Code auf der Unterklasse definitiv reduziert. – Jimmy

Antwort

1

Eine andere Option, die Sie verwenden oder vielleicht als Teil Ihrer Lösung hinzufügen können, besteht darin, den Benutzer zu fragen, ob er das Objekt, das Sie übergeben, verwalten kann. Zu diesem Zeitpunkt liegt es in der Verantwortung des Kunden sicherzustellen, dass er weiß, wie er mit dem Objekt, das Sie senden, umgehen kann.

Zum Beispiel, wenn Ihr Verbraucher Essen genannt wird, würden Sie so etwas wie:

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

.... etc 

Aber dann am Ende mit einer Menge davon/else Klassen, so dass Sie sich selbst eine Fabrik schaffen, die bestimmt, welche Verbraucher, den du willst. Ihre Factory führt im Grunde die Verzweigung if/else durch, um zu bestimmen, welcher Konsument das übergebene Objekt verarbeiten kann, und gibt den Konsumenten an Sie zurück.

So sieht es so etwas wie

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; 
    } 
} 

Dann wird Ihr Code wird:

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

Natürlich in der canProcess Methode des Verbrauchers, wäre es im Grunde eine instanceof oder eine andere Funktion Ihrer derive um festzustellen, ob es mit Ihrer Klasse umgehen kann.

Sie verschieben also die Verantwortung von Ihrer Klasse in eine Factory-Klasse, um festzustellen, welche Objekte bearbeitet werden können. Der Trick besteht natürlich darin, dass alle Verbraucher eine gemeinsame Schnittstelle implementieren müssen.

Ich realisiere, dass mein Pseudo-Code sehr einfach ist, aber es ist nur um die allgemeine Idee zu zeigen. Dies kann oder kann in Ihrem Fall nicht funktionieren und/oder wird abhängig von der Struktur Ihrer Klassen zu viel, kann aber die Lesbarkeit Ihres Codes erheblich verbessern und die Logik für jeden Typ in der eigenen Klasse enthalten ohne instanceof und wenn/dann überall verstreut.

+0

Hallo! Es wäre ein bisschen ein Overkill, dies für die Art von Methoden zu tun, mit denen ich gerade zu tun habe, aber ich werde wahrscheinlich einen Ort finden, an dem ich so etwas brauche, um eine gute Trennung zu erreichen. Danke vielmals! – elias

2

Ich denke, der zweite Ansatz wäre besser .. Design zu einer Schnittstelle ist immer eine bessere Art zu entwerfen .. Auf diese Weise können Sie leicht zwischen Ihrer Implementierung wechseln ..

Und wenn Sie Schnittstellen verwenden, werden Sie nicht Typumwandlung tun müssen, wie Sie leicht das Konzept der polymorphism ausnutzen können .. Das heißt, Sie `Basisklasse Referenz die auf eine Unterklasse-Objekt haben ..

aber wenn Sie nur Methoden gemeinsam fruits und vegetables in Ihrer Schnittstelle und spezifische Implementierung in Ihrer Implementierung Klasse halten wollen .. dann in diesem Fall typecasting erforderlich wäre ..

So können Sie eine generische Methode an der Schnittstelle haben Ebene .. Und spezifische Methode auf der Implementierungsebene ..

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, ich habe einen Code geändert haben eat() Methode, um Parameter des Typs zu nehmen Food .. Das wird nicht viel Unterschied machen .. Sie Vegetable Objekt Food Referenz passieren kann ..

+0

+1 Ich würde versuchen, 'Food' und' doSomething' haben verwandte Namen wie Schnittstelle 'Iterable' und Methode' Iterator() 'tun –

+1

@PeterLawrey .. haha ​​:) Danke für den Vorschlag .. Bearbeitet Code .. Jetzt ist es sieht gut aus :) –

+0

Hallo!Die Sache ist, der Code in den Methoden sind nicht wirklich Anliegen von Obst oder Gemüse. Ich habe meine Frage aktualisiert, um klarer zu sein. Danke für Ihre Aufmerksamkeit! :) – elias

0

würde ich Erstellen und abstrahieren Sie die Basisklasse (sagen wir - Essen, aber ich kenne Ihre Domain nicht, etwas anderes könnte besser passen) und beginnen Sie, Methoden nacheinander zu migrieren.

Falls Sie sehen, dass 'doSomethingWithVeg' und 'doSomthingWithFruit' etwas anders sind - erstellen Sie die Methode 'doSomething' an der Basisklasse und verwenden Sie abstrakte Methoden, um nur die Teile zu machen, die unterschiedlich sind (ich denke, die Hauptgeschäftslogik kann) vereinigt sein, und nur kleinere Probleme wie Schreiben in DB/Datei sind unterschiedlich).

Wenn Sie eine Methode bereit haben - testen Sie sie. Wenn du sicher bist, dass es in Ordnung ist, geh zu dem anderen. Wenn Sie fertig sind, sollten die Obst- und Gemüse-Klassen keine Methoden haben, außer den Implementierungen der abstrakten (die winzigen Unterschiede zwischen ihnen).

Hoffe es hilft ..

0

Dies ist eine grundlegende OOD-Frage. da Obst und Gemüse vom Typ Pflanze sind. Ich würde vorschlagen:

interface Plant { 
    doSomething(); 
} 

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

und das gleiche mit der Frucht.

Es scheint mir, dass DoOtherStuff-Methoden für die betreffende Klasse privat sein sollten.

0

Sie können auch in Erwägung ziehen, dass sie beide mehrere Schnittstellen anstelle von nur einer implementieren. Auf diese Weise codieren Sie die aussagekräftigste Schnittstelle gemäß den Umständen, was dazu beiträgt, Casting zu vermeiden. Art von was Comparable<T> tut. Es hilft den Methoden (wie denen, die Objekte sortieren), wo es ihnen egal ist, was die Objekte sind, die einzige Voraussetzung ist, dass sie vergleichbar sein müssen. z.B. In Ihrem Fall können beide einige Schnittstellen implementieren, die Edible genannt werden, dann nehmen Sie beide als Edible, wo ein Edible erwartet wird.

1

Wenn Sie Funktionen haben, die für Obst und Gemüse jeweils spezifisch sind, und ein Client, der beide Typen verwendet, unterscheidet (unter Verwendung von instanceof) - das ist ein Kohärenz-gegen-Kopplungsproblem.

Vielleicht überlegen, ob diese Funktionalität nicht in der Nähe von Obst und Gemüse selbst statt mit dem Kunden besser platziert ist. Der Client kann dann irgendwie auf die Funktionalität (über eine generische Schnittstelle) verwiesen werden, ohne sich darum zu kümmern, mit welcher Instanz er es zu tun hat. Der Polymorphismus würde zumindest aus der Sicht des Kunden erhalten bleiben.

Aber das ist theoretisch und möglicherweise nicht praktisch oder überentwickelt für Ihren Anwendungsfall. Oder Sie könnten am Ende nur instanceof woanders in Ihrem Design verstecken. instanceof wird eine schlechte Sache sein, wenn Sie anfangen, mehr Vererbungsgeschwister neben Obst und Gemüse zu haben. Dann würden Sie beginnen, die Open Closed Principle zu verletzen.

+0

Hmm ... Danke, sehr vernünftige Antwort. In meinem Fall ist es ziemlich sicher, dass es kein anderes Geschwister geben wird. Ich versuche nur, Dinge umzuorganisieren. Ich denke, ich gehe immer noch mit meiner ersten Option. – elias