pl: Welches Entwurfsmuster steckt in Traits

problematische Seite

Seit PHP 5.4.0 gibt es in PHP eine Methode der Wiederverwendung von Code, die Traits genannt wird.

Leider hat man vergessen, in der weiteren Erklärung zu erklären welchem Entwurfsmuster PHP-Traits entsprechen. weiß das jemand?

MFG

  1. problematische Seite

    http://scg.unibe.ch/research/traits

    LG andaris

  2. problematische Seite

    Hallo pl,

    eher nicht.

    Traits an sich sind ein Konzept der OO, d.h. weniger als ein Pattern. Sie können von einzelnen Sprachen unterschiedlich implementiert werden.

    PHP mischt den Trait in die Klasse ein, so dass die Trait-Methoden nachher wie native Methoden der Klasse genutzt werden können.

    In JS kann man das im nachbilden, indem man Methoden aus einem Trait-Prototypen in ein Objekt oder einen Klassen-Prototypen[1] kopiert. Ich nehme an, dass das in Perl ähnlich geht.

    In einer Sprache, wo Traits nicht Teil der Sprache sind und auch das Methodendictionary nicht zur Laufzeit manipulierbar ist, benötigt man Techniken wie Delegation und Injection, um sich zu behelfen. Das ist dann sprachspezifisch. Zum Pattern reichts es aber meiner Meinung nach immer noch nicht.

    Rolf

    --
    sumpsi - posui - clusi

    1. Ja ich weiß, das ist auch nur ein Objekt. ↩︎

    1. problematische Seite

      PHP mischt den Trait in die Klasse ein, so dass die Trait-Methoden nachher wie native Methoden der Klasse genutzt werden können.

      Das heißt, daß man innerhalb dieser Methoden die aufrufende Instanz zur Verfügung hat und damit auf

      • weitere Methoden dieser Instanz sowie
      • deren Eigenschaften zugreifen können sollte.

      Leider habe ich gerade hierzu kein Beispiel gefunden.

      In einer Sprache, wo Traits nicht Teil der Sprache sind und auch das Methodendictionary nicht zur Laufzeit manipulierbar ist, benötigt man Techniken wie Delegation und Injection, um sich zu behelfen.

      Das ist so nicht richtig. Traits als Solche kann man auch mit älteren Perl- und PHP- Versionen wo Traits nicht Bestandteil der Sprache sind bauen.

      Was das Entwurfsmuster betrifft. das muss wahrscheinlich erst erfunden werden 😉

      MFG

      1. problematische Seite

        Hallo pl,

        Traits als Solche kann man auch mit älteren Perl- und PHP- Versionen wo Traits nicht Bestandteil der Sprache sind bauen.

        Ja. Sicher. Sag ich doch. Mit Delegation (um den Aufruf zur Trait-Methode zu delegieren) und Injektion (um dem Trait das nutzende Objekt verfügbar zu machen).

        Wenn du das als falsch ansiehst, dann zeig doch bitte mal ein PHP Beispiel für deine Sicht her. Und bitte nicht mit __call. Das wäre eine Form von Delegation.

        Was das Entwurfsmuster betrifft. das muss wahrscheinlich erst erfunden werden

        Wie gesagt:

        Traits an sich sind ein Konzept der OO, d.h. weniger als ein Pattern.

        Rolf

        --
        sumpsi - posui - clusi
        1. problematische Seite

          Hallo pl,

          Traits als Solche kann man auch mit älteren Perl- und PHP- Versionen wo Traits nicht Bestandteil der Sprache sind bauen.

          Ja. Sicher. Sag ich doch. Mit Delegation (um den Aufruf zur Trait-Methode zu delegieren) und Injektion (um dem Trait das nutzende Objekt verfügbar zu machen).

          So wird es auch im Decorator-Pattern gemacht. Ein Nachteil im Vergleich zu einem statischen Trait ist, dass das Verhalten dynamisch hinzugefügt wird. Und ein Decorator kann auch nur auf öffentliche Methoden der Komponenten-Klasse zugreifen.

          1. problematische Seite

            Hallo pl,

            Traits als Solche kann man auch mit älteren Perl- und PHP- Versionen wo Traits nicht Bestandteil der Sprache sind bauen.

            Ja. Sicher. Sag ich doch. Mit Delegation (um den Aufruf zur Trait-Methode zu delegieren) und Injektion (um dem Trait das nutzende Objekt verfügbar zu machen).

            So wird es auch im Decorator-Pattern gemacht. Ein Nachteil im Vergleich zu einem statischen Trait ist, dass das Verhalten dynamisch hinzugefügt wird. Und ein Decorator kann auch nur auf öffentliche Methoden der Komponenten-Klasse zugreifen.

            Deswegen ist das Decoratorpattern keine Alternative zu Traits, denn man möchte in Methoden auf sämtliche Eigenschaften der Instanz zugreifen können und zwar uneingeschränkt.

            MFG

          2. problematische Seite

            Hallo 1unitedpower,

            So wird es auch im Decorator-Pattern gemacht

            Ja, sicher. Ich habe aber bewusst darauf verzichtet, den Decorator als mögliches Pattern für eine Trait-Implementierung zu bezeichnen, weil es in beiden möglichen Richtungen logisch nicht passt.

            (1) Der Trait dekoriert das Nutzerobjekt: Das ist ein vom Schwanz her aufgezäumtes Pferd. Traits sind wiederverwendbar und sollten vom Nutzerobjekt nicht mehr wissen als unbedingt nötig. Ein Trait, der ein Objekt dekoriert, muss aber die gesamte public-Schicht dieses Objekts durchleiten. Geht gar nicht.

            (2) Das Nutzerobjekt dekoriert den Trait: Das widerspricht der Idee, dass ein Dekorator dazu dient, ein Objekt zu erweitern, das an sich eigenständig nutzbar und vollständig ist. Der Trait ist kein solches Objekt. Hinzu kommt, dass ein dekoriertes Objekt nichts davon merken sollte, dass es dekoriert wird. Viele Traits benötigen aber Elemente der nutzenden Klasse (was in PHP durch ein geteiltes $this und durch ”just use it“ implementiert wird und aus meiner Sicht eine architektonische Schlangengrube darstellt).

            Rolf

            --
            sumpsi - posui - clusi
            1. problematische Seite

              Ja, sicher. Ich habe aber bewusst darauf verzichtet, den Decorator als mögliches Pattern für eine Trait-Implementierung zu bezeichnen, weil es in beiden möglichen Richtungen logisch nicht passt.

              Ich habe nur an die erste Richtung gedacht.

              (1) Der Trait dekoriert das Nutzerobjekt: Das ist ein vom Schwanz her aufgezäumtes Pferd. Traits sind wiederverwendbar und sollten vom Nutzerobjekt nicht mehr wissen als unbedingt nötig. Ein Trait, der ein Objekt dekoriert, muss aber die gesamte public-Schicht dieses Objekts durchleiten. Geht gar nicht.

              Die Public-API durchzuschleifen ist nicht elegant, da gebe ich dir absolut recht. Das ist ja aber auch nicht notwendig. Ich würde auch nicht versuchen mit dem Decorator das Verhalten von Traits zu simulieren. Trotzdem kann ich ich mit beiden Techniken Ähnliches erreichen: Ich erweitere einen Datentypen mit einer gewünschten Funktionalität.

        2. problematische Seite

          Traits als Solche kann man auch mit älteren Perl- und PHP- Versionen wo Traits nicht Bestandteil der Sprache sind bauen.

          Ja. Sicher. Sag ich doch. Mit Delegation

          Delegation braucht man dafür nicht.

          (um den Aufruf zur Trait-Methode zu delegieren) und Injektion (um dem Trait das nutzende Objekt verfügbar zu machen).

          Das nutzende Objekt braucht man nicht im Tait sondern in den Methoden eines Trait. So nutzt man nicht einen Trait schlechthin sondern Methoden eines Trait. Von daher wird die aufrufende Instanz nicht an den Trait übergeben sondern an die Methode beim Aufruf derselben, gewöhnlich mit dem Pfeiloperator $instance->foo().

          Und seitdem in Perl wie auch in PHP Instanzen Referenzen sind, kann man diese Referenzen natürlich auch anderweitig übergeben. Von einer Injektion ist jedoch nur dann die Rede, wenn eine solche Übergabe in den Konstruktor erfolgt. Hierzu wäre anzumerken, daß Traits keine Instanzen ezeugen und damit ist der Begriff der Injektion hier falsch.

          MFG

          1. problematische Seite

            Hallo pl,

            wir sprachen von dem Szenario, wo Traits nicht Teil der Sprache sind. Da funktioniert $instance->foo() nicht, um die Methode foo des Traits aufzurufen.

            Wie würde dein PHP 5.3 Nachbau von Traits aussehen?

            Rolf

            --
            sumpsi - posui - clusi
            1. problematische Seite

              Wie würde dein PHP 5.3 Nachbau von Traits aussehen?

              Wie eine ganz normale Methode die eine Instanz übergeben bekommt.

              Und wie gesagt, ich suche das Beispiel dafür, wie in Methoden die in PHP über Traits definiert sind, auf Eigenschaften und Methoden der aufrufenden Instanz zugegriffen wird. Also ich gehe mal davon aus, daß infolge der Anwendung des Pfeiloperators mit $o->method() eine Übergabe stattfindet, so daß man in der Methode die Instanz in $this vorfindet. Ist das so?

              MFG

              1. problematische Seite

                Hallo pl,

                warum probierst Du es nicht aus

                Im einfachen Fall sieht das so aus:

                <?php
                trait RSS {
                    function _rss() {
                        echo "Here is your RSS feed for the $this->pageName forum\n";
                    }
                }
                class Forum {
                    use RSS;
                    private $pageName;
                    function __construct($name) {
                        $this->pageName = $name;
                    }
                    function control() {
                        $this->_rss();
                    }
                }
                
                $f = new Forum("Demo");
                $f->control();
                

                Aber ich finde es unschön, dass der Trait an den Daten des Objekts herummatscht. Besser finde ich:

                <?php
                trait RSS {
                    function _rss() {
                        echo "Here is your RSS feed for the {$this->getName()} forum\n";
                    }
                    abstract function getName();
                }
                class Forum {
                    use RSS;
                    private $pageName;
                    function __construct($name) {
                        $this->pageName = $name;
                    }
                    function control() {
                        $this->_rss();
                    }
                    function getName() {
                        return $this->pageName;
                    }
                }
                
                $f = new Forum("Demo");
                $f->control();
                

                Hier wird die nutzende Klasse gezwungen, dem Trait die erforderliche Schnittstelle bereitzustellen.

                Unter PHP 5.3 dagegen muss man den Trait entweder als Objekt bereitstellen oder statische Methoden verwenden. Ich würde es vielleicht über eine Servicefactory lösen.

                <?php
                class RSS {
                    function render($forum) {
                        echo "Here is your RSS feed for the {$forum->getName()} forum\n";
                    }
                }
                class Forum {
                    private $pageName;
                    function __construct($name) {
                        $this->pageName = $name;
                    }
                    function control($serviceFactory) {
                        $rss = $serviceFactory->getService("RSS");    // so
                        $rss = $serviceFactory->getRssService();      // oder so
                        $rss->render($this);
                    }
                    function getName() {
                        return $this->pageName;
                    }
                }
                
                $f = new Forum("Demo");
                $f->control($serviceFactory);
                

                Vermutlich würde ich diesen Ansatz auch dann wählen, wenn ich PHP 5.4 oder höher hätte. Grund: Der Code für den RSS Feed wird nur gebraucht wenn der RSS Feed explizt angefordert wird. Ansonsten braucht sich PHP damit nicht zu befassen. Und das Darstellen der Topics in HTML würde ich vermutlich über einen weiteren Service implementieren.

                Wobei - im konkreten Fall könnte es genügen, zwei unterschiedliche Templates anzuwenden...

                Rolf

                --
                sumpsi - posui - clusi
                1. problematische Seite

                  warum probierst Du es nicht aus

                  OK:

                  trait RSS {
                      function _rss() {
                          print_r($this);
                      }
                  }
                  
                  class FF{
                      use RSS;
                      function construct(){
                          $this->DIR = "/";
                      }
                  }
                  
                  $f = new FF();
                  $f->_rss();
                  

                  Warum sehe ich da die Eigenschaft nicht? Per Default ist die public.

                  MFG

                  1. problematische Seite

                    Warum sehe ich da die Eigenschaft nicht? Per Default ist die public.

                    War Tippfehler __construct() muss es heißen, sorry. MFG

                  2. problematische Seite

                    Hallo pl,

                    public ist nicht das Problem. print_r gibt auch private parts[1] aus

                    Das Problem liegt anderswo. Aber ich habe auch erstmal eine Weile gesucht...

                    Edit: Oh. Man sollte fertiglesen im Thread bevor man antwortet. Blödmann, Ich...

                    Rolf

                    --
                    sumpsi - posui - clusi

                    1. Äh... ↩︎

                    1. problematische Seite

                      neee, lag am guck, wird immer schlächter 😉

                      (Brille nützt da nichts, ist nervlich bedingt)

                2. problematische Seite

                  Der Code für den RSS Feed wird nur gebraucht wenn der RSS Feed explizt angefordert wird.

                  Genau das ist der Sinn einer Factory. In meinem FW sind alle Methoden der Factory als Traits implementiert, in Perl wie in PHP. MFG

                3. problematische Seite

                  Unschön ist, daß ein use Traitname; klassenweit deklariert werden muss. Das setzt voraus, daß man bereits beim Klassenentwurf angeben muss welche Traits verwendet werden sollen. Abgesehen davon daß der Code des Trait dann auch schon kompiliert wird bevor man ihn verwendet.

                  Für eine Factory ist sowas ungeeignet. MFG

                  1. problematische Seite

                    Hallo pl,

                    ist halt ein statisches Konzept. Willst Du's zur Runtime haben, mach's wie weiter oben vorgeschlagen mit einer Servicefactory.

                    Traits sind nicht zum Nachladen von Code vorgesehen.

                    Rolf

                    --
                    sumpsi - posui - clusi
                    1. problematische Seite

                      Traits sind nicht zum Nachladen von Code vorgesehen.

                      Meine Traits schon 😉 Die ich im Übrigen automatisch lade.

                      MFG

                    2. problematische Seite

                      Traits sind nicht zum Nachladen von Code vorgesehen.

                      Es gibt dennoch genügend Fälle wo auch ohnedessen Traits sinnvoll sind. In meinem Beispiel gänge das auch vorausschauend zu programmieren. Denn es steht bereits beim Klassenentwurf fest, daß die Klasse Forum wie die Klasse RSS eine Methode benutzt die in beiden Fällen identisch sind.

                      Ein solcher Fall ist im Grunde ein Fall für Vererbung, also derartige Methoden in derjenigen Klasse zu definieren von der beide der o.g. Klassen erben. Hinsichtlich dessen, es dennoch nicht so zu machen, ein paar Überlegungen:

                      1. Die Basisklasse wird immer kompiliert, von daher definiert man in der Basisklasse nur Methoden die tatsächlich für jeden Programmablauf gebraucht werden.
                      2. Methoden in Traits zu definieren hat den Vorteil, daß man bei Erweiterungen den Code der Basisklasse nicht ändern muss.

                      Allein aus diesen beiden Üerlegungen ergeben sich wichtige Konsequenzen für ein Framework von Skalierbarkeit über Performanze bis Kommerz.

                      Deswegen hab ichs hier auch nochmal aufgeschrieben.

                4. problematische Seite

                  Aber ich finde es unschön, dass der Trait an den Daten des Objekts herummatscht.

                  Nun, das haben Methoden so an sich daß sie mit Eigenschaften arbeiten. Sonst wären es ja keine Methoden.

                  Wobei - im konkreten Fall könnte es genügen, zwei unterschiedliche Templates anzuwenden...

                  Im konkreten Fall werden RSS Feed's erstellt. Das Template ist für alle Feeds gleich:

                  <?xml version="1.0"?>
                  <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
                  <channel>
                  <atom:link href="%selfurl%" rel="self" type="application/rss+xml" />
                  
                  <title>%title%</title>
                  <description>%descr%</description>
                  <link>%link%</link>
                  <language>de-de</language>
                  <dc:creator>%pubisher%</dc:creator>
                  
                  <TMPL_LOOP NAME="items">
                  <item>
                      <dc:creator>%author%</dc:creator>
                      <title>%title%</title>
                      <dc:date>%date%</dc:date>
                      <description><![CDATA[%descr%]]></description>
                      <guid isPermaLink="true">%link%</guid>
                      <link>%link%</link>
                  </item>
                  </TMPL_LOOP>
                  
                  </channel>
                  </rss>
                  

                  Und kann mit jeder TE gerendert werden die HTML::Template Kompatibel ist (C, PHP, Perl).

                  MFG

        3. problematische Seite

          Mein Beispiel:

          class Forum{
            function control(){
              # erzeugt den RSS Feed über einen Parameter im Forum selbst
              else if( $this->param('rss') ){
                $this->_rss();
              }
            }
          }
          

          _rss() ist eine Methode die in einem Trait definiert ist. Damit ist das möglich:

          ; erzeugt den RSS Feed durch Bindung einer anderen Klasse an beliebigen URL
          [/forum.rss]
          short=Forum
          class=RSS
          tabn=forum
          

          Also daß diese Methode von einer anderen Klasse aus genutzt werden kann. Und natürlich muss innerhalb der _rss() Methode auf die Eigenschaften der aufrufenden Instanz zugegriffen werden um das XML-Template für den RSS Feed mit den richtigen Daten rendern zu können.

          Aber eigentlich suche ich ja ein diebezügliches Beispiel in PHP.

          MFG

  3. problematische Seite

    Seit PHP 5.4.0 gibt es in PHP eine Methode der Wiederverwendung von Code, die Traits genannt wird.

    Und noch eine Frage: In der Doku steht, daß man Methoden eines Trait genauso verwendet wie jede andere Methode. Für mein Verständnis heißt das, daß man in Trait-Methoden $this zur Verfügung hat. Ist das so?

    MFG

    1. problematische Seite

      Hallo pl,

      ja, ist so. Und du hast recht: die Doku erwähnt das nicht ausdrücklich, aber die Kommentare auf dieser Seite gehen darauf ein. Such einfach man nach $this.

      Rolf

      --
      sumpsi - posui - clusi
      1. problematische Seite

        ja, ist so. Und du hast recht: die Doku erwähnt das nicht ausdrücklich, aber die Kommentare auf dieser Seite gehen darauf ein. Such einfach man nach $this.

        Hab ich da wohl übersehen, danke. Also ich denke daß vielen PHP-Programmierern gar nicht klar ist daß mit dem Pfeiloperator eine Übergabe der Instanz stattfindet wenn Methoden aufgerufen werden.

        MFG