Gerd: Parameter zum Klassenaufruf zwischenspeichern

Hey!

Also ich übergebe einer Funktion nacheinandere mehrere verschiedene Objekte mit verschiedenen Konstruktoren. Ausserdem erhält die Funktion 2 weitere Parameter die immer benötigt werden.

  
foobar(new Object1($int, $string1), $wichtig1, $wichtig2);  
foobar(new Object2($string1, $string2, $string3, $object), $wichtig1, $wichtig2);  

Jetzt ist es aber so, dass in der Funktion anhand der 2 letzten Parameter eine Unterscheidung getroffen wird ob das Objekt aus dem ersten Parameter überhaupt benötigt wird. Das Objekt ist zu dem Zeitpunkt aber schon instanziert und das gefällt mir nicht.
Deshalb möchte ich der Funktion foobar($object, $wichtig1, $wichtig2) gern das Instanzieren der Objekte überlassen. Mein Problem ist jetzt, dass ich mir nicht sicher bin, wie ich die Parameter zu den Konstruktoren am elegantesten übergebe.

Ich habe über 2 Varianten nachgedacht.

Variante 1: foobar("Klassenname", $objectparam1, $objectparam2 /* , $objectparam3, ... */, $wichtig1, $wichtig2);

Dann würde ich in foobar() alle Parameter auswerten, die letzten beiden zur Fallunterscheidung nehmen, den ersten zur Instanzierung des Objekts und alles dazwischen in der Reihenfolge als Parameter übergeben.

Dabei gefällt mir aber nicht, dass ich umständlich die Anzahl der Parameter von foobar() ermitteln und zuordnen muss und, dass die Reihenfolge der Parameter nicht flexibel ist.

Variante 2: foobar("Klassenname", array("publicvar1"=>$string1, "publicvar2"=>$string2), $wichtig1, $wichtig2);

In dem Fall würde ich die Unterscheidung ganz sicher anhand des dritten und vierten Parameters treffen können. func_get_args() und die Verarbeitung kann ich mir sparen.
Das schöne daran ist, dass ich die Eigenschaften der Klassen direkt mit übergeben kann anstatt im Konstruktor die Zuordnung z.b. $this->publicvar1 = $string1; vornehmen zu müssen.

Ich tendiere stark zur zweiten Variante aber ich würde gern wissen ob es noch eine bessere Möglichkeit gibt.

Danke schonmal!

  1. Lieber Gerd,

    wie findest Du diese Schreibweise?

    Variante 3:

    foobar(  
        array(  
            "Klassenname" => array(  
                "publicvar1" => $string1,  
                "publicvar2" => $string2  
            )  
        ),  
        $wichtig1,  
        $wichtig2  
    );
    

    Ob das alles eine "Lösung" ist, oder ob Du Deine Funktionen anders interagieren lassen solltest, kann ich nicht beantworten, da mir dazu die nötige Erfahrung fehlt.

    Liebe Grüße,

    Felix Riesterer.

    --
    ie:% br:> fl:| va:) ls:[ fo:) rl:| n4:? de:> ss:| ch:? js:) mo:} zu:)
    1. Hey Felix!

      wie findest Du diese Schreibweise?

      Auf den ersten Blick ein wenig unübersichtlicher als wenn man nur 1 Array für die Parameter nimmt, würde ich sagen. Andererseits spar ich mir dadurch einen vierten Parameter in die Funktion einzubauen (nicht, dass das schwer wäre). Ich behalte deinen Vorschlag mal mit im Hinterkopf.
      Ich schau mal ob noch jemand einen anderen Vorschlag hat.

      Danke dir!

  2. Tach!

    Deshalb möchte ich der Funktion foobar($object, $wichtig1, $wichtig2) gern das Instanzieren der Objekte überlassen. Mein Problem ist jetzt, dass ich mir nicht sicher bin, wie ich die Parameter zu den Konstruktoren am elegantesten übergebe.

    Als Array.

    Variante 1: foobar("Klassenname", $objectparam1, $objectparam2 /* , $objectparam3, ... */, $wichtig1, $wichtig2);
    Dann würde ich in foobar() alle Parameter auswerten, die letzten beiden zur Fallunterscheidung nehmen, den ersten zur Instanzierung des Objekts und alles dazwischen in der Reihenfolge als Parameter übergeben.
    Dabei gefällt mir aber nicht, dass ich umständlich die Anzahl der Parameter von foobar() ermitteln und zuordnen muss und, dass die Reihenfolge der Parameter nicht flexibel ist.

    Musst du nicht. func_get_args() kennst du schon. Aus dem davon gelieferten Array poppst du die letzten beiden Elemente und instantiierst dann das Objekt mit dem restlichen Array. Mehr dazu gleich.

    Variante 2: foobar("Klassenname", array("publicvar1"=>$string1, "publicvar2"=>$string2), $wichtig1, $wichtig2);

    Wenn dir positionierte Parameter reichen, dann kannst du die Keys aus dem Array weglassen - jedenfalls für meinen Lösungsvorschlag.

    In dem Fall würde ich die Unterscheidung ganz sicher anhand des dritten und vierten Parameters treffen können. func_get_args() und die Verarbeitung kann ich mir sparen.

    Warum stehen die beiden denn hinten? Damit man etwas schwerer drauf zugreifen muss? Wie auch immer, bei Einzel-Variablen à la Variante 1 würden sie trotzdem stören und müssten mit array_shift() aus dem func_get_args()-Array entsorgt werden. (Also, nicht ganz entsorgt, du brauchst sie ja schließlich für die Unterscheidung. Aber damit schlägst du zwei Fliegen mit einer Klappe, das Entfernen aus dem Array und das Extrahieren für deine Verwendung.)

    Das schöne daran ist, dass ich die Eigenschaften der Klassen direkt mit übergeben kann anstatt im Konstruktor die Zuordnung z.b. $this->publicvar1 = $string1; vornehmen zu müssen.

    Den Satz versteht man wohl nur richtig, wenn man den Aufbau der Klassen kennt. Wenn deine Funktion das Objekt erzeugt, muss sie entweder die Parameter an den Konstruktor übergeben oder sie nach der Instantiierung setzen. Letzteres macht sich nicht gut, wenn dabei private Eigenschaften oder Berechnungen, die der Konstruktor ausführen sollte, im Spiel sind.

    Ich tendiere stark zur zweiten Variante aber ich würde gern wissen ob es noch eine bessere Möglichkeit gibt.

    PHP kennt zwar call_user_func(_array)(), aber damit kann man nur bestehende Funktionen, Klassen- und Objekt-Methoden aufrufen, nicht jedoch Objekte erstellen. Das geht nur mit Reflection (und eval(), aber schön geht anders).

    $instance = (new ReflectionClass($klassenname))->newInstanceArgs($parameterArray);

    Wie du deine Parameter ins Array bekommst, kannst du nach deinem Geschmack lösen. Bei Variante 2 (ohne Keys) gehts es direkt, bei der ersten mit zwei (Extra-)Funktionsaufrufen.

    dedlfix.

    1. Hey dedlfix!

      Variante 2: foobar("Klassenname", array("publicvar1"=>$string1, "publicvar2"=>$string2), $wichtig1, $wichtig2);

      Wenn dir positionierte Parameter reichen, dann kannst du die Keys aus dem Array weglassen - jedenfalls für meinen Lösungsvorschlag.

      Die Keys hätten den Vorteil, dass ich vorbelegte Parameter nicht beachten müsste wenn ein Konstruktor z.B. so aussähe:
      public function __construct($foo, $bar = null, $foobar = false){/* .. */}

      Ich würde also $foobar setzen können ohne, dass ich $bar übergeben muss.

      Den Satz versteht man wohl nur richtig, wenn man den Aufbau der Klassen kennt. Wenn deine Funktion das Objekt erzeugt, muss sie entweder die Parameter an den Konstruktor übergeben oder sie nach der Instantiierung setzen. Letzteres macht sich nicht gut, wenn dabei private Eigenschaften oder Berechnungen, die der Konstruktor ausführen sollte, im Spiel sind.

      Die Klassen sind im Moment noch ziemlich "roh". Es gibt noch keine interne Logik.
      Deren Aufbau orientiert sich dann an dem Ergebnis der Diskussion hier^^
      Problemen mit dem Konstruktor oder privaten Eigenschaften könnte ich also vorbeugen.

      $instance = (new ReflectionClass($klassenname))->newInstanceArgs($parameterArray);
      Wie du deine Parameter ins Array bekommst, kannst du nach deinem Geschmack lösen. Bei Variante 2 (ohne Keys) gehts es direkt, bei der ersten mit zwei (Extra-)Funktionsaufrufen.

      Der Ansatz sieht auch sehr gut aus. Allerdings wieder mit dem Nachteil, dass ich vorbelegte Parameter beachten muss. Vermutlich ist mein "Problem" allgemein eine "Geschmacksfrage"?

      Ich danke dir erstmal für den Denkanstoß! Ich werde mal schauen welche Lösung "für mich" am besten funktioniert. Am Ende muss ich halt die Vor- und Nachteile abwiegen.

      Dank dir!

      1. Tach!

        Variante 2: foobar("Klassenname", array("publicvar1"=>$string1, "publicvar2"=>$string2), $wichtig1, $wichtig2);
        Wenn dir positionierte Parameter reichen, dann kannst du die Keys aus dem Array weglassen - jedenfalls für meinen Lösungsvorschlag.
        Die Keys hätten den Vorteil, dass ich vorbelegte Parameter nicht beachten müsste wenn ein Konstruktor z.B. so aussähe:
        public function __construct($foo, $bar = null, $foobar = false){/* .. */}

        Das ist so nicht richtig. Stattdessen interessieren die Parameter des Konstruktors überhaupt nicht mehr, weil du ja alles nachher selbst setzt oder auch nicht.

        Ich würde also $foobar setzen können ohne, dass ich $bar übergeben muss.

        Nein, das geht nicht. Es ist nicht möglich, $foobar zu übergeben und $bar wegzulassen. Das geht in Sprachen, wo man die Parameter beim Übergeben explizit benennen kann, aber nicht in PHP. Du kannst nur positioniert Parameter übergeben und das lückenlos von links aus. Rechts können welche fehlen, aber ab da dann alle.

        Du kannst $bar nur gedachterweise auslassen, indem du den Defaultwert (hier: null) übergibst. Aber dann hast du ja doch was übergeben. Beispiel htmlspecialchars($string, $flags = ENT_COMPAT | ENT_HTML401, $encoding = 'UTF-8', $double_encode = true). Möchte man das Encoding angeben, ist aber mit den Flags zufrieden, muss man sie trotzdem notieren, weil sonst der Encoding-Parameter eins nach links rutscht und dann als Flags angesehen werden würde.

        Wenn der Konstruktor auf normalem Wege aufgerufen wird und optionale Parameter enthält, musst du auch von links lückenlos die Parameter übergeben. Wenn die Parameter ohne Keys in einem Array stehen, ist das nichts anderes. Wenn du stattdessen abweichend davon benannte Parameter nachbilden willst, musst du auf die Möglichkeiten des Konstruktors (wie Berechnungen anstellen oder auf private Eigenschaften/Methoden zugreifen) verzichten. Allerdings gibt es da noch die Möglichkeit der Setter-Funktionen (oder der __set()-Magie). (Setter sind von Java zum Beispiel sattsam bekannt. Ich würde sie aber nur ungern gnadenlos in PHP verwenden wollen. Wenn keine Zugriffslogik benötigt wird und nur 1:1 auf die private Variable zugegriffen wird, muss nur unnötigerweise jedes Mal ein Funktionsaufruf abgearbeitet werden. Das macht das Programm aber weder schneller noch fehlerfreier. "Aber wenn man mal später eine Kapslung braucht ..." - "YAGNI!")

        dedlfix.

  3. Moin!

    Also ich übergebe einer Funktion nacheinandere mehrere verschiedene Objekte mit verschiedenen Konstruktoren. Ausserdem erhält die Funktion 2 weitere Parameter die immer benötigt werden.

    foobar(new Object1($int, $string1), $wichtig1, $wichtig2);
    foobar(new Object2($string1, $string2, $string3, $object), $wichtig1, $wichtig2);

    
    >   
    > Jetzt ist es aber so, dass in der Funktion anhand der 2 letzten Parameter eine Unterscheidung getroffen wird ob das Objekt aus dem ersten Parameter überhaupt benötigt wird. Das Objekt ist zu dem Zeitpunkt aber schon instanziert und das gefällt mir nicht.  
      
    Da würde ich jetzt erstmal ganz simpel sagen: Na und?  
      
    Und zweitens würde ich mich fragen, was das denn für eine grausame Funktion ist?  
      
    Die ist aus mehreren Gründen grausam:  
      
    Erstens hat sie eine mangelhafte Signatur: Optionale Elemente gehören ans Ende der Parameterliste, und offenbar ist das Objekt optional.  
      
    Zweitens: Wieso verlangt die Funktion ein Objekt, dass sie hinterher nicht braucht? Wenn man das offenbar anhand des übergebenen Parameters ermitteln kann, warum wird das nicht außerhalb der Funktion ermittelt?  
      
    
    > Deshalb möchte ich der Funktion foobar($object, $wichtig1, $wichtig2) gern das Instanzieren der Objekte überlassen. Mein Problem ist jetzt, dass ich mir nicht sicher bin, wie ich die Parameter zu den Konstruktoren am elegantesten übergebe.  
      
    Gar nicht!  
      
    Wenn die Funktion die Objekte herstellt, erfüllt sie eine weitere Aufgabe zusätzlich, die ihr nicht zusteht. Außerdem hast du dir damit ziemlich komplexen Code eingehandelt, der in der Funktion zum Erstellen der Objekte enthalten sein muss, denn ich vermute mal, dass es nicht nur genau ZWEI verschiedene Objekte sind, die du da erstellen willst, sondern potentiell noch ein paar mehr. Und alle haben sie unterschiedlich lange Parameterlisten des Konstruktors (was auch wieder sehr fragwürdig ist im Hinblick auf Vererbung und Selbstähnlichkeit, aber sofern die Objekte zumindest alle dasselbe Interface gegenüber der Funktion implementieren, kann das mit den notwendigen Unterschieden der Unterklassen durchaus begründet sein).  
      
    Du willst in der Funktion keinen Code haben, der dir Objekte herstellt.  
      
    Was man sich bauen könnte: Du übergibst der Funktion anstelle des Objekts ein Callable (in diesem Fall vermutlich eine anonyme Funktion), die du in der Funktion aufrufen kannst, wenn du das Objekt brauchst. Dann weiß das Callable, wie das Objekt zu instanziieren ist, und gibt es zurück.  
      
    ~~~php
      
    foobar(function () use ($int, $string1) { return new Object1($int, $string1);}, $wichtig1, $wichtig2);  
    foobar(function () use ($string1, $string2, $string3, $objekt) { return new Object2($string1, $string2, $string3, $objekt);}, $wichtig1, $wichtig2);  
    
    

    In der Funktion dann:

      
    if ($wichtig1 == "braucht objekt") {  
        $obj = $callable();  
    }  
    
    

    Diese Lösung ist aber aus meiner Sicht auch sehr eklig.

    Zum einen: Deine Funktionssignatur müsste sich entweder generell nicht um den übergebenen Parameter kümmern (hinsichtlich Typehinting - ich habe das blöde Gefühl, dass sie es jetzt schon nicht tut), und innerhalb der Funktion wäre dann zu prüfen, ob der übergebene erste Parameter ein Callable oder ein anderweitiges Objekt ist. Nur bei einem Callable muss man das Objekt erst durch einen Aufruf auspacken.

    Die Alternative mit Typehint würde erzwingen, dass die Funktion IMMER ein Callable erwartet anstelle eines Objektes, und das würde alle normalen Funktionsaufrufe mit existentem Objekt sehr unschön verkomplizieren.

    Ich würde also im Ganzen davon abraten, das so umzusetzen.

    Ich hab aber im Moment auch noch keine Idee, was man da optimieren könnte. Deine Beschreibung deutet an, dass die Klassenstruktur sowieso nicht optimal ist. Du hast außerdem außer deinem Gefühl für Unschönheit noch kein Argument GEGEN die Instanziierung gebracht. Das einzige Argument, das mir einfiele, wäre Performance - und das dann gültige Gegenargument ist: Tu nix im Konstruktor, außer die Parameter im Objekt lokal abzuspeichern.

    Das einzige dann noch verbleibende Gegenargument wäre Speicherverbrauch. Um den kommst du aber bei keiner Lösung drum herum, denn ein Array mit den Konstruktorparametern kostet auch Speicher, ebenso wie das Callable.

    Ich habe über 2 Varianten nachgedacht.

    Variante 1: foobar("Klassenname", $objectparam1, $objectparam2 /* , $objectparam3, ... */, $wichtig1, $wichtig2);

    Optionale Parameter gehören ans Ende der Parameterliste. Wenn du sie nach vorne setzt, wird dein Funktionscode sofort hundertmal unübersichtlicher, weil du jetzt eine Standardfunktion von PHP, nämlich das Parsen der Parameter, selbst erledigen musst. Und sowas geht auch nicht umsonst, sondern kostet ebenfalls CPU-Zeit. Vermutlich sogar mehr, als das sinnlos instanziierte Objekt zu erstellen.

    Variante 2: foobar("Klassenname", array("publicvar1"=>$string1, "publicvar2"=>$string2), $wichtig1, $wichtig2);

    Das ist genauso schlecht. Deine Funktion hat nicht die Aufgabe, Objekte zu bauen.

    - Sven Rautenberg

    1. Hey!

      Da würde ich jetzt erstmal ganz simpel sagen: Na und?

      Und ich: "Und was?!"

      Und zweitens würde ich mich fragen, was das denn für eine grausame Funktion ist?
      Die ist aus mehreren Gründen grausam:

      Ah, na schauen wir doch mal...

      Erstens hat sie eine mangelhafte Signatur: Optionale Elemente gehören ans Ende der Parameterliste, und offenbar ist das Objekt optional.

      Nein, ist es nicht. Das Objekt (bzw. dessen Klassenname und die Parameter sind Pflicht).

      Zweitens: Wieso verlangt die Funktion ein Objekt, dass sie hinterher nicht braucht? Wenn man das offenbar anhand des übergebenen Parameters ermitteln kann, warum wird das nicht außerhalb der Funktion ermittelt?

      Weil die Funktion ein Cache für benötigte Objekte anlegt. Ob die Objekte später aber auch tatsächlich benötigt werden, regelt eine externe Controller-Klasse.

      Wenn die Funktion die Objekte herstellt, erfüllt sie eine weitere Aufgabe zusätzlich, die ihr nicht zusteht.

      Ja, das überlasse ich dann wohl der Controller-Klasse. Ändert aber erstmal nichts an meinem "Problem".

      Und alle haben sie unterschiedlich lange Parameterlisten des Konstruktors (was auch wieder sehr fragwürdig ist im Hinblick auf Vererbung und Selbstähnlichkeit, aber sofern die Objekte zumindest alle dasselbe Interface gegenüber der Funktion implementieren, kann das mit den notwendigen Unterschieden der Unterklassen durchaus begründet sein).

      Sie "extenden" alle die selbe Klasse.

      Deine Beschreibung deutet an, dass die Klassenstruktur sowieso nicht optimal ist.

      Controller A sammelt Daten über Model B (welche Daten) und Daten über Model B (welche Bedingungen). Danach vergleicht A B mit C und liefert die Schnittmenge. Die Frage ist halt nur, warum ich die Ergebnisse von, in dem Fall, B instanzieren sollte bevor sie in der Schnittmenge landen? Was ist an der Klassenstruktur nicht optimal?

      Du hast außerdem außer deinem Gefühl für Unschönheit noch kein Argument GEGEN die Instanziierung gebracht. Das einzige Argument, das mir einfiele, wäre Performance - und das dann gültige Gegenargument ist: Tu nix im Konstruktor, außer die Parameter im Objekt lokal abzuspeichern.

      Ja deshalb hätte ich auch eigentlich eher gern die Parameter im Konstruktor. Allerdings kann ich "zur Not" natürlich auch in der externen Logik dafür sorgen, dass die Instanzierung einer "Konvention" erfolgt. Z.B. Objekt instanzieren, dann eine Funktion der Elternklasse aufrufen um die Parameter zu initialisieren.

      Das einzige dann noch verbleibende Gegenargument wäre Speicherverbrauch. Um den kommst du aber bei keiner Lösung drum herum, denn ein Array mit den Konstruktorparametern kostet auch Speicher, ebenso wie das Callable.

      Schon 2 "einzige"? ;)
      Aber ja, am Ende braucht ein Objekt natürlich auch mehr Speicher als ein paar Strings (Klassenname+Parameter) in einem Array.

      Das ist genauso schlecht. Deine Funktion hat nicht die Aufgabe, Objekte zu bauen.

      Richtig, sie soll sie nur cachen. Bzw. nur deren Namen und Parameter.

      1. Moin!

        Erstens hat sie eine mangelhafte Signatur: Optionale Elemente gehören ans Ende der Parameterliste, und offenbar ist das Objekt optional.

        Nein, ist es nicht. Das Objekt (bzw. dessen Klassenname und die Parameter sind Pflicht).

        Wenn es Pflicht ist, kann es nicht überflüssig sein.

        Wenn du intern in der Funktion zwar zu dieser Erkenntnis gelangst, hat das dennoch nach außen hin keinerlei Relevanz: Die innere Funktionsweise hat die Außenwelt nicht zu interessieren.

        Zweitens: Wieso verlangt die Funktion ein Objekt, dass sie hinterher nicht braucht? Wenn man das offenbar anhand des übergebenen Parameters ermitteln kann, warum wird das nicht außerhalb der Funktion ermittelt?

        Weil die Funktion ein Cache für benötigte Objekte anlegt. Ob die Objekte später aber auch tatsächlich benötigt werden, regelt eine externe Controller-Klasse.

        Caching ist eine ganz schreckliche Aufgabe für "schöne Architektur", weil sie orthogonal zu allen Strukturen verläuft und mit denselben Grundbausteinen auf nahezu jeder Ebene wirken kann. Die Kunst ist, sich dadurch nicht die Architektur zu zerbröseln.

        Ein generell guter Tipp ist, Caching explizit zu separieren und als Decorator-Pattern zu implementieren. Das erfordert ggf. einen geringen Mehraufwand, um für jede normale Klasse auch noch ein vom Interface her identisches Duplikat mit nur der Cache-Funktion zu erstellen (die eigentliche Funktion bleibt in der Originalklasse und wird bei Cache-Miss vom Decorator aufgerufen, das Ergebnis dann im Cache gespeichert und zurückgegeben), aber es hat den unschlagbaren Vorteil, dass man sich gedanklich bei der Nutzung der Klasse eben gerade KEINE Gedanken machen muss, ob man sich jetzt mit dem Cache herumschlägt, oder mit dem Original: Man ruft immer dieselbe Methode des übergebenen Objekts auf.

        Wenn man ein Cache-Objekt hat, und der Cache hat einen Eintrag zu den Parametern, kriegt man das Ergebnis hoffentlich schneller, als wenn man die Original-Methode bemühen muss.

        Wenn die Funktion die Objekte herstellt, erfüllt sie eine weitere Aufgabe zusätzlich, die ihr nicht zusteht.

        Ja, das überlasse ich dann wohl der Controller-Klasse. Ändert aber erstmal nichts an meinem "Problem".

        Dein "Problem" ist nach meiner bisherigen Information eher weniger, dass du da eventuell sinnlose Objekte instanziierst, sondern dass es architektonisch Verbesserungsbedarf gibt.

        Und es hilft bei der Problembehebung auch nicht, wenn du versuchst, die Darstellung im Sinne deiner gewünschten Lösung zu abstrahieren - das führt gern mal zum XY-Problem: Du hast Problem X und denkst, dass Lösung Y gilt, fragst aber bei Problemen statt zu Problem X (was korrekt wäre) nur zum Problem mit Lösung Y.

        Immerhin wissen wir jetzt, dass es irgendwas mit Caching zu tun hat. Verrätst du mehr Details?

        Und alle haben sie unterschiedlich lange Parameterlisten des Konstruktors (was auch wieder sehr fragwürdig ist im Hinblick auf Vererbung und Selbstähnlichkeit, aber sofern die Objekte zumindest alle dasselbe Interface gegenüber der Funktion implementieren, kann das mit den notwendigen Unterschieden der Unterklassen durchaus begründet sein).

        Sie "extenden" alle die selbe Klasse.

        Erklärst du, warum es verschieden lange Konstruktorparameter-Listen gibt?

        Deine Beschreibung deutet an, dass die Klassenstruktur sowieso nicht optimal ist.

        Controller A sammelt Daten über Model B (welche Daten) und Daten über Model B (welche Bedingungen). Danach vergleicht A B mit C und liefert die Schnittmenge. Die Frage ist halt nur, warum ich die Ergebnisse von, in dem Fall, B instanzieren sollte bevor sie in der Schnittmenge landen? Was ist an der Klassenstruktur nicht optimal?

        Das kann ich dir nicht sagen, weil ich keine Details kenne. Die Nennung deines abstrahierten Codes kann ich für die Antwort nicht ernstlich heranziehen, denn da fehlt es am Kontext.

        Du hast außerdem außer deinem Gefühl für Unschönheit noch kein Argument GEGEN die Instanziierung gebracht. Das einzige Argument, das mir einfiele, wäre Performance - und das dann gültige Gegenargument ist: Tu nix im Konstruktor, außer die Parameter im Objekt lokal abzuspeichern.

        Ja deshalb hätte ich auch eigentlich eher gern die Parameter im Konstruktor. Allerdings kann ich "zur Not" natürlich auch in der externen Logik dafür sorgen, dass die Instanzierung einer "Konvention" erfolgt. Z.B. Objekt instanzieren, dann eine Funktion der Elternklasse aufrufen um die Parameter zu initialisieren.

        Ich verstehe nicht ganz genau, was du meinst, aber ich ahne, dass es mit Decorator-Pattern hier einen Ausweg geben könnte.

        Das einzige dann noch verbleibende Gegenargument wäre Speicherverbrauch. Um den kommst du aber bei keiner Lösung drum herum, denn ein Array mit den Konstruktorparametern kostet auch Speicher, ebenso wie das Callable.

        Schon 2 "einzige"? ;)
        Aber ja, am Ende braucht ein Objekt natürlich auch mehr Speicher als ein paar Strings (Klassenname+Parameter) in einem Array.

        Bist du dir da sicher?

        Ein Array benötigt pro Element 144 Byte. Speicherst du Strings, kommen diese Bytes noch obendrauf, plus ein wenig Overhead für Verwaltungsinformationen.

        Für ein Objekt weiß ich es leider nicht genau, aber da die sehr speicheraufwendige Hash-Doppelverlinkte-Liste-Konstruktion des Arrays entfällt, könnte das Rennen am Ende tatsächlich positiv für das Objekt ausgehen. Pro Instanz braucht man ja nur die Instanzvariablen speichern, der Code wird nur einmal benötigt und dürfte in der Regel bereits geladen sein.

        - Sven Rautenberg

      2. Tach!

        Da würde ich jetzt erstmal ganz simpel sagen: Na und?
        Und ich: "Und was?!"
        Und zweitens würde ich mich fragen, was das denn für eine grausame Funktion ist?
        Die ist aus mehreren Gründen grausam:
        Ah, na schauen wir doch mal...

        Es klingt ein bisschen, als ob du dich angegriffen fühlst. Aber das ist ganz sicher nicht Svens Absicht. Er kritisiert deinen Code, weil er vermutlich zurecht eine Menge Verbesserungspotential hat. Du hast den Code geschrieben und verteidigst ihn nun. Das ist menschlich und verständlich. Aber wenn ich dir einen Tipp geben darf: Sven hat in der Regel die besten Ideen und Erfahrung, was moderne Software-Architektur betrifft. Versuch mal, die Emotionen beiseite zu lassen und lass es zu, dass dein Code kritisch hinterfragt wird. Wenn du seine Ideen wohlwollend zu verstehen versuchst, wird dein Code dadurch nicht schlechter. - Weiterhin hast du nur das direkte Problem erwähnt und nicht das Umfeld beschrieben. Da lässt sich schwer herauslesen, warum er so aussieht wie er jetzt ist und vor allem schlecht einschätzen, ob das zur Zielerreichung dienlich ist oder doch lieber was ganz anderes gemacht werden sollte. Deswegen kann es auch gut sein, dass Tipps für den Augenblick gut scheinen aber im großen Ganzen doch nicht optimal sind.

        Erstens hat sie eine mangelhafte Signatur: Optionale Elemente gehören ans Ende der Parameterliste, und offenbar ist das Objekt optional.
        Nein, ist es nicht. Das Objekt (bzw. dessen Klassenname und die Parameter sind Pflicht).

        Einige der Parameter des Objekts sind aber optional und wenn du sie nicht durch ein Array kapselst, dann ist es besser, wenn sie hinten stehen und die beiden anderen Pflichtparameter nicht behindern.

        Deine Beschreibung deutet an, dass die Klassenstruktur sowieso nicht optimal ist.
        [...] Was ist an der Klassenstruktur nicht optimal?

        Das kann man erst sagen, wenn man die Hintergründe kennt. Versuch mal bitte etwas konkreter und nicht so abstrakt zu beschreiben, was du vorhast.

        dedlfix.

        1. Hey!

          Es klingt ein bisschen, als ob du dich angegriffen fühlst.

          Selbstverständlich wenn erstmal so eine Eröffnung (inklusive Ausführung) kommt
          "Da würde ich jetzt erstmal ganz simpel sagen: Na und?
          Und zweitens würde ich mich fragen, was das denn für eine grausame Funktion ist?"

          Wenn es bislang nur um "rohe" Klassen ging und keine einzige Zeile Code und auch nicht der Zusammenhang bekannt sind, so einzusteigen, hat schon was für sich.

          Weiterhin hast du nur das direkte Problem erwähnt und nicht das Umfeld beschrieben. Da lässt sich schwer herauslesen, warum er so aussieht wie er jetzt ist und vor allem schlecht einschätzen, ob das zur Zielerreichung dienlich ist oder doch lieber was ganz anderes gemacht werden sollte. Deswegen kann es auch gut sein, dass Tipps für den Augenblick gut scheinen aber im großen Ganzen doch nicht optimal sind.

          Genau. Wie kann man also erstmal reinpoltern ohne zu wissen um was es geht aber direkt alles in Grund und Boden stampfen? Nein danke, darauf kann ich dann auch verzichten. Ich werde mir Svens Vorschläge/Hinweise anschauen aber hier hat sich der thread für mich erstmal erledigt.

          Einige der Parameter des Objekts sind aber optional und wenn du sie nicht durch ein Array kapselst, dann ist es besser, wenn sie hinten stehen und die beiden anderen Pflichtparameter nicht behindern.

          Und wann habe ich gesagt, dass ich das so mache? Bis jetzt waren alles nur Überlegungen wie ich es umsetzen _könnte_.

          Das kann man erst sagen, wenn man die Hintergründe kennt. Versuch mal bitte etwas konkreter und nicht so abstrakt zu beschreiben, was du vorhast.

          Da gibt es nichts zu beschreiben weil ich (fast) nur mit rohen Klassen zum testen arbeite.

          Eine Funktion sammelt Informationen über verschiedene Model. Die Model erweitern alle eine Elternklasse, was aber nicht bedeutet, dass sie zwangsläufig die selben Konstruktoren besitzen müssen. Eine Andere Funktion bildet eine Schnittmenge mit den tatsächlich benötigten Models und initialisiert die entsprechenden Klassen.

          "Cheeseburger" braucht zum Beispiel die Model "Cheese('gauda', '1 scheibe')" und "Burger('weizenbrötchen', '1 formfleisch', '2 scheiben gurken', 'zwiebeln')".
          "Hamburger" braucht nur "Burger".

          Meine Funktion sammelt also "Cheese" und "Burger" und für welchen Burger sie gebraucht werden - übergeben von einer Controller-Funktion.
          Z.B. Cheeseburger: Cheese('gauda', '1 scheibe'), Burger('weizenbrötchen', '1 formfleisch' /* , ..*/)
          DoppelWopper: Cheese('gauda', '2 scheiben'), Burger('weizenbrötchen', '2 formfleisch', '4 scheiben gurken')

          Eine andere Controller-Funktion sucht nun alle Model die für einen bestimmten Burger gebraucht werden. Also zu Cheeseburgern den Cheese und zu Hamburgern nicht. Cheese wird also "aussortiert" und nicht erst ausgepackt und drauf gelegt und nach dem Vergleich wieder runter genommen. Speicher hin oder her (wenn sich nicht mal der Experte sicher ist), das ist einfach unlogisch.

          Ich werde dem Hinweis von Sven bezüglich Decorator-Patterns erstmal nachgehen und ansonsten meine favorisierte Version mit dem Array-Key-Value-Paar umsetzen.

          1. Tach!

            Wenn es bislang nur um "rohe" Klassen ging und keine einzige Zeile Code und auch nicht der Zusammenhang bekannt sind, so einzusteigen, hat schon was für sich.

            Naja, mit der notwendigen Erfahrung sieht man schon mit wenigen Blicken, wenn ein bestimmte Programmierung sagen wir mal komisch aussieht, weil sie schon ohne die eigentliche Zielsetzung zu kennen, ganz erheblich von den Best-Practice-Empfehlungen/-Erfahrungen abweicht (und auch nicht revolutionär innovativ oder ähnliches ist).

            Weiterhin hast du nur das direkte Problem erwähnt und nicht das Umfeld beschrieben. Da lässt sich schwer herauslesen, warum er so aussieht wie er jetzt ist und vor allem schlecht einschätzen, ob das zur Zielerreichung dienlich ist oder doch lieber was ganz anderes gemacht werden sollte. Deswegen kann es auch gut sein, dass Tipps für den Augenblick gut scheinen aber im großen Ganzen doch nicht optimal sind.
            Genau. Wie kann man also erstmal reinpoltern ohne zu wissen um was es geht aber direkt alles in Grund und Boden stampfen?

            Ich meinte damit eigentlich meine Vorschläge. Die gefallen dir vielleicht besser, weil sie mehr im Sinne deiner Lösung arbeiten, aber deine Lösung an sich bleibt damit immer noch in dem jetzigen Nicht-so-toll-Zustand. Manchmal ist es besser, die Lösung richtig auf den Prüfstand zu setzen, als sie sich - sagen wir mal - schönzusaufen. Das sieht dann zwar erstmal einwandfrei aus, aber die Kopfschmerzen kommen üblicherweise später auch noch.

            Nein danke, darauf kann ich dann auch verzichten. Ich werde mir Svens Vorschläge/Hinweise anschauen aber hier hat sich der thread für mich erstmal erledigt.

            Das ist schade, denn dadurch entgeht dir einiges an wertvollem Wissen. Wenn du deinen Groll noch etwas mehr glätten kannst, empfehle ich dir sehr, auf seine Rückfragen einzugehen.

            Einige der Parameter des Objekts sind aber optional und wenn du sie nicht durch ein Array kapselst, dann ist es besser, wenn sie hinten stehen und die beiden anderen Pflichtparameter nicht behindern.
            Und wann habe ich gesagt, dass ich das so mache? Bis jetzt waren alles nur Überlegungen wie ich es umsetzen _könnte_.

            Unsere Vorschläge sind auch nur Überlegungen. Sie können nur so gut sein, wie die Fragestellung. Je mehr vom eigentlichen Problem bekannt ist, desto besser kann man darauf eingehen. Es gibt immer eine Theorie-Lösung, die am besten funktioniert. Und es gibt den Kompromiss, den man in der Praxis mitunter eingehen muss. Bestmögliche Kenntnis der Aufgabenstellung ist sehr hilfreich bei der Suche nach dem optimalen Weg.

            Das kann man erst sagen, wenn man die Hintergründe kennt. Versuch mal bitte etwas konkreter und nicht so abstrakt zu beschreiben, was du vorhast.
            Da gibt es nichts zu beschreiben weil ich (fast) nur mit rohen Klassen zum testen arbeite.

            Eine Funktion sammelt Informationen über verschiedene Model. Die Model erweitern alle eine Elternklasse, was aber nicht bedeutet, dass sie zwangsläufig die selben Konstruktoren besitzen müssen. Eine Andere Funktion bildet eine Schnittmenge mit den tatsächlich benötigten Models und initialisiert die entsprechenden Klassen.

            Das ist leider nicht die Beschreibung des Problems sondern die Beschreibung eines Lösungsversuches. Vielleicht ergibt sich eine ganz andere Lösung, wenn man etwas weiter unten, nämlich am Klassenkonzept ansetzt.

            "Cheeseburger" braucht zum Beispiel die Model "Cheese('gauda', '1 scheibe')" und "Burger('weizenbrötchen', '1 formfleisch', '2 scheiben gurken', 'zwiebeln')".
            "Hamburger" braucht nur "Burger".

            Ich nehme mal an, das war ein nicht sehr auf die Aufgabenstellung bezogenes Beispiel. Eine gute Programmarchitektur ist selten eine 1:1-Abbildung der Realität. Ein Programm arbeitet am besten, wenn man im Sinne der Philosophie der verwendeten Programmiersprache und mit allgemein bewährten Programmiermustern und Algorithmen das Problem zu lösen versucht und nicht die Realität in ein Programm zu pressen versucht. Und selbst wenn, dann arbeitet ein Burger-Restaurant nicht mit halbwegs vorgefertigten Burgern, weil die Gurken in der Zwischenlagerzeit das Brötchen aufweichen. Stattdessen kommen erst bei der "Endmontage" alle Zutaten einzeln aus den jeweiligen Töpfen. Tut mir leid, aber das ist nicht besonders brauchbar, um eine Lösung für deine eigentliche Aufgabenstellung zu finden. Selbst wenn sie derzeit groß mit "Test" überschrieben ist.

            dedlfix.

          2. Moin!

            Es klingt ein bisschen, als ob du dich angegriffen fühlst.

            Selbstverständlich wenn erstmal so eine Eröffnung (inklusive Ausführung) kommt
            "Da würde ich jetzt erstmal ganz simpel sagen: Na und?
            Und zweitens würde ich mich fragen, was das denn für eine grausame Funktion ist?"

            Was als Argument gemeint war, "es" einfach so zu tun und nicht verzweifelt nach einer Instanzvermeidungsstrategie zu suchen. Es ist auch der Versuch, mehr Informationen herauszukitzeln, denn wie auch dedlfix immer klarer erkennt: Wir haben es hier mit einem klassischen X-Y-Problem zu tun: Du hast Problem X und denkst, es wird von Y gelöst, und fragst nun nach Hilfe zu Y, anstatt das Problem X zu beschreiben.

            Ich glaube, ich habe im Laufe der Zeit in Foren ganz gute Antennen dafür entwickelt, ob jemand nach seinem wirklichen Problem fragt, oder ob das wirkliche Problem sich noch eine Ebene dahinter verbirgt.

            Wenn es bislang nur um "rohe" Klassen ging und keine einzige Zeile Code und auch nicht der Zusammenhang bekannt sind, so einzusteigen, hat schon was für sich.

            Das wäre aus meiner Sicht noch ein Grund weniger, sich persönlich angegriffen zu fühlen - was auch nicht meine Absicht ist und war.

            Genau. Wie kann man also erstmal reinpoltern ohne zu wissen um was es geht aber direkt alles in Grund und Boden stampfen? Nein danke, darauf kann ich dann auch verzichten. Ich werde mir Svens Vorschläge/Hinweise anschauen aber hier hat sich der thread für mich erstmal erledigt.

            Du hast gefragt, ob's zu deiner Lösung noch was besseres gibt.
            Meine Antwort enthielt dazu mindestens zwei konkrete Lösungsangebote: 1. So lassen ("na und"), 2. Closures. Und den Versuch, dem Problem auf den Grund zu gehen.

            Und wann habe ich gesagt, dass ich das so mache? Bis jetzt waren alles nur Überlegungen wie ich es umsetzen _könnte_.

            Deine Schilderung klang so, als wäre konkreter Code dahinter, den du nicht nennen willst.

            Das kann man erst sagen, wenn man die Hintergründe kennt. Versuch mal bitte etwas konkreter und nicht so abstrakt zu beschreiben, was du vorhast.

            Da gibt es nichts zu beschreiben weil ich (fast) nur mit rohen Klassen zum testen arbeite.

            Eine Funktion sammelt Informationen über verschiedene Model. Die Model erweitern alle eine Elternklasse, was aber nicht bedeutet, dass sie zwangsläufig die selben Konstruktoren besitzen müssen. Eine Andere Funktion bildet eine Schnittmenge mit den tatsächlich benötigten Models und initialisiert die entsprechenden Klassen.

            An dieser Stelle sagt mir meine Erfahrung auch schon wieder: "Vorsicht, 'Code smell'". Models wie ich sie verstehe haben in der Regel nichts miteinander gemeinsam, so dass Vererbung von einer zentralen Klasse nicht notwendig sein sollte.

            Wenn wider Erwarten dafür doch starke Argumente vorliegen, würde ich untersuchen, ob man dem Problem besser mit Komposition statt Vererbung beikommen kann.

            "Cheeseburger" braucht zum Beispiel die Model "Cheese('gauda', '1 scheibe')" und "Burger('weizenbrötchen', '1 formfleisch', '2 scheiben gurken', 'zwiebeln')".
            "Hamburger" braucht nur "Burger".

            Meine Funktion sammelt also "Cheese" und "Burger" und für welchen Burger sie gebraucht werden - übergeben von einer Controller-Funktion.
            Z.B. Cheeseburger: Cheese('gauda', '1 scheibe'), Burger('weizenbrötchen', '1 formfleisch' /* , ..*/)
            DoppelWopper: Cheese('gauda', '2 scheiben'), Burger('weizenbrötchen', '2 formfleisch', '4 scheiben gurken')

            Also hast du ein Objekt-Konstruktionsproblem? Auch dafür gibts bereits schöne Lösungen, nur würde man vermutlich nicht Burger und Käse parallel in eine Funktion tun, um beide aufeinander zu legen oder den Käse wegzulassen, sondern man würde den Burger mit oder ohne den Käse bauen, und dann nur den in die Funktion tun.

            Dein Suchstichwort für Google hierfür heißt "Dependency Injection".

            Eine andere Controller-Funktion sucht nun alle Model die für einen bestimmten Burger gebraucht werden. Also zu Cheeseburgern den Cheese und zu Hamburgern nicht. Cheese wird also "aussortiert" und nicht erst ausgepackt und drauf gelegt und nach dem Vergleich wieder runter genommen. Speicher hin oder her (wenn sich nicht mal der Experte sicher ist), das ist einfach unlogisch.

            Da hast du vollkommen recht, dass man Objekte nicht erzeugen sollte, die man garantiert nicht braucht. Und schon sind wir wieder am Anfang der Diskussion und meiner Analyse: Wenn die Funktion das Objekt zwingend, weil als ersten Parameter mit nachfolgenden Pflichtparametern, verlangt, es dann aber wegwirft, stimmt was an der Parameterliste oder an der Funktion nicht.

            Leider bin ich dem wirklichen Problem X immer noch nicht näher gekommen, weil von dir dazu keine neuen Informationen kommen. Von "Objekte und Funktion" über "Caching" über "MVC-Modelschicht" bis zu "Dependency Injection" war jetzt schon alles dabei - und damit wurde vermutlich schon gut die Hälfte der typischen Konstruktionen genannt, und jede einzelne von denen hat ihre unterschiedliche, typische Lösung, die aber nie auf eine der anderen Probleme passt - und keine erzeugt Objekte, die dann wieder weggeworfen werden.

            - Sven Rautenberg

    2. moin,

      Und zweitens würde ich mich fragen, was das denn für eine grausame Funktion ist?

      Full Ack ;)

      Ich hab mich auch gefragt, was das werden soll. Falls das eine Factory ist, da ists besser, die Instanzen da zu holen/erstellen, wo sie gebraucht werden und nicht umgekehrt irgendwo reinzugeben (mehrfach gleich gar nicht), ansonsten blickt da kein Schwein mehr durch ;)

      Grundsätzlich gibts genau zwei Wege, Beziehungen zwischen Klassen herzustellen:
      1 Vererbung
      2 Delegation einzelner Methoden

      Horst

      --
      Unterschied zwischen Perl und PHP: Perl ist flüssig.
  4. Hey!

    Ok, ich versuche es nochmal anders und lasse die beiden letzten Parameter weg. Offenbar hängt ihr euch daran und an den Burgern zu sehr auf.
    Nehmen wir einfach mal irgendwelche Rezepte verschiedener Mahlzeiten eines 3-Gänge-Menüs.
    Vorspeise: Hühnernudesuppe
    Hauptgang: Backfisch
    Nachspeise: Sahneeis

    Das Rezept für die Suppe wird also gegeben. Jetzt haben wir aber einen Gast der Huhn nicht verträgt. Aus der Hühnernudelsuppe wird eine Nudelsuppe - das Huhn wird weg gelassen obwohl es im Rezept steht. Die Suppe wird also ohne Huhn "instanziert".
    Ausserdem haben wir einen Gast der gar keine Suppe will. Das Rezept ist bekannt, es wird aber nichts zubereitet. Das Rezept muss bekannt sein weil es zum Menü gehört und der nächste Gast eventuell auch genau die Suppe haben will die das Rezept verspricht.

    Jetzt haben wir also 1 Rezept welches in unterschiedlichen Varianten zubereitet wird - oder auch gar nicht.

    Dasselbe analog zum Backfisch und zum Sahneeis.

    Bevor also irgend etwas zubereitet wird, muss man wissen was alles da hinein gehört und was alles rein darf und dann daraus die Schnittmenge bilden bzw. das Rezept überspringen wenn ein Gast das Gericht oder gar das komplette Menü verweigert. Die Funktion sammelt in dem Beispiel zunächst die Rezepte des Menüs. Eine andere Funktion, der Koch, kümmert sich dann um die Zubereitung und achtet darauf, dass jeder Gast auch nur bekommt was er bekommen will und bekommen darf. Die Informationen über die Gäste liegen dem Koch neben den Rezepten vor.
    foobar('Rezept1', 'Huhn', 'Nudeln', 'Wasser');
    Gekocht werden soll new Rezept1('Nudeln', 'Wasser'); // Gast verträgt kein Huhn obwohl der Konstruktor so aussieht __construct($huhn = false, $nudeln = false, $wasser = true){}. Und die Rezepte sind immer verschieden.

    Was wäre also eine gute Lösung um die Rezepte zu kochen ohne, dass ich die Konstruktoren zur Angabe der Zutaten verwenden kann weil alle Konstruktoren verschieden sind und der Koch je nach Gast auch noch bestimmte Zutaten weg lassen muss?

    1. Tach!

      Ok, ich versuche es nochmal anders und lasse die beiden letzten Parameter weg. Offenbar hängt ihr euch daran und an den Burgern zu sehr auf.

      Nein, es sind nicht die Burger oder dein Rezeptversuch an sich, sondern dass beides unbrauchbare Dinge sind, um über die Erstellung eines Klassenmodells zu diskutieren. Ein Computer verarbeitet Daten. Dabei entstehen keine Gerichte. Ich kann dir bei diesen Real-World-Dingen, die man nicht direkt 1:1 im Computer abbildet, nicht raten, wie man das machen würde. Und auch nicht, ob das dann die beste Lösung für deine eigentliche Aufgabenstellung ist, von der du anscheinend gar nichts preisgeben möchtest.

      dedlfix.

      1. Hey!

        Fassen wir mal zusammen:
        Der eine meint, dass man Beispiele aus "der echten Welt" nicht 1:1 in Programmen abbilden kann.
        Der andere erklärt, ohne 1 Zeile Code gelesen zu haben, dass es sich um eine grausame Funktion handeln muss, hat dafür aber immerhin ein paar Lösungsansätze parat.
        Der dritte postet nur um zu bestätigen, dass es sich auch seiner Meinung nach um eine grausame Funktion handeln muss (ohne 1 Zeile Code gesehen zu haben).

        Ich gebe es auf.