OOP (Object Oriented Programming) ist ein wesentlicher Bestandteil der Entwicklung vieler moderner Projekte geworden, aber trotz seiner Popularität ist dieses Paradigma bei weitem nicht das einzige. Wenn Sie bereits wissen, wie man mit anderen Paradigmen arbeitet, und sich mit dem OOP-Okkultismus vertraut machen möchten, haben Sie ein wenig Longrid und zwei Megabyte Bilder und Animationen vor sich. Transformatoren dienen als Beispiele.

Die erste Antwort ist, warum? Die objektorientierte Ideologie wurde entwickelt, um das Verhalten einer Entität mit ihren Daten zu verbinden und reale Objekte und Geschäftsprozesse in Programmcode zu projizieren. Es wurde angenommen, dass ein solcher Code für eine Person leichter zu lesen und zu verstehen ist, da es üblich ist, dass Menschen die umgebende Welt als eine Vielzahl von Objekten wahrnehmen, die miteinander interagieren und einer bestimmten Klassifizierung zugänglich sind. Ist es für Ideologen möglich, das Ziel zu erreichen, ist es schwierig, eindeutig zu antworten, aber de facto haben wir viele Projekte, bei denen der Programmierer OOP benötigt.
Sie sollten nicht glauben, dass die OOP das Schreiben von Programmen auf wundersame Weise beschleunigen wird, und eine Situation erwarten, in der die Bewohner von Villaribo das OOP-Projekt bereits umgesetzt haben und die Bewohner von Villabaggio immer noch den kühnen Spaghetti-Code waschen. In den meisten Fällen ist dies nicht der Fall, und Zeit wird nicht in der Entwicklungsphase, sondern in den Supportphasen (Erweiterung, Änderung, Debugging und Test), dh auf lange Sicht, gespart. Wenn Sie ein einmaliges Skript schreiben müssen, das keine nachfolgende Unterstützung benötigt, ist OOP in dieser Aufgabe höchstwahrscheinlich nicht hilfreich. Ein wesentlicher Teil des Lebenszyklus der meisten modernen Projekte ist jedoch genau die Unterstützung und Erweiterung. OOP allein macht Ihre Architektur nicht fehlerfrei und umgekehrt kann es zu unnötigen Komplikationen kommen.
Manchmal kann es zu Kritik an der Leistung von OOP-Programmen kommen. Es ist wahr, dass ein geringer Overhead vorhanden ist, der jedoch so unbedeutend ist, dass er in den meisten Fällen zugunsten von Vorteilen vernachlässigt werden kann. In Engpässen, in denen Millionen von Objekten pro Sekunde in einem Thread erstellt oder verarbeitet werden sollen, lohnt es sich jedoch, zumindest die Notwendigkeit von OOP zu überarbeiten, da selbst ein minimaler Overhead in solchen Mengen die Leistung erheblich beeinträchtigen kann. Mithilfe der Profilerstellung können Sie den Unterschied erfassen und eine Entscheidung treffen. In anderen Fällen, in denen beispielsweise der Löwenanteil der Geschwindigkeit auf E / A beruht, ist das Verlassen von Objekten eine vorzeitige Optimierung.
Objektorientierte Programmierung lässt sich naturgemäß am besten anhand von Beispielen erklären. Wie versprochen werden unsere Patienten Transformatoren sein. Ich bin kein Transformator, und ich habe keine Comics gelesen, daher werde ich mich in den Beispielen von Wikipedia und Fantasie leiten lassen.
Klassen und Objekte
Sofort lyrischer Exkurs: Ein objektorientierter Ansatz ist ohne Klassen möglich, aber wir werden uns entschuldigen, ich entschuldige mich für das Wortspiel, das klassische Schema, bei dem Klassen unser Alles sind.
Die einfachste Erklärung: Eine Klasse ist eine Zeichnung eines Transformators, und Instanzen dieser Klasse sind bestimmte Transformatoren, z. B. Optimus Prime oder Oleg. Und obwohl sie nach einer Zeichnung zusammengesetzt sind, können sie auf die gleiche Weise gehen, sich verwandeln und schießen. Beide haben ihren eigenen einzigartigen Zustand. Ein Zustand ist eine Reihe sich ändernder Eigenschaften. Daher können wir in zwei verschiedenen Objekten derselben Klasse einen unterschiedlichen Namen, Alter, Ort, Ladungsstand, Munitionsmenge usw. beobachten. Die Existenz dieser Eigenschaften und ihre Typen werden in der Klasse beschrieben.
Eine Klasse beschreibt also, welche Eigenschaften und Verhaltensweisen ein Objekt besitzt. Und ein Objekt ist eine Instanz mit einem eigenen Status dieser Eigenschaften.
Wir sagen "Eigenschaften und Verhalten", aber es klingt irgendwie abstrakt und unverständlich. Für einen Programmierer ist es vertrauter, so zu klingen: „Variablen und Funktionen“. Tatsächlich sind „Eigenschaften“ dieselben gewöhnlichen Variablen, sie sind einfach Attribute eines Objekts (sie werden Objektfelder genannt). In ähnlicher Weise ist „Verhalten“ die Funktion eines Objekts (sie werden Methoden genannt), die auch Attribute des Objekts sind. Der Unterschied zwischen der Methode des Objekts und der üblichen Funktion besteht nur darin, dass die Methode über die Felder Zugriff auf ihren eigenen Status hat.
Insgesamt haben wir Methoden und Eigenschaften, die Attribute sind. Wie arbeite ich mit Attributen? In den meisten PLs ist der Attributreferenzoperator der Punkt (außer bei PHP und Perl). Es sieht ungefähr so aus (Pseudocode):
In den Bildern werde ich die folgende Notation verwenden:

Ich habe keine UML-Diagramme verwendet, da sie nicht ausreichend visuell sind, wenn auch flexibler.
Animation Nummer 1Was sehen wir aus dem Code?
1.
Dies ist eine spezielle lokale Variable (innerhalb von Methoden), mit der ein Objekt über seine Methoden auf seine eigenen Attribute zugreifen kann. Ich mache Sie darauf aufmerksam, dass nur auf Ihre eigene, dh wenn der Transformator seine eigene Methode aufruft oder seinen eigenen Zustand ändert. Wenn der Aufruf außen so aussieht:
optimus.x , dann, wenn Optimus von innen auf sein Feld x selbst zugreifen möchte, sieht der Aufruf in seiner Methode so
aus.x ,
dh "
Ich (Optimus) beziehe mich auf mein Attribut x ". In den meisten Sprachen wird diese Variable so genannt, es gibt jedoch Ausnahmen (z. B. self).
2.
Konstruktor ist eine spezielle Methode, die beim Erstellen eines Objekts automatisch aufgerufen wird. Der Konstruktor kann wie jede andere Methode beliebige Argumente akzeptieren. In jeder Sprache wird ein Konstruktor durch seinen Namen angegeben. Irgendwo sind dies speziell reservierte Namen wie __construct oder __init__, und irgendwo muss der Name des Konstruktors mit dem Namen der Klasse übereinstimmen. Der Zweck der Konstruktoren besteht darin, das Objekt zu initialisieren und die erforderlichen Felder auszufüllen.
3.
new ist ein Schlüsselwort, das zum Erstellen einer neuen Instanz einer Klasse verwendet werden muss. Zu diesem Zeitpunkt wird ein Objekt erstellt und der Konstruktor aufgerufen. In unserem Beispiel wird 0 als Startposition des Transformators an den Konstruktor übergeben (dies ist die obige Initialisierung). Das neue Schlüsselwort fehlt in einigen Sprachen, und der Konstruktor wird automatisch aufgerufen, wenn versucht wird, die Klasse als Funktion aufzurufen, z. B.: Transformer ().
4.
Die Konstruktor- und Ausführungsmethoden
arbeiten mit dem internen Zustand, unterscheiden sich jedoch im Übrigen nicht von normalen Funktionen . Auch die Syntax der Deklaration stimmt überein.
5. Klassen können Methoden besitzen, die keinen Status benötigen, und als Ergebnis ein Objekt erstellen. In diesem Fall wird die Methode
statisch gemacht .
Srp
(Prinzip der
Einzelverantwortung / Erstes
SOLID- Prinzip). Sie kennen es wahrscheinlich bereits aus anderen Paradigmen: „Eine Funktion sollte nur eine abgeschlossene Aktion ausführen“. Dieses Prinzip gilt auch für Klassen: "Eine Klasse muss für eine Aufgabe verantwortlich sein." Leider ist es bei Klassen schwieriger, die Grenze zu definieren, die überschritten werden muss, damit das Prinzip verletzt wird.
Es gibt Versuche, dieses Prinzip zu formalisieren, indem der Zweck einer Klasse mit einem Satz ohne Gewerkschaften beschrieben wird. Dies ist jedoch eine sehr kontroverse Technik. Vertrauen Sie also Ihrer Intuition und beeilen Sie sich nicht bis zum Äußersten. Sie müssen kein Schweizer Messer aus einer Klasse machen, aber eine Million Klassen mit einer Methode zu produzieren, ist auch dumm.
Verein
Traditionell können in den Feldern eines Objekts nicht nur gewöhnliche Variablen von Standardtypen gespeichert werden, sondern auch andere Objekte. Und diese Objekte können wiederum einige andere Objekte usw. speichern und so einen Baum (manchmal ein Diagramm) von Objekten bilden. Diese Beziehung wird Assoziation genannt.
Angenommen, unser Transformator ist mit einer Pistole ausgestattet. Obwohl nein, besser mit zwei Waffen. In jeder Hand. Die Waffen sind die gleichen (sie gehören zur gleichen Klasse oder, wenn Sie möchten, nach einer Zeichnung hergestellt), beide können gleichermaßen schießen und nachladen, aber jede hat ihren eigenen Munitionsspeicher (eigener Zustand). Wie soll man es jetzt in OOP beschreiben? Nach Verein:
class Gun(){
Animation Nummer 2this.gun_left.fire () und this.gun_right.fire () sind Aufrufe von untergeordneten Objekten, die auch durch Punkte erfolgen. Am ersten Punkt wenden wir uns dem Attribut von uns selbst zu (this.gun_right), um das Waffenobjekt zu erhalten, und am zweiten Punkt wenden wir uns der Methode des Waffenobjekts zu (this.gun_right.fire ()).
Fazit: Der Roboter wurde hergestellt, die Servicewaffe wurde ausgegeben, jetzt werden wir herausfinden, was hier passiert. In diesem Code ist ein Objekt ein integraler Bestandteil eines anderen Objekts geworden. Dies ist der Verein. Es gibt wiederum zwei Arten:
1.
Zusammensetzung - ein Fall, in dem in der Transformatorenfabrik, in der Optimus gesammelt wird, beide Waffen mit Nägeln fest an seinen Händen befestigt sind und nach Optimus 'Tod die Waffen mit ihm sterben. Mit anderen Worten, der Lebenszyklus des Kindes ist der gleiche wie der Lebenszyklus des Elternteils.
2.
Aggregation - der Fall, wenn eine Waffe als Waffe in seiner Hand ausgegeben wird und nach Optimus 'Tod diese Waffe von seinem Kameraden Oleg aufgenommen und dann in seine eigene Hand genommen oder einem Pfandhaus übergeben werden kann. Das heißt, der Lebenszyklus eines untergeordneten Objekts hängt nicht vom Lebenszyklus des übergeordneten Objekts ab und kann von anderen Objekten verwendet werden.
Die orthodoxe OOP-Kirche predigt uns eine grundlegende Dreifaltigkeit -
Verkapselung, Polymorphismus und Vererbung , auf der der gesamte objektorientierte Ansatz basiert. Sortieren wir sie der Reihe nach.

Vererbung
Vererbung ist ein Systemmechanismus, der es, so paradox es auch klingen mag, ermöglicht, die Eigenschaften und das Verhalten anderer Klassen von einigen Klassen zur weiteren Erweiterung oder Änderung zu erben.
Was ist, wenn wir nicht dieselben Transformatoren stempeln möchten, sondern einen gemeinsamen Rahmen erstellen möchten, sondern ein anderes Bodykit? OOP erlaubt uns einen solchen Streich, indem wir die Logik in Ähnlichkeiten und Unterschiede aufteilen, gefolgt von der Beseitigung von Ähnlichkeiten in der Elternklasse und Unterschieden in Nachkommenklassen. Wie sieht es aus?
Optimus Prime und Megatron sind beide Transformatoren, aber einer ist ein Autobot und der andere ist ein Decepticon. Angenommen, die Unterschiede zwischen Autobots und Decepticons bestehen nur darin, dass Autobots in Autos und Decepticons in Luftfahrt umgewandelt werden. Alle anderen Eigenschaften und Verhaltensweisen machen keinen Unterschied. In diesem Fall kann das Vererbungssystem wie folgt gestaltet werden: Gemeinsame Funktionen (Laufen, Schießen) werden in der Transformer-Basisklasse und Unterschiede (Transformation) in den beiden untergeordneten Klassen Autobot und Decepticon beschrieben.
class Transformer(){
Animation Nummer 3Dieses Beispiel zeigt, wie die Vererbung zu einer der Möglichkeiten wird, Code (
DRY-Prinzip ) mithilfe der übergeordneten Klasse zu deduplizieren, und bietet gleichzeitig Möglichkeiten zur Mutation in untergeordneten Klassen.
Überlastung
Wenn Sie eine vorhandene Methode in der übergeordneten Klasse in der übergeordneten Klasse überschreiben, funktioniert die Überladung. Dadurch können wir das Verhalten der übergeordneten Klasse nicht ergänzen, sondern ändern. Zum Zeitpunkt des Aufrufs der Methode oder des Zugriffs auf das Feld des Objekts erfolgt die Suche nach dem Attribut vom Nachkommen bis zur Wurzel - dem übergeordneten Element. Das heißt, wenn die fire () -Methode für den Autobot aufgerufen wird, wird die Methode zuerst in der Nachkommenklasse Autobot gesucht, und da sie nicht vorhanden ist, steigt die Suche einen Schritt höher - zur Transformer-Klasse, wo sie erkannt und aufgerufen wird.
Unangemessene Verwendung
Es ist merkwürdig, dass eine übermäßig tiefe Hierarchie der Vererbung zu dem gegenteiligen Effekt führen kann - Komplikationen, wenn versucht wird, herauszufinden, wer von wem geerbt wird und welche Methode in welchem Fall aufgerufen wird. Darüber hinaus können nicht alle Architekturanforderungen mithilfe der Vererbung implementiert werden. Daher sollte die Vererbung ohne Fanatismus angewendet werden. Es gibt Empfehlungen, die gegebenenfalls eine bevorzugte Zusammensetzung gegenüber der Vererbung fordern. Jede Kritik an der Vererbung, die ich getroffen habe, wird durch erfolglose Beispiele verstärkt, wenn die Vererbung als
goldener Hammer verwendet wird . Dies bedeutet jedoch keineswegs, dass die Vererbung grundsätzlich immer schädlich ist. Mein Narkologe sagte, dass der erste Schritt darin besteht, zuzugeben, dass Sie von der Vererbung abhängig sind.
Wie kann bei der Beschreibung der Beziehungen zweier Entitäten festgestellt werden, wann die Vererbung angemessen ist und wann die Zusammensetzung angemessen ist? Sie können den beliebten Spickzettel verwenden: Fragen Sie sich,
Entität A ist Entität B ? Wenn ja, ist höchstwahrscheinlich eine Vererbung angemessen. Wenn
Entität A Teil von Entität B ist , ist unsere Wahl die Zusammensetzung.
In Bezug auf unsere Situation wird es so klingen:
- Ist Autobot Transformer? Ja, dann wählen wir die Vererbung.
- Ist die Waffe Teil des Transformators? Ja, das bedeutet Komposition.
Versuchen Sie für einen Selbsttest die umgekehrte Kombination, Sie erhalten Müll. Dieser Spickzettel hilft in den meisten Fällen, aber es gibt andere Faktoren, auf die Sie sich verlassen sollten, wenn Sie zwischen Komposition und Vererbung wählen. Darüber hinaus können diese Methoden kombiniert werden, um verschiedene Arten von Problemen zu lösen.
Die Vererbung ist statisch
Ein weiterer wichtiger Unterschied zwischen Vererbung und Komposition besteht darin, dass die Vererbung statischer Natur ist und Klassenbeziehungen nur in der Interpretations- / Kompilierungsphase herstellt. Wie wir in den Beispielen gesehen haben, können Sie die Beziehung von Entitäten im laufenden Betrieb direkt zur Laufzeit ändern - manchmal ist dies sehr wichtig, daher müssen Sie dies bei der Auswahl von Beziehungen berücksichtigen (es sei denn, es besteht natürlich der Wunsch,
Metaprogrammierung zu verwenden).
Mehrfachvererbung
Wir haben eine Situation untersucht, in der zwei Klassen von einem gemeinsamen Nachkommen geerbt werden. In einigen Sprachen können Sie jedoch das Gegenteil tun: Erben Sie eine Klasse von zwei oder mehr Eltern, indem Sie deren Eigenschaften und Verhalten kombinieren. Die Fähigkeit, von mehreren Klassen anstelle von einer zu erben, ist eine Mehrfachvererbung.

Im Allgemeinen gibt es in den Illuminati-Kreisen die Meinung, dass Mehrfachvererbung eine Sünde ist, die ein
rautenförmiges Problem und Verwirrung mit den Designern mit sich bringt. Darüber hinaus können Aufgaben, die durch Mehrfachvererbung gelöst werden können, durch andere Mechanismen gelöst werden, z. B. den Schnittstellenmechanismus (über den wir auch sprechen werden). Fairerweise sollte jedoch beachtet werden, dass die Mehrfachvererbung für die Implementierung von
Verunreinigungen zweckmäßig ist.
Abstrakte Klassen
Zusätzlich zu normalen Klassen gibt es in einigen Sprachen abstrakte Sprachen. Sie unterscheiden sich von normalen Klassen darin, dass Sie kein Objekt einer solchen Klasse erstellen können. Warum brauchen wir eine solche Klasse, wird der Leser fragen? Es wird benötigt, damit Nachkommen davon geerbt werden können - gewöhnliche Klassen, deren Objekte bereits erstellt werden können.
Die abstrakte Klasse enthält zusammen mit den üblichen Methoden abstrakte Methoden ohne Implementierung (mit einer Signatur, aber ohne Code), die der Programmierer, der eine untergeordnete Klasse erstellen möchte, implementieren muss. Abstrakte Klassen sind nicht erforderlich, helfen jedoch beim Aufbau eines Vertrags, der die Implementierung eines bestimmten Satzes von Methoden erfordert, um einen Programmierer mit schlechtem Speicher vor einem Implementierungsfehler zu schützen.
Polymorphismus
Polymorphismus ist eine Systemeigenschaft, mit der Sie viele Implementierungen einer Schnittstelle durchführen können. Nichts ist klar. Wenden wir uns den Transformatoren zu.
Angenommen, wir haben drei Transformatoren: Optimus, Megatron und Oleg. Transformatoren sind Kämpfe, daher haben sie die attack () -Methode. Der Spieler weist das Spiel durch Drücken der "Kampf" -Taste auf seinem Joystick an, die attack () -Methode für den Transformator aufzurufen, für den der Spieler spielt. Aber da die Transformatoren unterschiedlich sind und das Spiel interessant ist, wird jeder von ihnen auf irgendeine Weise angreifen. Nehmen wir an, Optimus ist ein Objekt der Autobot-Klasse, und Autobots sind mit Kanonen mit Plutoniumsprengköpfen ausgestattet (ja, Fans von Transformatoren sind nicht böse). Megatron ist ein Decepticon und schießt aus einer Plasmakanone. Oleg ist Bassist und nennt ihn Namen. Und was nützt das?
Die Verwendung von Polymorphismus in diesem Beispiel besteht darin, dass der Spielcode nichts über die Implementierung seiner Anfrage weiß, wer wie angreifen soll. Seine Aufgabe besteht einfach darin, die attack () -Methode aufzurufen, deren Signatur für alle Charakterklassen gleich ist. Auf diese Weise können Sie neue Charakterklassen hinzufügen oder vorhandene Methoden ändern, ohne den Spielcode zu ändern. Das ist bequem.
Kapselung
Die Kapselung ist die Kontrolle des Zugriffs auf die Felder und Methoden eines Objekts. Die Zugriffskontrolle impliziert nicht nur mögliche / inkonsequente, sondern auch verschiedene Validierungen, Ladevorgänge, Berechnungen und anderes dynamisches Verhalten.
In vielen Sprachen ist die Datenverschlüsselung Teil der Kapselung. Hierfür gibt es Zugriffsmodifikatoren (wir beschreiben diejenigen, die in fast allen OOP-Sprachen verfügbar sind):
- publi - Jeder kann auf das Attribut zugreifen
- privat - Nur Methoden dieser Klasse können auf das Attribut zugreifen
- geschützt - genau wie privat erhalten nur die Erben der Klasse Zugang, einschließlich
class Transformer(){ public function constructor(){ } protected function setup(){ } private function dance(){ } }
Wie wähle ich den Zugriffsmodifikator aus? Im einfachsten Fall: Wenn die Methode für externen Code zugänglich sein soll, wählen Sie public aus. Ansonsten privat. Wenn eine Vererbung vorliegt, kann ein Schutz erforderlich sein, wenn die Methode nicht extern, sondern von Nachkommen aufgerufen werden soll.
Accessoren (Getter und Setter)
Getter und Setter sind Methoden, deren Aufgabe es ist, den Zugriff auf Felder zu steuern. Der Getter liest und gibt den Wert des Feldes zurück, und der Setter nimmt im Gegenteil den Wert als Argument und schreibt ihn in das Feld. Dies ermöglicht es, solche Methoden mit zusätzlichen Behandlungen zu versehen. Beispielsweise kann ein Setter beim Schreiben eines Werts in ein Objektfeld den Typ überprüfen oder prüfen, ob der Wert im Bereich gültiger Werte liegt (Validierung). Im Getter können Sie eine verzögerte Initialisierung oder Zwischenspeicherung hinzufügen, wenn der tatsächliche Wert tatsächlich in der Datenbank liegt. Es gibt viele Anwendungen.
Einige Sprachen verfügen über syntaktischen Zucker, mit dem solche Accessoren als Eigenschaften maskiert werden können, wodurch der Zugriff auf externen Code transparent wird. Dies vermutet nicht, dass er nicht mit einem Feld funktioniert, sondern mit einer Methode, die eine SQL-Abfrage ausführt oder aus einer Datei unter der Haube liest. So werden Abstraktion und Transparenz erreicht.
Schnittstellen
Die Aufgabe der Schnittstelle besteht darin, die Abhängigkeit von Entitäten voneinander zu verringern und mehr Abstraktion hinzuzufügen.
Nicht alle Sprachen haben diesen Mechanismus, aber in OOP-Sprachen mit statischer Typisierung ohne sie wäre es wirklich schlecht. Oben haben wir abstrakte Klassen betrachtet und das Thema Verträge angesprochen, die zur Implementierung einiger abstrakter Methoden verpflichtet sind. Die Schnittstelle sieht also einer abstrakten Klasse sehr ähnlich, ist jedoch keine Klasse, sondern nur ein Dummy mit einer Aufzählung abstrakter Methoden (ohne Implementierung). Mit anderen Worten, die Schnittstelle ist deklarativer Natur, dh ein sauberer Vertrag ohne ein bisschen Code.
In der Regel haben Sprachen mit Schnittstellen keine Vererbung mehrerer Klassen, es gibt jedoch eine Vererbung mehrerer Schnittstellen. Auf diese Weise kann die Klasse die Schnittstellen auflisten, die sie implementieren möchte.
Klassen mit Schnittstellen bestehen aus einer Viele-zu-Viele-Beziehung: Eine einzelne Klasse kann mehrere Schnittstellen implementieren, und jede Schnittstelle kann wiederum von vielen Klassen implementiert werden.
Die Schnittstelle ist zweiseitig verwendbar:
- Auf einer Seite der Schnittstelle befinden sich Klassen, die diese Schnittstelle implementieren.
- Auf der anderen Seite stehen Verbraucher, die diese Schnittstelle als Beschreibung der Art der Daten verwenden, mit denen sie (Verbraucher) arbeiten.
Wenn beispielsweise ein anderes Objekt als das grundlegende Verhalten serialisiert werden kann, lassen Sie es die serialisierbare Schnittstelle implementieren. Und wenn das Objekt geklont werden kann, lassen Sie es eine andere Schnittstelle implementieren - "geklont". Und wenn wir eine Art Transportmodul haben, das Objekte über das Netzwerk überträgt, akzeptiert es alle Objekte, die die serialisierbare Schnittstelle implementieren.
Stellen Sie sich vor, der Transformatorrahmen ist mit drei Steckplätzen ausgestattet: einem Steckplatz für Waffen, einem Energieerzeuger und einer Art Scanner. Diese Steckplätze haben bestimmte Schnittstellen: In jedem Steckplatz können nur geeignete Geräte installiert werden. Sie können einen Raketenwerfer oder eine Laserkanone im Steckplatz für Waffen, einen Kernreaktor oder RTG (thermoelektrischer Radioisotopgenerator) im Steckplatz für den Stromgenerator und ein Radar oder Lidar im Steckplatz für den Scanner installieren. Unter dem Strich verfügt jeder Steckplatz über eine universelle Verbindungsschnittstelle, und bestimmte Geräte sollten dieser Schnittstelle entsprechen. Beispielsweise werden auf Motherboards verschiedene Arten von Steckplätzen verwendet: Über einen Prozessorsteckplatz können Sie verschiedene für diesen Sockel geeignete Prozessoren anschließen, und über einen SATA-Steckplatz können Sie jedes SSD- oder HDD-Laufwerk oder sogar eine CD / DVD anschließen.
Ich mache Sie darauf aufmerksam, dass das resultierende Steckplatzsystem für Transformatoren ein Beispiel für die Verwendung von Kompositionen ist. Wenn die Geräte in den Steckplätzen während der Lebensdauer des Transformators austauschbar sind, handelt es sich bereits um eine Aggregation. , , , «» : IWeapon, IEnergyGenerator, IScanner.
№4, , , .
- . , : () Transformer, - () ( Radar, RocketLauncher, NuclearReactor . .)
In diesem Code können wir neue Komponenten für Transformatoren erstellen, ohne die Zeichnungen der Transformatoren selbst zu beeinflussen. Gleichzeitig und umgekehrt können wir neue Transformatoren erstellen, indem wir vorhandene Komponenten kombinieren oder neue Komponenten hinzufügen, ohne vorhandene zu ändern.Ente tippen
, ,
:
- , , , , — .
, : - , , . , .
, , , . , ! , . , :

ISP
(Prinzip der Schnittstellentrennung / Viertes SOLID-Prinzip) empfiehlt, keine kühnen, universellen Schnittstellen zu erstellen. Stattdessen sollten Schnittstellen in kleinere und spezialisierte unterteilt werden. Dies hilft, sie bei der Implementierung von Klassen flexibler zu kombinieren, ohne unnötige Methoden implementieren zu müssen.Abstraktion
In OOP dreht sich alles um Abstraktion. Es gibt Fanatiker, die behaupten, dass Abstraktion Teil der OOP-Dreifaltigkeit sein sollte (Einkapselung, Polymorphismus, Vererbung). Und mein Inspektor für Bewährungstests sagte das Gegenteil: Abstraktion ist jeder Programmierung inhärent und nicht nur OOP, daher sollte sie getrennt sein. Auf der anderen Seite kann das Gleiche über den Rest der Prinzipien gesagt werden, aber Sie werden keine Wörter aus einem Song löschen. Auf die eine oder andere Weise ist eine Abstraktion erforderlich, insbesondere in OOP.Abstraktionsebene
Hier kann man einen bekannten Witz nicht übersehen:- Jedes architektonische Problem kann durch Hinzufügen einer zusätzlichen Abstraktionsebene gelöst werden, mit Ausnahme des Problems einer großen Anzahl von Abstraktionen.In unserem Beispiel mit Schnittstellen haben wir eine Abstraktionsschicht zwischen Transformatoren und Komponenten implementiert, um die Architektur flexibler zu gestalten. Aber zu welchem Preis? Wir mussten die Architektur komplizieren. Mein Psychotherapeut sagte, dass die Fähigkeit, zwischen der Einfachheit der Architektur und der Flexibilität der Anwendung zu balancieren, eine Kunst ist. Wenn man sich für einen Mittelweg entscheidet, sollte man sich nicht nur auf die eigene Erfahrung und Intuition verlassen, sondern auch auf den Kontext des aktuellen Projekts. Da die Person noch nicht gelernt hat, die Zukunft zu sehen, muss analytisch abgeschätzt werden, welcher Abstraktionsgrad und mit welcher Wahrscheinlichkeit in diesem Projekt nützlich sein wird, wie viel Zeit für die Entwicklung einer flexiblen Architektur erforderlich ist und ob sich die aufgewendete Zeit in Zukunft auszahlt.Eine falsche Auswahl der Abstraktionsebene führt zu einem von zwei Problemen:- , , , ( )
- , , , , . ( )
Es ist auch wichtig zu verstehen, dass der Abstraktionsgrad nicht für das gesamte Projekt als Ganzes bestimmt wird, sondern separat für verschiedene Komponenten. An einigen Stellen reicht das Abstraktionssystem möglicherweise nicht aus, aber irgendwo im Gegenteil - Büste. Die falsche Wahl der Abstraktionsebene kann jedoch durch rechtzeitiges Refactoring korrigiert werden. Das Schlüsselwort ist aktuell . Ein verzögertes Refactoring ist problematisch, wenn auf dieser Abstraktionsebene bereits viele Mechanismen implementiert sind. Die Durchführung eines Refactoring-Rituals in laufenden Systemen kann zu akuten Schmerzen an schwer erreichbaren Stellen eines Programmierers führen. Es geht darum, wie man das Fundament in einem Haus ändert - es ist billiger, ein Haus nebenan von Grund auf neu zu bauen.«-». , , .
. , . , , .
. ( , , ), . , .
. ( , ). , . , , - .
Vierte Ebene. Sie können auch Ihre eigene Aggregation in die Komponenten aufnehmen, sodass Sie die Materialien und Teile auswählen können, aus denen diese Komponenten zusammengesetzt sind. Dieser Ansatz gibt dem Spieler die Möglichkeit, nicht nur die Transformatoren mit den erforderlichen Komponenten zu füllen, sondern diese Komponenten auch unabhängig voneinander aus verschiedenen Teilen herzustellen. Ehrlich gesagt habe ich in Spielen noch nie einen solchen Abstraktionsgrad erreicht, und das nicht ohne Grund! Dies geht schließlich mit einer erheblichen Komplikation der Architektur einher, und die Anpassung des Gleichgewichts in solchen Spielen wird zur Hölle. Aber ich schließe nicht aus, dass solche Spiele existieren.
Wie Sie sehen, hat jede beschriebene Schicht im Prinzip das Recht auf Leben. Es hängt alles davon ab, welche Flexibilität wir in das Projekt einbringen möchten. Wenn die Leistungsbeschreibung nichts dazu aussagt oder der Autor des Projekts selbst nicht weiß, was ein Unternehmen möglicherweise benötigt, können Sie sich ähnliche Projekte in diesem Bereich ansehen und sich auf diese konzentrieren.Entwurfsmuster

, , ,
. , , , , , , .
, , , . , , , . ! , .
— , , . , , , . , , .
— . , « », .
Fazit
class -. (, , . .), , . - . , .
. , , , . « — » « ».
Ich meine es ernst.
Wenn wir den Fall von Singleton betrachten, hat seine weit verbreitete Verwendung ohne Kenntnis des Falls in vielen Projekten ernsthafte architektonische Probleme verursacht. Und Liebhaber des Nagelns von Nägeln mit einem Mikroskop nannten ihn freundlicherweise ein Antimuster. Sei umsichtig.Leider gibt es beim Entwerfen keine eindeutigen Rezepte für alle Gelegenheiten, bei denen das, was angemessen und wo unangemessen ist. Dies wird mit der Erfahrung allmählich in Ihren Kopf passen.