Ankündigung

Einklappen
Keine Ankündigung bisher.

[Erledigt] Statische Methoden oder Instanzmethoden in Framework

Einklappen

Neue Werbung 2019

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • #16
    Naja technisch ist ein DIC ja dafür da Dinge vorzuhalten ( in welcher Form auch immer ) die injiziert werden sollen. Festlegen was wo injiziert werden darf bedarf einer Service / Dependency Firewall, was wohl irgendwo recht komplex werden kann.

    Ich hab jetzt so noch keine gute DI-Firewall-Implementierung gesehen.
    [URL="https://gitter.im/php-de/chat?utm_source=share-link&utm_medium=link&utm_campaign=share-link"]PHP.de Gitter.im Chat[/URL] - [URL="https://raindrop.io/user/32178"]Meine öffentlichen Bookmarks[/URL] ← Ich habe dir geholfen ? [B][URL="https://www.amazon.de/gp/wishlist/348FHGUZWTNL0"]Beschenk mich[/URL][/B].

    Kommentar


    • #17
      @monolith:
      Autowiring beschreibt eine Technik, Abhängigkeiten einer Klasse automatisch zu injizieren. Im Idealfall so, dass du die Abhängigkeiten einer Klasse weder durch irgendwelche speziellen Konventionen, noch durch Annotations oder Ableitung/Implementierung einer Klasse/eines Interface erst dazu befähigst.

      Das heißt, dass du einer Klasse (beispielsweise) im Construktor via Type-Hinting nur noch mitteilst, welche Instanzen anderer Klassen du gerne hättest. Auf diese Weise kommunizierst du nur noch mit Interfaces (oder mit Contracts, wenn dir da mehr LSP durchscheint). Ob ein Type "aktuell" an eine konkrete Implementation gekoppelt ist, ist erst mal unwichtig und kann später auch angepasst werden (in Sprachen wie Ruby gibt es leider nicht mal die Möglichkeit, körperlose Klassen wie Interfaces zu nutzen). Nirgendwo kommt man mit dem DIC in Berührung, außer im Bootstrapping-Stadium.

      Statische Klassen/Methoden sind dagegen, wie der Name schon vor gibt, statisch. Man kann statische Klassen nicht in ein Interface konvertieren und so an verschiedenen Stellen seiner Applikation unterschiedliche Implementationen dieses Interfaces bereitstellen. Will ich eine Klasse testen, die intern durch statische Abhängigkeiten mit der Außenwelt verbunden ist, dann muss ich diesen Umstand akzeptieren. Anders kann ich bei der Erstellung der Klasse, welche ich dank des sonst bestehenden Autowiring dieses mal mit einfachsten PHP-Mitteln auch komplett von Hand machen kann, die Abhängigkeiten durch (beispielsweise) testfreundliche Implementationen ersetzen.

      Kommentar


      • #18
        Will ich eine Klasse testen, die intern durch statische Abhängigkeiten mit der Außenwelt verbunden ist, dann muss ich diesen Umstand akzeptieren
        Was zwar bei facades ebenfalls gilt, aber dennoch fürs Testen der Zugriff auf Instanzen möglich ist.

        Ich durchblicke das zwar nicht ganz aber grundsätzlich ist das Konzept der static facades doch so mächtig wie autowiring?

        Kommentar


        • #19
          Autowiring ist expliziter.

          Klar kannst du Static Facades konfigurierbar machen. Sogar in Abhängigkeit zu einer konkreten Testsituation. Allerdings müssen die Static Facades eben konfigurierbar sein, was man ständig im Design der Anwendung berücksichtigen sollte.

          Wenn eine Klasse aber eine neue Abhängigkeit einführt, oder eine vorhandene Abhängigkeit ändert, dann funktionieren deine Testcases unter Umständen immer noch. Sie testen dann aber eine ältere, wahrscheinlich falsche Realität.

          Kommentar


          • #20
            Ok danke für die Erläuterungen. Ich behalte autowiring im Blick. Wenn es sich durchsetzt werde ich mich nicht dagegen sperren, vorerst aber nur beobachten und noch nicht in großem Maßstab selber nutzen. Wenn Laravel 5 erschienen ist wird es bestimmt Resonanz dazu geben und die schaue ich mir dann an.

            Kommentar


            • #21
              Static facades sind sowas wie das Aushängeschild von Laravel, ich bezweifel dass das irgendwann Aufgrund von auto-wiring aufgegeben wird.
              [URL="https://gitter.im/php-de/chat?utm_source=share-link&utm_medium=link&utm_campaign=share-link"]PHP.de Gitter.im Chat[/URL] - [URL="https://raindrop.io/user/32178"]Meine öffentlichen Bookmarks[/URL] ← Ich habe dir geholfen ? [B][URL="https://www.amazon.de/gp/wishlist/348FHGUZWTNL0"]Beschenk mich[/URL][/B].

              Kommentar


              • #22
                @monolith
                Das Autowiring hat sich in anderen Bereichen bereits längst durchgesetzt. So ist es im Java Umfeld bereits seit mind. JEE5 (Mai 2006) Enthalten und wird in jeder größeren Anwendung verwendet. Einzig müssen die Punkte an denen Instanzen injeziert werden sollen mit "@Inject" bzw. "@Autowired" annotiert werden, was aber dank nativer Unterstützung für Annotationen kein Problem darstellt.

                Im PHP Umfeld ist es jedoch noch recht unbekannt und noch nicht so mächtig (meine Meinung).

                Kommentar


                • #23
                  @monolith: Du musst da nichts im Auge behalten. Autowiring ist nur eine Hilfe, eine Automatisierung für etwas Grundlegenderes.

                  Es geht eigentlich um Inversion of Control (IoC). Eine Klasse sollte nicht von einer Implementierung, sondern von einem Interface, Contract oder einfacher "von einem abstrakten Lösungansatz" abhängig sein. Der konkrete Lösungsansatz, bzw. die konkrete Implementation wird von Außen, also von der auftraggebenden, übergeordneten Schicht zur Verfügung gestellt.

                  Auch Autowiring ist eigentlich nichts wirklich neues. Aber in der php-welt haben sich alternative Konzepte durchgesetzt, weil vieles auf Grund der PHP-Natur eben bei jedem Scriptstart neu initialisiert werden muss (lazy vs eager). Wenn man sich nur ganz kurz mit Autowiring beschäftigt, dann merkt man schnell, dass hier eigentlich etwas gegen die Natur von PHP gearbeitet wird. Läd man einen Controller, wird alles, was der Controller benotigt, vorab geladen. Egal, ob man es tatsächlich verwendet, oder nicht. Das heißt, dass man hier auch vorab bei OOA und OOD viel richtig machen muss, damit alles perfekt arbeitet und kein unnötiger Overhead entsteht.

                  Wenn man alles aber erst so weit hat, spart man sich schnell viel Arbeit. Autowiring macht gutes IoC-Design so einfach und komfortabel wie Autoloading von Klassen oder Instanzbeschaffung via Static Facades.

                  Bei PHP-DI4 kann man dann auf automatisch erzeugte Lazy-Proxies zurückgreifen, die bei größeren Implementationen erst dann eine Abhängigkeiten auflösen, wenn sie auch wirklich gebraucht wird. Das ist die eigentliche Headline. So kann man sich bei IoC theoretisch so gehen lassen, wie mit den "alternativen" Konzepten wie Static Facades oder der Überallverfügbarmachung einer DIC- oder SL-Instanz.

                  @tr0y: Auch die Laravel-macher sehen diesen Designaspekt mittlerweile nicht mehr unbedingt als sinnvoll. Wie schon geschrieben beherrscht auch laravel seit einiger Zeit Autowiring nativ und das wird mejner Ansicht nach in zukünftigen Versionen einen höheren Stellenwert bekommen.

                  Kommentar


                  • #24
                    @tr0y: Jedenfalls nicht mit L5, da ist es ja noch enthalten. Bleibt also noch bis mindestens Ende 2015.

                    Bei PHP-DI4 kann man dann auf automatisch erzeugte Lazy-Proxies zurückgreifen, die bei größeren Implementationen erst dann eine Abhängigkeiten auflösen, wenn sie auch wirklich gebraucht wird
                    Definitiv schön. Besteht die Chance, dass vor Sankt Nimmerleinstag nativ in PHP erleben zu dürfen? Der Sinn von lazy loading von Objekten erschließt sich nämlich sofort.

                    Kommentar


                    • #25
                      Wahrscheinlich nicht. Aber das übersteigt meine hellseherischen Fähigkeiten.

                      Kommentar


                      • #26
                        Bin grade zufällig auf den Thread gestoßen und will rein aus Interesse mitreden, obwohl er vllt nicht mehr ganz aktuell ist. Und zwar habe ich in diesem Zusammenhang eine kleine Frage: Soll durch das Autowiring ausschließlich Abhängigkeiten zu Klassen gelöst werden, die wiederum aus Klassen bestehen,... und KEINE primitiven (string, int,...) Parameter im Konstruktor haben? Oder ist auch das Auflösen von Parametern, die vorher irgendwo definiert wurden, urch Autowiring, wodurch sie in die jeweiligen Konstruktoren eingesetzt werden, zulässig.

                        Kommentar


                        • #27
                          Zitat von hartCoder Beitrag anzeigen
                          Bin grade zufällig auf den Thread gestoßen und will rein aus Interesse mitreden, obwohl er vllt nicht mehr ganz aktuell ist. Und zwar habe ich in diesem Zusammenhang eine kleine Frage: Soll durch das Autowiring ausschließlich Abhängigkeiten zu Klassen gelöst werden, die wiederum aus Klassen bestehen,... und KEINE primitiven (string, int,...) Parameter im Konstruktor haben? Oder ist auch das Auflösen von Parametern, die vorher irgendwo definiert wurden, urch Autowiring, wodurch sie in die jeweiligen Konstruktoren eingesetzt werden, zulässig.
                          Klar. Ich nutze PHPDI viel. Da geht das über eine zentrale Konfigurations-Datei. Generell versucht ein DIC alles automatisch aufzulösen, was aufgelöst werden kann. Da gibt es dann die von dir beschriebenen (aber nicht einzigen) Ausnahmen.

                          Nehmen wir an, wir haben die Klasse Test:

                          PHP-Code:
                          class Test {
                              
                          /**
                               * @var LoggerInterface
                               */
                              
                          private $logger;

                              
                          /**
                               * @param LoggerInterface $logger
                               */
                              
                          public function __construct(LoggerInterface $logger) {
                                  
                          $this->logger $logger;
                              }

                              
                          /**
                               */
                              
                          public function doSomething() {
                                  
                          $this->logger->info('Yeah');
                              }

                          LoggerInterface ist ein Interface. Kann also nicht instantiiert und damit auch nicht automatisch injiziert werden. Da kommt dann unsere Config ins Spiel:

                          PHP-Code:
                          return [
                              
                          LoggerInterface::class => factory(function () {
                                  
                          $logger = new Monolog('Test');
                                  
                          $logger->pushHandler(/* ... */);
                                  
                          /* ... */
                                  
                          return $logger;
                              }),
                          ]; 
                          Damit kann ich dann sagen, was injiziert werden soll, wenn ein LoggerInterface angefordert wird. Man kann so seine Applikation lose mit Komponenten verdrahten und benötigt dafür keinerlei "Magic". Wichtig ist hier nur, dass man versteht, wie man lose Kopplung ermöglicht. Dazu sollte man sich vor allem die SOLID-Prinzipien verinnerlicht haben. Es hilft sicher auch, ein paar DesignPattern zu kennen, weil man so leicht lernt in wiederverwendbaren Mustern zu denken.

                          Es ist auch nicht wirklich notwendig, seine ganze Anwendung mit Interfaces zu versorgen - zumindest dann nicht, wenn die Anwendung nicht absichtlich so gebaut wird um maximal (+sinnvoll) erweiterbar / änderbar zu sein. Normale Klassen haben ja auch ein Interface, von dem man ableiten kann.

                          Dann gibt es noch die scalar Arguments:

                          PHP-Code:
                          return [
                              
                          'logger.pushover.token' => PUSHOVER_TOKEN,
                              
                          'logger.pushover.user' => PUSHOVER_USER,

                              
                          LoggerInterface::class => factory(function (Container $container) {
                                  
                          $logger = new Monolog('Test');
                                  
                          $token $container->get('logger.pushover.token');
                                  
                          $user $container->get('logger.pushover.user');
                                  
                          $logger->pushHandler(new PushoverHandler($token$user));
                                  
                          /* ... */
                                  
                          return $logger;
                              }),
                          ]; 
                          oder

                          PHP-Code:
                          return [
                              
                          'logger.pushover.token' => PUSHOVER_TOKEN,
                              
                          'logger.pushover.user' => PUSHOVER_USER,

                              
                          LoggerInterface::class => factory(function (Container $container) {
                                  
                          $logger = new Monolog('Test');
                                  
                          $logger->pushHandler($container->get(PushoverHandler::class));
                                  
                          /* ... */
                                  
                          return $logger;
                              }),

                              
                          PushoverHandler::class => object(PushoverHandler::class)
                                  ->
                          constructParameter('token'link('logger.pushover.token'))
                                  ->
                          constructParameter('users'link('logger.pushover.user')),
                          ]; 
                          Aber warum dieser ganze Aufwand? Warum nicht gleich statische Methoden? In diesem Beispiel wird sicher schnell klar, dass man jetzt ohne große Mühe einen beliebigen PSR-3 kompatiblen Logger einsetzen kann, ohne irgendwo etwas an der Applikation anpassen zu müssen. Da ich in meiner Applikation nur mit einem LoggerInterface kommuniziere, weiss ich nichts von den tollen Extensions, die ein Monolog vielleicht noch so mitbringt.

                          Monolog ist auch so gut, warum sollte ich das wechseln wollen?
                          Monolog ist sicher ziemlich ausgereift. Ich könnte den Spieß umdrehen und einwerfen, dass ein Entwickler vielleicht zuvor auf etwas viel einfacheres als Monolog gesetzt hat und jetzt Monolog einführen will. Wenn der Entwickler zuvor mit einem DIC gearbeitet hat und ein generisches Interface wie LoggerInterface nutzt, dann hat er Glück. Sein Vorhaben ist in wenigen Minuten umgesetzt.

                          Ein Logger ist auch kein wirklich gutes Beispiel. Nehmen wir einen Onlineshop. Da haben wir vielleicht sowas wie einen Warenkorb. Jetzt hat so ein Anzugträger aus der Marketingabteilung mit einem Kollegen aus einer anderen Firma eine wilde Nacht gehabt und will jetzt unbedingt ein totally awesomeness neues Feature in den Shop implementen(, gestern) und das aber nur für Stammkunden über 30 (so typische Lastenhefte eben). Statt den Warenkorb jetzt kaputtzumachen und da irgendwelche Weichen einzubauen, die bei manchen Kunden rechtsdrehende rote Glöckchen im Hintergrund spielen, während der Kunde ahnungslos den fancy gestalteten Warenkorb betrachtet, kann man via DI abhängig vom aktuellen Kunden einen anderen Warenkorb (aufbauend auf dem gleichen Interface) implementieren, der dann die gewünschten Änderungen umsetzt.

                          Richtig knuffig wird es dann wenn man merkt, dass einem das Klassendesign auf einmal -ohne große Anpassungen- automatisierte >tests< ermöglicht. Das ist das, was man nur in der Freizeit machen kann, weil jeder Controller, der was auf sich hält, da als erstes den Rotstift ansetzt. Das funktioniert aber auch nur dann wieder richtig, wenn man sich an die SOLID-Spielregeln gehalten hat. Also möglichst wenige Abhängigkeiten und wenig Kompetenz pro Klasse.

                          Es gehört dann auch noch ne Menge Übung dazu, bis man das alles korrekt einsetzen kann. Es lohnt sich aber.

                          Kommentar


                          • #28
                            Sehr, sehr gut erläutert. Ich finde deine Ausführungen superklasse.
                            Code:
                            Es ist auch nicht wirklich notwendig, seine ganze Anwendung mit Interfaces zu versorgen
                            Vollkommen richtig, dem Sinn ebend entsprechend. Nochmals vielen Dank für deine Mühe.

                            Kommentar


                            • #29
                              Man sollte zu erst einmal differenzieren zwischen:
                              - Static Design
                              - Static Factory Methods
                              - Static Facades ( the laravel way )

                              Bei ersterem erklärt sich der Supergau von selbst, zuerst brichst du prinzipiell mit der OOP-Intention indem du Dienste eine Klasse statisch machst, OOP lebt von der Handhabung von objekten, nicht von Prozeduralem Abklatsch ( was static's generell ja sind ).

                              Static Factory Methods sind einem Pattern unterworfen, das sicherstellen soll das ein Objekt das instanziiert werden kann, dies mit einer "eingebauten Factory" die statisch aufgerufen werden kann von statten gehen soll(te). Im Grunde kein Schlechter Gedanke, das ist auch Interface-Kompatibel, auch aus Objekt-Sicht.

                              Static Facades sind eigentlich nur Wrapper eines öffentlichen objekt-orientierten Interface in eine Prozedurale ( static ) Umgebung. Solange ein Static Facade ein oder mehrere Interfaces statisch repräsentiert bricht man damit zwar nicht das OOP-Konzept, man liefert aber eine "Magische" Anlaufstelle um Aufgaben zu erledigen für das prinzipiell ein Objekt nötig gewesen wär.

                              Die Intention von Laravel solche static Interfaces zu liefern war das man einen Einfachen Zugriff auf Framework-Komponenten hat, was soweit auch okay ist. Nur dediziert man damit Abhängigkeiten in die Runtime: Was bedeutet das Dependencies erst identifiziert werden wenn auf eine spezifische Abhängigkeit zugegriffen wird, statt beim Erzeugen ( Construction ) eines Objekts.

                              Das wiederum erschwert die Wartbarkeit und die Fehlerverfolgung und lagert explizite Debugging-Mechanismen die die Wartung erleichtern könnten in die Anwendung ein ( Overhead ). Auch ist die Ausgrenzung von spezifischen Komponenten in spezifischen Scopes ( einem spezifischen Klassenumfeld ) mehr als nur Schwierig und Grausam umzusetzen ( noch mehr Overhead ).

                              Es empfiehlt sich daher mit Container zu arbeiten die Auto-wire unterstützen und alle Notwendigen Abhängikeiten besorgen und injizieren. Wenn man unbedingt einen erleichterten Zugriff auf eine Komponente liefern will, sollte man dessen Implementierung auf lokale Helper-Methoden begrenzen.

                              Ich bastel aktuell selbst an einem Web-Framework, und werde Static Facades nicht verwenden. Ich begrenze das ganze auf Helper-Methoden und Static Factory Methods. Beispiel
                              [URL="https://gitter.im/php-de/chat?utm_source=share-link&utm_medium=link&utm_campaign=share-link"]PHP.de Gitter.im Chat[/URL] - [URL="https://raindrop.io/user/32178"]Meine öffentlichen Bookmarks[/URL] ← Ich habe dir geholfen ? [B][URL="https://www.amazon.de/gp/wishlist/348FHGUZWTNL0"]Beschenk mich[/URL][/B].

                              Kommentar


                              • #30
                                Zitat von tr0y Beitrag anzeigen
                                Static Factory Methods sind einem Pattern unterworfen, das sicherstellen soll das ein Objekt das instanziiert werden kann, dies mit einer "eingebauten Factory" die statisch aufgerufen werden kann von statten gehen soll(te). Im Grunde kein Schlechter Gedanke, das ist auch Interface-Kompatibel, auch aus Objekt-Sicht.
                                Das bedeutet aber dann auch eine Gleichschaltung der möglichen Constructor-Parameter, was du bei autowiring per-se nicht hast. Ein Beispiel:

                                PHP-Code:
                                Car::createInstance($owner$configuration)->startEngine(); 
                                Hier hat man sowohl eine harte Bindung an die statische Klasse Car, als auch eine harte Vorgabe, mit welchen Parametern gearbeitet werden kann.

                                Dem gegenüber steht die nicht-statische Variante:

                                PHP-Code:
                                $carFactory->createInstance($owner$configuration)->startEngine(); 
                                Hier kannst du $carFactory bereits via DI einen ServiceLocator verpassen, der dann die Instanziierung des Objekts vornimmt:

                                PHP-Code:
                                public function createInstance(Owner $ownerConfiguration $configuration) {
                                    return 
                                $this->objectFactory->create(Entity::class, ['owner' => $owner'configuration' => $configuration]);

                                So könnte Entity auch andere als angegebene Abhängigkeiten oder Parameter haben, die in der Konfiguration des DIC oder automatisch aufgelöst werden.

                                Zitat von tr0y Beitrag anzeigen
                                Static Facades sind eigentlich nur Wrapper eines öffentlichen objekt-orientierten Interface in eine Prozedurale ( static ) Umgebung. Solange ein Static Facade ein oder mehrere Interfaces statisch repräsentiert bricht man damit zwar nicht das OOP-Konzept, man liefert aber eine "Magische" Anlaufstelle um Aufgaben zu erledigen für das prinzipiell ein Objekt nötig gewesen wär.

                                Die Intention von Laravel solche static Interfaces zu liefern war das man einen Einfachen Zugriff auf Framework-Komponenten hat, was soweit auch okay ist. Nur dediziert man damit Abhängigkeiten in die Runtime: Was bedeutet das Dependencies erst identifiziert werden wenn auf eine spezifische Abhängigkeit zugegriffen wird, statt beim Erzeugen ( Construction ) eines Objekts.
                                Daher koennen viele DI-Frameworks auch einen Lazy-Instantiation-Wrapper um Objekte legen.

                                Das Problem von static facades ist, dass du dann in deinem Code eine Abhängigkeit zu einer konkreten statischen Klasse hast, die du auch nicht austauschen/konfigurieren kannst. Das ist das Gleiche, als würdest du prozedural programmieren.

                                Zitat von tr0y Beitrag anzeigen
                                Das wiederum erschwert die Wartbarkeit und die Fehlerverfolgung und lagert explizite Debugging-Mechanismen die die Wartung erleichtern könnten in die Anwendung ein ( Overhead ). Auch ist die Ausgrenzung von spezifischen Komponenten in spezifischen Scopes ( einem spezifischen Klassenumfeld ) mehr als nur Schwierig und Grausam umzusetzen ( noch mehr Overhead ).
                                Hier kann ich dir nicht mehr folgen.

                                Zitat von tr0y Beitrag anzeigen
                                Es empfiehlt sich daher mit Container zu arbeiten die Auto-wire unterstützen und alle Notwendigen Abhängikeiten besorgen und injizieren. Wenn man unbedingt einen erleichterten Zugriff auf eine Komponente liefern will, sollte man dessen Implementierung auf lokale Helper-Methoden begrenzen.

                                Ich bastel aktuell selbst an einem Web-Framework, und werde Static Facades nicht verwenden. Ich begrenze das ganze auf Helper-Methoden und Static Factory Methods. Beispiel
                                Counterexample:

                                PHP-Code:
                                $container->call(function (Baud\Application $app) {
                                    
                                $app->bootstrap(function (App\Main $mainServer $server) {

                                        
                                # Implement custom frame "main"
                                        
                                $this->implement($main);

                                        
                                # set environments
                                        
                                $this->environment('test', function() use ($server) {
                                            
                                # limit environment "test" to a ip pattern
                                            
                                $this->address('192.168.*.*');

                                            
                                # create server global value injection
                                            
                                $request = ['PATH_INFO' => '/foo/bar'];

                                            
                                # inject to server superglobal copy and morph to request
                                            
                                return $server->inject($request)->asRequest();
                                        });
                                    })->
                                run();
                                }); 
                                Ich weiss jetzt nicht viel über deine dargestellte Anwendung. Abhängig davon, was ->environment ist und tut, kann man hier auch wieder mit einer ObjectFactory (Abwandlung eines ServiceLocators für Factories) arbeiten und den Parameter $server dort verbauen.

                                Kommentar

                                Lädt...
                                X