Heim >Backend-Entwicklung >PHP-Tutorial >Subtyp -Polymorphismus - Austausch der Implementierung zur Laufzeit
Kernpunkte
Viele Menschen können an der Korrelation zwischen Vererbung und Polymorphismus im objektorientierten Design zweifeln? Wahrscheinlich nur wenige, die meisten von ihnen sind möglicherweise auf Unwissenheit oder enges Denken zurückzuführen. Aber hier gibt es ein kleines Problem, das nicht ignoriert werden kann. Es ist zwar einfach, die Logik des Vererbung zu verstehen, aber die Dinge werden schwieriger, wenn sie sich mit den Details des Polymorphismus befassen. Der Begriff „Polymorphismus“ ist an sich entmutigend, mit seiner akademischen Definition voller unterschiedlicher Perspektiven, was es noch schwieriger macht, zu verstehen, was tatsächlich dahinter steckt. Periphere Konzepte wie Parameterpolymorphismus und Ad -hoc -Polymorphismus (normalerweise implementiert mit Vermittlung/Überlastung) haben in einigen Programmiersprachen erhebliche Anwendungen sollte aufgegeben werden, ohne zu überprüfen, ob der Implementierer den erwarteten Typ hat. Kurz gesagt, meiste Zeit wird jeder allgemeine Hinweis auf Polymorphismus in der objektorientierten Programmierung implizit als systemauskündige Fähigkeit angesehen, um eine Reihe von Verträgen oder Schnittstellen zu definieren, die oder die Schnittstelle von verschiedenen Implementierungen folgt. Dieser "kanonische" Polymorphismus wird oft als Subtyp -Polymorphismus bezeichnet, da der Implementierer einer Grenzfläche als Subtyp von ihnen angesehen wird, unabhängig davon, ob es eine tatsächliche Hierarchie gibt. Wie erwarten könnte, ist das Verständnis der Natur des Polymorphismus nur die Hälfte des Lernprozesses. "Code" (in vielen Fällen ist es ein billiger Euphemismus für Spielzeugcode). In diesem Artikel werde ich Ihnen zeigen, wie Sie die Vorteile des Polymorphismus nutzen können, indem Sie eine einfügbare Cache -Komponente entwickeln. Die Kernfunktionalität kann später auf Ihre Bedürfnisse erweitert werden, indem zusätzliche Cache -Treiber entwickelt werden.
Definieren Sie die Schnittstelle und Implementierung von Komponenten
Das Menü mit Optionen zur Auswahl fehlt keineswegs beim Erstellen erweiterbarer Cache -Komponenten (wenn Sie skeptisch darüber sind, schauen Sie sich einfach an, was sich hinter einigen beliebten Frameworks befindet). Hier können die von mir anbietenden Komponenten jedoch die clevere Fähigkeit haben, verschiedene Cache -Treiber zur Laufzeit auszutauschen, ohne einen Client -Code zu ändern. Wie können Sie das ohne große Anstrengungen während des Entwicklungsprozesses tun? Nun, der erste Schritt sollte ... ja, definieren Sie einen isolierten Cache -Vertrag, auf den später verschiedene Implementierungen folgen, wodurch die Vorteile des Polymorphismus ausnutzt. Auf seiner grundlegendsten Ebene lautet der obige Vertrag wie folgt:
<code class="language-php"><?php namespace LibraryCache; interface CacheInterface { public function set($id, $data); public function get($id); public function delete($id); public function exists($id); }</code>
CacheInterface
Schnittstelle ist ein Skelettvertrag, der das Verhalten gemeinsamer Cache -Elemente abstrahiert. Mit der Schnittstelle können Sie problemlos einige spezifische Cache -Implementierungen erstellen, die ihren Verträgen entsprechen. Da ich es einfach und leicht verständlich halten möchte, ist der von mir eingerichtete Cache -Treiber nur ein schlankes Duo: Der erste verwendet das Dateisystem als das zugrunde liegende Backend für Cache/GET -Daten, während der zweite die APC -Erweiterung verwendet Hinter den Kulissen. Das Folgende ist eine dateibasierte Cache-Implementierung:
<code class="language-php"><?php namespace LibraryCache; class FileCache implements CacheInterface { const DEFAULT_CACHE_DIRECTORY = 'Cache/'; private $cacheDir; public function __construct($cacheDir = self::DEFAULT_CACHE_DIRECTORY) { $this->setCacheDir($cacheDir); } public function setCacheDir($cacheDir) { if (!is_dir($cacheDir)) { if (!mkdir($cacheDir, 0644)) { throw InvalidArgumentException('The cache directory is invalid.'); } } $this->cacheDir = $cacheDir; return $this; } public function set($id, $data) { if (!file_put_contents($this->cacheDir . $id, serialize($data), LOCK_EX)) { throw new RuntimeException("Unable to cache the data with ID '$id'."); } return $this; } public function get($id) { if (!$data = unserialize(@file_get_contents($this->cacheDir . $id, false))) { throw new RuntimeException("Unable to get the data with ID '$id'."); } return $data; } public function delete($id) { if (!@unlink($this->cacheDir . $id)) { throw new RuntimeException("Unable to delete the data with ID '$id'."); } return $this; } public function exists($id) { return file_exists($this->cacheDir . $id); } }</code>Die treibende Logik der Klasse sollte leicht zu verstehen sein. Das bisher relevanteste ist, dass es ein ordentliches polymorphes Verhalten enthüllt, da es die frühe
treu erreicht. Obwohl diese Fähigkeit süß und charmant ist, würde ich es nicht zu schätzen wissen, dass das Ziel hier ist, eine Cache -Komponente zu erstellen, die zur Laufzeit Backends wechseln kann. Lassen Sie uns für Lehrzwecke zusätzliche Anstrengungen unternehmen und eine weitere optimierte Implementierung von FileCache
zum Leben erwecken. Die folgende Implementierung entspricht dem Schnittstellenvertrag, diesmal jedoch durch APC zur Erweiterung der Bündelungsmethode: CacheInterface
CacheInterface
<code class="language-php"><?php namespace LibraryCache; class ApcCache implements CacheInterface { public function set($id, $data, $lifeTime = 0) { if (!apc_store($id, $data, (int) $lifeTime)) { throw new RuntimeException("Unable to cache the data with ID '$id'."); } } public function get($id) { if (!$data = apc_fetch($id)) { throw new RuntimeException("Unable to get the data with ID '$id'."); } return $data; } public function delete($id) { if (!apc_delete($id)) { throw new RuntimeException("Unable to delete the data with ID '$id'."); } } public function exists($id) { return apc_exists($id); } }</code>Klasse ist nicht die schillernste APC -Wrapper, die Sie jemals in Ihrer Karriere gesehen haben. Sie enthält alle Funktionen, die Sie zum Speichern, Abrufen und Löschen von Daten aus dem Speicher benötigen. Lassen Sie uns uns selbst begrüßen, da wir ein leichtes Cache -Modul erfolgreich implementiert haben, dessen spezifisches Backend aufgrund seines Polymorphismus nicht nur zur Laufzeit leicht zu tauschen ist, sondern es ist auch äußerst einfach, in Zukunft weitere Backends hinzuzufügen. Schreiben Sie einfach eine andere Implementierung, die
entspricht. Ich sollte jedoch betonen, dass der tatsächliche Subtyp -Polymorphismus durch die Implementierung von Verträgen erreicht wird, die durch Grenzflächenkonstruktion definiert sind, was ein sehr häufiger Ansatz ist. Nichts kann Sie jedoch davon abhalten, weniger orthodox zu sein und das gleiche Ergebnis zu erzielen, indem Sie eine Schnittstelle wechseln, die als eine Reihe von abstrakten Methoden deklariert ist (in einer abstrakten Klasse). Wenn Sie sich riskant fühlen und diesen Bypass gehen möchten, können Sie den Vertrag und die entsprechende Umsetzung wie folgt rekonstruieren: ApcCache
<code class="language-php"><?php namespace LibraryCache; interface CacheInterface { public function set($id, $data); public function get($id); public function delete($id); public function exists($id); }</code>
<code class="language-php"><?php namespace LibraryCache; class FileCache implements CacheInterface { const DEFAULT_CACHE_DIRECTORY = 'Cache/'; private $cacheDir; public function __construct($cacheDir = self::DEFAULT_CACHE_DIRECTORY) { $this->setCacheDir($cacheDir); } public function setCacheDir($cacheDir) { if (!is_dir($cacheDir)) { if (!mkdir($cacheDir, 0644)) { throw InvalidArgumentException('The cache directory is invalid.'); } } $this->cacheDir = $cacheDir; return $this; } public function set($id, $data) { if (!file_put_contents($this->cacheDir . $id, serialize($data), LOCK_EX)) { throw new RuntimeException("Unable to cache the data with ID '$id'."); } return $this; } public function get($id) { if (!$data = unserialize(@file_get_contents($this->cacheDir . $id, false))) { throw new RuntimeException("Unable to get the data with ID '$id'."); } return $data; } public function delete($id) { if (!@unlink($this->cacheDir . $id)) { throw new RuntimeException("Unable to delete the data with ID '$id'."); } return $this; } public function exists($id) { return file_exists($this->cacheDir . $id); } }</code>
<code class="language-php"><?php namespace LibraryCache; class ApcCache implements CacheInterface { public function set($id, $data, $lifeTime = 0) { if (!apc_store($id, $data, (int) $lifeTime)) { throw new RuntimeException("Unable to cache the data with ID '$id'."); } } public function get($id) { if (!$data = apc_fetch($id)) { throw new RuntimeException("Unable to get the data with ID '$id'."); } return $data; } public function delete($id) { if (!apc_delete($id)) { throw new RuntimeException("Unable to delete the data with ID '$id'."); } } public function exists($id) { return apc_exists($id); } }</code>
von oben nach unten ist dies in der Tat ein polymorpher Ansatz, der gegen die zuvor diskutierte Methode ist. Persönlich ist dies nur meine persönliche Aussage. Ich bevorzuge es, Schnittstellenkonstrukte zu verwenden, um Verträge zu definieren und abstrakte Klassen nur bei der Einkapselung von Kesselplattenimplementierungen zu verwenden, die von mehreren Subtypen gemeinsam genutzt werden. Sie können die Methode auswählen, die Ihren Anforderungen am besten entspricht. Zu diesem Zeitpunkt konnte ich den Vorhang ablegen, einige ausgefallene Ende -Kommentare schreiben, uns über unsere beeindruckenden Codierungsfähigkeiten rühmen und mit der Flexibilität unserer Cache -Komponenten prahlen, aber das wäre ein Schlupf für uns. Wenn es einen Client -Code gibt, der mehrere Implementierungen konsumiert, weisen Polymorphismen ihre verlockendsten Aspekte auf, ohne zu überprüfen, ob diese Implementierungen Fälle von irgendeiner Art sind, solange sie den erwarteten Vertrag erfüllen. Lassen Sie uns den Aspekt aufzeigen, indem Sie die Cache -Komponente mit einer grundlegenden Client -Ansichtsklasse verbinden, mit der wir mühelos ein ordentliches HTML -Caching machen können.
setzen Sie den Cache -Treiber
in die VerwendungCaching HTML -Ausgang über unser Beispiel -Cache -Modul ist sehr einfach und ich werde zu anderen Zeiten lange Erklärungen speichern. Der gesamte Cache -Prozess kann in eine einfache Ansichtsklasse vereinfacht werden, ähnlich wie folgt:
<code class="language-php"><?php namespace LibraryCache; abstract class AbstractCache { abstract public function set($id, $data); abstract public function get($id); abstract public function delete($id); abstract public function exists($id); }</code>
<code class="language-php"><?php namespace LibraryCache; class FileCache extends AbstractCache { // the same implementation goes here }</code>Der schillernste Typ ist der Klassenkonstruktor, der die frühen Implementierer von
und der CacheInterface
-Methode verwendet. Da die Verantwortung der letzten Methode darin besteht, die Vorlage der Ansicht nach dem Ausgangspuffer der Ansicht zu speichern, wäre es schön, diese Fähigkeit zu nutzen und das gesamte HTML -Dokument zu unterragen. Angenommen, die Standardvorlage der Ansicht hat die folgende Struktur: render()
<code class="language-php"><?php namespace LibraryCache; class ApcCache extends AbstractCache { // the same implementation goes here }</code>Lassen Sie uns nun ein wenig Spaß haben und das Dokument zwischenspeichern, indem wir der Ansicht eine Instanz der
-Klasse angeben: ApcCache
<code class="language-php"><?php namespace LibraryView; interface ViewInterface { public function setTemplate($template); public function __set($field, $value); public function __get($field); public function render(); }</code>Es ist sehr gut, oder? Aber warte! Ich war so aufgeregt, dass ich vergessen habe zu erwähnen, dass das obige Code -Snippet auf jedem System explodieren würde, in dem die APC -Erweiterung nicht installiert ist (Naughty System Administrator!). Bedeutet dies, dass das sorgfältig gefertigte Cache -Modul nicht mehr wiederverwendbar ist? Genau hier kommt der dateibasierte Treiber ins Spiel, der in den Client-Code eingebaut werden kann, ohne Beschwerden zu erhalten:
<code class="language-php"><?php namespace LibraryView; use LibraryCacheCacheInterface; class View implements ViewInterface { const DEFAULT_TEMPLATE = 'default'; private $template; private $fields = array(); private $cache; public function __construct(CacheInterface $cache, $template = self::DEFAULT_TEMPLATE) { $this->cache = $cache; $this->setTemplate($template); } public function setTemplate($template) { $template = $template . '.php'; if (!is_file($template) || !is_readable($template)) { throw new InvalidArgumentException( "The template '$template' is invalid."); } $this->template = $template; return $this; } public function __set($name, $value) { $this->fields[$name] = $value; return $this; } public function __get($name) { if (!isset($this->fields[$name])) { throw new InvalidArgumentException( "Unable to get the field '$field'."); } return $this->fields[$name]; } public function render() { try { if (!$this->cache->exists($this->template)) { extract($this->fields); ob_start(); include $this->template; $this->cache->set($this->template, ob_get_clean()); } return $this->cache->get($this->template); } catch (RuntimeException $e) { throw new Exception($e->getMessage()); } } }</code>In der obigen einzigen Codezeile wird ausdrücklich angegeben, dass die Ansicht das Dateisystem anstelle des gemeinsamen Speichers verwendet, um ihre Ausgabe zu speichern. Dieses dynamische Backend für Cache -Cache -Backend veranschaulicht kurz, warum Polymorphismus bei der Gestaltung hochentkoppelter Module so wichtig ist. Es ermöglicht es uns, Dinge zur Laufzeit leicht zu verbinden, ohne die Artefakte im Zusammenhang mit Anfälligkeit/Starrheit in andere Teile unseres Systems zu verbreiten.
Schlussfolgerung
Polymorphismus ist in der Tat eines dieser guten Dinge im Leben, und sobald Sie es verstanden haben, fragen Sie sich, wie Sie ohne den Fall so lange fortgesetzt werden können. Polymorphe Systeme sind von Natur aus orthogonaler, leichter zu skalieren und weniger anfällig für Verstoß gegen Kernparadigmen wie das offene/geschlossene Prinzip und das weise „interface-orientierte Programmierprinzip“. Obwohl unser Cache -Modul eher primitiv ist, ist es ein herausragendes Beispiel für diese Vorteile. Wenn Sie Ihre Bewerbung nicht neu gestellt haben, um die Vorteile des Polymorphismus zu nutzen, sollten Sie sich besser beeilen, weil Sie den Jackpot verpasst haben! Bilder aus Fotolien
FAQs über Subtyp -Polymorphismen (FAQ)
Subtyp -Polymorphismus, auch als Einschlusspolymorphismus bekannt, ist eine Form des Polymorphismus, in der ein Name Instanzen vieler verschiedener Kategorien darstellt, die durch eine öffentliche Superklasse verbunden sind. Der Parameterpolymorphismus hingegen ermöglicht eine Funktion oder einen Datentyp, einen Wert auf die gleiche Weise zu verarbeiten, ohne sich auf ihren Typ zu verlassen. Der Parameterpolymorphismus ist eine Möglichkeit, eine Sprache ausdrucksvoller zu gestalten und gleichzeitig die Sicherheit des statischen Typs aufrechtzuerhalten.
In Java wird der Subtyp -Polymorphismus durch Verwendung von Vererbung und Schnittstellen erreicht. Superklass -Referenzvariablen können auf Subklassobjekte verweisen. Auf diese Weise kann Java entscheiden, welche Methode zur Laufzeit aufgerufen werden soll, was als Dynamic Method Planing bezeichnet wird. Es ist eines der leistungsstarken Merkmale von Java, die es ihm ermöglichen, den dynamischen Polymorphismus zu unterstützen.
Betrachten wir natürlich ein einfaches Beispiel in Java. Angenommen, wir haben eine Superklasse namens "Animal" und zwei Unterklassen "Hund" und "Katze". Sowohl der "Hund "- als auch die" Katzen "-Klasse schreiben die" Sound "-Methode der" Tier "-Klasse neu. Wenn wir nun ein "Tier" -Referenz erstellen, das auf ein "Hund" oder "Katzen" -Objekt zeigt und die "Sound" -Methode aufruft, entscheidet Java zur Laufzeit, welche "Sound" der Klasse aufrufen soll. Dies ist ein Beispiel für Subtyp -Polymorphismus.
Subtyp-Polymorphismus ist ein grundlegender Aspekt der objektorientierten Programmierung. Es ermöglicht Flexibilität und Wiederverwendbarkeit des Codes. Mit dem Subtyp -Polymorphismus können Sie eine gemeinsame Schnittstelle für einen Satz von Klassen entwerfen und diese Schnittstelle dann einheitlich mit den Objekten dieser Klassen interagieren. Dies führt zu sauberer, intuitiveren und leichter zu verwaltenden Code.
Liskov Substitution Prinzip (LSP) ist ein Prinzip des objektorientierten Designs, das besagt, dass ein Programm, wenn es eine Basisklasse verwendet, in der Lage sein sollte, eine seiner Unterklassen ohne das Programm zu verwenden. Mit anderen Worten, Objekte von Superklassen sollten in der Lage sein, durch Objekte von Unterklassen ersetzt zu werden, ohne die Richtigkeit des Programms zu beeinflussen. Der Subtyp -Polymorphismus ist eine direkte Anwendung von LSP.
Nein, nicht alle Programmiersprachen unterstützen den Subtyp -Polymorphismus. Es ist hauptsächlich ein Merkmal von statisch typisierten objektorientierten Programmiersprachen wie Java, C und C#. Dynamisch getippte Sprachen wie Python und JavaScript haben unterschiedliche Formen des Polymorphismus, die als Ententypen bezeichnet werden.
Können Sie das Konzept der Up-Conversion im Subtyp-Polymorphismus erklären?
Was ist eine Down-Conversion im Kontext des Subtyp-Polymorphismus?
Wie fördert der Subtyp -Polymorphismus die Wiederverwendbarkeit von Code?
Das obige ist der detaillierte Inhalt vonSubtyp -Polymorphismus - Austausch der Implementierung zur Laufzeit. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!