Wie folgendes sauber designen



  • 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 null
    

    Sogar 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;
    };
    


  • Passt das nicht zum Composite?

    Was macht dein open?

    Wenn ich einen Ordner habe möchte ich auch an die darin enthaltenen Dateien und Unterordner kommen. Wenn ich aber in der Basis soetwas habe wie getFilesystemObjects (), dann muss ich diese Methode auch in der File-Klasse implementieren. Eine Datei nach ihren Unterverzeichnissen zu fragen ist aber aus meiner sich ein Fehler. Würde ich das ganze aber entsprechend implementieren, dass File::getFilesystemObjects () mit einem Fehler reagiert, würde das die Arbeit extrem erschweren. Also muss ich File::getFilesystemObjects () so bauen, dass null oder eine leere Collection oder ähnliches zurückgegeben wird.
    Aber ich habe eben generell eine Ablehnung gegen Methoden, die keine sinvolle Aktion durchführen, sondern notgedrungen irgendwie implementiert werden müssen, um das Interface der Basisklasse zu befriedigen. Wenn möglich ist, dass es einige Derivate geben wird, die eine Fähigkeit nicht besitzen sollte die Basis diese Fähigkeit nicht anbieten. Dateien haben nicht die Fähigkeit ihre Unterordner zu liefern.

    Das ist mein Standpunkt.

    Optimizers Variante macht das ganze sauber ohne irgendwelche sinnlos-Methoden oder andere merkwürdige Dinge.

    Ich habe also bereits die Lösung. Trotzdem danke.


Anmelden zum Antworten