Erforderlicher Eintrag
Ich kann nicht garantieren, dass die hier dargelegten Interpretationen allgemein anerkannter Begriffe und Prinzipien mit denen übereinstimmen, die kalifornische Professoren in der zweiten Hälfte des letzten Jahrhunderts in soliden wissenschaftlichen Artikeln vorgestellt haben. Ich kann nicht garantieren, dass meine Interpretationen von den meisten IT-Fachleuten in der Branche oder in der akademischen Gemeinschaft vollständig geteilt oder geteilt werden. Ich kann nicht einmal garantieren, dass meine Interpretationen Ihnen im Interview helfen, obwohl ich davon ausgehe, dass sie nützlich sein werden.
Ich garantiere jedoch, dass der von Ihnen geschriebene Code leichter zu pflegen und zu ändern ist, wenn das fehlende Verständnis durch meine Interpretationen ersetzt wird und diese angewendet werden. Ich verstehe auch sehr gut, dass ich in den Kommentaren schreiben werde, was wütend geschrieben wird, wodurch ich absolut krasse Auslassungen und Inkonsistenzen korrigieren kann.
Solche kleinen Garantien werfen Fragen nach den Gründen auf, warum der Artikel geschrieben wurde. Ich glaube, dass diese Dinge überall dort unterrichtet werden sollten, wo sie Programmieren unterrichten, bis hin zum Informatikunterricht an Schulen, an denen sie eingehend studiert werden. Trotzdem wurde es für mich eine erschreckend normale Situation, als ich herausfand, dass der Gesprächspartner mein Kollege ist und seit mehreren Jahren arbeitet, aber über die Kapselung „Ich habe dort etwas gehört“. Die Notwendigkeit, all dies an einem Ort zu sammeln und einen Link zu geben, wenn Fragen auftauchen, ist für eine lange Zeit reif. Und dann gab mir mein „Haustierprojekt“ eine Menge Denkanstöße.
Hier können sie mir widersprechen, dass es zu früh ist, diese Dinge in der Schule zu lernen, und im Allgemeinen ist das Licht in OOP nicht zusammengekommen. Erstens hängt es davon ab, wie Sie lernen. Zweitens gelten 70% des Materials in diesem Artikel nicht nur für OOP. Was ich separat vermerken werde.

OOP auf den Punkt gebracht
Dies ist wahrscheinlich der schwierigste Abschnitt für mich. Dennoch müssen Sie die Basis festlegen und sehr kurz beschreiben, was das Wesen von OOP ist, um zu verstehen, warum Kapselung, Polymorphismus, Vererbung und SOLID-Prinzipien es gestärkt haben. Und ich werde dies tun, indem ich darüber spreche, wie Sie überhaupt an so etwas denken könnten.
Es begann mit Dijkstra, der bewies, dass jeder Algorithmus auf drei Arten ausgedrückt werden kann, um den folgenden Befehl auszuwählen: lineare Ausführung (in Reihenfolge), Verzweigung nach Bedingung, Schleifenausführung, während die Bedingung erfüllt ist. Mit diesen drei Verbindungen können Sie einen beliebigen Algorithmus erstellen. Darüber hinaus wurde empfohlen, Programme zu schreiben, die auf die lineare Anordnung von Befehlen nacheinander, Verzweigungen und Schleifen beschränkt sind. Dies wurde als " prozedurale Strukturprogrammierung" bezeichnet (danke für die Klarstellung sshikov ).
Auch hier stellen wir fest, dass die Befehlsfolge zu Unterprogrammen kombiniert werden muss und jedes Unterprogramm mit einem Befehl ausgeführt werden kann.
Neben der Reihenfolge der Aktionen ist es für uns wichtig, welche Aktionen ausgeführt werden. Und sie werden an Daten ausgeführt, die zum Speichern in Variablen üblich geworden sind. Variablen speichern Daten. Sie werden nach ihrem Typ interpretiert. Natürlich zum Zähneknirschen, aber bitte etwas Geduld.
Von Anfang an wurde ein mehr oder weniger allgemeiner Satz primitiver Datentypen gebildet. Ganzzahlen, reelle Zahlen, Boolesche Variablen, Arrays, Strings. Algorithmen + Datenstrukturen = Programme, wie Nicklaus Wirth hinterlassen hat.
Außerdem gab es von Anfang an in verschiedenen Formen einen solchen Datentyp wie ein Unterprogramm. Oder ein Stück Code, wenn Sie wollen. Einige könnten sagen, dass die Verwendung von Unterprogrammen als Variablen das Vorrecht der funktionalen Programmierung ist. Trotzdem ist die Möglichkeit, einen Teil des variablen Codes zu erstellen, auch im Assembler vorhanden. Lassen Sie diese Möglichkeit auf "Nun, hier ist die Bytenummer im RAM, in dem sich diese Unterroutine befindet, und dann den Befehl CALL mit dem Aufrufstapel in den Zähnen und drehen Sie, wie Sie können".
Natürlich gab es nur wenige Datentypen, und die Leute begannen darüber nachzudenken, verschiedenen PLs die Möglichkeit zu geben, ihre eigenen Datentypen zu erstellen. Eine Variation dieser Funktion waren die sogenannten Aufnahmen. Nachfolgend finden Sie zwei Beispiele für die Aufzeichnung in einer nicht vorhandenen Programmiersprache (im Folgenden: NEPL - Nicht vorhandene Programmiersprache):
type Name: record consisting of FirstName: String, MiddleName: String, LastName: String. type Point: record consisting of X: Double, Y: Double.
Das heißt, anstatt zwei oder drei verwandte Variablen zu ziehen, gruppieren Sie sie in einer Struktur mit benannten Feldern. Anschließend deklarieren Sie eine Variable vom Typ Name und verweisen beispielsweise auf das Feld Vorname.
Was ist an dieser „erweiterten“ Variablen für unser Thema so wertvoll? Dass es von hier aus nur einen kleinen Schritt zum OOP gibt. Ich habe nicht nur einen fetten Absatz hervorgehoben, um anzuzeigen, dass Codeteile auch in Variablen eingefügt werden können. Sehen Sie, wie Variablen zu Objekten werden:
type Name: class consisting of FirstName: String, MiddleName: String, LastName: String, GetFullName: subprogram with no parameters returns String. type Point: class consisting of X: Double, Y: Double, ScalarMultiply: subprogram with (Double) parameters returns Point.
NB NEPL entwickelt sich aktiv und hat das Schlüsselwort record bereits durch class ersetzt.
Das heißt, wir können auf das Feld "GetFullName" zugreifen und es aufrufen . Eine Variable enthält nicht nur Daten, die ihren Status beschreiben, sondern auch das Verhalten. Somit wird die Variable zu einem Objekt, das einige Fähigkeiten und Zustände besitzt. Und wir arbeiten bereits nicht nur mit Variablen, sondern auch mit kleinen Systemen, denen Befehle erteilt werden können.
In meiner Jugend hat mich diese Idee fasziniert. Denken Sie nur, Sie können jede Art von Daten erstellen . Und Sie arbeiten nicht mit einigen Zahlen, sondern mit den Objekten der Welt, die Sie erschaffen. Keine Qual mit langweiligen Arrays oder komplizierten Zahlen. Wir arbeiten direkt mit Objekten vom Typ Spieler, Feind, Kugel, Boss! Ja, in meiner Jugend wollte ich Videospiele machen.
In Wirklichkeit stellte sich heraus, dass alles nicht so einfach war. Und ohne einige „verstärkende“ Ideen wird OOP das Leben eines Programmierers in die Hölle verwandeln. Aber bevor wir fortfahren, geben wir noch ein paar Begriffe:
- Die Datentypen, die durch ihr Verhalten in OOP „verstärkt“ werden, werden als Klassen bezeichnet .
- Variablen dieser Typen werden als Objekte bezeichnet .
- Die Routinen, die das Verhalten von Objekten definieren, werden als Methoden bezeichnet . In der Regel hat jede Klasse ihre eigenen Methoden und nicht jedes Objekt. Damit sich jedes Objekt einer bestimmten Klasse wie andere Objekte derselben Klasse verhält. Ich werde mich freuen, aus den Kommentaren über Sprachen zu erfahren, in denen die Dinge anders sind.
Heilige Dreifaltigkeit
Es ist historisch so passiert, dass sie bei Interviews nach diesen Dingen fragen. Sie werden in jedem OOP-Lehrbuch beschrieben. Warum? Denn wenn Sie ein OOP-Programm ohne Rücksicht auf Kapselung und Polymorphismus entwerfen, erhalten Sie einen "Sarg, Sarg, Friedhof, unbegleitet". Vererbung ist nicht so unbedingt erforderlich, aber dieses Konzept ermöglicht es Ihnen, OOP besser zu verstehen und ist eines der Hauptwerkzeuge beim Entwerfen mit OOP.
Kapselung
Beginnen wir mit der Definition aus Wikipedia: "Packen von Daten und Funktionen in eine einzige Komponente." Die Definition scheint klar, aber gleichzeitig zu verallgemeinert. Lassen Sie uns daher darüber sprechen, warum dies überhaupt notwendig ist, was es uns geben wird und wie Daten und Funktionen genau in eine einzelne Komponente gepackt werden.
Ich habe bereits einen Artikel geschrieben, der sich mit der Kapselung befasste. Und da wurde mir zu Recht vorgeworfen, ich habe die Kapselung auf das Verbergen von Informationen reduziert, und das sind etwas andere Dinge. Insbesondere hat EngineerSpock eine so elegante Formulierung wie den unveränderlichen Schutz hergestellt . Ich gebe meinen Fehler zu und erkläre dann, warum ich ihn gemacht habe.
In der Zwischenzeit meine vorläufige Definition des Einkapselungsprinzips oder, wenn Sie möchten, des Einkapselungsprozesses, der nicht nur das Einkapselungsprinzip beschreibt, sondern auch, was damit erreicht werden soll:
Jede Software-Entität mit einem nicht trivialen Status muss in ein geschlossenes System umgewandelt werden, das nur von einem korrekten Status in einen anderen übertragen werden kann.
Über den Teil, in dem "jede Software-Entität, die einen nicht trivialen Zustand hat", etwas später. Im Moment werden wir ausschließlich über Objekte sprechen. Und zum zweiten Teil meiner Definition. Warum brauchen wir das?
Hier ist alles einfach: Was nur von einem richtigen Zustand in einen anderen übertragen werden kann, kann nicht gebrochen werden. Das heißt, wir müssen sicherstellen, dass kein Objekt beschädigt werden kann. Es klingt, gelinde gesagt, ehrgeizig. Wie erreicht man das?
In der Null muss alles, was sich auf das Objekt bezieht, an einer Stelle liegen, innerhalb derselben architektonischen Grenze, sagen wir. Falls es sich als sehr abstrus herausstellte, wiederhole ich die Definition aus Wikipedia: "Daten und Funktionen in eine einzige Komponente packen".
Erstens, um die Schnittstelle und ihre Implementierung klar zu trennen. Ich denke, alle meine Kollegen sind mit der Abkürzungs- API vertraut. Jedes Objekt sollte also eine eigene API oder PI haben, wenn wir akribisch sein wollen. Das, wofür sie es erschaffen und was andere verwenden werden, was sie verursachen werden. Wie soll es sein? Damit niemand daran denken würde, in ein Objekt zu gehen und es unangemessen zu benutzen. Aber nicht mehr als das.
In einem Buch erinnere ich mich leider nicht an welches, dies wurde am Beispiel einer Mikrowelle erklärt. Es gibt Knöpfe darauf. Stifte Sie ermöglichen es Ihnen, Essen aufzuwärmen. Sie müssen die Mikrowelle nicht abwickeln und dort etwas löten, um die gestrige Suppe aufzuwärmen. Sie haben eine Schnittstelle, Schaltflächen. Legen Sie einen Teller, drücken Sie ein paar Knöpfe, warten Sie eine Minute und seien Sie glücklich.
Überlegen Sie sich einfach, welche Tasten der Benutzer Ihres Objekts drücken muss, und trennen Sie sie von den internen Innereien. Und auf keinen Fall zusätzliche Buttons hinzufügen! Das war der erste.
Zweitens respektieren Sie die Grenze zwischen der Schnittstelle und der Implementierung und lassen Sie andere sie respektieren. Im Prinzip ist diese Idee intuitiv und schwebt in vielen Formen unter der Volksweisheit. Nehmen Sie zumindest "wenn Sie etwas undokumentiertes ausgenutzt haben und dann etwas für Sie kaputt gegangen ist, sind Sie schuld." Ich denke, mit "Drehen Sie die Mikrowelle nicht, bis sie so funktioniert, wie Sie es brauchen" ist alles klar. Nun darüber, wie man andere dazu bringt, die berüchtigte Grenze zu respektieren.
Hier kommt die bloße Verschleierung von Informationen zur Rettung. Ja, Sie können jederzeit zustimmen, fragen, Codekonventionen einrichten und auf eine Codeüberprüfung hinweisen, dass dies nicht möglich ist. Die Möglichkeit, über diese Grenze hinauszuklettern, bleibt jedoch bestehen. Dies ist die Verschleierung von Informationen, die zur Rettung kommen.
Wir können die berüchtigte Grenze nicht überschreiten, wenn unser Code nichts über seine Existenz und die dahinter stehenden Dinge erfahren kann. Und selbst wenn er es herausfindet, wird der Compiler so tun, als gäbe es kein solches Feld oder keine solche Methode, und selbst wenn es so ist, ist es nicht notwendig, es zu berühren. Ich weigere mich zu kompilieren, und im Allgemeinen, wer Sie sind, haben wir Sie nicht genannt, verwenden Sie den Schnittstellenteil.

Hier kommen alle möglichen öffentlichen, privaten und anderen Zugriffsmodifikatoren aus Ihrer Lieblingssprache ins Spiel. Das "Verstecken von Informationen" ist der zuverlässigste Weg, um die Vorteile der Einkapselung im Abfluss zu halten. Auf jeden Fall macht es keinen Sinn, alles, was eine Klasse betrifft, an einem Ort zu gruppieren, wenn der Code verwendet, was er will und wo er will. Mit der Verschleierung von Informationen sollte eine solche Situation jedoch grundsätzlich nicht mehr entstehen. Und diese Methode ist so zuverlässig, dass in den Köpfen von Tausenden und Abertausenden von Programmierern (einschließlich mir, die bereits da sind) der Unterschied zwischen Kapselung und Verstecken von Informationen irgendwie ausgeglichen wird.
Was ist, wenn Ihr Lieblings-YP es Ihnen nicht erlaubt, Informationen zu verbergen? Zu diesem Thema können Sie Spaß daran haben, über Kommentare zu sprechen. Ich sehe den nächsten Ausweg. Aufsteigend:
- Dokumentieren Sie nur den Schnittstellenteil und betrachten Sie alles, was nicht dokumentiert ist, als Implementierung.
- Trennen Sie die Implementierung über eine Code-Konvention von der Schnittstelle (Beispiel: In Python gibt es die Variable __all__, die angibt, was genau aus dem Modul importiert wird, wenn Sie aufgefordert werden, alles zu importieren).
- Machen Sie genau diese Code-Konventionen streng genug, damit sie automatisch überprüft werden können. Danach wird jede Verletzung mit einem Kompilierungsfehler und einem fehlgeschlagenen Build gleichgesetzt.
Noch einmal:
- Alles, was mit einer Klasse zu tun hat, ist in einem Modul zusammengefasst.
- Zwischen den Klassen werden strenge architektonische Grenzen gezogen.
- In jeder Klasse ist der Schnittstellenteil von der Implementierung dieses Schnittstellenteils selbst getrennt.
- Die Grenzen zwischen den Klassen müssen respektiert und gezwungen werden, andere zu respektieren!
Ich werde mit einem Beispiel zu NEPL enden, das sich noch sehr aktiv entwickelt und nach zehn Absätzen Zugriffsmodifikatoren erhalten hat:
type Microwave: class consisting of private fancyInnerChips: List of Chip, private foodWarmingThing: FoodWarmerController, private buttonsPanel: ButtonsPanel, public GetAccessToControlPanel: subprogram with no parameters returns ButtonsPanel, public OpenDoor: subprogram with no parameters returns nothing, public Put: subprogram with (Food) parameters return nothing, public CloseDoor: subprogram with no parameters returns nothing. type ButtonsPanel: class consisting of private buttons: List of ButtonState, public PressOn: subprogram with no parameters returns nothing, public PressOff: subprogram with no parameters returns nothing, public PressIncreaseTime: subprogram with no parameters returns nothing, public PressDecreaseTime: subprogram with no parameters returns nothing, public PressStart: subprogram with no parameters returns nothing, public PressStop: subprogram with no parameters returns nothing.
Ich hoffe, aus dem Code geht hervor, was das Wesentliche des Beispiels ist. Ich werde nur einen Punkt klarstellen: GetAccessToControlPanel prüft, ob wir überhaupt die Mikrowelle berühren können. Was ist, wenn sie kaputt ist? Dann können Sie auf nichts klicken. Sie können nur eine Fehlermeldung erhalten.
Die Tatsache, dass ButtonsPanel reibungslos zu einer separaten Klasse geworden ist, bringt uns zu einer wichtigen Frage: Was ist die „Einzelkomponente“ aus der Definition der Kapselung in Wikipedia? Wo und wie sollen die Grenzen zwischen den Klassen liegen? Wir werden auf jeden Fall etwas später auf dieses Thema zurückkommen.
SpoilerPrinzip der Einzelverantwortung
Verwendung außerhalb von OOP
So viele Programmierer haben in einem Java / C ++ / C # -Tutorial / Ersetzen Ihrer ersten OOP-Sprache etwas über die Kapselung gelernt. Daher ist die Einkapselung im Massenbewusstsein irgendwie mit OOP verbunden. Kehren wir jedoch zu den beiden Definitionen der Kapselung zurück.
Daten und Funktionen in eine einzige Komponente packen.
Jede Software-Entität mit einem nicht trivialen Status muss in ein geschlossenes System umgewandelt werden, das nur von einem korrekten Status in einen anderen übertragen werden kann.
Hast du es bemerkt? An Klassen und Objekten ist überhaupt nichts!
Also ein Beispiel. Du bist ein DBA . Ihre Aufgabe ist es, eine Art relationale Datenbank im Auge zu behalten. Lassen Sie es zum Beispiel auf MySQL sein. Ihre wertvolle Datenbank verwendet mehrere Programme. Sie haben keine Kontrolle über einige von ihnen. Was zu tun ist?
Erstellen Sie eine Reihe gespeicherter Prozeduren. Wir setzen sie zu einer Schaltung zusammen, die wir als Schnittstelle bezeichnen werden. Wir erstellen einen Benutzer für unsere Programme ohne Rechte. Dies ist der Befehl CREATE USER. Mit dem Befehl GRANT erhalten Benutzer nur das Recht, diese gespeicherten Prozeduren über das Schnittstellenschema auszuführen.
Das ist alles. Wir haben eine Datenbank, genau die Software-Entität mit einem nicht trivialen Zustand, der leicht zu brechen ist. Damit wir es nicht kaputt machen, erstellen wir eine Schnittstelle aus gespeicherten Prozeduren. Und nach den Mitteln von MySQL selbst machen wir es so, dass Entitäten von Drittanbietern nur diese Schnittstelle verwenden können.
Beachten Sie, dass die berüchtigte Kapselung, wie sie ist und wie sie beschrieben wurde, ihr volles Potenzial entfaltet. Zwischen der relationalen Darstellung von Daten und Objekten besteht jedoch eine solche Lücke, dass sie mit umfangreichen ORM- Frameworks geschlossen werden muss.
Aus diesem Grund werden Klassen und Objekte in der Definition der Kapselung nicht angezeigt. Die Idee ist viel breiter als OOP. Und es bringt zu viel Nutzen, nur in Lehrbüchern über OOP-Sprachen darin zu sprechen.
Polymorphismus
Polymorphismus hat viele Formen und Definitionen. Es reicht aus, dass Kondratius mir genügt, wenn ich Wikipedia öffne. Hier werde ich über Polymorphismus sprechen, wie Straustrup ihn formuliert hat: eine Schnittstelle - viele Implementierungen .
In dieser Form kann die Idee des Polymorphismus die Position von Schriftstellern mit einem Auge für die Einkapselung erheblich stärken. Wenn wir die Schnittstelle von der Implementierung getrennt haben, muss derjenige, der die Schnittstelle verwendet, nicht wissen, dass sich in der Implementierung etwas geändert hat. Wer die Schnittstelle (idealerweise) nutzt, muss gar nicht wissen, dass sich die Implementierung überhaupt geändert hat! Und das eröffnet endlose Möglichkeiten zur Erweiterung und Modifikation. Hat Ihr Vorgänger entschieden, dass es am besten ist, das Essen mit einem Militärradar zu erwärmen? Wenn dieses verrückte Genie die Schnittstelle von der Implementierung trennt und sie klar formalisiert, kann das Militärradar an andere Bedürfnisse angepasst und seine Schnittstelle zum Erhitzen von Lebensmitteln mithilfe einer Mikrowelle realisiert werden.
NEPL entwickelt sich schnell und erfasst unter dem Einfluss von C # den Datentyp der Schnittstelle (achten Sie darauf, dass Sie nicht über den Wortlaut stolpern).
type FoodWarmer: interface consisting of GetAccessToControlPanel: no parameters returns FoodWarmerControlPanel, OpenDoor: no parameters returns nothing, Put: have (Food) parameters returns nothing, CloseDoor: no parameters returns nothing. type FoodWarmerControlPanel: interface consisting of PressOn: no parameters returns nothing, PressOff: no parameters returns nothing, PressIncreaseTime: no parameters returns nothing, PressDecreaseTime: no parameters returns nothing, PressStart: no parameters returns nothing, PressStop: no parameters returns nothing. type EnemyFinder: interface consisting of FindEnemies: no parameters returns List of Enemy. type Radar: class implementing FoodWarmer, EnemyFinder and consisting of private secretMilitaryChips: List of Chip, private giantMicrowavesGenerator: FoodWarmerController, private strangeControlPanel: AlarmClock, public GetAccessToControlPanel: subprogram with no parameters returns FoodWarmerControlPanel, public OpenDoor: subprogram with no parameters returns nothing, public Put: subprogram with (Food) parameters return nothing, public CloseDoor: subprogram with no parameters returns nothing, public FindEnemies: subprogram with no parameters returns List of Enemy. type AlarmClock: class implementing FoodWarmerControlPanel and consisting of private mechanics: List of MechanicPart, public PressOn: subprogram with no parameters returns nothing, public PressOff: subprogram with no parameters returns nothing, public PressIncreaseTime: subprogram with no parameters returns nothing, public PressDecreaseTime: subprogram with no parameters returns nothing, public PressStart: subprogram with no parameters returns nothing, public PressStop: subprogram with no parameters returns nothing. type Microwave: class implementing FoodWarmer and consisting of private fancyInnerChips: List of Chip, private foodWarmingThing: FoodWarmerController, private buttonsPanel: ButtonsPanel, public GetAccessToControlPanel: subprogram with no parameters returns FoodWarmerControlPanel, public OpenDoor: subprogram with no parameters returns nothing, public Put: subprogram with (Food) parameters return nothing, public CloseDoor: subprogram with no parameters returns nothing. type ButtonsPanel: class implementing FoodWarmerControlPanel and consisting of private buttons: List of ButtonState, public PressOn: subprogram with no parameters returns nothing, public PressOff: subprogram with no parameters returns nothing, public PressIncreaseTime: subprogram with no parameters returns nothing, public PressDecreaseTime: subprogram with no parameters returns nothing, public PressStart: subprogram with no parameters returns nothing, public PressStop: subprogram with no parameters returns nothing.
Wenn eine Klasse als Implementierung einer Schnittstelle deklariert ist, muss sie alle Methoden dieser Schnittstelle implementieren. Andernfalls teilt uns der Compiler "fi" mit. Und wir haben zwei Schnittstellen: FoodWarmer und FoodWarmerControlPanel. Schauen Sie sie sich genau an und analysieren Sie dann die Implementierungen.
Als Erbe der schwierigen sowjetischen Vergangenheit erhielten wir das Radar der Dual-Use-Klasse, mit dem Lebensmittel erhitzt und der Feind gefunden werden kann. Anstelle des Bedienfelds wird ein Alarm verwendet, da der Plan überschritten wird und sie irgendwo platziert werden müssen. Glücklicherweise implementierte das unbenannte MNS des Forschungsinstituts für Chemie, Düngemittel und Gifte, auf das sie schoben, die FoodWarmer-Schnittstellen für das Radar und das FoodWarmerControlPanel für den Wecker.
Nach einer Generation kam jemandem der Gedanke, dass es besser ist, das Essen mit einer Mikrowelle zu erhitzen, und es ist besser, die Mikrowelle mit Knöpfen zu steuern. Und jetzt die erstellten Klassen Microwave und ButtonsPanel. Und sie implementieren die gleichen Schnittstellen. FoodWarmer und FoodWarmerControl. Was gibt uns das?
Wenn wir überall in unserem Code eine Variable wie FoodWarmer zum Erhitzen von Lebensmitteln verwendet haben, können wir die Implementierung einfach durch eine modernere ersetzen, und niemand wird etwas bemerken. Das heißt, Code, der die Schnittstelle verwendet, kümmert sich nicht um Implementierungsdetails. Oder auf die Tatsache, dass es sich komplett geändert hat. Wir können sogar die FoodWarmerFactory-Klasse erstellen, die je nach Konfiguration Ihrer Anwendung unterschiedliche FoodWarmer-Implementierungen erzeugt.
Schauen Sie sich auch die geschlossenen Felder in der Klasse Mikrowelle und Radar an. Dort haben wir einen Wecker und eine Tafel mit Knöpfen. Aber draußen geben wir eine Variable vom Typ FoodWarmerControlPanel.
Irgendwo auf Picabu gab es eine Geschichte darüber, wie ein bestimmter Kandidat das Prinzip des Polymorphismus wie folgt erklärte:
Hier habe ich einen Stift. Ich kann ihr meinen Namen schreiben, aber ich kann ihn dir ins Auge stechen. Dies ist das Prinzip des Polymorphismus.
Das Bild ist lustig, die Situation ist schrecklich und die Erklärung des Prinzips des Polymorphismus ist nutzlos.
Das Prinzip des Polymorphismus ist nicht, dass eine Stiftklasse mit etwas Schreck gleichzeitig die Schnittstellen eines Briefpapiers und eines kalten Stahls erkennt. Das Prinzip des Polymorphismus ist, dass alles, was gestochen werden kann, im Auge stecken bleiben kann. Weil es gestochen werden kann. Und das Ergebnis wird anders sein, sollte aber im Idealfall visuelle Entbehrungen hervorrufen. Und die Methode des Polymorphismus ermöglicht es Ihnen, diese Tatsache in dem Modell widerzuspiegeln, das Sie für Ihre Welt erstellen.
Verwendung außerhalb von OOP
Es gibt im wahrsten Sinne des Wortes eine so lustige und lustige Sprache wie Erlang. Und es hat so etwas wie Verhalten. Pass auf deine Hände auf:
Der Code dort ist in Module unterteilt. Sie können den Modulnamen als Variable verwenden. Das heißt, Sie können einen Funktionsaufruf von einem Modul wie folgt schreiben:
Um sicherzugehen, dass das Modul bestimmte Funktionen hat, gibt es ein Merkmal der Sprache wie Verhalten. In einem Modul, das andere Module verwendet, definieren Sie die Anforderungen für variable Module mithilfe der Behaviour_info-Deklaration. Und dann teilen die Module, die Ihr Dad-Modul, das behaviour_info festgelegt hat, mithilfe der Verhaltensdeklaration verwendet, dem Compiler mit: "Wir verpflichten uns, dieses Verhalten zu implementieren, damit das Dad-Modul uns verwenden kann."
Mit dem Modul gen_server können Sie beispielsweise einen Server erstellen, der synchron oder asynchron in einem separaten Prozess (in Erlang gibt es keine Threads, nur Tausende kleiner paralleler Prozesse) Anforderungen von anderen Prozessen ausführt. Und in gen_server wird die gesamte Logik gesammelt, die sich auf Anforderungen von anderen Prozessen bezieht. Die direkte Verarbeitung dieser Anforderungen erfolgt jedoch durch diejenigen, die das Verhalten von gen_server implementieren. Und während andere Module es korrekt implementieren (selbst wenn leere Stubs vorhanden sind), ist es gen_server nicht einmal wichtig, wie diese Anforderungen behandelt werden. Darüber hinaus kann das Verarbeitungsmodul im laufenden Betrieb geändert werden.
Eine Schnittstelle - viele Implementierungen. Wie Straustrup uns vermacht hat. Wie in intelligenten Büchern über OOP geschrieben. Und jetzt ein Zitat aus Wikipedia ins Studio:
Erlang — , .
« — » , , .
. .NET. . CLR . CLR Microsoft , , , ( ECMA-335).
.NET , Windows, Windows Phone, XBox ( XNA, , ), . Microsoft. , , . , Mono Project. .NET. .
, . Microsoft , .NET . . , , .NET Core. , .NET Core .NET Framework, , , . , .
, « — » - . , .
, . , , , . , .
, , . , , NEPL. . Name? , . EtiquetteInfo - .
import class EtiquetteInfo from Diplomacy. type PoliteName: class consisting of private FirstName: String, private MiddleName: String, private LastName: String, for descendants GetPoliteFirstName: subprogram with (EtiquetteInfo) parameters returns String, for descendants GetPoliteMiddleName: subprogram with (EtiquetteInfo) parameters returns String, for descendants GetPoliteLastName: subprogram with (EtiquetteInfo) parameters returns String, public GetFullName: subprogram with (EtiquetteInfo) parameters returns String. subprogram GetPoliteFirstName.PoliteName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as return _EtiquetteInfo.PoliteFirstName(FirstName). subprogram GetPoliteMiddleName.PoliteName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as return _EtiquetteInfo.PoliteMiddleName(MiddleName). subprogram GetPoliteLastName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as return _EtiquetteInfo.PoliteLastName(LastName). subprogram GetFullName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as return GetPoliteFirstName(_EtiquetteInfo) + GetPoliteMiddleName(_EtiquetteInfo) + GetPoliteLastName(_EtiquetteInfo).
, GetFullName - , ( , , ?). , , - . , , . , , , , . , . PoliteName . ExoticPoliteName — . , , .
- . ExoticPoliteName, PoliteName, . PoliteExoticName. , PoliteName.
import class EtiquetteInfo from Diplomacy. type PoliteExoticName: class extending PoliteName and consisting of private MoreMiddleNames: List of String, for descendants overridden GetPoliteMiddleName: subprogram with (EtiquetteInfo) parameters returns String, public overriden GetFullName: subprogram with (EtiquetteInfo) parameters returns String. subprogram GetPoliteMiddleName.PoliteExoticName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as String AggregatedMiddleName = String.Join(" ", MoreMiddleNames), return base.GetPoliteMiddleName(_EtiquetteInfo + AggregatedMiddleName). subprogram GetFullName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as String Prefix = "", String FirstName = GetFirstName(_EtiquetteInfo), if _EtiquetteInfo.ComplimentIsAppropriate(FirstName) then Prefix = "Oh, joy of my heart, dear ", return Prefix + base.GetFullName(_EtiquetteInfo).
: PoliteName . PoliteExoticName -.
, , . , GetPoliteFirstName GetPoliteLastName. . GetFullName, , .
, , PoliteName, PoliteExoticName, GetFullName. , PoliteName, , . , , base.GetFullName(etiquetteInfo). , , .
, " ". , . : , . . .
, . . , Boolean, , . , Object. . , , , , Object, .
, NEPL . PoliteName Object, PoliteExoticName PoliteName Object . , NEPL :
subprogram Foo.Bar with no parameters returning nothing implemented as PoliteExoticName _PoliteExoticName = GetSomePoliteExoticName(), PoliteName _PoliteName = _PoliteExoticName, Object _Object = _PoliteExoticName.
, , _Object.GetFullName, , . PoliteName PoliteExoticName - Object, - _Object, .
? , . . , ( Object) , - -.
, , , , . ? , . - , . . - , .
Ist es so? , . . . , «» , . ?
. , NEPL for descendants.
type PoliteName: class consisting of private FirstName: String, private MiddleName: String, private LastName: String, for descendants GetPoliteFirstName: subprogram with (EtiquetteInfo) parameters returns String, for descendants GetPoliteMiddleName: subprogram with (EtiquetteInfo) parameters returns String, for descendants GetPoliteLastName: subprogram with (EtiquetteInfo) parameters returns String, public GetFullName: subprogram with (EtiquetteInfo) parameters returns String.
PoliteExoticName FirstName, «, , , , ». GetPoliteFirstName FirstName.

, , Square Shape, Shape Square . , . Shape , , . Square, Shape. Warum? , Shape, .
. , ? -, . -, , , , . , , .
, . , . « »? , . . , .
. , . , , . . , . , , .
, , . . ( , ), , . , , . , , . .
, ) , ) , , , 999 1000 . , , .
()
, . - , . , , . - , .
SOLID
— , , . - . SOLID — , , … ? ? , , .
S — The Single Responsibility Principle
. .
.
. . .
, « » - . . « ?». , , ? .
, SRP :
.
, ? — , . ? . « ». , . ? .
, ? . , .txt-. , , , .txt-. - , . , , … .txt-. Warum? . , , .txt-.
NB ( , ) «», « , ».

. , , , .txt-. . , . , . Aber! -, , , . -, , , .

, , . , ) , ) .html-. , , .txt/.html.
, , , . , , .txt-. Was könnte schief gehen?
- . , . , , . , , , , , , . .
- , . 900 USD 900.00$? 20190826T130000 2019 ? , ?
- .txt-? .csv? , .txt. ? ? - ? - ? , ?
? — . , -.
, , , . , .
DRY — Don't Repeat Yourself
:
, . .
, , , - , . , , .
SRP . , , . , « ». , , , — .
. HTML . , «» . , HTML 90- . , . - , . , HTML- . CSS .
? -, CSS , «» . , . -, CSS- html- , - text-color . — . .
O — The Open/Closed Principle
, , , - . , «» «». / , , . « » . Überzeugen Sie sich selbst:
.
. , . , . , — .
, . . ? . , .
- , . , . , / .
- , . , . / /, . NB /. , : .
- , . , .
- , - , , . (), () «» .
- . , , ? , . , . , .
, . .
Schlecht
type SpellChecker: class consisting of public DoSpellCheck: subprogram with (String) parameters returns String. type CorporativeStyleChecker: class consisting of public DoCorporativeStyleCheck: subprogram with (String) parameters returns String. type TextProcessor: class consisting of private Text: String, private SpellChecker: SpellChecker, private CorporativeStyleChecker: CorporativeStyleChecker, public Process: subprogram with no parameters returns String. subprogram TextProcessor.Process with no parameters returning String implemented as String ProcessedText = Text, ProcessedText = SpellChecker.DoSpellCheck(ProcessedText), ProcessedText = CorporativeStyleChecker.DoCorporativeStyleCheck(ProcessedText), return ProcessedText.
,
type TextChecker: interface consisting of Check: have (String) parameters returns String. type SpellChecker: class implementing TextChecker and consisting of public Check: subprogram with (String) parameters returns String. type CorporativeStyleChecker: class implementing TextChecker and consisting of public Check: subprogram with (String) parameters returns String. type TextProcessor: class consisting of private Text: String, private SpellChecker: SpellChecker, private CorporativeStyleChecker: CorporativeStyleChecker, public Process: subprogram with no parameters returns String. subprogram TextProcessor.Process with no parameters returning String implemented as String ProcessedText = Text, List of SpellChecker Checkers = (SpellChecker, CorporativeStyleChecker), for each SpellChecker SpellChecker in Checkers do ProcessedText = SpellChecker.Check(ProcessedText) and nothing else, return ProcessedText.
O/CP . TextCheckersSupplier, .
type TextChecker: interface consisting of Check: have (String) parameters returns String. type SpellChecker: class implementing TextChecker and consisting of public Check: subprogram with (String) parameters returns String. type CorporativeStyleChecker: class implementing TextChecker and consisting of public Check: subprogram with (String) parameters returns String. type TextCheckersSupplier: class consisting of public GetCheckers: subprogram with no parameters returns List of TextChecker. type TextProcessor: class consisting of private Text: String, private CheckersSupplier: TextCheckersSupplier, public Process: subprogram with no parameters returns String. subprogram TextProcessor.Process with no parameters returning String implemented as String ProcessedText = Text, List of SpellChecker Checkers = CheckersSupplier.GetCheckers(), for each SpellChecker SpellChecker in Checkers do ProcessedText = SpellChecker.Check(ProcessedText) and nothing else, return ProcessedText.
? , , , . , TextProcessor. , TextCheckerSupplier , , . TextChecker' . , , , . , .
, , , , . .
L — The Liskov Substitute Principle
, :
, , , .
, , . ?
- NEPL:
type PoliteExoticName: class extending PoliteName and consisting of... subprogram Foo.Bar with no parameters returning nothing implemented as PoliteExoticName _PoliteExoticName = GetSomePoliteExoticName(), PoliteName _PoliteName = _PoliteExoticName, Object _Object = _PoliteExoticName.
, _PoliteName - . , , . , . PoliteName, . , , , PoliteName . , , , . .
, -, allex ( , ). , , :
-, .
, «Agile Principles, Patterns and Practices in C#». NEPL, .
Object _Object = GetObjectSomewhere(), PoliteExoticName IHopeItsActuallyName = _Object as PoliteExoticName,
, , . . , . - , . , . . ( , alias , ):
from UnboundedCollections import UnboundedSet as ThirdPartyUnboundedSet. from BoundedCollections import BoundedSet as ThirdPartBoundedSet. type Set: interface consisting of Add: have (Object) parameters returns nothing, Delete: have (Object) parameters returns nothing, IsMember: have (Object) parameters returns Boolean. type UnboundedSet: class implementing Set and consisting of private ThirdPartySet: ThirdPartyUnboundedSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. type BoundedSet: class implementing Set and consisting of private ThirdPartySet: ThirdPartyBoundedSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. subprogram BoundedSet.Add with (Object O) parameters returning nothing implemented as ThirdPartSet.Add(O).

. . . PersistentSet, - . , PersistentObject. - . Delete IsMember . Add...
from PersistentCollections import PersistentSet as ThirdPartyPersistentSet, PersistentObject. type PersistentSet: class implementing Set and consisting of private ThirdPartySet: ThirdPartyPersistentSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. subprogram PersistentSet.Add with (Object O) parameters returning nothing implemented as PersistentObject Po = O as PersistentObject, ThirdPartySet.Add(Po).

. PersistentSet Object, . , , Set , . ( ):
type MemberContainer: interface consisting of Delete: have (Object) parameters returns nothing, IsMember: have (Object) parameters returns Boolean. type Set: interface extending MemberContainer and consisting of Add: have (Object) parameters returns nothing. type PersistentSet: interface extending MemberContainer and consisting of Add: have (PersistingObject) parameters returns nothing.

C#.
, NEPLNEPL. List of String. , .
type List: class generalized with (T) parameters consisting of
Set PersistentSet.
from UnboundedCollections import UnboundedSet as ThirdPartyUnboundedSet. from BoundedCollections import BoundedSet as ThirdPartBoundedSet. from PersistentCollections import PersistentSet as ThirdPartyPersistentSet, PersistentObject. type Set: interface generalized with (T) parameters consisting of Add: have (T) parameters returns nothing, Delete: have (T) parameters returns nothing, IsMember: have (T) parameters returns Boolean. type UnboundedSet: class implementing Set of Object and consisting of private ThirdPartySet: ThirdPartyUnboundedSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. type BoundedSet: class implementing Set of Object and consisting of private ThirdPartySet: ThirdPartyBoundedSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. type PersistentSet: class implementing Set of PersistentObject and consisting of private ThirdPartySet: ThirdPartyPersistentSet, public Add: subprogram with (PersistentObject) parameters returning nothing, public Delete: subprogram with (PersistentObject) parameters returning nothing, public IsMember: subprogram with (PersistentObject) parameters returning Boolean.
()
. , , «» . , « — » . , . .
I — The Interface Segregation Principle
, . . .
, ? , - . , , SQL? , . , API . API , «interface», , . . Was könnte schief gehen?
, , - , DBA . , 100500 , . . , .
, , , «» . , « interface, ». DBA . GDPR, HIPAA , .
- . , . Usw.
? , . :
, ( ).
? interface «interface_billing», «interface_customer_data» . .
, , . pet-project. IActor. , . , IActor : ICollidable, IDisplayble, IUpdatable. ?

( , Camera), . , - . , . , , IDisplayble SpecEffect.
CollisionsController , - ICollidable. , , , SOLID . TileWall -, . CollisionsController . , , IActor , .

: , , .
D — The Dependency Inversion
-. , . , , . , , . , , , , - . , , - . , , - .
„Hier! .» — - ImportantClass. , , . , ImportantClass VeryImportantClass, , , EvenMoreImportantClass, , . , , , . , . , .

ImportantClass VeryImportantClass EvenMoreImportantClass. ImportantClass . , , . , IVeryImportantClass IEvenMoreImportantClass, ImportantClass.
ImportantClass VeryImportantClass . ImportantClass « », IVeryImportantClass .

. , , .
. .
. .
, «» «» - . , , . . , , . , . , -.
. , .
- , . ( MegaSender), . , , SOAP API.
-. SenderAccess. , MegaSender SenderAccess . SenderAccess MegaSender, , MegaSender, MegaSender , Apple i.
MegaSender. LightSender. , SenderAccess c LightSender. , , . , .
SenderAccess, , MegaSender . , SenderAccess MegaSender. MegaSender « ». , MegaSender, , , . . , LightSender , , LightSender, MegaSender.
, SenderAccess , SenderAccess LightSender . .
, IActor ICollidable, IUpdatable, IDisplayble. , IActor . Actor Player, Enemy, Door, Wall . , .
Blueprint. . , , , , , et cetera. , , , .
, , C#, . , - List<String>. , List<T> List<String>. Actor Actor<TBlueprint>.
, , - . Actor<EnemyBlueprint> Actor<DoorBlueprint> , . , .
- . , , . , . , . , IActor, , ActorsFactory .
. : .
, , . - , . () :
. . . TCP/HTTP/SMPP/SOAP, . ? , TCP/HTTP/SMPP/SOAP- TCP/HTTP/SMPP/SOAP- , . , - . ? Denk darüber nach. , « » « 1000 ».
. ? - SOLID'? , . , - , .
- , .
- , .
. , -, , . . - , , , , -. , .
. - , :
KISS — . . . , , . , , .
, , Actor . , — , - . , .
YAGNI — . - , , « », , - . , . « » - . , , . , , .
, OC/P . 50 . , , -. 50 , . , - .
? , , « », . « » , .
, SOLID . , .
, . , -, , . , .
- «Code Complete» . , « » - .
- «Clean Code» . «Clean Architecture».
- «Agile Principles, Patterns and Practices in C#» . SOLID . , language-agnostic.
PS
, . , , . : ! , , language-agnostic .
, language-agnostic. NEPL, : , , , . , , .
, . , , . :
, , , . , , . , . : , .