2009-07-03 12 views
14

Ich bin ein relativer Neuling, der in OOP-Begriffen denkt, und habe meinen 'Bauchgefühl' noch nicht gefunden, was den richtigen Weg betrifft. Als eine Übung versuche ich herauszufinden, wo Sie die Linie zwischen verschiedenen Arten von Objekten erstellen würden, indem ich die Getränke auf meinem Schreibtisch als Beispiel benutze.OOP. Objekte auswählen

Angenommen, ich erstelle ein Objekt Drink, die in Attribute wie volume und temperature und Methoden wie pour() und drink(), ich bin zu kämpfen, um zu sehen, wo bestimmte Getränke ‚Typen‘ kommen muss.

Sagen, ich habe einen Drink Typen von Tea, Coffee oder Juice, mein erster Instinkt ist die Unterklasse Drink, da sie Attribute und Methoden gemeinsam haben.

Das Problem wird dann sowohl Tea und Coffee Attribute haben wie sugars und milk, aber Juice nicht, während alle drei haben eine variant (Earl Grey, decaff und orange beziehungsweise).

ähnlich Tea und Coffee haben ein addSugar() Verfahren, während die keinen Sinn für ein Juice Objekt machen.

Also bedeutet das, dass die Super-Klasse diese Attribute und Methoden haben sollte, auch wenn sie nicht alle Unterklassen benötigen, oder definiere ich sie in den Unterklassen, speziell für Attribute wie variant, wo Jede Unterklasse hat ihre eigene Liste gültiger Werte?

Aber dann am Ende mit zwei addSugar() Methoden, auf den Tea und Coffee Unterklassen.

Oder vorausgesetzt, dass ich dann am Ende alle Attribute und Methoden auf die Super-Klasse setzen, wie die meisten von mindestens ein paar der Getränke-Typen geteilt werden, frage ich mich, was war der Sinn in Sub-Classing überhaupt?

Ich befürchte, ich versuche nur zu viel zu abstrahieren, aber ich möchte mich nicht in einer Ecke zurückhalten, wenn ich einen neuen Typ hinzufügen möchte, wie Water -mit variant still oder glitzernd-die Straße hinunter.

+0

Keine direkte Antwort auf Ihre Frage, aber ich fand es hilfreich: abgesehen von OOP Bücher und Quellen, suchen Sie nach * Refactoring * Material auch (z. B. M. Fowler Refactoring).Sie können konkrete Beispiele für Codesmells und entsprechende Aktionen finden, um sie zu beheben. Einige Beispiele ("Bedingtes Ersetzen durch Polymorphie", "Generalisierungstyp" usw.) zeigen Ihnen einige einfache Best Practices in OOP und können helfen, das Problem im Entwurf in der frühen Phase zu erkennen. – Groo

Antwort

13

Ein Teil des Problems besteht darin, dass reale Objekte nicht sauber in einer Hierarchie angeordnet sind. Jedes Objekt hat viele verschiedene Eltern. Ein Glas Saft ist sicherlich ein Getränk, aber es ist auch eine Nahrungsquelle - aber nicht jedes Getränk ist. Es gibt nicht viel Nahrung in einem Glas Wasser. Ebenso gibt es viele Nahrungsquellen, die keine Getränke sind. Aber die meisten Sprachen lassen Sie nur von einer Klasse erben. Eine Tasse Tee ist technisch eine Energiequelle (sie ist heiß, sie enthält Wärmeenergie), aber auch eine Batterie. Haben sie eine gemeinsame Basisklasse?

Und natürlich die Bonusfrage, was soll ich verhindern, dass ich Zucker in meinen Saft trinke, wenn ich will? Physisch ist das möglich. Aber es ist nicht konventionell gemacht. Was möchtest du modellieren?

Schließlich versuchen Sie nicht, "die Welt" zu modellieren. Modellieren Sie stattdessen Ihr Problem. Wie möchten Sie Ihre Anwendung zu Deal mit einer Tasse Tee? Welche Rolle spielt eine Tasse Tee bei Ihrer Bewerbung? Welche der vielen möglichen Eltern ist in Ihrem Fall sinnvoll?

Wenn Ihre Anwendung zwischen "Getränken, denen Sie Zucker hinzufügen können" und "Getränke, die Sie nur konsumieren können" unterscheiden muss, dann hätten Sie wahrscheinlich zwei unterschiedliche Basisklassen. Brauchen Sie sogar die gemeinsame "Getränkeklasse"? Vielleicht, vielleicht nicht. Einer Bar ist es vielleicht egal, dass es ein Getränk ist, dass es trinkbar ist. es ist ein Produkt, das verkauft werden kann, und in einem Fall müssen Sie den Kunden fragen, ob er Zucker darin haben möchte, und in dem anderen Fall ist das nicht notwendig. Aber die Basisklasse "Trinken" ist möglicherweise nicht notwendig.

Aber für die Person, die das Getränk trinkt, möchten Sie wahrscheinlich eine Basisklasse "Trinken" mit einer Consume() -Methode haben, denn das ist der wichtige Aspekt davon.

Schließlich, denken Sie daran, dass Ihr Ziel ist, ein funktionierendes Programm zu schreiben, und nicht, um das perfekte OOP-Klassendiagramm zu schreiben.Die Frage ist nicht, "wie kann ich verschiedene Arten von Getränken in OOP darstellen", sondern "wie kann ich meinem Programm ermöglichen, mit verschiedenen Arten von Getränken umzugehen". Die Antwort könnte sein, um sie in einer Klassenhierarchie mit einer gemeinsamen Drink-Basisklasse anzuordnen. Es könnte aber auch etwas anderes sein. Es könnte sein "alle Getränke gleich behandeln, und nur den Namen wechseln", in welchem ​​Fall eine Klasse reichlich ist. Oder es könnte sein, Getränke nur als eine andere Art von Verbrauchsmaterial oder nur als eine andere Art von Flüssigkeit zu behandeln, ohne ihre "trinkbare" Eigenschaft zu modellieren.

Wenn Sie einen Physiksimulator schreiben, dann sollten Sie vielleicht eine Batterie und eine Tasse Tee von der gleichen Basisklasse ableiten, da die Eigenschaft, die für Sie relevant ist, dass beide Energie speichern können. Und jetzt haben Saft und Tee plötzlich nichts gemeinsam. Sie könnten beide Getränke in einer anderen Welt sein, aber in Ihrer Bewerbung? Sie sind völlig verschieden. (Und bitte, keine Gewissensbisse darüber, wie Saft auch Energie enthält. Ich hätte ein Glas Wasser gesagt, aber dann würde jemand wohl Fusionskraft oder so etwas hervorbringen;))

Verliere nie dein Ziel aus den Augen. Warum müssen Sie Getränke in Ihrem Programm modellieren?

+0

Es wird um sich Zeit zu nehmen, dies zu verdauen, und die anderen Antworten auf meine ursprüngliche Frage, plus einige Hintergrundinformationen zu den aufgeworfenen Fragen, so kann ich nicht sagen, dass Sie alle das Problem gelöst haben, aber ich kann Ihre letzte Frage beantworten. Ich plane eine App, um Getränke Bestellungen für ein Büro, Baustelle, etc. zu nehmen, so würden Sie herumgehen, die Leute fragen, was sie wollten und am Ende mit einer Liste von wem was wollte enden. So könnte Ihre Bestellung aussehen wie: Tee, Milch und 2 Zucker für Alice und Bob. Tee, Milch, kein Zucker für Charlie. Kaffee, schwarz, ein Zucker für Dean Saft, Orange für Edward und Fiona. – creednmd

+0

Meine letzte Frage war zu deinem eigenen Vorteil, nicht für meine. :) Der Punkt ist, dass Sie entscheiden müssen, wie Sie diese Konzepte basierend auf Ihren eigenen Bedürfnissen modellieren und nicht auf einem universellen Konzept dessen, was ein Getränk "ist". Wie Neil sagte, kann man keine perfekte "platonische" Klassenhierarchie machen. Sie können nur ein Modell erstellen, das sich so verhält, wie Sie es benötigen. – jalf

+2

@Andy SO Kommentare sind kein Platz für komplexe Antworten, also kann ich einfach vorschlagen, dass Sie überhaupt keine Getränkehierarchie brauchen. Sie benötigen eine Liste von Personen und eine Zeichenfolge, die das gewünschte Getränk beschreibt. Dies liegt daran, dass es eine fast unendliche Vielfalt an möglichen Getränkeklassen gibt. –

13

Es gibt zwei Lösungen für das Sugart-Problem. Die erste ist eine Unterklasse wie HotDrinks hinzufügen Whcih die addSugar enthält und addMilk wie:

    Drink 
       /\ 
      HotDrink Juice 
     / \ 
     Tea  Coffee 

Das Problem ist, dass diese unordentlich wird, wenn es eine Menge von diesen sind.

Die andere Lösung besteht darin, Schnittstellen hinzuzufügen. Jede Klasse kann null oder mehr Schnittstellen implementieren. Sie haben also die ISweetened und ICowPowerEnabled-Schnittstellen. Sowohl Tee als auch Kaffee implementieren die ISweetened- und die ICowPowerEnabled-Schnittstelle.

+9

+1 für ICowPowerEnabled. Lol. – RCIX

17

Objektorientiertes Design wird nicht isoliert durchgeführt. Die Frage, die Sie stellen müssen, ist, was muss meine spezifische Anwendung (wenn überhaupt) mit einer Getränkeklasse tun? Die Antwort und somit das Design unterscheiden sich von Anwendung zu Anwendung. Der Weg Nummer 1 bei OO zu versagen ist zu versuchen, perfekte platonische Klassenhierarchien zu konstruieren - solche Dinge existieren nur in vollkommenen platonischen Welten und sind in der realen Welt, in der wir leben, nicht nützlich.

+0

Große Antwort. +1 – jalf

+2

+1 für den Aufruf von Plato. Hätten Sie auch die Höhlenwände erwähnt, wäre ich verrückt geworden. 8) – duffymo

+2

@duffymo Nun, das ist natürlich nur ein Schatten meiner wirklichen Antwort ... –

4

viel helfen Dieses pdf Sie könnten und es passt perfekt zu Ihrem Beispiel:

The Decorator Pattern

+1

@Nathan W, Ich habe den Link so bearbeitet, dass die Leute wissen, was du eigentlich empfiehlst (ich würde das Gleiche empfehlen) . Ich denke, du wirst wahrscheinlich mehr Stimmen bekommen :) –

+0

Cool, ich war besorgt, ich würde Stimmen für die "Hier lies gerade diese" Antwort bekommen –

1

Wenn Sie ein Neuling sind, würde ich keine Sorgen über nicht perfekt, Ihr Design-Muster zu bekommen. Tatsächlich würden viele argumentieren, dass es kein perfektes Designmuster gibt: P

Für Ihr Beispiel würde ich wahrscheinlich eine abstrakte Klasse für Getränke wie Tee und Kaffee erstellen (weil sie ähnliche Attribute teilen). Und versuchen Sie, die Getränkeklasse sehr wenige Attribute und Methoden zu haben. Wasser und Saft könnten sich von der abstrakten Getränkeklasse ableiten.

+0

sieht aus wie GameCat Beat mich dazu: P –

2

Zwei Dinge, die relevant sein könnten:

1) Neil Butterworth bringt sehr wichtige Unterscheidung zwischen einem Modell der realen Welt in einem bestimmten Kontext im Vergleich zu realer Welt selbst. Objektorientiertes Design wird manchmal auch als objektorientierte Modellierung bezeichnet. A model befasst sich nur mit den "interessanten" Aspekten der realen Welt. Was interessant ist, hängt vom Kontext Ihrer Anwendung ab.

2) Favour composition over inheritance. Ein Getränk hat Eigenschaften von Volumen und Temperatur (die in Ihrem Modellierungskontext möglicherweise relativ zur menschlichen Wahrnehmung sind, d. H. Extra kalt, kalt, warm, heiß) und besteht aus Zutaten (Wasser, Tee, Kaffee, Milch, Zucker, Aromen). Bitte beachten Sie, dass es nicht von Zutaten erbt, es ist aus ihnen gemacht. Soweit gute Wege zum Verfassen von Zutaten in die betreffenden Getränke versuchen decorator pattern.

1

Verwenden Sie Decorator Muster.

6

Um anderen Antworten hinzuzufügen, neigen Schnittstellen dazu, Ihnen größere Flexibilität bei der Entkopplung des Codes zu geben.

Sie müssen vorsichtig sein, ein Feature nicht mit einer Basisklasse zu verknüpfen, nur weil es allen abgeleiteten Klassen gemeinsam ist. Denken Sie eher darüber nach, ob diese Funktion extrahiert und auf andere, nicht verwandte Objekte angewendet werden kann - Sie werden feststellen, dass viele Methoden in Ihrer "echten" Basisklasse tatsächlich zu verschiedenen Schnittstellen gehören können.

Z. B. wenn Sie „die Zugabe von Zucker“ zu einem Getränke heute sind, können entscheiden, ob Sie es zu einem späterenPfannkuchen hinzuzufügen. Und dann könnten Sie am Ende mit Ihrem Code an vielen Stellen nach einem Drink fragen, mit AddSugar und verschiedenen anderen Methoden - es kann schwierig sein, sie umzugestalten.

Wenn Ihre Klasse weiß, wie etwas zu tun ist, kann sie die Schnittstelle unabhängig von implementieren, was die Klasse tatsächlich ist. Zum Beispiel:

interface IAcceptSugar 
{ 
    void AddSugar(); 
} 

public class Tea : IAcceptSugar { ... } 
public class Coffee : IAcceptSugar { ... } 
public class Pancake : IAcceptSugar { ... } 
public class SugarAddict : IAcceptSugar { ... } 

über eine Schnittstelle ein spezifisches Merkmal von dem Objekt anzufordern, die tatsächlich implementiert hilft es Ihnen, unabhängigen Code zu machen.

public void Sweeten(List<IAcceptSugar> items) 
{ 
    if (this.MustGetRidOfAllThisSugar) 
    { 
     // I want to get rid of my sugar, and 
     // frankly, I don't care what you guys 
     // do with it 

     foreach(IAcceptSugar item in items) 
      item.AddSugar(); 
    } 
} 

Ihr Code wird auch einfacher zu testen. Je einfacher die Schnittstelle ist, desto einfacher werden Sie die Verbraucher dieser Schnittstelle testen.

[Bearbeiten]

Vielleicht war ich nicht nicht ganz klar, so will ich klarstellen: Wenn AddSugar() macht die gleiche Sache für eine Gruppe von abgeleiteten Objekten, Sie werden es natürlich implementieren in ihrer Basisklasse. Aber in diesem Beispiel haben wir gesagt, dass Drink IAcceptSugar nicht immer implementieren muss.

So könnten wir mit einer gemeinsamen Klasse am Ende:

public class DrinkWithSugar : Drink, IAcceptSugar { ... } 

die AddSugar implementieren würde() die gleiche Art und Weise für ein paar Drinks.

Aber wenn Sie eines Tages erkennen, das ist nicht der beste Ort für Ihre Methode, müssen Sie andere Teile des Codes nicht ändern, wenn sie nur die Methode über die Schnittstelle verwenden.

Die Moral ist: Ihre Hierarchie kann sich ändern, solange Ihre Schnittstellen gleich bleiben.

+1

+1 "Verbraucher dieser Schnittstelle" ist in diesem Zusammenhang lustig. – duffymo

+0

Sie müssen für jedes Getränk, das die Schnittstelle implementiert, die AddSugar-Methode implementieren, wenn alle diese Getränke eine spezifische AddSugar-Basisdefinition haben sollten. Dies wird Sie in der Praxis zwingen, viel retititiven Code zu schreiben. –

+0

Sie müssen nie Code duplizieren. Nur Ihre Basisklasse muss AddSugar als Teil der Schnittstelle implementieren. Aber es kann sich herausstellen, dass eine der abgeleiteten Klassen perfekt zu allen anderen Methoden passt. Dann können Sie ganz einfach eine neue (zwischengeschaltete) Basisklasse erstellen, einige Methoden dorthin verschieben und Ihre Oberfläche für diejenigen, die wissen, wie sie implementiert werden, unverändert lassen. – Groo

1

Ein häufiger Fehler von neuen OO-Programmierern besteht darin, nicht zu erkennen, dass Vererbung für zwei verschiedene Zwecke, Polymorphismus und Wiederverwendung, verwendet wird. In einzelnen Vererbungssprachen müssen Sie normalerweise den Wiederverwendungsteil opfern und nur Polymorphie machen.

Ein weiterer Fehler ist die Verwendung der Vererbung. Während es eine gute Idee zu sein scheint, eine schöne Vererbungshierarchie zu entwerfen, ist es besser zu warten, bis ein polymorphes Problem auftaucht und dann die Vererbung erstellt.

Wenn Sie ein Problem haben, Klassen für 4 verschiedene Arten von Getränken zu erstellen, machen Sie 4 verschiedene Typen ohne Vererbung. Wenn Sie ein Problem bekommen, das besagt: "Ok, jetzt wollen wir einer Person diese Getränke geben und ausdrucken, was sie getrunken haben", dann ist das ein perfekter Polymorphismus. Fügen Sie eine Schnittstelle oder eine abstrakte Basisklasse mit einer Funktion drink() hinzu. Drücken Sie kompilieren, beachten Sie, dass alle 5 Klassen sagen, dass die Funktion nicht implementiert ist, implementieren Sie alle 5 Funktionen, drücken Sie kompilieren und Sie sind fertig.

Um Ihre Frage zu Ihrer Frage über das Hinzufügen von Milch oder Zucker, das Problem zu beantworten Ich gehe davon aus Sie laufen in ist, dass Sie ein Objekt Drinkable zu einem Person aber Drinkable übergeben wollen nicht addMilk oder addSugar hat. Dieses Problem kann auf zwei oder mehr Arten gelöst werden, einer ist ein sehr schlechter Weg, dies zu tun.

Die schlechte Art und Weise: Übergeben Sie eine Drinkable einen Person und eine Art Inspektion und Besetzung auf dem Objekt machen einen Tea oder Juice zu kommen.

void prepare (Drinkable drink) 
{ 
    if (drink is Juice) 
    { 
    Juice juice_drink = (Juick) drink; 
    juice_drink.thing(); 
    } 
} 

Eine Möglichkeit: Eine Möglichkeit ist, ein Getränk zu Person durch konkrete Klassen passieren und dann später zu trinken passieren.

class Person 
{ 
    void prepare_juice (Juice drink) 
    { 
    drink.thing1(); 
    } 
    void prepare_coffee (Coffee drink) 
    { 
    drink.addSugar(); 
    drink.addCream(); 
    } 
} 

Nachdem Sie die Person gebeten haben, das Getränk vorzubereiten, bitten Sie sie dann, es zu trinken.

0

Alles hängt von den Umständen ab - und das bedeutet wirklich - fangen Sie mit dem einfachsten Design an und refactor es, wenn Sie die Notwendigkeit sehen. Vielleicht würde es nicht sehr glänzend OOP aussehen - aber ich würde mit nur einer Klasse und einer Eigenschaft 'CanAddSugar' und 'SugarAmount' beginnen (später könnten Sie es zu einem Prädikat CanAddIngredient (IngredientName) verallgemeinern, so dass Menschen ihren Getränken Whiskey hinzufügen konnten:)

Es gibt eine tiefe Frage in all dem - welcher Unterschied zwischen zwei Objekten ist genug, um eine separate Klasse für sie zu erfordern? Ich habe keine gute Antwort darauf gesehen.