Das Problem der Objektinitialisierung in OOP-Anwendungen in PHP. Finden einer Lösung mithilfe von Registry-, Factory-Methode-, Service-Locator- und Dependency-Injection-Mustern. OOP-Muster mit Beispielen und Beschreibungen. Autorisierendes Registrierungs-PHP

Das Problem der Objektinitialisierung in OOP-Anwendungen in PHP. Finden einer Lösung mithilfe von Registry-, Factory-Methode-, Service-Locator- und Dependency-Injection-Mustern

Es ist einfach so, dass Programmierer erfolgreiche Lösungen in Form von Entwurfsmustern konsolidieren. Es gibt viel Literatur zu Mustern. Das Buch „Design Patterns“ von Erich Gamma, Richard Helm, Ralph Johnson und John Vlissides von The Gang of Four und vielleicht „Patterns of Enterprise Application Architecture“ von Martin Fowler gelten sicherlich als Klassiker. Das Beste, was ich mit Beispielen gelesen habe in PHP - das. Es ist einfach so, dass all diese Literatur für Leute, die gerade erst begonnen haben, OOP zu beherrschen, recht komplex ist. Deshalb kam mir die Idee, einige der Muster, die ich am nützlichsten finde, in stark vereinfachter Form vorzustellen. In anderen Kurz gesagt, dieser Artikel ist mein erster Versuch, Designmuster im KISS-Stil zu interpretieren.
Heute sprechen wir darüber, welche Probleme bei der Objektinitialisierung in einer OOP-Anwendung auftreten können und wie Sie einige gängige Entwurfsmuster verwenden können, um diese Probleme zu lösen.

Beispiel

Eine moderne OOP-Anwendung arbeitet mit Dutzenden, Hunderten und manchmal Tausenden von Objekten. Schauen wir uns nun genauer an, wie diese Objekte in unseren Anwendungen initialisiert werden. Die Objektinitialisierung ist der einzige Aspekt, der uns in diesem Artikel interessiert, daher habe ich beschlossen, die gesamte „zusätzliche“ Implementierung wegzulassen.
Nehmen wir an, wir haben eine überaus nützliche Klasse erstellt, die eine GET-Anfrage an einen bestimmten URI senden und HTML aus der Serverantwort zurückgeben kann. Damit unsere Klasse nicht zu einfach erscheint, lassen Sie sie auch das Ergebnis überprüfen und eine Ausnahme werfen, wenn der Server „falsch“ antwortet.

Class Grabber ( öffentliche Funktion get($url) (/** gibt HTML-Code zurück oder löst eine Ausnahme aus */) )

Erstellen wir eine weitere Klasse, deren Objekte für die Filterung des empfangenen HTML verantwortlich sind. Die Filtermethode verwendet HTML-Code und einen CSS-Selektor als Argumente und gibt ein Array von Elementen zurück, die für den angegebenen Selektor gefunden wurden.

Klasse HtmlExtractor ( öffentliche Funktion filter($html, $selector) (/** gibt Array gefilterter Elemente zurück */) )

Stellen Sie sich nun vor, wir müssten bei Google Suchergebnisse für bestimmte Schlüsselwörter erhalten. Dazu führen wir eine weitere Klasse ein, die die Grabber-Klasse zum Senden einer Anfrage und die HtmlExtractor-Klasse zum Extrahieren des erforderlichen Inhalts verwendet. Es enthält außerdem die Logik zum Erstellen des URI, einen Selektor zum Filtern des empfangenen HTML und zum Verarbeiten der erhaltenen Ergebnisse.

Klasse GoogleFinder ( private $grabber; private $filter; öffentliche Funktion __construct() ( $this->grabber = new Grabber(); $this->filter = new HtmlExtractor(); ) öffentliche Funktion find($searchString) ( /* * gibt ein Array fundierter Ergebnisse zurück */) )

Ist Ihnen aufgefallen, dass die Initialisierung der Grabber- und HtmlExtractor-Objekte im Konstruktor der GoogleFinder-Klasse erfolgt? Denken wir darüber nach, wie gut diese Entscheidung ist.
Natürlich ist es keine gute Idee, die Erstellung von Objekten in einem Konstruktor fest zu codieren. Und deshalb. Erstens können wir die Grabber-Klasse in der Testumgebung nicht einfach überschreiben, um das Senden einer echten Anfrage zu vermeiden. Fairerweise muss man sagen, dass dies mit der Reflection API möglich ist. Diese. Die technische Möglichkeit besteht, aber dies ist bei weitem nicht der bequemste und naheliegendste Weg.
Zweitens tritt das gleiche Problem auf, wenn wir die GoogleFinder-Logik mit anderen Grabber- und HtmlExtractor-Implementierungen wiederverwenden möchten. Die Erstellung von Abhängigkeiten ist im Klassenkonstruktor fest codiert. Und im besten Fall können wir GoogleFinder erben und seinen Konstruktor überschreiben. Und selbst dann nur, wenn der Umfang der Grabber- und Filtereigenschaften geschützt oder öffentlich ist.
Ein letzter Punkt: Jedes Mal, wenn wir ein neues GoogleFinder-Objekt erstellen, wird ein neues Paar von Abhängigkeitsobjekten im Speicher erstellt, obwohl wir ganz einfach ein Grabber-Objekt und ein HtmlExtractor-Objekt in mehreren GoogleFinder-Objekten verwenden können.
Ich denke, Sie verstehen bereits, dass die Abhängigkeitsinitialisierung außerhalb der Klasse verschoben werden muss. Wir können verlangen, dass bereits vorbereitete Abhängigkeiten an den Konstruktor der GoogleFinder-Klasse übergeben werden.

Klasse GoogleFinder ( private $grabber; private $filter; öffentliche Funktion __construct(Grabber $grabber, HtmlExtractor $filter) ( $this->grabber = $grabber; $this->filter = $filter; ) öffentliche Funktion find($searchString) ( /** gibt ein Array mit fundierten Ergebnissen zurück */) )

Wenn wir anderen Entwicklern die Möglichkeit geben möchten, ihre eigenen Grabber- und HtmlExtractor-Implementierungen hinzuzufügen und zu verwenden, sollten wir über die Einführung von Schnittstellen für sie nachdenken. In diesem Fall ist dies nicht nur sinnvoll, sondern auch notwendig. Ich glaube, wenn wir in einem Projekt nur eine Implementierung verwenden und nicht damit rechnen, in Zukunft neue zu erstellen, sollten wir uns weigern, eine Schnittstelle zu erstellen. Es ist besser, je nach Situation zu handeln und einfache Umgestaltungen dann durchzuführen, wenn ein echter Bedarf dafür besteht.
Jetzt haben wir alle notwendigen Klassen und können die GoogleFinder-Klasse im Controller verwenden.

Klassencontroller ( öffentliche Funktion action() ( /* Einige Sachen */ $finder = new GoogleFinder(new Grabber(), new HtmlExtractor()); $results = $finder->

Fassen wir die Zwischenergebnisse zusammen. Wir haben sehr wenig Code geschrieben und auf den ersten Blick nichts falsch gemacht. Aber... was ist, wenn wir ein Objekt wie GoogleFinder an einem anderen Ort verwenden müssen? Wir müssen seine Entstehung duplizieren. In unserem Beispiel ist dies nur eine Zeile und das Problem ist nicht so auffällig. In der Praxis kann das Initialisieren von Objekten recht komplex sein und bis zu 10 Zeilen oder sogar mehr in Anspruch nehmen. Es treten auch andere für die Codeduplizierung typische Probleme auf. Wenn Sie während des Refactoring-Prozesses den Namen der verwendeten Klasse oder die Objektinitialisierungslogik ändern müssen, müssen Sie alle Stellen manuell ändern. Ich denke, du weißt, wie es passiert :)
Normalerweise wird Hardcode einfach gehandhabt. In der Konfiguration sind normalerweise doppelte Werte enthalten. Dadurch können Sie Werte zentral an allen Stellen ändern, an denen sie verwendet werden.

Registrierungsvorlage.

Deshalb haben wir beschlossen, die Erstellung von Objekten in die Konfiguration zu verlagern. Lass uns das tun.

$registry = new ArrayObject(); $registry["grabber"] = new Grabber(); $registry["filter"] = new HtmlExtractor(); $registry["google_finder"] = neuer GoogleFinder($registry["grabber"], $registry["filter"]);
Wir müssen lediglich unser ArrayObject an den Controller übergeben und das Problem ist gelöst.

Klassencontroller ( private $registry; öffentliche Funktion __construct(ArrayObject $registry) ( $this->registry = $registry; ) öffentliche Funktion action() ( /* Einige Sachen */ $results = $this->registry["google_finder" ]->find("search string"); /* Etwas mit Ergebnissen machen */ ) )

Wir können die Idee von Registry weiterentwickeln. Erben Sie ArrayObject, kapseln Sie die Erstellung von Objekten in einer neuen Klasse, verbieten Sie das Hinzufügen neuer Objekte nach der Initialisierung usw. Meiner Meinung nach macht der angegebene Code jedoch vollständig klar, was die Registrierungsvorlage ist. Dieses Muster ist nicht generativ, aber es trägt in gewisser Weise zur Lösung unserer Probleme bei. Die Registrierung ist lediglich ein Container, in dem wir Objekte speichern und innerhalb der Anwendung weitergeben können. Damit Objekte verfügbar werden, müssen wir sie zunächst erstellen und in diesem Container registrieren. Schauen wir uns die Vor- und Nachteile dieses Ansatzes an.
Auf den ersten Blick haben wir unser Ziel erreicht. Wir haben aufgehört, Klassennamen fest zu codieren und Objekte an einem Ort zu erstellen. Wir erstellen Objekte in einer einzigen Kopie, was ihre Wiederverwendung garantiert. Wenn sich die Logik zum Erstellen von Objekten ändert, muss nur eine Stelle in der Anwendung bearbeitet werden. Als Bonus erhielten wir die Möglichkeit, Objekte in der Registry zentral zu verwalten. Wir können ganz einfach eine Liste aller verfügbaren Objekte erhalten und einige Manipulationen daran vornehmen. Schauen wir uns nun an, was uns an dieser Vorlage möglicherweise nicht gefällt.
Zuerst müssen wir das Objekt erstellen, bevor wir es bei der Registry registrieren. Dementsprechend besteht eine hohe Wahrscheinlichkeit, dass „unnötige Objekte“ entstehen, d. h. diejenigen, die im Speicher erstellt, aber nicht in der Anwendung verwendet werden. Ja, wir können Objekte dynamisch zur Registrierung hinzufügen, d. h. Erstellen Sie nur die Objekte, die zur Bearbeitung einer bestimmten Anfrage erforderlich sind. Auf die eine oder andere Weise müssen wir dies manuell steuern. Dementsprechend wird die Wartung mit der Zeit sehr schwierig.
Zweitens haben wir eine neue Controller-Abhängigkeit. Ja, wir können Objekte über eine statische Methode in Registry empfangen, sodass wir Registry nicht an den Konstruktor übergeben müssen. Aber meiner Meinung nach sollte man das nicht tun. Statische Methoden haben eine noch engere Verbindung als das Erstellen von Abhängigkeiten innerhalb eines Objekts und Schwierigkeiten beim Testen (zu diesem Thema).
Drittens verrät uns die Controller-Schnittstelle nichts darüber, welche Objekte sie verwendet. Wir können jedes in der Registrierung verfügbare Objekt im Controller abrufen. Es wird für uns schwierig sein zu sagen, welche Objekte der Controller verwendet, bis wir seinen gesamten Quellcode überprüft haben.

Fabrikmethode

Unser größter Kritikpunkt an der Registrierung ist, dass ein Objekt initialisiert werden muss, bevor auf es zugegriffen werden kann. Anstatt ein Objekt in der Konfiguration zu initialisieren, können wir die Logik zum Erstellen von Objekten in eine andere Klasse aufteilen, die wir „auffordern“ können, das benötigte Objekt zu erstellen. Klassen, die für die Erstellung von Objekten verantwortlich sind, werden Fabriken genannt. Und das Entwurfsmuster heißt Factory-Methode. Schauen wir uns eine Beispielfabrik an.

Class Factory ( öffentliche Funktion getGoogleFinder() ( return new GoogleFinder($this->getGrabber(), $this->getHtmlExtractor()); ) private Funktion getGrabber() ( return new Grabber(); ) private Funktion getHtmlExtractor() ( return new HtmlFiletr(); ) )

In der Regel werden Fabriken hergestellt, die für die Erstellung eines Objekttyps verantwortlich sind. Manchmal kann eine Fabrik eine Gruppe verwandter Objekte erstellen. Wir können die Zwischenspeicherung einer Eigenschaft nutzen, um die Neuerstellung von Objekten zu vermeiden.

Klassenfabrik ( private $finder; öffentliche Funktion getGoogleFinder() ( if (null === $this->finder) ( $this->finder = new GoogleFinder($this->getGrabber(), $this->getHtmlExtractor() ); ) return $this->finder; ) )

Wir können eine Factory-Methode parametrisieren und die Initialisierung abhängig vom eingehenden Parameter an andere Factorys delegieren. Dies wird bereits eine Abstract Factory-Vorlage sein.
Wenn wir die Anwendung modularisieren müssen, können wir verlangen, dass jedes Modul seine eigenen Fabriken bereitstellt. Wir können das Thema Fabriken weiterentwickeln, aber ich denke, dass das Wesentliche dieser Vorlage klar ist. Mal sehen, wie wir die Fabrik im Controller nutzen werden.

Klassencontroller ( private $factory; öffentliche Funktion __construct(Factory $factory) ( $this->factory = $factory; ) öffentliche Funktion action() ( /* Einige Sachen */ $results = $this->factory->getGoogleFinder( )->find("search string"); /* Etwas mit Ergebnissen machen */ ) )

Zu den Vorteilen dieses Ansatzes gehört seine Einfachheit. Unsere Objekte werden explizit erstellt und Ihre IDE führt Sie problemlos dorthin, wo dies geschieht. Wir haben auch das Registry-Problem gelöst, sodass Objekte im Speicher nur dann erstellt werden, wenn wir die Factory „auffordern“, dies zu tun. Aber wir haben noch nicht entschieden, wie wir die Controller mit den notwendigen Fabriken versorgen sollen. Hier gibt es mehrere Möglichkeiten. Sie können statische Methoden verwenden. Wir können die Controller die erforderlichen Fabriken selbst erstellen lassen und alle unsere Versuche, das Kopieren und Einfügen abzuschaffen, zunichte machen. Sie können eine Fabrik aus Fabriken erstellen und nur diese an den Controller übergeben. Allerdings wird es etwas komplizierter, Objekte in den Controller zu bekommen, und Sie müssen Abhängigkeiten zwischen Fabriken verwalten. Darüber hinaus ist nicht ganz klar, was zu tun ist, wenn wir Module in unserer Anwendung verwenden möchten, wie Modulfabriken registriert werden und wie Verbindungen zwischen Fabriken verschiedener Module verwaltet werden. Im Allgemeinen haben wir den Hauptvorteil der Fabrik verloren – die explizite Erstellung von Objekten. Und das Problem der „impliziten“ Controller-Schnittstelle haben wir immer noch nicht gelöst.

Service-Locator

Mit der Service Locator-Vorlage können Sie die fehlende Fragmentierung von Fabriken beheben und die Erstellung von Objekten automatisch und zentral verwalten. Wenn wir darüber nachdenken, können wir eine zusätzliche Abstraktionsschicht einführen, die für die Erstellung von Objekten in unserer Anwendung und die Verwaltung der Beziehungen zwischen diesen Objekten verantwortlich ist. Damit diese Ebene Objekte für uns erstellen kann, müssen wir ihr das Wissen darüber vermitteln, wie das geht.
Begriffe für Service Locator-Muster:
  • Ein Service ist ein fertiges Objekt, das aus einem Container bezogen werden kann.
  • Dienstdefinition – Dienstinitialisierungslogik.
  • Ein Container (Service Container) ist ein zentrales Objekt, das alle Beschreibungen speichert und darauf basierend Dienste erstellen kann.
Jedes Modul kann seine Dienstbeschreibungen registrieren. Um einen Service aus dem Container zu erhalten, müssen wir ihn per Schlüssel anfordern. Es gibt viele Möglichkeiten, Service Locator zu implementieren; in der einfachsten Version können wir ArrayObject als Container und einen Abschluss als Beschreibung von Diensten verwenden.

Klasse ServiceContainer erweitert ArrayObject ( öffentliche Funktion get($key) ( if (is_callable($this[$key])) ( return call_user_func($this[$key]); ) throw new \RuntimeException("Dienstdefinition kann nicht gefunden werden unter der Schlüssel [ $key ]"); ) )

Dann sieht die Definitionsregistrierung so aus:

$container = new ServiceContainer(); $container["grabber"] = function () ( return new Grabber(); ); $container["html_filter"] = function () ( return new HtmlExtractor(); ); $container["google_finder"] = function() use ($container) ( return new GoogleFinder($container->get("grabber"), $container->get("html_filter")); );

Und die Verwendung im Controller ist wie folgt:

Klassencontroller ( private $container; öffentliche Funktion __construct(ServiceContainer $container) ( $this->container = $container; ) öffentliche Funktion action() ( /* Einige Sachen */ $results = $this->container->get( "google_finder")->find("search string"); /* Etwas mit Ergebnissen machen */ ) )

Ein Service-Container kann sehr einfach oder sehr komplex sein. Der Symfony Service Container bietet beispielsweise viele Funktionen: Parameter, Dienstumfänge, Suche nach Diensten anhand von Tags, Aliasen, privaten Diensten, die Möglichkeit, Änderungen am Container vorzunehmen, nachdem alle Dienste hinzugefügt wurden (Compiller-Durchläufe) und vieles mehr. DIExtraBundle erweitert die Möglichkeiten der Standardimplementierung weiter.
Aber kehren wir zu unserem Beispiel zurück. Wie Sie sehen, löst Service Locator nicht nur die gleichen Probleme wie die vorherigen Vorlagen, sondern erleichtert auch die Verwendung von Modulen mit eigenen Servicedefinitionen.
Darüber hinaus erhielten wir auf der Framework-Ebene eine zusätzliche Abstraktionsebene. Durch die Änderung der ServiceContainer::get-Methode können wir nämlich beispielsweise das Objekt durch einen Proxy ersetzen. Und der Anwendungsbereich von Proxy-Objekten ist nur durch die Vorstellungskraft des Entwicklers begrenzt. Hier können Sie das AOP-Paradigma, LazyLoading usw. implementieren.
Die meisten Entwickler betrachten Service Locator jedoch immer noch als Anti-Pattern. Denn theoretisch können wir so viele sogenannte haben Containerfähige Klassen (d. h. Klassen, die einen Verweis auf den Container enthalten). Zum Beispiel unser Controller, innerhalb dessen wir jeden Service erhalten können.
Mal sehen, warum das schlecht ist.
Zuerst noch einmal testen. Anstatt Mocks nur für die in Tests verwendeten Klassen zu erstellen, müssen Sie den gesamten Container simulieren oder einen echten Container verwenden. Die erste Option passt nicht zu Ihnen, weil... Zweitens muss man in Tests viel unnötigen Code schreiben Dies widerspricht den Prinzipien des Unit-Tests und kann zu zusätzlichen Kosten für die Wartung der Tests führen.
Zweitens wird es für uns schwierig sein, eine Umgestaltung vorzunehmen. Durch die Änderung eines Dienstes (oder einer Servicedefinition) im Container sind wir gezwungen, auch alle abhängigen Dienste zu überprüfen. Und dieses Problem kann nicht mit Hilfe einer IDE gelöst werden. Es wird nicht so einfach sein, solche Orte in der gesamten Anwendung zu finden. Zusätzlich zu den abhängigen Diensten müssen Sie auch alle Orte überprüfen, an denen der umgestaltete Dienst aus dem Container bezogen wird.
Nun, der dritte Grund ist, dass das unkontrollierte Abrufen von Diensten aus dem Container früher oder später zu einem Durcheinander im Code und unnötiger Verwirrung führt. Das ist schwer zu erklären, Sie müssen nur immer mehr Zeit aufwenden, um zu verstehen, wie dieser oder jener Dienst funktioniert. Mit anderen Worten: Sie können nur dann vollständig verstehen, was er tut oder wie eine Klasse funktioniert, wenn Sie ihren gesamten Quellcode lesen.

Abhängigkeitsspritze

Was können Sie sonst noch tun, um die Verwendung eines Containers in einer Anwendung einzuschränken? Sie können die Kontrolle über die Erstellung aller Benutzerobjekte, einschließlich Controller, an das Framework übertragen. Mit anderen Worten: Benutzercode sollte die get-Methode des Containers nicht aufrufen. In unserem Beispiel können wir dem Container eine Definition für den Controller hinzufügen:

$container["google_finder"] = function() use ($container) ( return new Controller(Grabber $grabber); );

Und entfernen Sie den Container im Controller:

Klassencontroller ( private $finder; öffentliche Funktion __construct(GoogleFinder $finder) ( $this->finder = $finder; ) öffentliche Funktion action() ( /* Einige Sachen */ $results = $this->finder->find( "Suchzeichenfolge"); /* Etwas mit Ergebnissen machen */ ) )

Dieser Ansatz (wenn Clientklassen kein Zugriff auf den Service-Container gewährt wird) wird als Abhängigkeitsinjektion bezeichnet. Aber auch diese Vorlage hat sowohl Vor- als auch Nachteile. Solange wir uns an das Prinzip der Einzelverantwortung halten, sieht der Code sehr schön aus. Zunächst haben wir den Container in den Client-Klassen entfernt, wodurch deren Code viel klarer und einfacher wird. Wir können den Controller einfach testen, indem wir die erforderlichen Abhängigkeiten ersetzen. Wir können jede Klasse unabhängig von anderen (einschließlich Controller-Klassen) mithilfe eines TDD- oder BDD-Ansatzes erstellen und testen. Beim Erstellen von Tests können wir vom Container abstrahieren und später eine Definition hinzufügen, wenn wir bestimmte Instanzen verwenden müssen. All dies wird unseren Code einfacher und klarer und das Testen transparenter machen.
Aber es ist notwendig, die andere Seite der Medaille zu erwähnen. Tatsache ist, dass Controller sehr spezifische Klassen sind. Beginnen wir mit der Tatsache, dass der Controller in der Regel eine Reihe von Aktionen enthält, was bedeutet, dass er gegen den Grundsatz der Einzelverantwortung verstößt. Daher verfügt die Controller-Klasse möglicherweise über viel mehr Abhängigkeiten, als zum Ausführen einer bestimmten Aktion erforderlich sind. Die Verwendung einer verzögerten Initialisierung (das Objekt wird zum Zeitpunkt seiner ersten Verwendung instanziiert und davor wird ein einfacher Proxy verwendet) löst das Leistungsproblem bis zu einem gewissen Grad. Aber auch aus architektonischer Sicht ist es nicht ganz richtig, viele Abhängigkeiten zu einem Controller zu erstellen. Darüber hinaus ist das Testen von Controllern in der Regel ein unnötiger Vorgang. Alles hängt natürlich davon ab, wie das Testen in Ihrer Anwendung organisiert ist und wie Sie selbst dazu stehen.
Aus dem vorherigen Absatz haben Sie erkannt, dass die Verwendung von Dependency Injection Architekturprobleme nicht vollständig beseitigt. Überlegen Sie daher, wie es für Sie bequemer ist, einen Link zum Container in Controllern zu speichern oder nicht. Hier gibt es keine einzige richtige Lösung. Ich denke, beide Ansätze sind gut, solange der Controller-Code einfach bleibt. Aber Sie sollten auf keinen Fall zusätzlich zu den Controllern auch Conatiner-Aware-Dienste erstellen.

Schlussfolgerungen

Nun ist es an der Zeit, alles zusammenzufassen, was gesagt wurde. Und es wurde viel gesagt... :)
Um die Arbeit beim Erstellen von Objekten zu strukturieren, können wir die folgenden Muster verwenden:
  • Registrierung: Die Vorlage hat offensichtliche Nachteile. Der grundlegendste davon ist die Notwendigkeit, Objekte zu erstellen, bevor sie in einen gemeinsamen Container gelegt werden. Offensichtlich werden wir durch die Nutzung mehr Probleme als Vorteile bekommen. Dies ist eindeutig nicht die beste Verwendung der Vorlage.
  • Fabrikmethode: Der Hauptvorteil des Musters: Objekte werden explizit erstellt. Der Hauptnachteil: Controller müssen sich entweder selbst um die Erstellung von Fabriken kümmern, was das Problem der fest codierten Klassennamen nicht vollständig löst, oder das Framework muss dafür verantwortlich sein, Controllern alle notwendigen Fabriken zur Verfügung zu stellen, was nicht so offensichtlich ist. Es besteht keine Möglichkeit, den Prozess der Objekterstellung zentral zu verwalten.
  • Service-Locator: Eine erweiterte Möglichkeit, die Erstellung von Objekten zu steuern. Eine zusätzliche Abstraktionsebene kann verwendet werden, um häufige Aufgaben beim Erstellen von Objekten zu automatisieren. Zum Beispiel:
    Klasse ServiceContainer erweitert ArrayObject ( öffentliche Funktion get($key) ( if (is_callable($this[$key])) ( $obj = call_user_func($this[$key]); if ($obj Instanz von RequestAwareInterface) ( $obj- >setRequest($this->get("request")); ) return $obj; ) throw new \RuntimeException("Dienstdefinition kann unter dem Schlüssel [ $key ] nicht gefunden werden"); ) )
    Der Nachteil von Service Locator besteht darin, dass die öffentliche API von Klassen nicht mehr informativ ist. Es ist notwendig, den gesamten Code der Klasse zu lesen, um zu verstehen, welche Dienste darin verwendet werden. Eine Klasse, die einen Verweis auf einen Container enthält, ist schwieriger zu testen.
  • Abhängigkeitsspritze: Im Wesentlichen können wir denselben Service-Container wie für das vorherige Muster verwenden. Der Unterschied besteht darin, wie dieser Behälter verwendet wird. Wenn wir es vermeiden, Klassen vom Container abhängig zu machen, erhalten wir eine klare und explizite Klassen-API.
Das ist noch nicht alles, was ich Ihnen über das Problem der Objekterstellung in PHP-Anwendungen erzählen möchte. Es gibt auch das Prototype-Muster. Wir haben die Verwendung der Reflection-API nicht in Betracht gezogen, das Problem des verzögerten Ladens von Diensten und viele andere Nuancen außer Acht gelassen. Der Artikel ist ziemlich lang geworden, also fasse ich ihn zusammen :)
Ich wollte zeigen, dass Dependency Injection und andere Muster nicht so kompliziert sind, wie allgemein angenommen wird.
Wenn wir von Dependency Injection sprechen, dann gibt es beispielsweise KISS-Implementierungen dieses Musters

Berühren Sie die Struktur der zukünftigen Datenbank. Ein Anfang ist gemacht, und wir können nicht zurückweichen, und ich denke nicht einmal darüber nach.

Wir werden etwas später zur Datenbank zurückkehren, aber jetzt beginnen wir mit dem Schreiben des Codes für unsere Engine. Aber zuerst ein bisschen Hardware. Beginnen.

Der Anfang der Zeit

Im Moment haben wir nur einige Ideen und Verständnis für die Funktionsweise des Systems, das wir implementieren möchten, aber es gibt noch keine Implementierung selbst. Wir haben nichts, womit wir arbeiten können: Wir haben keine Funktionalität – und wie Sie sich erinnern, haben wir sie in zwei Teile geteilt: intern und extern. Das Alphabet erfordert Buchstaben, aber externe Funktionalität erfordert interne Funktionalität – hier fangen wir an.

Aber nicht so schnell. Damit es funktioniert, müssen Sie etwas tiefer gehen. Unser System stellt eine Hierarchie dar, und jedes hierarchische System hat einen Anfang: einen Einhängepunkt in Linux, eine lokale Festplatte in Windows, ein System eines Staates, eines Unternehmens, einer Bildungseinrichtung usw. Jedes Element eines solchen Systems ist jemandem untergeordnet und kann mehrere Untergebene haben, und um seine Nachbarn und deren Untergebenen anzusprechen, verwendet es Vorgesetzte oder den Anfang selbst. Ein gutes Beispiel für ein hierarchisches System ist ein Stammbaum: Ein Ausgangspunkt wird ausgewählt – ein Vorfahre – und los geht es. In unserem System benötigen wir auch einen Ausgangspunkt, von dem aus wir Zweige wachsen lassen – Module, Plugins usw. Wir brauchen eine Art Schnittstelle, über die alle unsere Module „kommunizieren“. Für die weitere Arbeit müssen wir uns mit dem Konzept vertraut machen. Designmuster“ und einige ihrer Implementierungen.

Designmuster

Es gibt sehr viele Artikel darüber, was es ist und welche Sorten es gibt; das Thema ist ziemlich abgedroschen und ich werde Ihnen nichts Neues erzählen. Auf meinem Lieblings-Wiki gibt es Informationen zu diesem Thema: eine Kutsche mit Rutsche und noch ein bisschen mehr.

Entwurfsmuster werden oft auch als Entwurfsmuster oder einfach als Muster bezeichnet (vom englischen Wort „pattern“, übersetzt „Muster“). Wenn ich weiter unten in den Artikeln über Muster spreche, meine ich Designmuster.

Von der riesigen Liste aller möglichen gruseligen (und nicht so gruseligen) Musternamen interessieren uns bisher nur zwei: Registry und Singleton.

Registrierung (oder registrieren) ist ein Muster, das auf einem bestimmten Array arbeitet, zu dem Sie einen bestimmten Satz von Objekten hinzufügen und daraus entfernen und Zugriff auf jedes dieser Objekte und seine Funktionen erhalten können.

Einzelgänger (oder Singleton) ist ein Muster, das sicherstellt, dass nur eine Instanz einer Klasse existieren kann. Es kann nicht kopiert, in den Ruhezustand versetzt oder aufgeweckt werden (Apropos PHP-Magie: __clone(), __sleep(), __wakeup()). Singleton verfügt über einen globalen Zugangspunkt.

Die Definitionen sind weder vollständig noch verallgemeinert, reichen aber zum Verständnis aus. Wir brauchen sie sowieso nicht einzeln. Wir interessieren uns für die Fähigkeiten jedes dieser Muster, aber für eine Klasse: Ein solches Muster heißt Singleton-Registrierung oder Singleton-Registrierung.

Was bringt uns das?
  • Wir haben garantiert eine einzige Instanz der Registry, in die wir jederzeit Objekte hinzufügen und sie von überall im Code aus verwenden können;
  • Es wird unmöglich sein, es zu kopieren und andere unerwünschte (in diesem Fall) Zauber der PHP-Sprache zu verwenden.

Zu diesem Zeitpunkt reicht es aus zu verstehen, dass eine einzige Registrierung es uns ermöglicht, eine modulare Struktur des Systems zu implementieren, was wir bei der Erörterung der Ziele in wollten, und Sie werden den Rest verstehen, wenn die Entwicklung fortschreitet.

Nun, genug der Worte, lasst uns etwas erschaffen!

Erste Zeilen

Da sich diese Klasse auf die Funktionalität des Kernels bezieht, erstellen wir zunächst im Stammverzeichnis unseres Projekts einen Ordner namens „core“, in dem wir alle Klassen von Kernelmodulen ablegen. Wir beginnen mit der Registry, also nennen wir die Datei Registry.php

Wir sind nicht an der Möglichkeit interessiert, dass ein neugieriger Benutzer eine direkte Adresse zu unserer Datei in die Browserzeile eingibt, daher müssen wir uns davor schützen. Um dieses Ziel zu erreichen, müssen wir lediglich eine bestimmte Konstante in der ausführbaren Hauptdatei definieren, die wir überprüfen. Die Idee ist nicht neu; soweit ich mich erinnere, wurde sie in Joomla verwendet. Dies ist eine einfache und funktionierende Methode, sodass wir hier auf Fahrräder verzichten können.

Da wir etwas schützen, das verbunden ist, nennen wir die Konstante _PLUGSECURE_ :

If (!defined("_PLUGSECURE_")) ( die("Direkter Modulaufruf ist verboten!"); )

Wenn Sie nun versuchen, direkt auf diese Datei zuzugreifen, wird nichts Nützliches herauskommen, was bedeutet, dass das Ziel erreicht wurde.

Als nächstes schlage ich vor, einen bestimmten Standard für alle unsere Module festzulegen. Ich möchte jedem Modul eine Funktion zur Verfügung stellen, die einige Informationen darüber zurückgibt, beispielsweise den Namen des Moduls. Diese Funktion muss in der Klasse erforderlich sein. Um dieses Ziel zu erreichen, schreiben wir Folgendes:

Schnittstelle StorableObject (öffentliche statische Funktion getClassName(); )

So. Wenn wir nun eine beliebige Klasse ohne Funktion verbinden getClassName() Wir werden eine Fehlermeldung sehen. Ich werde mich jetzt nicht darauf konzentrieren, es wird uns später zumindest zum Testen und Debuggen nützlich sein.

Es ist Zeit für den Kurs unserer Singles-Registrierung. Wir beginnen mit der Deklaration der Klasse und einiger ihrer Variablen:

Class Registry implementiert StorableObject ( //Modulname, lesbar private static $className = "Registry"; //Registrierungsinstanz private static $instance; //Array von Objekten private static $objects = array();

Bisher ist alles logisch und verständlich. Wie Sie sich erinnern, haben wir nun eine Registry mit Singleton-Eigenschaften. Schreiben wir also sofort eine Funktion, die es uns ermöglicht, mit der Registry auf diese Weise zu arbeiten:

Öffentliche statische Funktion singleton() ( if(!isset(self::$instance)) ( $obj = __CLASS__; self::$instance = new $obj; ) return self::$instance; )

Wörtlich: Die Funktion prüft, ob eine Instanz unserer Registry existiert: Wenn nicht, erstellt sie sie und gibt sie zurück; wenn sie bereits existiert, gibt sie sie einfach zurück. In diesem Fall brauchen wir keine Magie, also erklären wir es zum Schutz für privat:

Private Funktion __construct()() private Funktion __clone()() private Funktion __wakeup()() private Funktion __sleep() ()

Jetzt brauchen wir eine Funktion, um ein Objekt zu unserer Registrierung hinzuzufügen – diese Funktion heißt Setter und ich habe beschlossen, sie auf zwei Arten zu implementieren, um zu zeigen, wie wir Magie nutzen können, und um eine alternative Möglichkeit zum Hinzufügen eines Objekts bereitzustellen. Die erste Methode ist eine Standardfunktion, die zweite führt die erste durch die Magie von __set() aus.

//$object – Pfad zum verbundenen Objekt //$key – Zugriffsschlüssel auf das Objekt im Register öffentliche Funktion addObject($key, $object) ( require_once($object); //ein Objekt in einem Array von Objekten erstellen self::$objects[ $key] = new $key(self::$instance); ) //eine alternative Methode durch magische öffentliche Funktion __set($key, $object) ( $this->addObject($key, $ Objekt); )

Um nun ein Objekt zu unserer Registrierung hinzuzufügen, können wir zwei Arten von Einträgen verwenden (sagen wir, wir haben bereits eine Registrierungsinstanz $registry erstellt und möchten die Datei config.php hinzufügen):

$registry->addObject("config", "/core/config.php"); //reguläre Methode $registry->config = "/core/config.php"; //über die PHP-Zauberfunktion __set()

Beide Einträge erfüllen die gleiche Funktion: Sie verbinden die Datei, erstellen eine Instanz der Klasse und platzieren sie mit dem Schlüssel im Register. Hier gibt es einen wichtigen Punkt, den wir in Zukunft nicht vergessen dürfen: Der Objektschlüssel im Register muss mit dem Klassennamen im verbundenen Objekt übereinstimmen. Wenn Sie sich den Code noch einmal ansehen, werden Sie verstehen, warum.

Welche Aufnahme Sie verwenden, bleibt Ihnen überlassen. Ich bevorzuge die Aufnahme mit der magischen Methode – sie ist „schöner“ und kürzer.

Damit haben wir das Hinzufügen eines Objekts geklärt, jetzt benötigen wir eine Funktion für den Zugriff auf ein verbundenes Objekt per Schlüssel – einen Getter. Ich habe es auch mit zwei Funktionen implementiert, ähnlich dem Setter:

//Objekt aus dem Register abrufen //$key – der Schlüssel im Array öffentliche Funktion getObject($key) ( //überprüfen, ob die Variable ein Objekt ist if (is_object(self::$objects[$key])) ( //Wenn ja, dann geben wir dieses Objekt zurück return self::$objects[$key]; ) ) //eine ähnliche Methode durch magische öffentliche Funktion __get($key) ( if (is_object(self::$objects[$ key])) ( return self: :$objects[$key]; ) )

Um Zugriff auf das Objekt zu erhalten, haben wir wie beim Setter zwei äquivalente Einträge:

$registry->getObject("config"); //reguläre Methode $registry->config; //über die PHP-Zauberfunktion __get()

Der aufmerksame Leser wird sofort die Frage stellen: Warum rufe ich in der magischen Funktion __set() nur eine reguläre (nicht magische) Funktion zum Hinzufügen von Objekten auf, aber im Getter __get() kopiere ich den Funktionscode getObject() anstelle desselben Aufrufs? Ehrlich gesagt kann ich diese Frage nicht genau genug beantworten. Ich sage nur, dass ich Probleme hatte, als ich mit der __get()-Magie in anderen Modulen arbeitete, aber wenn ich den Code „frontal“ umschreibe, gibt es keine derartigen Probleme.

Vielleicht habe ich deshalb in Artikeln oft Vorwürfe gegen PHP-Zaubermethoden und Ratschläge gesehen, diese nicht zu verwenden.

„Jede Magie hat ihren Preis.“ © Rumpelstilzchen

Zu diesem Zeitpunkt ist die Hauptfunktionalität unserer Registrierung bereits fertig: Wir können eine einzelne Instanz der Registrierung erstellen, Objekte hinzufügen und sowohl mit herkömmlichen Methoden als auch mit den magischen Methoden der PHP-Sprache auf sie zugreifen. „Was ist mit dem Löschen?“— Wir werden diese Funktion vorerst nicht brauchen und ich bin mir nicht sicher, ob sich in Zukunft etwas ändern wird. Am Ende können wir immer die notwendige Funktionalität hinzufügen. Wenn wir nun aber versuchen, eine Instanz unserer Registry zu erstellen,

$registry = Registry::singleton();

wir erhalten eine Fehlermeldung:

Fataler Fehler: Die Klassenregistrierung enthält 1 abstrakte Methode und muss daher als abstrakt deklariert werden oder die verbleibenden Methoden (StorableObject::getClassName) in ... implementieren.

Alles nur, weil wir vergessen haben, eine erforderliche Funktion zu schreiben. Erinnern Sie sich, dass ich ganz am Anfang über eine Funktion gesprochen habe, die den Namen des Moduls zurückgibt? Dies ist, was für die volle Funktionalität noch hinzugefügt werden muss. Es ist einfach:

Öffentliche statische Funktion getClassName() ( return self::$className; )

Jetzt sollte es keine Fehler mehr geben. Ich schlage vor, eine weitere Funktion hinzuzufügen. Sie ist nicht erforderlich, kann sich aber früher oder später als nützlich erweisen. Wir werden sie in Zukunft zum Überprüfen und Debuggen verwenden. Die Funktion gibt die Namen aller zu unserer Registrierung hinzugefügten Objekte (Module) zurück:

Öffentliche Funktion getObjectsList() ( //das Array, das wir zurückgeben werden $names = array(); //den Namen jedes Objekts aus dem Array der Objekte abrufen foreach(self::$objects as $obj) ( $names = $ obj->getClassName() ; ) //den Namen des Registermoduls zum Array hinzufügen array_push($names, self::getClassName()); //und zurückgeben return $names; )

Das ist alles. Damit ist das Register abgeschlossen. Lassen Sie uns seine Arbeit überprüfen? Bei der Überprüfung müssen wir etwas verbinden – es sei eine Konfigurationsdatei vorhanden. Erstellen Sie eine neue core/config.php-Datei und fügen Sie den Mindestinhalt hinzu, den unsere Registrierung erfordert:

//Vergessen Sie nicht, die Konstante zu überprüfen if (!defined("_PLUGSECURE_")) ( die("Direkter Modulaufruf ist verboten!"); ) class Config ( //Modulname, lesbar privat static $className = "Config "; öffentliche statische Funktion getClassName() ( return self::$className; ) )

So ähnlich. Fahren wir nun mit der Verifizierung selbst fort. Erstellen Sie im Stammverzeichnis unseres Projekts eine Datei index.php und schreiben Sie den folgenden Code hinein:

Define("_PLUGSECURE_", true); //eine Konstante zum Schutz vor direktem Zugriff auf Objekte definiert require_once "/core/registry.php"; //das Register verbunden $registry = Registry::singleton(); //eine Register-Singleton-Instanz erstellt $registry->config = "/core/config.php"; //verbinde unsere bisher nutzlose Konfiguration //zeige die Namen der angeschlossenen Module an echo " In Verbindung gebracht"; foreach ($registry->

  • " . $names . "
  • "; }

    Wenn Sie dennoch auf Magie verzichten möchten, kann die 5. Zeile durch eine alternative Methode ersetzt werden:

    Define("_PLUGSECURE_", true); //eine Konstante zum Schutz vor direktem Zugriff auf Objekte definiert require_once "/core/registry.php"; //das Register verbunden $registry = Registry::singleton(); //eine Register-Singleton-Instanz erstellt $registry->addObject("config", "/core/config.php"); //verbinde unsere bisher nutzlose Konfiguration //zeige die Namen der angeschlossenen Module an echo " In Verbindung gebracht"; foreach ($registry->getObjectsList() as $names) ( echo "

  • " . $names . "
  • "; }

    Öffnen Sie nun den Browser und schreiben Sie http://localhost/index.php oder einfach http://localhost/ in die Adressleiste (relevant, wenn Sie Standard-Open-Server oder ähnliche Webservereinstellungen verwenden)

    Als Ergebnis sollten wir etwa Folgendes sehen:

    Wie Sie sehen, gibt es keine Fehler, was bedeutet, dass alles funktioniert, wozu ich Ihnen gratuliere :)

    Heute werden wir damit aufhören. Im nächsten Artikel kehren wir zur Datenbank zurück und schreiben eine Klasse für die Arbeit mit MySQL SUDB, verbinden sie mit der Registry und testen die Arbeit in der Praxis. Auf Wiedersehen!

    Dieses Muster löst wie Singleton selten eine positive Reaktion bei Entwicklern aus, da es beim Testen von Anwendungen zu denselben Problemen führt. Trotzdem schimpfen sie, nutzen sie aber aktiv aus. Wie Singleton ist das Registry-Muster in vielen Anwendungen zu finden und vereinfacht auf die eine oder andere Weise die Lösung bestimmter Probleme erheblich.

    Betrachten wir beide Optionen der Reihe nach.

    Was als „reine Registry“ oder einfach Registry bezeichnet wird, ist eine Implementierung einer Klasse mit einer statischen Schnittstelle. Der Hauptunterschied zum Singleton-Muster besteht darin, dass es die Möglichkeit blockiert, mindestens eine Instanz einer Klasse zu erstellen. Vor diesem Hintergrund macht es keinen Sinn, die magischen Methoden __clone() und __wakeup() hinter dem privaten oder geschützten Modifikator zu verstecken.

    Registry-Klasse muss über zwei statische Methoden verfügen – einen Getter und einen Setter. Der Setter legt das übergebene Objekt mit einer Bindung an den angegebenen Schlüssel im Speicher ab. Der Getter gibt dementsprechend ein Objekt aus dem Speicher zurück. Ein Store ist nichts anderes als ein assoziatives Schlüssel-Wert-Array.

    Für die vollständige Kontrolle über die Registrierung wird ein weiteres Schnittstellenelement eingeführt – eine Methode, mit der Sie ein Objekt aus dem Speicher löschen können.

    Zusätzlich zu den Problemen, die mit dem Singleton-Muster identisch sind, gibt es zwei weitere:

    • Einführung einer anderen Art von Abhängigkeit – von Registrierungsschlüsseln;
    • Zwei verschiedene Registrierungsschlüssel können einen Verweis auf dasselbe Objekt haben

    Im ersten Fall lässt sich eine zusätzliche Abhängigkeit nicht vermeiden. Bis zu einem gewissen Grad binden wir uns an Schlüsselnamen.

    Das zweite Problem wird gelöst, indem eine Prüfung in die Methode Registry::set() eingeführt wird:

    Öffentliche statische Funktion set($key, $item) ( if (!array_key_exists($key, self::$_registry)) ( foreach (self::$_registry as $val) ( if ($val === $item) ( throw new Exception("Item bereits vorhanden"); ) ) self::$_registry[$key] = $item; ) )

    « Bereinigen Sie das Registrierungsmuster„führt zu einem weiteren Problem – der Erhöhung der Abhängigkeit aufgrund der Notwendigkeit, über den Klassennamen auf den Setter und Getter zuzugreifen. Sie können keinen Verweis auf ein Objekt erstellen und damit arbeiten, wie es beim Singleton-Muster der Fall war, als dieser Ansatz verfügbar war:

    $instance = Singleton::getInstance(); $instance->Foo();

    Hier haben wir die Möglichkeit, einen Verweis auf eine Singleton-Instanz beispielsweise in einer Eigenschaft der aktuellen Klasse zu speichern und damit zu arbeiten, wie es die OOP-Ideologie erfordert: Übergeben Sie ihn als Parameter an aggregierte Objekte oder verwenden Sie ihn in Nachkommen.

    Um dieses Problem zu beheben, gibt es Implementierung der Singleton-Registrierung, was vielen Leuten nicht gefällt, weil es wie redundanter Code erscheint. Ich denke, der Grund für diese Einstellung ist ein Missverständnis oder eine bewusste Missachtung der Prinzipien von OOP.

    _registry[$key] = $object; ) statische öffentliche Funktion get($key) ( return self::getInstance()->_registry[$key]; ) private Funktion __wakeup() ( ) private Funktion __construct() ( ) private Funktion __clone() ( ) ) ?>

    Um Geld zu sparen, habe ich bewusst auf Kommentarblöcke für Methoden und Eigenschaften verzichtet. Ich glaube nicht, dass sie notwendig sind.

    Wie ich bereits sagte, besteht der grundlegende Unterschied darin, dass es jetzt möglich ist, einen Verweis auf das Registrierungsvolume zu speichern und nicht jedes Mal umständliche Aufrufe statischer Methoden zu verwenden. Diese Option scheint mir etwas richtiger zu sein. Es spielt keine große Rolle, meiner Meinung zuzustimmen oder nicht zuzustimmen, genau wie meine Meinung selbst. Keine Implementierungsfeinheiten können das Muster von einer Reihe der genannten Nachteile befreien.

    Ich beschloss, kurz über Muster zu schreiben, die in unserem Leben oft verwendet werden, mehr Beispiele, weniger Wasser, los geht's.

    Singleton

    Der Hauptpunkt der „Single“ ist, dass, wenn Sie sagen „Ich brauche eine Telefonzentrale“, sie Ihnen sagen würden: „Dort wurde sie bereits gebaut“ und nicht „Lass uns sie noch einmal bauen.“ Ein „Einzelgänger“ ist immer allein.

    Klasse Singleton ( private static $instance = null; private Funktion __construct())( /* ... @return Singleton */ ) // Vor Erstellung über neue private Singleton-Funktion schützen __clone() ( /* ... @return Singleton * / ) // Schutz vor Erstellung durch Klonen der privaten Funktion __wakeup() ( /* ... @return Singleton */ ) // Schutz vor Erstellung durch unserialisieren der öffentlichen statischen Funktion getInstance() ( if (is_null(self::$instance ) ) ( self::$instance = new self; ) return self::$instance; ) )

    Register (Registrierung, Eintragungsprotokoll)

    Wie der Name schon sagt, dient dieses Muster dazu, darin abgelegte Datensätze zu speichern und diese Datensätze entsprechend (namentlich) zurückzugeben, wenn sie benötigt werden. Im Beispiel einer Telefonzentrale handelt es sich um ein Register über die Telefonnummern der Bewohner.

    Klassenregistrierung ( private $registry = array(); öffentliche Funktion set($key, $object) ( $this->registry[$key] = $object; ) öffentliche Funktion get($key) ( return $this->registry [$key]; ) )

    Singleton-Registrierung- nicht verwechseln mit)

    Die „Registratur“ ist oft ein „Einzelgänger“, aber das muss nicht immer so sein. Beispielsweise können wir in der Buchhaltung mehrere Journale erstellen, in einem Mitarbeiter von „A“ bis „M“, im anderen von „N“ bis „Z“. Jede dieser Zeitschriften wird ein „Register“ sein, aber keine „einzelne“, da es bereits zwei Zeitschriften gibt.

    Klasse SingletonRegistry ( private static $instance = null; private $registry = array(); private function __construct() ( /* ... @return Singleton */ ) // Vor Erstellung über neue private Singleton-Funktion schützen __clone() ( / * ... @return Singleton */ ) // Vor Erstellung durch Klonen privater Funktion schützen __wakeup() ( /* ... @return Singleton */ ) // Vor Erstellung durch unserialisieren der öffentlichen statischen Funktion getInstance() ( if ( is_null(self::$instance)) ( self::$instance = new self; ) return self::$instance; ) public function set($key, $object) ( $this->registry[$key] = $ Objekt; ) öffentliche Funktion get($key) ( return $this->registry[$key]; ) )

    Multiton (Pool von „Singles“) oder anders gesagtRegistrierung Singleton ) - Nicht mit Singleton Registry verwechseln

    Häufig wird das „Register“ gezielt zur Speicherung von „Singles“ genutzt. Aber weil Das „Registrierungs“-Muster ist kein „generatives Muster“, aber ich möchte das „Register“ im Zusammenhang mit dem „Singleton“ betrachten.Deshalb haben wir uns ein Muster ausgedacht Multiton, was lautIm Kern handelt es sich um ein „Register“, das mehrere „Singles“ enthält, von denen jede einen eigenen „Namen“ hat, unter dem darauf zugegriffen werden kann.

    Kurz: Ermöglicht das Erstellen von Objekten dieser Klasse, jedoch nur, wenn Sie dem Objekt einen Namen geben. Es gibt kein Beispiel aus dem wirklichen Leben, aber ich habe das folgende Beispiel im Internet gefunden:

    Klassendatenbank ( private static $instances = array(); private Funktion __construct() ( ) private Funktion __clone() ( ) öffentliche statische Funktion getInstance($key) ( if(!array_key_exists($key, self::$instances)) ( self::$instances[$key] = new self(); ) return self::$instances[$key]; ) ) $master = Database::getInstance("master"); var_dump($master); // object(Database)#1 (0) ( ) $logger = Database::getInstance("logger"); var_dump($logger); // object(Database)#2 (0) ( ) $masterDupe = Database::getInstance("master"); var_dump($masterDupe); // object(Database)#1 (0) ( ) // Schwerwiegender Fehler: Aufruf von privatem Database::__construct() aus ungültigem Kontext $dbFatalError = new Database(); // Schwerwiegender PHP-Fehler: Aufruf an private Database::__clone() $dbCloneError = clone $masterDupe;

    Objektpool

    Im Wesentlichen ist dieses Muster eine „Registrierung“, die nur Objekte speichert, keine Strings, Arrays usw. Datentypen.

    Fabrik

    Die Essenz des Musters wird fast vollständig durch seinen Namen beschrieben. Wenn Sie bestimmte Gegenstände benötigen, beispielsweise Saftkisten, müssen Sie nicht wissen, wie diese in einer Fabrik hergestellt werden. Sie sagen einfach: „Gib mir einen Karton Orangensaft“ und die „Fabrik“ schickt Ihnen das gewünschte Paket zurück. Wie? All dies entscheidet die Fabrik selbst, sie „kopiert“ beispielsweise einen bereits bestehenden Standard. Der Hauptzweck der „Fabrik“ besteht darin, es bei Bedarf zu ermöglichen, den Prozess des „Aussehens“ einer Saftverpackung zu ändern, und dem Verbraucher selbst muss hierüber nichts mitgeteilt werden, damit er dies anfordern kann wie vorher. In der Regel beschäftigt sich eine Fabrik mit der „Produktion“ nur einer Art von „Produkt“. Es wird nicht empfohlen, unter Berücksichtigung der Produktion von Autoreifen eine „Saftfabrik“ zu errichten. Wie im Leben wird das Fabrikmuster oft von einer einzelnen Person erstellt.

    Abstrakte Klasse AnimalAbstract ( protected $species; öffentliche Funktion getSpecies() ( return $this->species; ) ) Klasse Cat erweitert AnimalAbstract ( protected $species = "cat"; ) Klasse Dog erweitert AnimalAbstract ( protected $species = "dog"; ) Klasse AnimalFactory ( öffentliche statische Funktion Factory($animal) ( switch ($animal) ( case „cat“: $obj = new Cat(); break; case „dog“: $obj = new Dog(); break; default : throw new Exception("Animal Factory konnte kein Tier der Art erstellen" . $animal . """, 1000); ) return $obj; ) ) $cat = AnimalFactory::factory("cat"); // object(Cat)#1 echo $cat->getSpecies(); // cat $dog = AnimalFactory::factory("dog"); // object(Dog)#1 echo $dog->getSpecies(); // Hund $hippo = AnimalFactory::factory("hippopotamus"); // Dies wird eine Ausnahme auslösen

    Ich möchte Sie darauf aufmerksam machen, dass es sich bei der Factory-Methode auch um ein Muster handelt; sie wird Factory-Methode genannt.

    Baumeister (Baumeister)

    Wir haben also bereits verstanden, dass es sich bei „Factory“ um einen Getränkeautomaten handelt, der bereits alles bereithält und Sie nur sagen, was Sie brauchen. „Builder“ ist eine Anlage, die diese Getränke herstellt und alle komplexen Vorgänge umfasst und je nach Anforderung komplexe Objekte aus einfacheren (Verpackung, Etikett, Wasser, Aromen usw.) zusammensetzen kann.

    Klasse Bottle ( public $name; public $liters; ) /** * alle Builder müssen */ interface BottleBuilderInterface ( public function setName(); public function setLiters(); public function getResult(); ) class CocaColaBuilder implementiert BottleBuilderInterface ( private $ Flasche; öffentliche Funktion __construct() ( $this->bottle = new Bottle(); ) öffentliche Funktion setName($value) ( ​​​​$this->bottle->name = $value; ) öffentliche Funktion setLiters($value) ( ​​$ this->bottle->liters = $value; ) public function getResult() ( return $this->bottle; ) ) $juice = new CocaColaBuilder(); $juice->setName("Coca-Cola Light"); $juice->setLiters(2); $juice->getResult();

    Prototyp

    Es ähnelt einer Fabrik und dient auch der Herstellung von Objekten, jedoch mit einem etwas anderen Ansatz. Stellen Sie sich vor, Sie wären in einer Bar, Sie hätten Bier getrunken und es ginge Ihnen aus. Sagen Sie dem Barkeeper: „Mach mir noch eins von der gleichen Art.“ Der Barkeeper wiederum schaut sich das Bier an, das Sie trinken, und fertigt auf Wunsch eine Kopie an. PHP verfügt bereits über eine Implementierung dieses Musters, sie heißt .

    $newJuice = $juice klonen;

    Verzögerte Initialisierung

    Ein Chef sieht beispielsweise eine Liste von Berichten für verschiedene Arten von Aktivitäten und denkt, dass diese Berichte bereits vorhanden sind. Tatsächlich werden jedoch nur die Namen der Berichte angezeigt, und die Berichte selbst wurden noch nicht erstellt und werden erst generiert bei der Bestellung (z. B. durch Klicken auf die Schaltfläche „Bericht anzeigen“). Ein Sonderfall der verzögerten Initialisierung ist die Erstellung eines Objekts zum Zeitpunkt des Zugriffs. Auf Wikipedia finden Sie einen interessanten Beitrag, aber... Laut Theorie wäre das richtige Beispiel in PHP beispielsweise eine Funktion

    Adapter oder Wrapper (Adapter, Wrapper)

    Dieses Muster entspricht voll und ganz seinem Namen. Damit ein „sowjetischer“ Stecker über eine Euro-Steckdose funktioniert, ist ein Adapter erforderlich. Genau das macht ein „Adapter“ – er dient als Zwischenobjekt zwischen zwei anderen, die nicht direkt miteinander zusammenarbeiten können. Trotz der Definition sehe ich in der Praxis immer noch den Unterschied zwischen Adapter und Wrapper.

    Klasse MyClass ( öffentliche Funktion methodA() () ) Klasse MyClassWrapper ( öffentliche Funktion __construct())( $this->myClass = new MyClass(); ) öffentliche Funktion __call($name, $arguments)( Log::info(" Sie sind dabei, die Methode $name aufzurufen."); return call_user_func_array(array($this->myClass, $name), $arguments); ) ) $obj = new MyClassWrapper(); $obj->methodA();

    Abhängigkeitsspritze

    Mit der Abhängigkeitsinjektion können Sie einen Teil der Verantwortung für bestimmte Funktionen auf andere Objekte verlagern. Wenn wir beispielsweise neues Personal einstellen müssen, können wir keine eigene Personalabteilung einrichten, sondern eine Abhängigkeit von einem Personalvermittlungsunternehmen einführen, das wiederum auf unsere erste Anfrage „Wir brauchen eine Person“ entweder als arbeitet HR-Abteilung selbst oder finden Sie ein anderes Unternehmen (mithilfe eines „Service Locator“), das diese Dienste bereitstellt.
    Mit „Dependency Injection“ können Sie einzelne Teile des Unternehmens verschieben und austauschen, ohne dass die Gesamtfunktionalität verloren geht.

    Klasse AppleJuice () // Diese Methode ist eine primitive Implementierung des Dependency-Injection-Musters und außerdem sehen Sie diese Funktion getBottleJuice())( $obj = new Apfelsaft Apfelsaft)( return $obj; ) ) $bottleJuice = getBottleJuice();

    Stellen Sie sich nun vor, wir wollen keinen Apfelsaft mehr, sondern Orangensaft.

    Klasse AppleJuice() Klasse Orangensaft() // Diese Methode implementiert die Abhängigkeitsinjektionsfunktion getBottleJuice())( $obj = new Orangensaft; // Überprüfe das Objekt, falls sie uns Bier zugesteckt haben (Bier ist kein Saft) if($obj caseof Orangensaft)( return $obj; ) )

    Wie Sie sehen, mussten wir nicht nur die Saftsorte ändern, sondern auch die Prüfung der Saftsorte, was nicht sehr praktisch ist. Es ist viel korrekter, das Prinzip der Abhängigkeitsinversion zu verwenden:

    Schnittstelle Juice () Klasse AppleJuice implementiert Juice () Klasse OrangeJuice implementiert Juice () Funktion getBottleJuice())( $obj = new OrangeJuice; // Überprüfen Sie das Objekt, falls sie uns Bier zugesteckt haben (Bier ist kein Saft) if($obj Instanz von Saft)( return $obj; ) )

    Abhängigkeitsinversion wird manchmal mit Abhängigkeitsinjektion verwechselt, aber es besteht kein Grund, sie zu verwechseln, weil Die Abhängigkeitsumkehr ist ein Prinzip, kein Muster.

    Service-Locator

    „Service Locator“ ist eine Methode zur Implementierung von „Dependency Injection“. Abhängig vom Initialisierungscode werden unterschiedliche Objekttypen zurückgegeben. Die Aufgabe besteht darin, unser Saftpaket, das von einem Bauunternehmer, einer Fabrik oder etwas anderem hergestellt wurde, dorthin zu liefern, wo der Käufer es wünscht. Wir sagen dem Locator „Geben Sie uns einen Lieferservice“ und bitten Sie den Service, den Saft an die gewünschte Adresse zu liefern. Heute gibt es einen Gottesdienst und morgen vielleicht einen anderen. Für uns spielt es keine Rolle, um welchen konkreten Service es sich handelt. Für uns ist es wichtig zu wissen, dass dieser Service das liefert, was wir ihm sagen und wo wir es sagen. Die Dienste wiederum implementieren das „Deliver<предмет>An<адрес>».

    Wenn wir über das wirkliche Leben sprechen, wäre wahrscheinlich die PDO-PHP-Erweiterung ein gutes Beispiel für einen Service Locator, denn Heute arbeiten wir mit einer MySQL-Datenbank und morgen können wir mit PostgreSQL arbeiten. Wie Sie bereits verstanden haben, spielt es für unsere Klasse keine Rolle, an welche Datenbank sie ihre Daten sendet. Wichtig ist, dass sie dies kann.

    $db = neues PDO(" MySQL:dbname=test;host=localhost", $user, $pass); $db = new PDO(" pgsql:dbname=test host=localhost", $user, $pass);

    Der Unterschied zwischen Abhängigkeitsinjektion und Service Locator

    Wenn Sie es noch nicht bemerkt haben, würde ich es gerne erklären. Abhängigkeitsspritze Infolgedessen gibt es keinen Dienst zurück (der irgendwo etwas liefern kann), sondern ein Objekt, dessen Daten es verwendet.

    Ich werde versuchen, Ihnen von meiner Implementierung des Registry-Musters in PHP zu erzählen. Registry ist ein OOP-Ersatz für globale Variablen, der dazu dient, Daten zu speichern und zwischen Systemmodulen zu übertragen. Dementsprechend ist es mit Standardeigenschaften ausgestattet – Schreiben, Lesen, Löschen. Hier ist eine typische Implementierung.

    Nun, auf diese Weise erhalten wir einen dummen Ersatz der Methoden $key = $value - Registry::set($key, $value) $key - Registry::get($key) unset($key) - Remove Registry::remove ($key) Es wird nur unklar, warum dieser zusätzliche Code. Bringen wir unserer Klasse also bei, das zu tun, was globale Variablen nicht können. Fügen wir Pfeffer hinzu.

    getMessage()); ) Amdy_Registry::unlock("test"); var_dump(Amdy_Registry::get("test")); ?>

    Zu den typischen Aufgaben des Musters habe ich die Möglichkeit hinzugefügt, eine Variable vor Änderungen zu schützen. Dies ist bei großen Projekten sehr praktisch, da Sie nicht versehentlich etwas einfügen. Praktisch zum Beispiel für die Arbeit mit Datenbanken
    define('DB_DNS', 'mysql:host=localhost;dbname= ’);
    define('DB_USER', ' ’);
    define('DB_PASSWORD', ' ’);
    define('DB_HANDLE');

    Amdy_Regisrtry::set(DB_HANDLE, neues PDO(DB_DNS, DB_USER, DB_PASSWORD));
    Amdy_Registry::lock(DB_HANDLE);

    Zur Erläuterung des Codes verwenden wir zum Speichern von Daten die statische Variable $data. Die Variable $lock speichert Daten über Schlüssel, die für Änderungen gesperrt sind. Im Netzwerk prüfen wir, ob die Variable gesperrt ist und ändern oder fügen sie dem Register hinzu. Beim Löschen überprüfen wir auch die Sperre; der Getter bleibt bis auf den standardmäßigen optionalen Parameter unverändert. Nun, es lohnt sich, auf die Ausnahmebehandlung zu achten, die aus irgendeinem Grund selten verwendet wird. Übrigens habe ich bereits einen Entwurf zu Ausnahmen, warte auf den Artikel. Unten ist ein Codeentwurf zum Testen, hier ist ein Artikel zum Testen, es würde auch nicht schaden, ihn zu schreiben, obwohl ich kein Fan von TDD bin.

    Im nächsten Artikel werden wir die Funktionalität weiter erweitern, indem wir die Dateninitialisierung hinzufügen und „Laziness“ implementieren.