Wie folgendes sauber designen
-
Ein objekt kann entweder eine Datei oder ein Verzeichnis sein. Jenachdem kann man verschiedene Dinge mit dem Objekt machen.
Wie krig ich das in C++ sauber hin.
Soetwas, wie
class DirectoryOrFile { void fileAction () { if (isDirectory()) { throw "Funktioniert nur mit Dateien"; } ... // eigentliche Aktion } ... };ist kacke.
Aber eine Hierarchie ist besser, aber auch nicht das wahre
class DirectoryOrFile { ... }; class File : public DirectoryOrFile { void fileAction () { ... // eigentliche Aktion } ... }; class Directory : public DirectoryOrFile { ... };Angenommen ich will mit einem DirectoryOrFile-Objekt arbeiten, dann muss ich jedesmal dynamic_casten oder was auch immer, jedenfalls den Laufzeittyp bestimmen. Beispielsweise könnte ich eine Verzeichnis-Stuktur durchlaufen wollen, um bestimmte Dateien zu suchen, oder einfach um die Stuktur auszugeben oder was auch immer.
In Scala ging das ganze mit case classes und pattern matching wunderbar. Vielleicht bin ich auch genau aus dem Grund gerade zu verblendet und der Ansatz ist grundsätzlich daneben.
-
Im Prinzip ist dein zweiter Ansatz ja schon mal i.O. Dieses fileAction() ist aber dummerweise nichtssagend, deshalb ist das ganze etwas komisch. Denk dir doch mal ein paar richtige Aktionen aus. Z.B. kann ich sowohl ein File als auch Directory löschen. Also machste eine delete() in die Basis rein. Du könntest auch getChildren() in die Basis machen. Wenn es ein File ist, gibt getChildren halt immer null Elemente zurück oder wirft eine Exception. Und ein Directory halt >=0 Elemenete.
class DirectoryOrFile { public: void delete(); virtual list<DirectoryOrFile> getChildren() = 0; string name(); }; class File : public DirectoryOrFile { public: list<DirectoryOrFile> getChildren() { return list<DirectoryOrFile>; // 0 Elemente }; class Directory : public DirectoryOrFile { private: list<DirectoryOrFile> _content; public: list<DirectoryOrFile> getChildren() { return _content; };So würdest du ohne Casts auskommen. Ich würde aber die Basisklasse anders benennen, da es so ziemlich auf zwei Typen eingeschränkt ist.
Ein anderer Ansatz ist es, so wie boost::filesystem zu machen. Wie die das machen, kannst du ja in deren Doku nachlesen.
Es gibt aber nicht nur ein einzige richtiges Design.
-
Tjoa wie gesagt es gibt mehrere Ansätze. Eine andere Möglichkeit wäre noch, dass du es wie z.B. in Java machst.
Da gibt es eine File-Klasse, die sowohl eine Datei als auch ein Directory darstellt. Da sind dann eben so allgemeine Methoden drinnen wie löschen, testen ob die Datei bzw. das Directory schon existiert usw...
Wenn du dann z.B. Dateien schreiben/lesen willst benutzt du dazu halt andere Klassen (in Java z.B. InputStream/OutputStream) die dann mit diesen File-Objekten arbeiten.
-
Naja, das öffnen eines Files würde er ja dann trotzdem mit fstrean machen, auch wenn er jetzt das so macht. Javas File-Klasse ist ja praktisch das was er da oben bzw. ich gemacht haben.
-
Klar, ähnelt sich ja auch alles ein bisschen. Aber bei Java hast du eben einfach diese File-Klasse, und das wars. D.h. man muss da nicht noch groß rumvererben. Hier hast du ja eine Klasse für File und eine für Directory.
-
Ja, aber ich als Anwender der Klasse, interessiere mich nicht dafür ob es verebt wurde. Ich benutze eh nur die Basisklasse. Das ist ja das tolle an Polymorphie, das ich beim coden nicht mit speziellen Klassen hantieren muß. Ich arbeite ja auch nur mit der DirectoryOrFile Klasse. Ob es zur Laufzeit eine vererbte ist, ist mir ja egal.
Du gehst jetzt vom Standpunkt des Designers aus. Aber das ist ja sein Bier. Wer garantiert dir denn eigentlich, das in Java 1.6 java.io.File nicht auch nur noch als Basisklasse fungiert, und SUN ebend mal zwei spezielle Klasse von File abgeleitet hat? Das wirst du garnicht merken, wenn du da nicht explizit nachschaust. Der Fall ist jetzt nur hypothetisch aber nicht unrealistisch.
-
Helium schrieb:
Angenommen ich will mit einem DirectoryOrFile-Objekt arbeiten, dann muss ich jedesmal dynamic_casten oder was auch immer, jedenfalls den Laufzeittyp bestimmen. Beispielsweise könnte ich eine Verzeichnis-Stuktur durchlaufen wollen, um bestimmte Dateien zu suchen, oder einfach um die Stuktur auszugeben oder was auch immer.
Natürlich, aber was ist so furchtbar tragisch daran? Außerdem hindert dich niemand daran für bestimmte Aufgaben direct
file- oderdirectory-Objekte zu speichern.
-
Artchi schrieb:
Ja, aber ich als Anwender der Klasse, interessiere mich nicht dafür ob es verebt wurde. Ich benutze eh nur die Basisklasse. Das ist ja das tolle an Polymorphie, das ich beim coden nicht mit speziellen Klassen hantieren muß. Ich arbeite ja auch nur mit der DirectoryOrFile Klasse. Ob es zur Laufzeit eine vererbte ist, ist mir ja egal.
Unsinn. Was sollte gegen folgendes sprechen:
class filesystem_object { /* ... */ }; class directory : public filesystem_object { // directory-specific methods }; class file : public filesystem_object { // file-specific methods }; container<filesystem_object*> search_results_; container<directory*> favorites_; container<file*> foo_documents_;
-
Artchi schrieb:
Ja, aber ich als Anwender der Klasse, interessiere mich nicht dafür ob es verebt wurde. Ich benutze eh nur die Basisklasse. Das ist ja das tolle an Polymorphie, das ich beim coden nicht mit speziellen Klassen hantieren muß. Ich arbeite ja auch nur mit der DirectoryOrFile Klasse. Ob es zur Laufzeit eine vererbte ist, ist mir ja egal.
Du gehst jetzt vom Standpunkt des Designers aus. Aber das ist ja sein Bier. Wer garantiert dir denn eigentlich, das in Java 1.6 java.io.File nicht auch nur noch als Basisklasse fungiert, und SUN ebend mal zwei spezielle Klasse von File abgeleitet hat? Das wirst du garnicht merken, wenn du da nicht explizit nachschaust. Der Fall ist jetzt nur hypothetisch aber nicht unrealistisch.
Richtig ich gehe vom Standpunkt des Designers aus. Geht ja auch darum wie er es designen will, wenn ich das richtig verstanden habe.
Icb bin generell kein großer Freund der Vererbung und wenn man sowas auch ohne lösen kann, dann würde ich es eben auch lieber ohne machen. Deshalb finde ich persönlich hier das JDK als Vorbild gar nicht mal so schlecht.
-
finix schrieb:
Artchi schrieb:
Ja, aber ich als Anwender der Klasse, interessiere mich nicht dafür ob es verebt wurde. Ich benutze eh nur die Basisklasse. Das ist ja das tolle an Polymorphie, das ich beim coden nicht mit speziellen Klassen hantieren muß. Ich arbeite ja auch nur mit der DirectoryOrFile Klasse. Ob es zur Laufzeit eine vererbte ist, ist mir ja egal.
Unsinn. Was sollte gegen folgendes sprechen:
class filesystem_object { /* ... */ }; class directory : public filesystem_object { // directory-specific methods }; class file : public filesystem_object { // file-specific methods }; container<filesystem_object*> search_results_; container<directory*> favorites_; container<file*> foo_documents_;Wieso? Habe ich gesagt das DAS schlecht sein soll?
Völlig anderer Kontext und hat nichts mit neps Kontext zu tun.
-
Artchi schrieb:
Wieso? Habe ich gesagt das DAS schlecht sein soll?
Völlig anderer Kontext und hat nichts mit neps Kontext zu tun.Hier, und da ist nix mit anderer Kontext:
Artchi schrieb:
Ja, aber ich als Anwender der Klasse, interessiere mich nicht dafür ob es verebt wurde. Ich benutze eh nur die Basisklasse. Das ist ja das tolle an Polymorphie, das ich beim coden nicht mit speziellen Klassen hantieren muß. Ich arbeite ja auch nur mit der DirectoryOrFile Klasse. Ob es zur Laufzeit eine vererbte ist, ist mir ja egal.
(Hervorhebung von mir.)
-
Composite Pattern.
-
finix! Willst du mich verarschen? Lies erstmal meinen Text und interpretier ihn richtig. Er bezieht sich wenigstens auf das Problem des Fragestellers. Wenn ich mit Objekten rumhantiere, wo ich den genauen Typ nicht kenne,m arbeite ich mit der Basis.
Das finix-Beispiel mit den Listen ist eine ganz andere Situation, wo man selbst die speziellen Typen KENNT. Hier geht es darum, das der Fragesteller _nicht_ casten will, weil er die genauen Typen ebend NICHT kennt. Also sage ich: "Ich arbeite nur mit der Basis, dann interessiert mich der genaue Typ nicht!"
Und ihr? Ihr prsäentiert mir SPEZIALISIERTE Listen, wo ich entweder von VORNHEREIN weiß um was es sich handelt (weil ich schon spezielle Typen erhalte) oder wo ich casten muß. Wenn ich aber nur Basistypen erhalte und nicht casten will, wie soll ich dann eine spezialisierte Liste auf File und Directory benutzen? Ist doch ein Widerspruch.
-
Helium schrieb:
Ein objekt kann entweder eine Datei oder ein Verzeichnis sein. Jenachdem kann man verschiedene Dinge mit dem Objekt machen.
Wie krig ich das in C++ sauber hin.
Soetwas, wie
class DirectoryOrFile { void fileAction () { if (isDirectory()) { throw "Funktioniert nur mit Dateien"; } ... // eigentliche Aktion } ... };ist kacke.
Aber eine Hierarchie ist besser, aber auch nicht das wahre
class DirectoryOrFile { ... }; class File : public DirectoryOrFile { void fileAction () { ... // eigentliche Aktion } ... }; class Directory : public DirectoryOrFile { ... };Angenommen ich will mit einem DirectoryOrFile-Objekt arbeiten, dann muss ich jedesmal dynamic_casten oder was auch immer, jedenfalls den Laufzeittyp bestimmen. Beispielsweise könnte ich eine Verzeichnis-Stuktur durchlaufen wollen, um bestimmte Dateien zu suchen, oder einfach um die Stuktur auszugeben oder was auch immer.
In Scala ging das ganze mit case classes und pattern matching wunderbar. Vielleicht bin ich auch genau aus dem Grund gerade zu verblendet und der Ansatz ist grundsätzlich daneben.
Ich würde den zweiten Ansatz verwenden. Die Basisklasse stellt ein abstraktes FileSystemObject dar, mit dem man allerdings auch schon eine ganze Menge machen kann. Zum Beispiel löschen, kopieren, ...
Ich denke nicht, dass du bei dem zweiten Ansatz oft casten musst. Zum Beispiel kann ein Directory folgende Funktionen haben:
FileSystemObject[] getFileSystemObjects(bool recursive) File[] getFiles(bool recursive) Directory[] getDirectories(bool recursive)Und schon ist immer klar, mit was du hantierst. Auch Sachen wie Suchen können entsprechend definiert werden, dass du dann nicht schauen musst, ob es eine Datei oder ein Verzeichnis ist. Alles was man braucht, ist eine Möglichkeit zu sagen "Ich will Verzeichnisse suchen".
Und jetzt fallen mir nicht viele Fälle ein, wo man dynamisch casten müsste. Sollte IMHO wirklich selten vorkommen. Aus Bequemlichkeitsgründen könnte man dann in FileSystemObject noch Methoden wie isFile() und isDirectory() einbauen. Oder/Und:
File asFile() // liefert vielleicht null Directory asDirectory() // liefert vielleicht nullSogar intern könnte ein Directory seine Kinder je nach Typ in unterschiedlichen Datenstrukturen ablegen.
-
Optimizer schrieb:
Ich denke nicht, dass du bei dem zweiten Ansatz oft casten musst. Zum Beispiel kann ein Directory folgende Funktionen haben:
FileSystemObject[] getFileSystemObjects(bool recursive) File[] getFiles(bool recursive) Directory[] getDirectories(bool recursive)Und schon ist immer klar, mit was du hantierst.
Verdammt, klar. So ist's gut

-
Artchi schrieb:
Wer garantiert dir denn eigentlich, das in Java 1.6 java.io.File nicht auch nur noch als Basisklasse fungiert, und SUN ebend mal zwei spezielle Klasse von File abgeleitet hat?
die api von java.io.File garantiert mir das, da File nicht abstrakt ist, und ich File so wie ich will als datei oder ordner instantiieren kann und keine möglichkeit besteht diesen entwurf zu ändern ohne dass ich was davon mitbekomme.
-
war ja klar

-
Schau dir mal das Composite Pattern an.
-
Nach Composite:
Ich hab 'ne Komponente (Component) FilesystemObject, eine Datei ist ein Blatt (Leaf), ein Ordner ein Kompositum (Composite).Optimizer schlägt dann aber etwas vor, was im Composite nicht vorgesehen ist:
Sogar intern könnte ein Directory seine Kinder je nach Typ in unterschiedlichen Datenstrukturen ablegen.
Das halte ich in diesem speziellen Fall für praxistauglicher.
Auch das
File[] getFiles(bool recursive) Directory[] getDirectories(bool recursive)hat nicht wirklich was mit Composite zu tun, macht das ganze aber in meinem Fall denke ich brauchbar.
Gehen tut prinzipiell natürlich beides (willkürliches, im prinzip sinnfreies Beispiel):
void showLastAccessDate (Directory directory) { foreach (Direcotry subdirectory in directory.getDirectories()) { showLastAccessDate (subdirectory); } foreach (File file in director.getFiles()) { print (file.getLastAccessDate ()); } }vs
void showLastAccessDate (FilesystemObject filesystemObject) { if (filesystemObject.getLastAccessDate() != null) // (1) print (filesystemObject.getLastAccessDate(); foreach (FilesystemObject subFilesystemObject in filesystemObject.getFylesystemObjects()) { showLastAccessDate (subFilesystemObject); } }Es ist aber ziemlich sicher, das es bei dieser festen Hierarchie bleibt. Composite wäre natürlich erweiterbarer (hier uninteressat), aber hier denke ich unbequemer. Ich mag es nicht, wenn ich nicht sicher bin, ob auch etwas zurückkommt, oder nicht und ich jedesmal vorher testen muss, wie bei (1), wobei es hier natürlich noch harmlos ist.
-
Warum nicht so?
class FSObject { public: virtual void open() = 0; virtual unsigned int size() = 0; protected: FSObject* parent; }; class File : public FSObject { }; class Directory : public FSObject { protected: list<FSObject*> inhalt; };