Aggregation und Komposition realisieren
-
Fingolfin schrieb:
void m_SetBetrag(const int& value); int m_GetBetrag(void);
Du tippst wohl gerne? Üblicher ist es jedenfalls, nur private Datenelemente mit m_ zu prefixen, ints direkt zu übergeben ("int value") und das zweite "void" ist auch nicht nötig.
Shade of Mine schrieb:
nein.
nur wenn du zeiger im sinne von ressourcen verwendest - jeder zeiger zeigt auf seinen (mit new/new[] angelegten) speicher.die meisten zeiger verwendet man aber nicht in diesem zusammenhang (zumindest ich nicht)
Hm, ich schon, zumindest scoped_ptr. Nur zum Beobachten habe ich eher Referenzen. Aber hier im Thread ging es imho auch eher um besitzende Zeiger, und die sind auch ohne Container-Verwendung schon problematisch genug:
Marc++us schrieb:
Aggregation, 0..* Elemente dyn. Array (vector<T*>, list<T*>) aus Zeigern auf Objektinstanzen
-
Fingolfin schrieb:
Danke für die Info, aber könntest du das vielleicht etwas genauer erklären? Mich würde schon interessieren, was daran so schlecht ist. *wißbegierig ist
C++ Templates - The Complete Guide schrieb:
A precursor of valarray had been designed with the intention that compilers aiming at amrket for scientific computation would recognize the array type and use highly optimized internal code for their operations. Such compilers would have "understood" the types in some sesnse. However, this never happend.
[..]Hier gehts jetzt darum, dass Vandervoorde vorgeschlagen hat, valarray mit expression templates zu implementieren[..]The valarray porposal came late in the C++ standardization process and practically rewrote all the text regarding valarray in the standard. It was rejected as a result, and instead a few tweaks were added to the existing text to allow implementations based on expression templates.[..]At time of this writing, no such implementation is known, and standard valarrays are, generally speaking, quite inefficient at performing the operations for which they where designed.das sagt wohl alles
[/quote]
-
operator void schrieb:
Aber hier im Thread ging es imho auch eher um besitzende Zeiger, und die sind auch ohne Container-Verwendung schon problematisch genug:
Marc++us schrieb:
Aggregation, 0..* Elemente dyn. Array (vector<T*>, list<T*>) aus Zeigern auf Objektinstanzen
Nope, bei einer Aggregation sind die Zeiger verweisend und nicht besitzend, da die Lebensdauer des Objekts nichts mit der Lebensdauer des Objekts zu tun hat, das die Aggregation besitzt. Die Speicherverwaltung des Objekts "macht jemand anderes".
-
@ShadeofMine
Danke für die Info
operator void:
Du tippst wohl gerne? Üblicher ist es jedenfalls, nur private Datenelemente mit m_ zu prefixen, ints direkt zu übergeben ("int value") und das zweite "void" ist auch nicht nötig.Oh, da ich gerade erst mit C++ anfange, wollte ich gleich auf Nummer sicher gehen, um später nicht umlernen zu müssen. Naja, manchmal schießt man halt über's Ziel hinaus.
Ich hätte da noch eine Frage:
Ich habe jetzt folgende Klasse;
class Konto { private: int m_Betrag; protected: public: void m_SetBetrag(const int& value); int m_GetBetrag(void); };
und:
class Person { private: std::vector<Konto> m_Konto; //Ich gehe davon aus, daß bei Tod der Person //die Konten aufgelöst werden, deswegen diese // Komposition public: void AddKonto(); };
Das ist jetzt also 'ne Komposition mit beliebig vielen Elementen. Klappt auch alles wunderbar.
Jetzt habe ich noch eine Frage.
Wie kann ich jetzt realisieren, daß ich per Konto an die Person rankomme, also quasi eine Komposition ohne Abhängigkeit in eine definierte Richtung.
Also z.B. so: konto.person. Ist das irgendwie möglich?Fingolfin
Edit: Frage hinzugefügt
-
Du kannst jedem Objekt von Konto einen Verweis auf das besitzende Objekt mitgeben. Das kann ein Zeiger oder eine Referenz (hier besser, da Kardinalität 1 vorliegt) auf ein Person-Objekt sein, das in der Klasse Konto vorhanden ist. Du mußt ja bedenken, daß eine Assoziation immer zwei Seiten hat - mit der Komposition deckst Du nur die Besitzer-Seite ab, aber Du kannst den Pfad auch von der anderen Seite her gehen (falls notwendig).
Dies wäre die Analyse-Sicht!
Jeder Programmierer wird wahrscheinlich aufheulen, da man hier eine unschöne "Überkreuz-Verbindung" bekommt, wahrscheinlich würde man sich dann während des Entwurfs dazu entschliessen, eine zusätzliche Assoziationsklasse einzuführen, die Konto mit Person verbindet (also eine dritte Klasse). Ich denke das führt hier aber zunächst zu weit.
---
Führst Du das Beispiel weiter aus wirst Du übrigens bemerken, daß die Komposition hier eine zu starke Kopplung ist - ein Konto wird ja nicht wirklich entfernt, nur weil der Besitzer stirbt. Es wird dann als geschlossen weitergeführt und noch einige Jahre in der Datenbank stehen. Ist in den kleinen Modellbeispielen ein oft gemachter Fehler, daß z.B. Person-Objekte zerstört werden, weil die Person stirbt. Das entspricht oft nicht der Realität, da die Objekte dann mit dem Attribut "verstorben" weiterleben (schöner Satz), damit man weiterhin einen korrekten Datenbestand hat. Denn schließlich gab es ja Zahlungen und Verweise auf den lebenden Besitzer, die werden ja nicht alle ungültig nur weil die Person heute gestorben ist. Aber damit macht auch wieder die 3. Klasse Sinn, die die Assoziationsklasse spielt (z.B. "AktivesKonto").
-
Marc++us schrieb:
Du kannst jedem Objekt von Konto einen Verweis auf das besitzende Objekt mitgeben. Das kann ein Zeiger oder eine Referenz (hier besser, da Kardinalität 1 vorliegt) auf ein Person-Objekt sein, das in der Klasse Konto vorhanden ist. Du mußt ja bedenken, daß eine Assoziation immer zwei Seiten hat - mit der Komposition deckst Du nur die Besitzer-Seite ab, aber Du kannst den Pfad auch von der anderen Seite her gehen (falls notwendig).
Dies wäre die Analyse-Sicht!
Jeder Programmierer wird wahrscheinlich aufheulen, da man hier eine unschöne "Überkreuz-Verbindung" bekommt, wahrscheinlich würde man sich dann während des Entwurfs dazu entschliessen, eine zusätzliche Assoziationsklasse einzuführen, die Konto mit Person verbindet (also eine dritte Klasse). Ich denke das führt hier aber zunächst zu weit.
Mir kann gar nix zu weit führen Könntest du diese "Konnektor-Klasse" vielleicht an meinem kleinen Beispiel darstellen? Ich bin kein Programmier-Anfänger, bloß einer in C++.
Marc++us schrieb:
Führst Du das Beispiel weiter aus wirst Du übrigens bemerken, daß die Komposition hier eine zu starke Kopplung ist - ein Konto wird ja nicht wirklich entfernt, nur weil der Besitzer stirbt. Es wird dann als geschlossen weitergeführt und noch einige Jahre in der Datenbank stehen. Ist in den kleinen Modellbeispielen ein oft gemachter Fehler, daß z.B. Person-Objekte zerstört werden, weil die Person stirbt. Das entspricht oft nicht der Realität, da die Objekte dann mit dem Attribut "verstorben" weiterleben (schöner Satz), damit man weiterhin einen korrekten Datenbestand hat. Denn schließlich gab es ja Zahlungen und Verweise auf den lebenden Besitzer, die werden ja nicht alle ungültig nur weil die Person heute gestorben ist. Aber damit macht auch wieder die 3. Klasse Sinn, die die Assoziationsklasse spielt (z.B. "AktivesKonto").
Das war mir schon klar, mir ist auf Anhieb nur kein besseres Beispiel eingefallen.
Fingolfin
-
Marc++us schrieb:
Nope, bei einer Aggregation sind die Zeiger verweisend und nicht besitzend, da die Lebensdauer des Objekts nichts mit der Lebensdauer des Objekts zu tun hat, das die Aggregation besitzt. Die Speicherverwaltung des Objekts "macht jemand anderes".
Oh, okay. Dann bezieht sich mein Posting halt nur auf die 0..1-Komposition (über einen scoped_ptr)
-
So, ich habe das jetzt per Pointer umgesetzt bekommen:
Erstmal die Klassen:
class Person; class Konto { private: int m_Betrag; protected: public: void m_SetBetrag(const int& value); int m_iGetBetrag(); Person *pParentPerson; Konto(Person *pPerson); ~Konto(); }; class Person { private: std::vector<Konto> m_Konten; public: void AddKonto(); };
Konstruktor von Konto:
Konto::Konto(Person *pPerson) { pParentPerson=pPerson; };
Implementierung von dem Hinzufügen eines Kontos in Person.
void Person::AddKonto() { Konto value(this); m_Konten.push_back(value); }
Jetzt funktionieren solche Späße tatsächlich.
Person.m_Konten[0].pParentKonto->m_iGetBetrag();
Klar brauche ich das in dem Fall nicht wirklich, aber einige Methoden der Klasse Konto müssen ja vielleicht auf Daten ihrer Person Zugriff erlangen.
Mich würde jetzt doch noch sehr dieselbe Realisierung per Referenz oder Verbindungsklasse interessieren. Oder vielleicht noch eine einfachere Heransgehensweise, falls es diese gibt.
Danke schonmal,
Fingolfin
-
Viel kann man mit Referenzen auch nicht rausholen:
class Konto { private: int m_Betrag; Person* m_Besitzer; // 1 public: explicit Konto(Person& Besitzer) // 2 : m_Besitzer(&Besitzer) { } int GetBetrag() const; // 3 void SetBetrag(int value); Person& Besitzer() const // 4 { return *m_Besitzer; } };
1: m_Besitzer weiterhin als Zeiger, damit man Konten gegenseitig zuweisen kann (mit Referenzen wäre das nicht möglich, weil man Referenzen selbst auch nicht zuweisen kann; std::vector will aber zuweisbare Elemente).
2: Konstruktor sollte explicit sein (siehe Buch oder Doku) und nimmt eine Referenz an (dadurch kann man kein Konto ohne Besitzer erstellen, weil es keine Nullreferenzen gibt).
3: GetBetrag sollte auf jeden Fall const sein, siehe http://www.gotw.ca/gotw/006.htm.
4: Nach außen hin bekommt man auch nur eine Referenz. Ob diese Funktion const sein sollte, hängt wohl vom Design ab... (?)
-
@operator void
Vielen Dank für die Infos. Sieht so aus als bliebe ich da eher bei Pointern, die verstehe ich mittlerweile wenigstens .
Interessieren würde mich dennoch die Assoziationsklasse von der Marc++us erzählt hat und inwiefern diese der "schönere" Weg wäre.
Vielen Dank schonmal,
Fingolfin
-
Bei Referenzen gibt es imho nicht viel zu verstehen - dafür hat man dann halt einen intuitiven Weg, "dieser Parameter darf nicht 0 sein"/"diese Funktion gibt nie 0 zurück" auszudrücken. Pointer dann halt nur, wo es sein muss.
Das mit der Verbindungsklasse würde mich aber auch interessieren
-
operator void schrieb:
Das mit der Verbindungsklasse würde mich aber auch interessieren
schaut euch zB das MEDIATOR Pattern an. In "Pattern Hatching" ist das gut beschrieben. aber bei google gibts sicher auch genug treffer.
-
Das gab's schon, bevor der Begriff "Design Pattern" eingeführte wurde... ist ein normaler Trick, wie man eine Assoziationsklasse im Entwurf realisiert. Das gab's noch zu den Zeiten von ADT und ADO mit Modula2... steht auch in "UML 2 für Dummies", Kapitel 14.
Mediator-Pattern ist in der Tat wohl der aktuelle Begriff dafür.
-
Danke schonmal, aber es wäre echt nett, wenn das mal jemand einfach an dem kleinen Konto-Person Beispiel quellcodeseitig zeigen könnte. Ich besitze keines der Bücher und per Google landet man dann doch eher bei wissenschaftlichem Material zu dem Thema.
Das wäre sehr nett,
Fingolfin
-
@operator void
Ich habe das mit der Referenz mal ausprobiert, um die Wartezeit auf die Assoziationsklasse zu verkürzen und bin dabei auf ein Problem gestoßen.
Ich ahbe alles so umgesetzt, wie du es gesagt hattest und meine bestehenden Funktionen (AddKonto usw.) nicht angerührt.Ich habe versucht folgendermaßen den Namen einer Person auszugeben:
cout<<"\n"<<my_person.m_aKonten[0].Besitzer.m_strGetName();
Wie gesagt ist das ein bissel blödes Beispiel, da das so in der Praxis selten benötigt wird, aber mich interessiert es dennoch.
Ich erhalte folgenden Fehler:
error: request for member ` m_strGetName' in `((&my_person) + 8)->std::vector<_Tp, _Alloc>::operator[](unsigned int) [with _Tp = Konto, _Alloc = std::allocator<Konto>](0)->Besitzer', which is of non-aggregate type ` <unknown type>'
Scheint ja irgendwie am vector zu hängen.
Vielen Dank schonmal,
Fingolfin
-
Besitzer**()**?
-
Marc++us schrieb:
Das gab's schon, bevor der Begriff "Design Pattern" eingeführte wurde...
natuerlich.
Design Pattern sind ja keine neue Inovationen, sondern geben den techniken bestimmte Namen.Ich finde es praktisch. Man muss nicht ewig erklaeren was man assoziationsklasse und Zwischenstufe meint, sondern sagt einfach: google mal nach Mediator Pattern
-
<imho>
Genau. Die Patterns sollen weniger Lösungsvorschläge für Designprobleme sein (auch wenn ich sie hauptsächlich so verwende und damit ganz sicher nicht der einzige bin), nach meinem Verständnis ist die Essenz der Design Patterns, Namen für gebräuchliche Praktiken zu finden, damit man darüber kommunizieren kann. Dahinter steckt die Theorie (oder der Wunsch?) dass sich Software aus solchen Mustern zusammensetzt, so dass man diese eigentlich nur alle identifizieren muss, und dann auf einer ganz anderen Ebene über Softwarekonstruktion sprechen kann.
</imho>
-
operator void schrieb:
Besitzer**()**?
Oh Mann, du hast natürlich recht. Das ist jetzt schon das zweite mal, daß ich Stunden mit dem Fehler verbringe. *sichselbstindenhinterntritt
Eine Frage hätte ich dann doch noch (wie immer )
Vorher (also mit Pointern) habe ich das Hinzufügen eines Kontos zu der Person folgendermaßen geregelt:
void Person::AddKonto() { Konto value(this); m_Konten.push_back(value); }
Wie erfolgt das denn jetzt bei den Referenzen? Ich erhalte auf die Art immer:
error: no matching function for call to `Konto::Konto(Person* const)' candidates are: Konto::Konto(const Konto&) error: Konto::Konto(Person&)
Wobei mir der Candidates error gar nix sagt, schließlich habe ich den Konstruktor exakt aus operator voids beispiel übernommen. Der müßte doch wenigstens 'ne Person erwarten und kein Konto.
Vielen Dank schonmal,
Fingolfin
-
(Oh, ich sollte mal besser lesen :))
Du musst this dereferenzieren, weil this ein Pointer und keine Referenz ist (warum auch immer?!).
Der andere Konstruktor ist der automatisch generierte Kopierkonstruktor (siehe Literatur deiner Wahl ).