Anmelden

Archiv verlassen und diese Seite im Standarddesign anzeigen : 2 Klassen gleichzeitig verwalten



micha
24-02-2003, 18:58
Hi,

folgendes Problem:

Ich habe hier 2 Klassen Foo und Bar, die beide die gleichen Funktionen haben, allerdings auf anderen Bibliotheken aufbauen. Um das Ganze transparenter zu gestalten, hab ich mir gedacht, ich fasse beide Klassen in einer dritten namens FooBar zusammen, die eine zusätzliche Funktion setLibrary() hat, mit der man dann zwischen den 2 wechseln kann.


class Foo
{
public:
Foo () {}
~Foo() {}

void setText (const string &text) {}
};



class Bar
{
public:
Bar() {}
~Bar() {}

void setText (const string &text) {}
};




#include "foo.h"
#include "bar.h"

class FooBar
{
public:
FooBar () {}
~FooBar () {}

enum Library {Foo, Bar} {}

void setLibrary (const Library &lib) {}
void setText (const string &text) {}

private:
Foo *f;
Bar *b;
};


Wie kann ich es jetzt anstellen, das ich nicht in jedem Funktionsaufruf in FooBar überprüfen muss, an welche der 2 Klassen ich das weiterleiten muss ?

Wenn Foo und Bar eine gemeinsame Basisklasse (z.B. BarFoo, um die letztmögliche Kombination noch auszuschöpfen ;) ) hätten, die alle benötigten Funktionen enthält, dann könnte man in FooBar noch einen Zeiger auf BarFoo einfügen, dem man dann bei Aufruf von setLibrary() entweder den Zeiger von Foo oder Bar zuweist, und dann in den restlichen Funktionen nur noch mit BarFoo arbeitet.
Sollte möglich sein, oder gibts da noch bessere Möglichkeiten ?

Gruß micha

anda_skoa
24-02-2003, 20:08
Das was du beschreibst, ist glaub ich ein Object Adapter.

Ich hab mal ein bischen probiert :)
Ich denke das ist jetzt ein Class Adapter



#include <string>
#include <iostream>

using namespace std;

class Foo
{
public:
~Foo() { cerr << "~Foo" << endl; }

void setText(const string& text)
{
m_text = text;
}

string text() const
{
return m_text + "Foo";
}
private:
string m_text;
};

class Bar
{
public:
~Bar() { cerr << "~Bar" << endl; }

void setText(const string& text)
{
m_text = text;
}

string text() const
{
return m_text + "Bar";
}
private:
string m_text;
};

class FooBarIface
{
public:
virtual ~FooBarIface() {};

virtual void setText(const string& text) = 0;
virtual string text() const = 0;
};

class Foo2 : public FooBarIface, private Foo
{
public:
virtual ~Foo2() {cerr << "~Foo2" << endl;};

void setText(const string& text)
{
Foo::setText(text);
}

string text() const
{
return Foo::text();
}
};

class Bar2 : public FooBarIface, private Bar
{
public:
virtual ~Bar2() {cerr << "~Bar2" << endl;};

void setText(const string& text)
{
Bar::setText(text);
}

string text() const
{
return Bar::text();
}
};

class FooBar : public FooBarIface
{
public:
enum Type { Foo, Bar };
FooBar(Type t)
{
if (t == Foo)
d = new Foo2();
else
d = new Bar2();
}

virtual ~FooBar()
{
cerr << "~FooBar()" << endl;
delete d;
cerr << "d deleted" << endl;
}

void setText(const string& text)
{
d->setText(text);
}

string text() const
{
return d->text();
}

private:
FooBarIface* d;
};


int main()
{
FooBar f(FooBar::Foo);
FooBar b(FooBar::Bar);

f.setText("abc");
b.setText("abc");

cout << "f: " << f.text() << endl;
cout << "b: " << b.text() << endl;

return 0;
}


Das Interface wird public, die Implementation private vererbt.
Die Funktionen im Iface können natürlich auch anders heißen, als in den Klassen Foo und Bar.

Ciao,
_

micha
25-02-2003, 18:19
Hi,

erst mal danke für Deine Mühe ;)
Das Ganze ist mir aber ein bischen zu komplex, da wimmelts ja nur so von Klassen ;) Dazu kommt, das ich dann in allen Klassen die gleichen Funktionen stehen hab, was ziemlich unübersichtlich und schwer pflegbar wird, da es nicht bei dem setText () bleibt ( z.Z. gibts 32 Funktionen/Klasse). Dann muss ich noch die Klassen dynamisch wechseln können, deshalb fällt die Übergabe der Klasse als Parameter im Konstruktor weg.
Ich hab jetzt noch ein wenig getestet, und bin hierbei hängen geblieben:



#include <iostream>

using namespace std;

/*
*Basis Klasse, enthält alle gemeinsamen
*öffentlichen Funktion von foo und bar.
*Alle Funktionen sind wie setText() aufgebaut,
*und dienen nur zum Zwischenspeichern der Daten.
*Funktionen zum Lesen und Schreiben sind dann in
*foo und bar implementiert.
*/
class base
{
public:

base () {}
virtual ~base () {}

virtual void setText (const string &text) { _text = text; }
virtual bool read (const string &file) {}
virtual bool write (void) {}

private:

string _text;
};

/*
*foo stellt Schnittstelle zu
*Bibliothek Foo zur Verfügung.
*/
class foo : public base
{
public:

foo () {}
~foo () {}

bool read (const string &file)
{
/*Implementation*/
}
bool write (void)
{
/*Implementation*/
}

};

/*
*bar stellt Schnittstelle zu
*Bibliothek Bar zur Verfügung
*/
class bar : public base
{
public:

bar () {}
~bar () {}

bool read (const string &file)
{
/*Implementation*/
}
bool write (void)
{
/*Implementation*/
}
};


class foobar
{
public:

foobar () {}
~foobar () {}

enum Library { Foo, Bar };

void setLibrary (const Library &lib) { (Foo == lib) ? ba = &f : ba = &b; }
void setText (const string &text) { ba->setText (text); }
bool read (const string &file) { return (ba->read (file)); }
bool write (void) { return (ba->write()); }

private:

base *ba;
foo f;
bar b;
};

int main (void)
{
foobar *fb = new foobar();
fb->setLibrary (foobar::Foo);
fb->setText ("hello foo");
fb->setLibrary (foobar::Bar);
fb->setText ("hello bar");
delete (fb);
return (0);
}


Was hälst Du davon, kann man das so machen, oder ist das schlechter Stil ?

Gruß micha

anda_skoa
25-02-2003, 19:03
Wenn du natürlich eine neue gemeinsame Basisklasse einführen kannst, geht das mit wesenlich weniger Klassen.

Ich bin davon ausgegangen, dass man die beiden Libs nicht ändern kann.

Das mit dem Übergeben im Constructor war nur ein Beispiel, man kann auch im Betrieb umschalten, wenn nötig.
Man braucht ja nur beide Objekte anlegen und den d Pointer entsprechend setzen.

Allerdings sollte man sich beim Umschalten Gedanken über den Zustand der beiden Klassen machen.
Weil für den Benutzer der Wrapper Klasse, der Zustand seiner Instanz davon abhängt, was er vorhin in welchem Modus gemacht hat.
Das kann ziemlich schnell unübersichtlich werden.

Ciao,
_

micha
25-02-2003, 21:12
Original geschrieben von anda_skoa
Wenn du natürlich eine neue gemeinsame Basisklasse einführen kannst, geht das mit wesenlich weniger Klassen.

Ich bin davon ausgegangen, dass man die beiden Libs nicht ändern kann.


Sorry, man übersieht leicht etwas, wenn man aus einem Projekt ein Teilproblem darzustellen versucht. Im Gegensatz zum Leser ist man selber ja voll drin ;)



Das mit dem Übergeben im Constructor war nur ein Beispiel, man kann auch im Betrieb umschalten, wenn nötig.
Man braucht ja nur beide Objekte anlegen und den d Pointer entsprechend setzen.


Stimmt, ich hab das setLibrary() jetzt allerdings rausgenommen und entscheide bei read() selber, welche Bibliothek genommen werden muss (es geht um id3lib und vorbisfile, bei vorbis gibts ne extra test-Routine, die id3lib machts einem da schon schwerer ;) )



Allerdings sollte man sich beim Umschalten Gedanken über den Zustand der beiden Klassen machen.
Weil für den Benutzer der Wrapper Klasse, der Zustand seiner Instanz davon abhängt, was er vorhin in welchem Modus gemacht hat.
Das kann ziemlich schnell unübersichtlich werden.


Das stimmt. Allerdings trifft das Beispiel in main() nicht ganz die reale Benutzung. Initialisiert wird eine der beiden Bibliotheken wie oben beschrieben durch den Aufruf von read (); Bei einem erneuten Aufruf von read() werden die alten Einstellungen zuerst resettet, danach neu eingelesen (Geschieht dann, wenn in dem Programm, das die Bibliothek nutzt, eine neue mp3 oder ogg Datei eingelesen wird).
Zur Zeit hab ich das noch so gelöst, dass in jeder Funktion der status des Zeigers auf base abefragt wird (wenn eine andere Funktion vor read() aufgerufen wird, ist ba == NULL). Gefällt mir allerdings noch nicht wirklich.

Und im Übrigen gibts ja noch die Api-Dokumentation dazu ;)