Herzlich Willkommen, lieber Gast!
  Sie befinden sich hier:

  Forum » C / C++ (ANSI-Standard) » C++ - Trennen von Daten und Oberfläche in einer Konsole

Forum | Hilfe | Team | Links | Impressum | > Suche < | Mitglieder | Registrieren | Einloggen
  Quicklinks: MSDN-Online || STL || clib Reference Grundlagen || Literatur || E-Books || Zubehör || > F.A.Q. < || Downloads   

Autor Thread - Seiten: > 1 <
000
30.10.2003, 09:37 Uhr
proga



Hallo Zusammen !!!

Ich musste für ein Schulprojekt eine Konsolenanwendung schreiben, die zur Verwaltung von Kunden und Konten in einer Bank dient. Die Daten sollten in sequenziellen Dateien abgelegt werden ( und später bei Bedarf in einer Datenbank ). Von Anfang an wollte ich die Daten, die verwaltet werden, von den Ausgaben bzw. den Eingaben in dem Konsolenfenster trennen, so dass ich später die Klassen die der Verwaltung dienen, in einer GUI-Anwendung wiederverwenden kann. Als erstes habe ich ein Klassendiagramm gemacht, hier die grobe Beschreibung:

1. Es gibt eine Klasse Kunde, die ein Datenelement m_Kundenart hat.

Würde ich die Daten von den Ausgaben bzw. den Eingaben nicht trennen, so würde ich Privatkunde, VIP-Kunde und Geschäftskunde von Kunde ableiten, und die Methode erfassen in allen Unterklassen überschreiben.

2. Es gibt eine Klasse Konto, von der die Klassen GiroKonto, Sparkonto und Wertpapierdepot abgeleitet werden.

Würde ich die Daten von den Ausgaben bzw. den Eingaben nicht trennen, so würde ich auch hier die Methode Erfassen virtuell machen, und dann in den abgeleiteten Klassen diese überschreiben, um zusätzliche Daten, die das jeweilige Konto besitzt, zu erfassen.

Nun zum Problem 1: Die Konten werden in einer Liste (list<Ckonto *>) verwaltet. Trennt man die Daten von den Ausgaben bzw. den Eingaben, muss man in der Klasse Konto, die Kontoart als Attrubit mitschleppen, sodass ich später bei der Erfassung eines neuen Kontos, prüfen kann, um welche Art von Konto es sich handelt, eine Typenumwandlung von Konto zu der jeweiligen Kontoart (z.B. Sparkonto) mache, um dann die benötigten Set-Methoden, die die Klasse zusätzlich hat, aufrufen kann.

Problem 2: Wie verwalte ich die Kontodaten in Dateien (oder in einer Datenbank). Gibt es eine Datei Konto, manche Einträge dann leere Attributwerte haben, oder muss man die Dateien wie Klassen behandeln, spricht eine Kontendatei, eine Datei für GiroDaten, und eine für Sparkontodaten? Wie macht man so etwas?

Das bedeutet also, das ich in diesem Fall (Verwaltung der Konten) die Polymorphie nicht nutzen kann, und zusätzlich auch noch unsauber programmieren muss, um die Daten von den Ausgaben bzw. den Eingaben zu trennen. Meine Frage ist nun, lohnt sich das überhaupt, ist mein Ansatz richtig? Bitte um Eure Meinung, und um Vorschläge, wie ihr das machen würdet.

Sorry, dass es so lang geworden ist :rolleyes:

Danke im Voraus.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
001
30.10.2003, 11:00 Uhr
(un)wissender
Niveauwart


Also, ich würde schlicht und einfach folgendes machen:
Du nutzt schlicht und einfach polymorphie, wie du es beschreiben hast.


C++:
class Konto {
    friend ostream& operator<<(ostream &out, const Konto& konto);
    virtual ostream& ausgabe(ostream &out);
}

ostream& operator<<(ostream &out, const Konto& konto)
{
    return konto.ausgabe(); //Ausgabe virtuell machen!
}


class Girokonto : public Konto {
    virtual ostream& ausgabe(ostream &out);
}



Damit kannste einfach die Sachen wegspeichen, du schreibst dann mit Gorikonto bspw. "Klasse: Girokonto" als erstes in den Stream, dann weißt du beim einlesen, welche Kontoklasse du instantziieren musst.
Da haste natrürlich keine Poymorphie, sonder´n if Abfragen.
Das könntest du aber dann wieder im operator>> machen, der dann eine virtuelle Methode einlesen aufruft.
Natürlich muss du dazu wissen, wieviele Konten vom jeweiligen Typ du hast.

Hoffe dich richtig verstanden zu haben!
--
Wer früher stirbt ist länger tot.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
002
30.10.2003, 11:35 Uhr
ao

(Operator)


Also zuerst mal, die Trennung von User-Interface und eigentlicher Funktion ist eine sehr gute Idee, wenn man so Dinge wie Konsole / GUI oder Betriebssystem-Portierung vorhat.

Frage: wie kriegt man das sauber hin?

1. Ich würde die Basisklasse nicht Kunde nennen, sondern BasisKunde oder KundeBasis oder so, jedenfalls klarmachen, dass es sich um eine Basisklasse handelt.

2. Die Basisklasse bekommt ein typsicheres API für die Elemente, die allen Kunden gemeinsam sind (z.B. Name und Kontonummer).

3. Die Basisklasse abstrakt machen, d.h. nicht instanzierbar. Damit ist klar, dass es Ableitungen geben muss. Dann ist es meiner Meinung nach nicht unsauber, ein Interface zu den Ableitungen in die Basisklasse einzubauen, etwa so (alles ungetestet, nur so hingekritzelt):

Zuerst ein kleines Datenmodell:


C++:
class BasisKunde
{
private:
    string m_sName;
    string m_sKontonummer;
};

class PrivatKunde : public BasisKunde
{
private:
    string m_sArbeitgeber; // hier pfaenden wir das Gehalt, har har
};

class VIPKunde : public BasisKunde
{
private:
    string m_sLieblingssekt; // fuers persoenliche Geburtstagsgeschenk
    string m_sHobby; // moechte er lieber zum Segeln oder zum Golfen eingeladen werden, schleim schleim
};

class GeschaeftsKunde : public BasisKunde
{
    string m_sHandelsRegisterEintrag;
};



Dann erweitern wir BasisKunde um ein Interface zu den Attributen der abgeleiteten Klassen:


C++:
class BasisKunde
{
public:

    // Die Namen aller Attribute von der abgeleiteten Klasse holen
    virtual void ListeAttribute (list<string> & rlsAttribute) = 0;

    // Den Wert eines Attributs holen
    virtual string HoleAttribut (string sAttributName) = 0;

    // Den Wert eines Attributs setzen
    virtual void SetzeAttribut (string sAttributName, string sAttributWert) = 0;
};



Die Klasse PrivatKunde implementiert diese virtuellen Methoden folgendermaßen, die anderen Ableitungen entsprechend:


C++:
void PrivatKunde::ListeAttribute (list<string> & rlsAttribute)
{
    rlsAttribute.clear();
    rlsAttribute.push_back ("Arbeitgeber");
}

string PrivatKunde::HoleAttribut (string sAttributName)
{
    if (sAttributName == "Arbeitgeber")
        return m_sArbeitgeber;
    else
        return ""; // Attribut nicht gefunden.
}

void PrivatKunde::SetzeAttribut (string sAttributName, string sAttributWert)
{
    if (sAttributName == "Arbeitgeber")
        m_sArbeitgeber = sAttributWert;
}



Damit kannst du, wenn du ein BasisKunde-Objekt in der Hand hast, zuerst mit ListAttributes die Namen der Attribute abfragen, die auf der Bildschirmmaske angezeigt werden müssen, und dann per HoleAttribut und SetzeAttribut den Wert jedes Attributs abholen bzw. setzen.

Mit den verschiedenen Kontoarten kannst du genauso verfahren.

Der Aufbau der Dateistruktur ist ein internes Detail deines Programms, mit dem der Benutzer nicht in Berührung kommt. Darum gibts auch keine Regeln dafür, wie "man" so was macht.

Es könnte ein guter Ansatz sein, das ganze File-Interface in eine eigene Klasse zu packen, das erleichtert spätere Änderungen (z.B. den Umstieg auf eine Datenbank)

ao
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
003
04.11.2003, 08:31 Uhr
proga




Zitat:
(un)wissender postete
Also, ich würde schlicht und einfach folgendes machen:
Du nutzt schlicht und einfach polymorphie, wie du es beschreiben hast.


C++:
class Konto {
    friend ostream& operator<<(ostream &out, const Konto& konto);
    virtual ostream& ausgabe(ostream &out);
}

ostream& operator<<(ostream &out, const Konto& konto)
{
    return konto.ausgabe(); //Ausgabe virtuell machen!
}


class Girokonto : public Konto {
    virtual ostream& ausgabe(ostream &out);
}


Ich glaube du hast nicht beachtet, dass mein Haupziel war, GUI von Daten zu trennen, und damit fällt diese Möglichkeit, die du beschreibst, weg.

Zitat:
(un)wissender postete
Damit kannste einfach die Sachen wegspeichen, du schreibst dann mit Gorikonto bspw. "Klasse: Girokonto" als erstes in den Stream, dann weißt du beim einlesen, welche Kontoklasse du instantziieren musst.
Da haste natrürlich keine Poymorphie, sonder´n if Abfragen.
Das könntest du aber dann wieder im operator>> machen, der dann eine virtuelle Methode einlesen aufruft.
Ich möchte zum Speichern/Auslesen der Klassendaten die Methoden read und write von ifstream bzw. ofstream benutzen. Das bedeutet, dass die Klassenobjekte alle die selbe größe haben müssen, und das ist nicht der Fall, denn z.B. ein Girokonto hat mehr Daten als ein Sparkonto.

Zitat:
(un)wissender postete
Natürlich muss du dazu wissen, wieviele Konten vom jeweiligen Typ du hast.
Das weiss ich eben nicht, und es soll mir eigentlich egal sein

Dieser Post wurde am 04.11.2003 um 08:32 Uhr von proga editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
004
04.11.2003, 08:52 Uhr
proga



2ao

Zitat:
ao postete
...Dann ist es meiner Meinung nach nicht unsauber, ein Interface zu den Ableitungen in die Basisklasse einzubauen...

Ist es auch nicht Was ich mit unsauber meinte ist, dass man, bevor man die Set-/Get-Methoden der jeweiligen Klasse aufrufen kann, den Pointer auf die Basisklasse, auf einen Pointer der abgeleiteten Klasse casten muss. Und deswegen auch das Mitschleppen der Kundenart. Etwa so:

C++:
CKonto *pKonto = m_Liste.begin(); // z.B. den ersten Kunden aus der Liste
cout << pKunde->GetKundennummer() << endl;
cout << pKunde->GetName() << endl;
...
if (pKunde->GetKundenart() == kaVIP)
{
    CVIPKunde *pVIPKunde = static_cast<CVIPKunde *>(pKunde);
    cout << pVIPKunde->GetHobby();
}

Und das ist für mich eindeutig unsauberes Programmieren. Deinen Ansatz finde ich wirklich gut.
Und was die Dateiverwaltung angeht, ich habe mal irgendwo gelesen, dass es spezielle Verfahren gibt, wie man die Daten eines objektorientierten Programmes in einer relationalen Datenbank abbildet. Die Beschreibung war etwa so: Die Daten der Basisklasse in einer Tabelle ablegen, die zusätzlichen Daten der abgeleiteten in einer anderen Tabelle, die jedoch den gleichen Primärschlüssel bekommt. Z.B. Bein einem Privatkunden würde das etwa so aussehen:

Tabelle Kunde:
Kundennummer=123456,
Name="Schmitz",
Vorname="Jürgen",
Adresse="Paskalstr. 656"

Tabelle PrivatKunde:
Kundennnummer=123456,
Hobby="Sport"

Und darum habe ich auch gefragt, ob sich einer damit auskennt.

Dieser Post wurde am 04.11.2003 um 09:03 Uhr von proga editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
005
04.11.2003, 09:12 Uhr
virtual
Sexiest Bit alive
(Operator)



Zitat:
proga postete

C++:
CKonto *pKonto = m_Liste.begin(); // z.B. den ersten Kunden aus der Liste
cout << pKunde->GetKundennummer() << endl;
cout << pKunde->GetName() << endl;
...
if (pKunde->GetKundenart() == kaVIP)
{
    CVIPKunde *pVIPKunde = static_cast<CVIPKunde *>(pKunde);
    cout << pVIPKunde->GetHobby();
}

Und das ist für mich eindeutig unsauberes Programmieren.

Es geht auch anders:

C++:
CKonto *pKonto = m_Liste.begin(); // z.B. den ersten Kunden aus der Liste
cout << pKunde->GetKundennummer() << endl;
cout << pKunde->GetName() << endl;
...
CVIPKunde *pVIPKunde = dynamic_cast<CVIPKunde *>(pKunde);
if (NULL != pVIPKunde)
{
    cout << pVIPKunde->GetHobby();
}


Letztlich ist das aber auch nur eine kleine Verbesserung. Schlechtes Design ist es deshalb, weil Du im Programmfragment wissen mußt, welche Eigenschaften die verschiedenen Kundentypen haben (also das ganze if ist schlecht). Dieses Wissen gehört soweit wie möglich allein der jeweiligen Kundenklasse.
Entweder du hast einfach nur diese unflexible Ausgabe von Kunden über cout; dann solltest Du einen Ausgabeoperator für Kunden und abgeleitete Klassen definieren.
Oder Du suchst eine mehr generische Lösung, dann wirst Du vermutlich eine Viewklasse mit einem halbwegs abstraktem Interface bauen müssen, die dann auf ebenso generische Art die Properties der Kunden erfragt. Einen brauchbaren Ansatz hat ao schon geliefert. Fehlt nur noch ein entsprechender View.
--
Gruß, virtual
Quote of the Month
Ich eß' nur was ein Gesicht hat (Creme 21)
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
006
05.11.2003, 08:44 Uhr
proga



Danke für eure Hilfe, echt super dieses Forum
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
007
05.11.2003, 09:00 Uhr
(un)wissender
Niveauwart



Zitat:

Ich glaube du hast nicht beachtet, dass mein Haupziel war, GUI von Daten zu trennen, und damit fällt diese Möglichkeit, die du beschreibst, weg.



Hä?
komme ich jetzt nicht mit?
Mein Ansatz ist sicherlich nicht so durchdacht und ausführlich wie der von ao, aber mit der GUI hatte ich nun überhaupt nichts am Hut!
Will heißen, mein Ansatz (ist eigentlich nur ein kleiner Vorschlag) ist völlig unabhängig von irgendwelchen GUIs oder Datenbanken.
--
Wer früher stirbt ist länger tot.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
Seiten: > 1 <     [ C / C++ (ANSI-Standard) ]  


ThWBoard 2.73 FloSoft-Edition
© by Paul Baecher & Felix Gonschorek (www.thwboard.de)

Anpassungen des Forums
© by Flo-Soft (www.flo-soft.de)

Sie sind Besucher: