Hollywood-Prinzip (IoC)

In diesem Artikel werde ich versuchen, über ein Konstruktionsprinzip mit dem Namen Inversion of Control / IoC zu sprechen, das auch als Hollywood-Prinzip bezeichnet wird. Ich werde zeigen, wie dies mit dem Prinzip zusammenhängt, Barbara Liskovo (LSP) zu ersetzen und zum heiligen Krieg zwischen privat und geschützt beizutragen.



Als Vorwort möchte ich ein paar Worte über mich sagen. Ich bin ausgebildeter Software-Ingenieur, arbeite seit mehr als 10 Jahren in der IT-Branche und schreibe seit kurzem gerne thematische Fachartikel. Einige von ihnen waren erfolgreich. Zuvor habe ich auf einer anderen Ressource leider nicht zugreifbar in Russland veröffentlicht (Gruß an Roskomnadzor). Wenn jemand sie kennenlernen möchte, wissen Sie, was zu tun ist.

Alle Codebeispiele werden, wie üblich, im Artikel mit Pseudocode dargestellt, der als "hated php" stilisiert ist.

Ausgangsaufgabe


Um es schneller und verständlicher zu machen, fahren wir sofort mit dem Beispiel fort. Von der Verkaufsabteilung wollte ich die Metriken sehen: Wie viel Geld verdienen wir monatlich, täglich, stündlich?

Wir lösen dieses Problem mit Hilfe von drei Teams, die regelmäßig nach einem Zeitplan eingesetzt werden:

  • MonthlyReportCommand
  • DailyReportCommand
  • HourlyRerortCommand

Wir brauchen die Schnittstellen:

interface ReportCommandInterface { public function createReport(): Money; } interface MoneyRepositoryInterface { /** @return Money[] */ public function getMoney(Period $period): array; } interface MetricRepositoryInterface { public function saveMoneyMetric(Period $period, Money $amount, string $metricType); } 

Wir schreiben Berichtsteams (das letzte ist weggelassen, als praktische Übung für diejenigen, die gut verstehen und üben wollen, schreiben Sie es selbst):

 class MonthlyReportCommand implements ReportCommandInterface { //lets assume constructor is already here public function createReport(): Money { $period = new Period(new DateTime('first day of previous month'), new DateTime('last day of previous month')); $moneyRecords = $this->moneyRepository->getMoney($period); $amount = $this->calculateTotals($moneyRecords); $this->metricRepository->saveMoneyMetric($period, $amount, 'monthly income'); } /** @param Money[] $moneyRecords */ private function calculateTotals(array $moneyRecords): Money { //here is calculating sum of money records } } class DailyReportCommand implements ReportCommandInterface { //lets assume constructor is already here public function createReport(): Money { $period = new Period(new DateTime('yesterday'), new DateTime('today')); $moneyRecords = $this->moneyRepository->getMoney($period); $amount = $this->calculateTotals($moneyRecords); $this->metricRepository->saveMoneyMetric($period, $amount, 'daily income'); } /** @param Money[] $moneyRecords */ private function calculateTotals(array $moneyRecords): Money { //here calculates sum of money records } } class HourlyReportCommand ... { //the same as previous two but hourly } 

Und wir sehen, dass der Code der berechneTotale () -Methode in allen Fällen exakt gleich ist. Das erste, was mir einfällt, ist, doppelten Code in eine gemeinsame abstrakte Klasse zu setzen. So:



 abstract class AbstractReportCommand { protected function calculateTotals(array $moneyRecords): Money { //here calculates sum of money records } } class MonthlyReportCommand extends AbstractReportCommand implements ReportCommandInterface { public function createReport(): Money { //realization is here, calls calculateTotals($moneyRecords) } } class DailyReportCommand extends AbstractReportCommand implements ReportCommandInterface { //the same as previous two but daily } class HourlyReportCommand ... { //the same as previous two but hourly } 

Die berechneTotale () -Methode ist Teil der internen Mechanismen unserer Klasse. Wir schließen es vorsichtig, weil es sollte nicht von externen Kunden aufgerufen werden - wir entwerfen es nicht dafür. Wir erklären diese Methode für geschützt, weil Wir haben vor, ihn bei den Erben anzurufen - das ist unser Ziel. Offensichtlich ist eine solche abstrakte Klasse so etwas wie einer Bibliothek sehr ähnlich - sie bietet nur einige Methoden (für PHP-Experten: d. H. Sie funktioniert wie Trait).

Das Geheimnis der abstrakten Klassen


Es ist Zeit, eine Pause vom Beispiel zu machen und sich an den Zweck abstrakter Klassen zu erinnern:

Eine abstrakte Klasse kapselt allgemeine Mechanismen und ermöglicht gleichzeitig den Erben, ihr eigenes Verhalten zu implementieren.

Abstraktion (lat. Abstractio - Distraction) ist eine Ablenkung von Details und Generalisierung. Momentan verallgemeinert die AbstractReportCommand-Klasse nur die Geldzählung für alle Berichte. Aber wir können unsere Abstraktion effizienter gestalten, indem wir das Hollywood-Prinzip anwenden, das so klingt:

"Rufen Sie uns nicht an, wir rufen Sie selbst an"

Um zu sehen, wie dies funktioniert, fügen wir in AbstractReportCommand einen allgemeinen Berichtsmechanismus ein:



 abstract class AbstractReportCommand implements ReportCommandInterface { /** @var MoneyRepositoryInterface */ private $moneyRepository; /** @var MetricRepositoryInterface */ private $metricRepository; //lets assume constructor is already here public function createReport(): Money { $period = $this->getPeriod(); $metricType = $this->getMetricType(); $moneyRecords = $this->moneyRepository->getMoney($period); $amount = $this->calculateTotals($moneyRecords); $this->metricRepository->saveMoneyMetric($period, $amount, $metricType); } abstract protected function getPeriod(): Period; abstract protected function getMetricType(): string; private function calculateTotals(array $moneyRecords): Money { //here calculates sum of money records } } class MonthlyReportCommand extends AbstractReportCommand { protected function getPeriod(): Period { return new Period(new DateTime('first day of previous month'), new DateTime('last day of previous month')); } protected function getMetricType(): string { return 'monthly income'; } } class DailyReportCommand extends AbstractReportCommand { protected function getPeriod(): Period { return new Period(new DateTime('yesterday'), new DateTime('today')); } protected function getMetricType(): string { return 'daily income'; } } class HourlyReportCommand ... { //the same as previous two but hourly } 

Was haben wir gemacht Keiner der Nachkommen einer abstrakten Klasse gilt für allgemeine Mechanismen (nennen Sie uns nicht). Stattdessen gibt die Abstraktion ihren Erben ein allgemeines Funktionsschema und verlangt, dass sie bestimmte Verhaltensmerkmale implementieren und nur die Ergebnisse verwenden (wir werden Sie herausfordern).

Aber was ist mit dem versprochenen IoC, LSP, privat gegen geschützt?


Was hat Inversion of Control damit zu tun? Woher kommt dieser Name? Ganz einfach: Zuerst legen wir die Reihenfolge der Aufrufe direkt in den endgültigen Implementierungen fest und steuern, was wann ausgeführt wird. Und später haben wir diese Logik auf eine allgemeine Abstraktion übertragen. Jetzt steuert die Abstraktion, was und wann aufgerufen wird, und Implementierungen befolgen dies einfach. Das heißt, wir haben die Steuerung umgekehrt.

Um dieses Problem zu beheben und Probleme mit dem Barbara Liskov-Substitutionsprinzip (LSP) zu vermeiden, können Sie die createReport () -Methode schließen, indem Sie final in die Methodendeklaration aufnehmen. Schließlich weiß jeder, dass LSP in direktem Zusammenhang mit der Vererbung steht.

 abstract class AbstractReportCommand implements ReportCommandInterface { final public function createReport(): Money { //bla-bla realization } ... } 

Dann werden alle Nachkommen der AbstractReportCommand-Klasse starr einer einzelnen Logik untergeordnet, die nicht neu definiert werden kann. Eiserne Disziplin, Ordnung, glänzende Zukunft.

Aus dem gleichen Grund wird der Vorteil von privat über geschützt deutlich. Alles, was sich auf allgemeine Funktionsmechanismen bezieht, sollte in einer abstrakten Klasse zusammengefasst und nicht für eine Neudefinition zugänglich sein - privat. Alles, was in besonderen Fällen neu definiert / implementiert werden muss, ist abstrakt geschützt. Alle Methoden sind für bestimmte Zwecke konzipiert. Wenn Sie nicht wissen, welchen Bereich Sie für eine Methode festlegen sollen, wissen Sie nicht, warum Sie sie erstellen. Dieser Entwurf ist eine Überarbeitung wert.

Schlussfolgerungen


Die Konstruktion von abstrakten Klassen ist bei der Verwendung von Inversion of Control immer vorzuziehen, da ermöglicht es Ihnen, die Idee der Abstraktion in vollen Zügen zu nutzen. Aber auch die Verwendung abstrakter Klassen als Bibliotheken kann in manchen Fällen gerechtfertigt sein.

Wenn Sie breiter hinsehen, wird unsere kleinstädtische Konfrontation zwischen dem Hollywood-Prinzip und der abstrakten Bibliotheksklasse zu einem Streit: ein Framework (Adult IoC) gegen eine Bibliothek. Es macht keinen Sinn zu beweisen, welcher von ihnen besser ist - jeder ist für einen bestimmten Zweck geschaffen. Das einzig Wichtige ist die bewusste Schaffung solcher Strukturen.

Vielen Dank an alle, die von Anfang bis Ende sorgfältig gelesen haben - Sie sind meine Lieblingsleser.

Source: https://habr.com/ru/post/de477700/


All Articles