Eddie: PHP: Erzeugungsmuster gesucht

Hallo allerseits,

ich habe folgendes immer wiederkehrendes Problem - und ich bin mir sicher, dass es dazu eine elegante Lösung geben muss!

Folgendes aktuelles Beispiel möchte ich nehmen: eine Klasse "Foto". Ein Foto hat etwa folgende Eigenschaften:
 - ID
 - Breite
 - Höhe
 - Dateigröße
 - Beschreibung
 - Pfad
 - regionale Zuordnung
 - ...

Beim Hochladen des Fotos ist das meiste davon erstmal unbekannt, insb. die ID. Aus diesem Grund benutze ich zur Zeit folgende Vererbungsstruktur:

FOTO
                   mit den sofort verf-
                   ügbaren Eigenschaften:
                   - Breite
                   - Höhe
                   - Dateigröße
                  /            \                  /              \                 /                \                /                  \               /                    \    NEWFOTO                          EXISTINGFOTO
     zuständig für die                mit allen anderen
     Ablage im Datei-                 Eigenschaften
     System und in der
     DB

Im Laufe der Erstellung wird in NEWFOTO irgendwann die ID bekannt, genauso wie der Pfad. Die aufrufende Klasse ("FileUpload") soll dann irgendwas zurückkriegen, um das Ergebnis repräsentieren zu können. Aber nur EXISTINGFOTO hat die nötigen Methoden dazu, bspw. eine HTML-Darstellung für das Foto.

Im Idealfall sollte also nach der Erstellung mittels NEWFOTO alles zur Verfügung stehen, was EXISTINGFOTO ausmacht, ich bräuchte also sowas wie Casting. Oder wie macht man das normalerweise?

Danke für eure Hilfe,
Eddie

--
Old men and far travelers may lie with authority.
  1. Moin!

    Folgendes aktuelles Beispiel möchte ich nehmen: eine Klasse "Foto". Ein Foto hat etwa folgende Eigenschaften:

    Und welche Methoden?

    Beim Hochladen des Fotos ist das meiste davon erstmal unbekannt, insb. die ID. Aus diesem Grund benutze ich zur Zeit folgende Vererbungsstruktur:

    Wie kommt das Foto denn an?

    Im Laufe der Erstellung wird in NEWFOTO irgendwann die ID bekannt, genauso wie der Pfad. Die aufrufende Klasse ("FileUpload") soll dann irgendwas zurückkriegen, um das Ergebnis repräsentieren zu können. Aber nur EXISTINGFOTO hat die nötigen Methoden dazu, bspw. eine HTML-Darstellung für das Foto.

    Klingt so, als solltest du nur eine einzige Klasse haben, und in der eine Methode (z.B. als Konstruktor oder Factory, oder simple Methode), die ein Bild ggf. auch als Upload entgegennehmen, passend ablegen und dann alle Eigenschaften initialisieren kann.

    - Sven Rautenberg

    --
    "Love your nation - respect the others."
    1. Hallo Sven,

      Folgendes aktuelles Beispiel möchte ich nehmen: eine Klasse "Foto". Ein Foto hat etwa folgende Eigenschaften:

      Und welche Methoden?

      Z.B. resize(), paintImgMap(), getHtmlRepresentation() was auch immer man mit Fotos machen kann...

      Wie kommt das Foto denn an?

      Per method="post" enctype="multipart/form-data".
      Ich würde dann den Konstruktur von NEWFOTO aufrufen: und zwar ohne Parameterübergabe (_construct())!
      Der Konstruktor schaut dann in den POST_VARS nach, ob er in der Lage ist, ein neues Foto zu erzeugen (dazu braucht er u.a. ein Bild im tmp-Verzeichnis). Dann erfolgt der DB-Eintrag und das Speichern des Fotos mit der neuen DB-ID.

      Klingt so, als solltest du nur eine einzige Klasse haben, und in der eine Methode (z.B. als Konstruktor oder Factory, oder simple Methode), die ein Bild ggf. auch als Upload entgegennehmen, passend ablegen und dann alle Eigenschaften initialisieren kann.

      Das ist das, was auch dedlfix vorschlägt ("KISS"). Nur habe ich dann natürlich doch eine ganze Menge zusätzlichen Code in der normalen Foto-Klasse, den ich niemals wieder für ein existierendes Foto brauche. Eigentlich nicht so schoen :-/

      Ich meine, ich könnte auch in der NEWFOTO-Klasse eine Methode bereitstellen: getExistingFoto(), die mein UploadManager dann aufruft. Das sähe dann also im UploadManager so aus:

         $outputHtml = '';  
         $newFoto = new NEWFOTO(); // versucht, ein Foto aus den post-Parametern zu erzeugen  
         if ($newFoto->isValid())  
         {  
            $finalFoto = $newFoto->getExistingFoto(); // erzeugt ein EXISTINGFOTO mit selber ID und gibt es zurück  
            $outputHtml = $finalFoto->getHtmlRepresentation();  
         }  
         else  
         {  
            $outputHtml = $newFoto->getError();  
         }  
         echo $outputHtml;
      

      Aber auch das ist nicht wirklich elegant :-/
      Eddie

      --
      Old men and far travelers may lie with authority.
      1. hi,

        Das ist das, was auch dedlfix vorschlägt ("KISS"). Nur habe ich dann natürlich doch eine ganze Menge zusätzlichen Code in der normalen Foto-Klasse, den ich niemals wieder für ein existierendes Foto brauche. Eigentlich nicht so schoen :-/

        Und wenn du newphoto von der "normalen" photo-Klasse die grundlegenden Methoden und Eigenschaften erben lässt - und die beim Upload relevanten Methoden und Eigenschaften dieser noch hinzufügst?

        gruß,
        wahsaga

        --
        /voodoo.css:
        #GeorgeWBush { position:absolute; bottom:-6ft; }
        1. Hallo wahsaga,

          Und wenn du newphoto von der "normalen" photo-Klasse die grundlegenden Methoden und Eigenschaften erben lässt - und die beim Upload relevanten Methoden und Eigenschaften dieser noch hinzufügst?

          Dann waere es genau andersrum, NEWFOTO hat dann bspw. lesenden Datenbankzugriff: das muesste ich alles überschreiben, klingt für mich ähnlich unsauber :-/

          Jetzt bastel ich gerade mit Interfaces rum, wird Zeit, dass ich das mal lerne. Vielleicht hilft ja dieser Ansatz weiter, s. dazu die Antwort auf Daniel's Posting.

          Eddie

          --
          Old men and far travelers may lie with authority.
  2. echo $begrüßung;

    Beim Hochladen des Fotos ist das meiste davon erstmal unbekannt, insb. die ID. Aus diesem Grund benutze ich zur Zeit folgende Vererbungsstruktur:
    Im Laufe der Erstellung wird in NEWFOTO irgendwann die ID bekannt, genauso wie der Pfad. Die aufrufende Klasse ("FileUpload") soll dann irgendwas zurückkriegen, um das Ergebnis repräsentieren zu können. Aber nur EXISTINGFOTO hat die nötigen Methoden dazu, bspw. eine HTML-Darstellung für das Foto.
    Im Idealfall sollte also nach der Erstellung mittels NEWFOTO alles zur Verfügung stehen, was EXISTINGFOTO ausmacht, ich bräuchte also sowas wie Casting. Oder wie macht man das normalerweise?

    Einen Typecast für Klassen gibt es unter PHP nicht. Eine unschöne Variante wäre ein statischer Aufruf der Methoden der anderen Klasse. Da du von einem Objekt-Kontext her kommst zeigt das $this bei diesem statischen Aufruf auf das aufrufende Objekt. Damit stehen dessen Eigenschaften zur Verfügung. Eigenschaften eines Objekts sind auch nicht auf die in der Klassendeklaration bekanntgegebenen begrenzt, sondern lassen sich gemäß der PHP-Philosophie durch schreibenden Zugriff anlegen.

    Ich plädiere jedoch für KISS. (Keep is simple, stupid.) Was spricht denn dagegen, _eine_ Klasse mit allen Eigenschaften zu erstellen, die sich aber erst nach und nach füllen? Stattdessen können ja Ersatzwerte wie NULL oder 0 oder Leerstring in den Eigenschaften stehen.

    echo "$verabschiedung $name";

  3. Hallo Eddie,

    Vererbung ist bei der Abbildung von irgend etwas realem meistens gefährlich. In vielen Fällen (auch in diesem) ist es besser, sie durch Relationen zu ersetzen. Vererbung eignet sich mehr für technische Klassen oder in den seltenen Fällen, wo wirklich feststeht, dass sich die Beziehung niemals ändern kann.

    Für deinen Fall sehe ich spontan zwei Lösungsmöglichkeiten:

    1. Eine zusätzliches Zustandsobjekt für ein Foto:
    class Foto {
      int breite;
      int höhe;
      int dateigröße;
      FotoState state;

    void store(...) {}
    }

    abstract class FotoState {
      ...
    }

    class FotoNew {
      ...
    }

    class FotoStored {
       String description;
       String path;
       ...
    }

    Die Idee ist also, dass das Fotoobjekt ein Zustandsobjekt referenziert, dass alle Information speichert, die Zustandsabhängig ist.
    Die Methode store(...) kann nun so implementiert werden, dass überprüft wird, ob das Foto neu ist, das Foto gespeichert und das Zustandsobjekt durch eine Instanz von FotoStored ersetzt wird.

    2. Trennen zwischen einem echten, gepseicherten Foto und einer beschreibung des Fotos:

    class FotoDescriptor {
      int breite;
      int höhe;
      int dateigröße;
      Foto foto;

    void store(...);
    }

    class Foto {
      String description;
      String path;
      ...
    }

    Die Methode store kann nun so implementiert werden, dass sie ein neues Foto-Objekt erzeugt und das Foto speichert. Anfangs ist die Eigenschaft foto einfach null.

    Ein entscheidender Vorteil dieser Lösungen ist auch, dass Du keine Objekte ersetzen musst. Bei Deiner Lösung müsstest Du Instanzen von NewFoto durch solche von ExisitingFoto ersetzen. Das ist aber meistens sehr schwierig, da Du wissen musst, wo das Objekt gerade überall benutzt wird.
    Bei diesen Lösungen hingegen wird einfach Information im Objekt hinzugefügt bzw ausgetauscht. Es kann natürlich passieren, dass an einer Stelle Information nicht verfügbar ist, die benötigt wird. Man muss also etwas darauf achten, in welchem Zustand ein Objekt gerade ist.

    Grüße

    Daniel

    1. Hallo Daniel,

      das klingt sehr vielversprechend! Im Grunde wären das dann eine Art "Wrapper-Klassen", die nach aussen verbergen, was innen geschieht, oder?

      Meinst du, ich könnte dein zweites Beispiel auch so realisieren? Macht das Sinn, oder habe ich es falsch verstanden? Erscheint mir relativ abenteuerlich, weil ich mit Interfaces noch nicht wirklich gearbeitet habe...

        
         interface FotoObject  
         {  
            function getWidth();  
            //  alle Methoden die ein normales (DB schon vorhanden!) Foto kennt...  
         }  
        
        
         class Foto implements FotoObject // normales Foto ohne Upload-Funktionalität  
         {  
            function _construct($photoID){...}  
        
            function getWidth()  
            {  
               return $this->width;  
            }  
            // im Grunde alle Methoden des Interfaces...  
         }  
        
        
         class FotoCreator implements FotoObject // alias FotoDescriptor in deinem Beispiel!  
         {  
            Foto foto = null;  
        
            _construct(){} // keine Parameter, übernimmt die Erzeugung (also store())  
        
            getWidth() // Wrapper  
            {  
               if ($this->foto)  
               {  
                  return $this->foto.getWidht();  
               }  
               else  
               {  
                  return $this->width;  
               }  
            }  
        
            // alle Methoden des Foto-Objekts...  
         }
      

      Zu abenteuerlich?

      Eddie

      --
      Old men and far travelers may lie with authority.
      1. Hallo Eddie,

        An der Lösung missfällt mir irgendwie, dass Du zwei Objekte hast, die letztenendes das gleiche Bild repräsentieren. Außerdem musst Du alle Funktionen doppelt implementieren.
        Ich würde dem "Wrapper"-Objekt keine Schnittstelle geben, um die Detailinformation des Fotos zu erfahren. Wenn die jemand benötigt, soll er sich das referenzierte Objekt geben lassen und darauf zugreifen.
        Nachdem ich deine anderen Postings gelesen habe, bin ich mir auch nicht mehr so ganz sicher, was wirklich die geeignete Lösung ist.
        Die geht es ja doch vor allem um die Erzeugung des Objekts und nicht darum, mit einem sich ändernden Zustand klar zu kommen.
        Wie lange exisieren denn diese NewFoto-Objekte und wird mit ihnen etwas gemacht, außer sie zu speichern?

        Die einfachste möglichkeit, wenn sie nur kurz existieren und nur gespeichert werden, wäre wirklich eine Factory:

        class FotoFactory {
          Foto createFoto(...);
        }

        Diese sollte das Foto abspeichern und gleich eine Instanz mit allen Daten erzeugen.

        Existiert das Objekt länger und Du brauchst wirklich so ein NewFoto-Objekt um das Speichern und Berechnen der Daten in irgend welchen Modulen zu erledigen, aber ansonsten passiert nichts mit dem Objekt, könntest Du so vorgehen:

        class FotoDescriptor {
          ...
        }

        class FotoFactory {
           Foto createFoto(FotoDescriptor fd);
        }

        Die Klasse FotoDescriptor hat nichts mit der Foto-Klasse gemeinsam. Man kann sie einfach mit den Parametern erzeugen, die die Factory benötigt. So hat man ein Objekt um die Parameter durch die gegend zu reichen und kann irgendwo eine Factory-Klasse haben, die dann das Foto erzeugt.

        Wenn Du auf ungespeicherten Fotos schon arbeiten willst, wie auf gespeicherten, nur dass eben noch nicht alle Eigenschaften zur Verfügung stehen, dann solltest Du eine der beiden vorher vorgeschlagenen Lösungen nehmen und zwar genau so, wie dort beschrieben.
        Der Zugriff auf die zusätzlichen Eigenschaften sollte dann wirklich über das referenzierte Objekt erfolgen. Versuche nicht, da wieder eine Vererbungshierarchie einzubauen, denn die Idee war ja gerade, diese loszuwerden und ein Foto durch zwei Objekte darzustellen, die jeweils einen Teil repräsentieren.
        Eine Änderungsmöglichkeit wäre, die Methode "store" noch herauszulösen in eine extra Klasse:

        FotoDB {
          void store(FotoDescriptor f)
        }

        Diese würde dann das Foto speichern und dem FotoDescriptor das entsprechende Foto-Objekt hinzufügen. Das gleiche könnte man mit der Variante mit den Zustandsklassen realisieren.
        Die Variante mit den Zustandsklassen hat übrigens dann einen Vorteil, wenn Du evtl. weitere Zustände brauchen könntest oder wenn Du für neue Fotos Daten brauchst, die Du für gespeicherte nicht mehr brauchst. Du könntest in FotoNew-Objekten z.B. HTTP-Parameter speichern. Das wäre glaube ich sogar die eleganteste Lösung. Allerdings nur, wenn Deine Problemstellung wirklich nach dieser Komplexität verlangt. Wenn es eine Factory-Klasse tut, sollte man dabei bleiben.

        Grüße

        Daniel