Wie organisieren und verwalten Sie Ihre Hilfsobjekte wie das Datenbankmodul, Benutzerbenachrichtigungen, Fehlerbehandlung usw. in einem PHP-basierten, objektorientierten Projekt?
Angenommen, ich habe ein großes PHP-CMS. Das CMS ist in verschiedene Klassen unterteilt. Einige Beispiele:
- das Datenbankobjekt
- Benutzerverwaltung
- eine API zum Erstellen / Ändern / Löschen von Elementen
- Ein Nachrichtenobjekt zum Anzeigen von Nachrichten für den Endbenutzer
- Ein Kontext-Handler, der Sie zur richtigen Seite führt
- Eine Navigationsleistenklasse, die Schaltflächen anzeigt
- ein Protokollierungsobjekt
- möglicherweise benutzerdefinierte Fehlerbehandlung
etc.
Ich beschäftige mich mit der ewigen Frage, wie diese Objekte am besten für jeden Teil des Systems zugänglich gemacht werden können, der sie benötigt.
Mein erster Ansatz vor vielen Jahren war es, eine globale $ -Anwendung zu haben, die initialisierte Instanzen dieser Klassen enthielt.
global $application;
$application->messageHandler->addMessage("Item successfully inserted");
Ich habe dann auf das Singleton-Muster und eine Factory-Funktion umgestellt:
$mh =&factory("messageHandler");
$mh->addMessage("Item successfully inserted");
aber damit bin ich auch nicht zufrieden. Unit-Tests und Kapselung werden für mich immer wichtiger, und nach meinem Verständnis zerstört die Logik hinter Globalen / Singletons die Grundidee von OOP.
Dann gibt es natürlich die Möglichkeit, jedem Objekt eine Reihe von Zeigern auf die benötigten Hilfsobjekte zu geben, wahrscheinlich die sauberste, ressourcenschonendste und testfreundlichste Methode, aber ich habe Zweifel an der Wartbarkeit auf lange Sicht.
Die meisten PHP-Frameworks, die ich untersucht habe, verwenden entweder das Singleton-Muster oder Funktionen, die auf die initialisierten Objekte zugreifen. Beide guten Ansätze, aber wie gesagt, ich bin mit keinem zufrieden.
Ich möchte meinen Horizont dahingehend erweitern, welche gemeinsamen Muster hier existieren. Ich suche nach Beispielen, zusätzlichen Ideen und Hinweisen auf Ressourcen, die dies aus einer langfristigen , realen Perspektive diskutieren .
Ich bin auch daran interessiert, etwas über spezialisierte, Nischen- oder einfach seltsame Ansätze für das Problem zu erfahren.
quelle
$mh=&factory("messageHandler");
es sinnlos ist und keinen Leistungsvorteil bringt. Darüber hinaus ist dies in 5.3 veraltet.Antworten:
Ich würde den von Flavius vorgeschlagenen Singleton-Ansatz vermeiden. Es gibt zahlreiche Gründe, diesen Ansatz zu vermeiden. Es verstößt gegen gute OOP-Prinzipien. Der Google-Testblog enthält einige gute Artikel über Singleton und wie man dies vermeidet:
http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-join-new-project.html http://googletesting.blogspot.com/2008/05/tott-using-dependancy -injection-to.html http://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html
Alternativen
ein Dienstleister
http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html
Abhängigkeitsspritze
http://en.wikipedia.org/wiki/Dependency_injection
und eine PHP-Erklärung:
http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection
Dies ist ein guter Artikel über diese Alternativen:
http://martinfowler.com/articles/injection.html
Implementieren der Abhängigkeitsinjektion (DI):
Ich glaube, Sie sollten fragen, was im Konstruktor benötigt wird, damit das Objekt funktioniert :
new YourObject($dependencyA, $dependencyB);
Sie können die benötigten Objekte (Abhängigkeiten) manuell (
$application = new Application(new MessageHandler()
) bereitstellen . Sie können aber auch ein DI-Framework verwenden (die Wikipedia-Seite enthält Links zu PHP DI-Frameworks ).Wichtig ist, dass Sie nur das übergeben, was Sie tatsächlich verwenden (eine Aktion aufrufen), NICHT das, was Sie einfach an andere Objekte übergeben, weil diese es benötigen. Hier ist ein kürzlich veröffentlichter Beitrag von 'Onkel Bob' (Robert Martin) über das manuelle DI im Vergleich zur Verwendung des Frameworks .
Noch ein paar Gedanken zu Flavius 'Lösung. Ich möchte nicht, dass dieser Beitrag ein Anti-Beitrag ist, aber ich denke, es ist wichtig zu sehen, warum die Abhängigkeitsinjektion zumindest für mich besser ist als globale.
Auch wenn es keine "echte" Singleton- Implementierung ist, denke ich, dass Flavius es falsch verstanden hat. Der globale Zustand ist schlecht . Beachten Sie, dass solche Lösungen auch schwer zu testende statische Methoden verwenden .
Ich weiß, dass viele Leute es tun, genehmigen und verwenden. Aber das Lesen von Misko Heverys Blog-Artikeln ( ein Experte für Google-Testbarkeit ), das erneute Lesen und das langsame Verdauen dessen, was er sagt, haben die Art und Weise, wie ich Design sehe, stark verändert.
Wenn Sie Ihre Anwendung testen möchten, müssen Sie beim Entwerfen Ihrer Anwendung einen anderen Ansatz wählen. Wenn Sie Test-First-Programmierung durchführen, werden Sie Schwierigkeiten mit folgenden Dingen haben: 'Als nächstes möchte ich die Protokollierung in diesem Code implementieren. Lassen Sie uns zuerst einen Test schreiben, der eine grundlegende Nachricht protokolliert, und dann einen Test erstellen, der Sie zwingt, einen globalen Logger zu schreiben und zu verwenden, der nicht ersetzt werden kann.
Ich habe immer noch Probleme mit all den Informationen, die ich von diesem Blog erhalten habe, und es ist nicht immer einfach zu implementieren, und ich habe viele Fragen. Aber ich kann auf keinen Fall zu dem zurückkehren, was ich zuvor getan habe (ja, Global State und Singletons (großes S)), nachdem ich verstanden habe, was Misko Hevery gesagt hat :-)
quelle
So würde ich es machen. Es erstellt das Objekt auf Anfrage:
Es ist die Art und Weise, wie ich es mache, es respektiert die OOP-Prinzipien, es ist weniger Code als das, was Sie gerade machen, und das Objekt wird nur erstellt, wenn der Code es zum ersten Mal benötigt.
Hinweis : Was ich vorgestellt habe, ist nicht einmal ein echtes Singleton-Muster. Ein Singleton würde nur eine Instanz von sich selbst zulassen, indem er den Konstruktor (Foo :: __ constructor ()) als privat definiert. Es ist nur eine "globale" Variable, die allen "Anwendungs" -Instanzen zur Verfügung steht. Deshalb denke ich, dass seine Verwendung gültig ist, da es gute OOP-Prinzipien NICHT missachtet. Natürlich sollte dieses "Muster" wie alles auf der Welt auch nicht überstrapaziert werden!
Ich habe gesehen, dass dies in vielen PHP-Frameworks verwendet wird, darunter auch in Zend Framework und Yii. Und Sie sollten ein Framework verwenden. Ich werde dir nicht sagen, welche.
Nachtrag Für diejenigen unter Ihnen, die sich Sorgen um TDD machen , können Sie immer noch eine Verkabelung für die Abhängigkeitsinjektion erstellen. Es könnte so aussehen:
Es gibt genug Raum für Verbesserungen. Es ist nur ein PoC, verwenden Sie Ihre Fantasie.
Warum so? Nun, die meiste Zeit wird die Anwendung nicht Unit-getestet, sondern tatsächlich ausgeführt, hoffentlich in einer Produktionsumgebung . Die Stärke von PHP ist seine Geschwindigkeit. PHP ist NICHT und wird niemals eine "saubere OOP-Sprache" wie Java sein.
Innerhalb einer Anwendung gibt es höchstens eine Anwendungsklasse und höchstens eine Instanz jedes ihrer Helfer (gemäß verzögertem Laden wie oben). Sicher, Singletons sind schlecht, aber nur dann, wenn sie nicht der realen Welt entsprechen. In meinem Beispiel tun sie es.
Stereotype "Regeln" wie "Singletons sind schlecht" sind die Quelle des Bösen, sie sind für faule Leute, die nicht bereit sind, für sich selbst zu denken.
Ja, ich weiß, das PHP-Manifest ist technisch gesehen SCHLECHT. Dennoch ist es eine erfolgreiche Sprache in ihrer hackigen Art.
Nachtrag
Ein Funktionsstil:
quelle
Ich mag das Konzept der Abhängigkeitsinjektion:
Fabien Potencier hat eine wirklich schöne Artikelserie über Dependency Injection und die Notwendigkeit, sie zu verwenden, geschrieben. Er bietet auch einen schönen und kleinen Dependency Injection Container namens Pimple an, den ich sehr gerne benutze (mehr Infos zu Github ).
Wie oben erwähnt, mag ich die Verwendung von Singletons nicht. Eine gute Zusammenfassung darüber, warum Singletons kein gutes Design sind, finden Sie hier in Steve Yegges Blog .
quelle
decupling from GOD object
: stackoverflow.com/questions/1580210/… mit einem sehr schönen BeispielDer beste Ansatz besteht darin, eine Art Container für diese Ressourcen zu haben. Einige der gebräuchlichsten Methoden zum Implementieren dieses Containers :
Singleton
Nicht empfohlen, da es schwer zu testen ist und einen globalen Zustand impliziert. (Singletonitis)
Registrierung
Beseitigt Singletonitis, Fehler Ich würde die Registrierung auch nicht empfehlen, da es sich auch um eine Art Singleton handelt. (Schwer zu Unit Test)
Erbe
Schade, dass es in PHP keine Mehrfachvererbung gibt, daher beschränkt sich dies auf die gesamte Kette.
Abhängigkeitsspritze
Dies ist ein besserer Ansatz, aber ein größeres Thema.
Traditionell
Der einfachste Weg, dies zu tun, ist die Verwendung der Konstruktor- oder Setter-Injektion (Übergeben des Abhängigkeitsobjekts mit dem Setter oder im Klassenkonstruktor).
Frameworks
Sie können Ihren eigenen Abhängigkeitsinjektor rollen oder einige der Abhängigkeitsinjektions-Frameworks verwenden, z. Yadif
Anwendungsressource
Sie können jede Ihrer Ressourcen im Anwendungs-Bootstrap (der als Container fungiert) initialisieren und an einer beliebigen Stelle in der App auf das Bootstrap-Objekt zugreifen.
Dies ist der in Zend Framework 1.x implementierte Ansatz
Ressourcenlader
Eine Art statisches Objekt, das die benötigte Ressource nur bei Bedarf lädt (erstellt). Dies ist ein sehr kluger Ansatz. Sie können es in Aktion sehen, z. B. die Implementierung der Dependency Injection-Komponente von Symfony
Injektion in eine bestimmte Schicht
Die Ressourcen werden nicht immer irgendwo in der Anwendung benötigt. Manchmal braucht man sie nur, zB in den Controllern (MV C ). Dann können Sie die Ressourcen nur dort injizieren.
Der übliche Ansatz hierfür ist die Verwendung von Docblock-Kommentaren zum Hinzufügen von Injektionsmetadaten.
Sehen Sie hier meinen Ansatz dazu:
Wie verwende ich die Abhängigkeitsinjektion in Zend Framework? - Paketüberfluss
Am Ende möchte ich hier einen Hinweis zu einer sehr wichtigen Sache hinzufügen - dem Caching.
Im Allgemeinen sollten Sie trotz der von Ihnen gewählten Technik überlegen, wie die Ressourcen zwischengespeichert werden. Der Cache ist die Ressource selbst.
Die Anwendungen können sehr groß sein, und das Laden aller Ressourcen bei jeder Anforderung ist sehr teuer. Es gibt viele Ansätze, einschließlich dieses Appserver-in-PHP - Project Hosting auf Google Code .
quelle
Wenn Sie Objekte global verfügbar machen möchten, könnte das Registrierungsmuster für Sie interessant sein. Inspiration finden Sie in der Zend-Registrierung .
So auch die Frage Registry vs. Singleton .
quelle
Objekte in PHP beanspruchen viel Speicher, wie Sie wahrscheinlich aus Ihren Unit-Tests gesehen haben. Daher ist es ideal, nicht benötigte Objekte so schnell wie möglich zu zerstören , um Speicher für andere Prozesse zu sparen. In diesem Sinne finde ich, dass jedes Objekt in eine von zwei Formen passt.
1) Das Objekt verfügt möglicherweise über viele nützliche Methoden oder muss mehrmals aufgerufen werden. In diesem Fall implementiere ich ein Singleton / eine Registrierung:
2) Das Objekt existiert nur für die Lebensdauer der Methode / Funktion, die es aufruft. In diesem Fall ist eine einfache Erstellung hilfreich, um zu verhindern, dass verweilende Objektreferenzen Objekte zu lange am Leben erhalten.
Das Speichern temporärer Objekte ÜBERALL kann zu Speicherverlusten führen, da Verweise darauf möglicherweise vergessen werden, das Objekt für den Rest des Skripts im Speicher zu belassen .
quelle
Ich würde mich für eine Funktion entscheiden, die initialisierte Objekte zurückgibt:
In der Testumgebung können Sie definieren, dass Modelle zurückgegeben werden. Mit debug_backtrace () können Sie sogar erkennen, wer die Funktion aufruft, und verschiedene Objekte zurückgeben. Sie können sich darin registrieren, wer welche Objekte erhalten möchte, um einige Einblicke zu erhalten, was tatsächlich in Ihrem Programm vor sich geht.
quelle
Warum nicht das schöne Handbuch lesen?
http://php.net/manual/en/language.oop5.autoload.php
quelle