Das objektorientierte Paradigma ist Ă€uĂerst praktisch fĂŒr Unternehmen: Es ermöglicht Ihnen, nahezu jede Idee umzusetzen und eine akzeptable Produktleistung zu erzielen. In diesem Fall meinen wir mit Produkt eine iOS-Anwendung, daher werden wir abschlieĂend von der Entwicklung speziell auf dieser Plattform ausgehen.
Wenn man die bekannten MĂ€ngel dieses beliebten Paradigmas ignoriert, enthĂ€lt die Liste seiner Minuspunkte den wichtigsten Vorteil - die FlexibilitĂ€t der Entwicklung. Warum ist das ein Minus? Es liegt auf der Hand, dass FlexibilitĂ€t neben der grundlegenden FĂ€higkeit, geschĂ€ftliche Probleme zu lösen, dies auf verschiedene Weise ermöglicht. Es ist wahr, dass es ein Dutzend falsche fĂŒr einen korrekten Ansatz gibt, obwohl die GeschĂ€ftsaufgabe in jedem Fall korrekt gelöst wird, jedoch mit Unterschieden in der Implementierung, deren Erweiterbarkeit und Transparenz bereits von der Richtigkeit des angewandten Ansatzes abhĂ€ngt.
Unter BerĂŒcksichtigung von Murphys Gesetz ist die Schlussfolgerung, dass es ohne die richtigen architektonischen EinschrĂ€nkungen wahrscheinlicher ist, dem Weg des Chaos zu folgen, dh die QualitĂ€t des Codes wird schnell abnehmen, nur weil das Paradigma dies zulĂ€sst. In diesem Artikel wird eine der möglichen architektonischen EinschrĂ€nkungen erörtert, die dazu beitragen, das Gleichgewicht zwischen den KrĂ€ften von Gut und Böse zu halten, dh die Dynamik des Entropiewachstums in der Codebasis des Projekts. Es ist wichtig, dass diese Dynamik direkt mit der Anzahl der Personen korreliert, die am Schreiben des Codes beteiligt sind. Daher ist bei groĂen Projekten die Richtigkeit der ausgewĂ€hlten EinschrĂ€nkungen besonders wichtig. Was ist der Punkt?
Der Punkt ist einfach. Lassen Sie uns vom nĂ€chsten Axiom abweichen - je mehr Eigenschaften in einem Objekt vorhanden sind, desto âschlechterâ ist es. Diese Aussage kann wie folgt erklĂ€rt werden: Mit zunehmender Anzahl interner ZustĂ€nde nehmen alle positiven Indikatoren des Objekts - Erweiterbarkeit, ModularitĂ€t, Transparenz, Testbarkeit - ab. Man kann EinwĂ€nde erheben und sagen, dass die KomplexitĂ€t des Objekts und seine FunktionalitĂ€t miteinander verbunden sind und dass ohne das erste das zweite nicht geschieht. Alles ist wahr, aber auf dem "zustandsbehafteten" Weg tritt das Wachstum der KomplexitĂ€t exponentiell auf, obwohl das Wachstum im Idealfall linear sein sollte oder, einfacher gesagt, ein neues Merkmal vorhandene Merkmale nicht komplizieren sollte.
PS Hier sollte klargestellt werden, dass Features als semantisch verwandte Ebenen der GeschĂ€ftslogik verstanden werden. Daher wird hĂ€ufig die Entscheidung getroffen, eine vorhandene Klasse zu komplizieren, anstatt eine neue zu erstellen. In solchen FĂ€llen ist es schwierig, WidersprĂŒche zum ersten SOLID-Prinzip zu finden.Ein Beispiel ist ein Standardbildschirm mit einer Liste von EntitĂ€ten mit einer Auswahl. Einige der Eigenschaften könnten instabil gemacht werden, aber IB bindet uns an den Standardkonstruktor des Controllers und dies zwingt uns, "Schmutz zu machen". Der Zugriff auf diese Eigenschaften wird implizit von jeder Klassenmethode in derselben Datei erhalten. Und der unangenehmste Moment ist, dass ihre VerĂ€nderlichkeit auch von nichts abgedeckt wird, und dies fĂŒhrt irgendwie zu irreversiblen Konsequenzen in Form einer starken Verbindung und folglich zu der Tatsache, dass die Behebung einiger Defekte das Auftreten neuer Defekte verursacht:

Wir können daraus schlieĂen, dass eine lineare Zunahme der KomplexitĂ€t eines Objekts mit einem gemeinsamen Zustand praktisch nicht erreichbar ist. Die FunktionalitĂ€t mit diesem Ansatz wird monolithisch und fĂŒr jede Art von Trennung schwierig. Ein gutes Beispiel ist der Antipattern-Massive-View-Controller, der weit verbreitet ist und das Ergebnis des Fehlens jeglicher EinschrĂ€nkungen fĂŒr das Projekt deutlich zeigt.

Am Beispiel von UIKit können Sie genau sehen, wie sich der zwingende Entwicklungsstil auf die KomplexitÀt des Codes auswirkt und an welchen Stellen das Framework das Erstellen von Eigenschaften in Klassen erzwingt.
Der einfachste Fall - das Verarbeiten eines Knopfdrucks - wird normalerweise nur durch Definieren der âschmutzigen Methodeâ ausgefĂŒhrt, dh durch eine Funktion, die nichts NĂŒtzliches empfangen kann, sodass Sie fĂŒr die Eigenschaften nach drauĂen gehen mĂŒssen:

Daher erschwert die "Funktion" der SchaltflĂ€che den Rest der FunktionalitĂ€t des Objekts tyrannisch, da alle Bewohner der Klasse Zugriff auf diese Daten haben. TatsĂ€chlich funktioniert fast jedes iOS-UI-Steuerelement auf Ă€hnliche Weise. Daher fĂ€llt es ein, eine Art Wrapper ĂŒber UI-Elemente zu implementieren, der beispielsweise als Verarbeitung der âOperationâ eines bestimmten Elements geschlossen wird. Die Schwierigkeit besteht jedoch darin, einen solchen Wrapper prĂ€zise und zuverlĂ€ssig zu gestalten. SchlieĂlich geht es nicht nur um die Eingabe, sondern auch um die Ausgabe von Informationen. Beispielsweise funktioniert die allgegenwĂ€rtige Tabelle mit Daten auch "schmutzig" und hat keine Ahnung von den angezeigten Daten, sodass Sie sie in einem Objekt speichern mĂŒssen:

Ist es bequem? Praktischerweise hat jeder immer Zugriff auf Daten. Flexibel, schnell, unerlĂ€sslich. Aber im Laufe der Zeit verwandelt sich alles in der Regel in einen konkreten Obelisken fĂŒr tausend Codezeilen und in die TrĂ€nen derer, die die Aufgabe hatten, in dieser Klasse zu arbeiten.
ZurĂŒck zu den EinschrĂ€nkungen: Aus dem obigen Code kann das folgende Prinzip gebildet werden: Code ohne Eigenschaften in Klassen ist viel sauberer und mit UnterstĂŒtzung und Erweiterung rentabler. Idealerweise sollte die Logik des Objekts in seinen âreinenâ Methoden liegen, die alle ihre AbhĂ€ngigkeiten als Eingabe verwenden und das Ergebnis ihrer AktivitĂ€t bei der Ausgabe haben.
Die Idee ist, den privaten Zustand des Objekts um eine Ebene zu senken. Das heiĂt, wenn private Objekte von auĂen schlieĂen, besteht unsere Aufgabe darin, noch weiter zu gehen und die Verkapselung auf der Ebene der Methoden selbst zu stĂ€rken. Auf den ersten Blick scheint es trotz der AsynchronitĂ€t der Plattform und der Notwendigkeit des Hauptrahmens, dass diese ganze Idee nicht nur unmöglich ist, sondern sich zumindest unnatĂŒrlich umsetzen wird. Und höchstwahrscheinlich wird es so sein, aber dies ist eines dieser Probleme, die durch die EinfĂŒhrung einer zusĂ€tzlichen Ebene von Abstraktionen gelöst werden können.
Wenn wir die gesamte Logik von Klassen in ihre Methoden integrieren wollen, ohne auf Eigenschaften zurĂŒckzugreifen, mĂŒssen wir eng mit Closures zusammenarbeiten. IOS verfĂŒgt ĂŒber Standardtools zum Verwalten asynchroner VorgĂ€nge wie GCD und OperationQueue. Es scheint, dass sie ausreichen wĂŒrden, aber wenn Sie versuchen, die Idee zum Leben zu erwecken, wird sich herausstellen, dass alles nicht so rosig ist, wie Sie möchten. Abgesehen von der Tatsache, dass es eine groĂe Chance gibt, eine RĂŒckruf-Hölle zu bekommen, wird sich bei diesem Ansatz herausstellen, dass der Code selbst umstĂ€ndlich ist, viele logische LĂŒcken aufweist und stark miteinander verbunden ist. Es ist möglich, dass ein solcher Code noch komplizierter ist als das, was wir so schnell versuchen zu entkommen.
Wenn Sie sich umschauen, können Sie sehen, dass es viel schönere und funktionalere Wege gibt, um dieses Ziel zu erreichen. Reaktive Programmierung wird seit langem in der kommerziellen Softwareentwicklung eingesetzt und ist ideal fĂŒr die asynchrone Front-End-Welt. In diesem Fall betrachten wir eine (ziemlich erfolgreiche) Implementierung des reaktiven Paradigmas fĂŒr Swift-Rx.

Es bietet eine einfache EntitĂ€t namens Observable. Dies ist eine Art Abstraktion ĂŒber den Ereignisfluss, die abonniert werden kann, und danach erhĂ€lt der Abonnent diese Ereignisse im Laufe der Zeit:

Der einfachste Weg, sich den Ablauf von Ereignissen vorzustellen, ist die Darstellung einer regulĂ€ren SchaltflĂ€che. Das Ereignis seiner Operation ist hier ein Stream, so dass jedes Objekt Ereignisse seines Klickens abonnieren und empfangen kann, und vor allem weiĂ die SchaltflĂ€che nichts ĂŒber ihre eigenen Abonnenten. Praktischerweise kann fast jede Aktion in eine Ă€hnliche Folge von Werten umgewandelt werden, und dies ist wichtig, da Observable miteinander kombiniert werden kann, da kein Standard-Framework dies ermöglicht.
Sie können beispielsweise mehrere Anforderungen senden, indem Sie auf die SchaltflĂ€che klicken (zweimaliges Tippen filtern), auf die Antwort warten, dann die Antwort mit den Angaben des Benutzers auf dem Bildschirm kombinieren, eine weitere Anforderung ausfĂŒhren und zum nĂ€chsten Bildschirm wechseln und das Ergebnis auf diesen ĂŒbertragen Dieser Rx ermöglicht es, Fehler beim Verlassen des Bildschirms kurz und bĂŒndig zu behandeln und diese Kette zu vervollstĂ€ndigen (Anforderungen abzubrechen), und diese gesamte Logik benötigt zwei Dutzend Zeilen eingegebenen Codes:
Es lohnt sich, ĂŒber die einzige Eigenschaft von disposeBag zu sprechen. Wie aus den Screenshots hervorgeht, wird jedes Abonnement darin abgelegt, und dies ist erforderlich, um ihre Lebensdauer zu steuern. Das heiĂt, die Abonnements sind gĂŒltig, solange die "Tasche" lebt, in der sie platziert wurden, in diesem Fall, wĂ€hrend der Controller lebt.ZusĂ€tzlich zur Kompaktheit ist es schwierig, im obigen Code einen Fehler zu machen, da jeder Verschluss etwas zurĂŒckgibt und keine Nebenwirkungen enthĂ€lt. Dies ist die Kraft, nach der wir gesucht haben.
Sie können einen weiteren wichtigen Punkt bemerken: Da die Klasse keine Eigenschaften hat, ist es nicht erforderlich, [schwaches Selbst] zu schreiben, was sich positiv auf die Lesbarkeit des Codes auswirkt. Alle Funktionen können und besser lokal in der Methode definiert werden, in der sie verwendet werden, oder in separaten Klassen ausgefĂŒhrt werden. Ăbrigens können in diesem Fall Links zu AbhĂ€ngigkeiten (ViewModel, Presenter usw.) als Argument an die Controller-Methode ĂŒbergeben werden. In diesem Fall mĂŒssen sie nicht in Eigenschaften gespeichert werden. Das ist richtig.
Nach der ĂberprĂŒfung ist es an der Zeit, Observable zu verwenden, um die Entwicklung zu vereinfachen. Wie genau? Kehren wir zur Idee der âsauberenâ Methoden zurĂŒck und versuchen, die Logik eines kleinen Bildschirms in seiner einzigen Methode zu implementieren. Aus GrĂŒnden der Ăbersichtlichkeit wĂ€hlen wir die Methode zum Beenden des Ladens der Ansicht (viewDidLoad). Wenn der Bildschirm in IB erstellt wird, mĂŒssen wir natĂŒrlich Eigenschaften fĂŒr Outlets erstellen. Dies ist jedoch nicht beĂ€ngstigend, da die Elemente selbst keine GeschĂ€ftslogik darstellen und daher die KomplexitĂ€t des Bildschirms nicht wesentlich beeinflussen. Wenn der Bildschirm jedoch aus Code besteht, können Sie auf Eigenschaften verzichten (mit Ausnahme des disposeBag), Elemente in unserer Methode erstellen und genau dort verwenden. Was ist mit der ImperativitĂ€t der zuvor beschriebenen UIKit-Elemente? Rx bietet zusĂ€tzlich zum Ansatz selbst reaktive Wrapper fĂŒr Standard-UI-Komponenten, sodass Sie in den meisten FĂ€llen die erforderliche Abfolge von Ereignissen vor Ort erhalten können. Oder binden Sie umgekehrt das vorhandene Observable beispielsweise an eine Tabelle. Bringen Sie eine Anforderung dazu, damit der Inhalt unmittelbar nach seiner Fertigstellung aktualisiert wird:
Die Bindung an Sammlungen ist recht flexibel, funktioniert jedoch standardmĂ€Ăig nur ĂŒber reloadData. FĂŒr Punktaktualisierungen gibt es eine wunderbare debuggte Bibliothek desselben Autors - RxDataSources. Damit können Sie AbstĂŒrze wĂ€hrend BatchUpdates vergessen.Was wird als nĂ€chstes passieren? Eine Einzelbildschirmmethode wĂ€chst und in einem ziemlich komplizierten Fall wird es schwierig, sie zu warten. Und wenn dies geschieht, wird plötzlich klar, dass diese Methode von nichts anderem als von sich selbst abhĂ€ngt, und der reaktive Ansatz hat den Code in logische Blöcke unterteilt, die leicht in separate Objekte eingefĂŒgt werden können, indem sie auf Ă€hnliche Weise entworfen werden. Diesmal nehmen die Methoden jedoch bereits einige AbhĂ€ngigkeiten vom Bildschirm und geben einige Ergebnisse zurĂŒck. Der positive Punkt ist, dass die Signatur in diesem Fall so inhaltlich wie möglich erhalten wird. Es ist ersichtlich, dass die Funktion ihre Arbeit erfordert und was ihr Ergebnis ist. Es könnte ungefĂ€hr so ââaussehen:

Separate Strukturen helfen dabei, den Header der Methode lesbar und ordentlich zu halten, da es viele AbhÀngigkeiten geben kann.
Es ist wichtig zu verstehen, dass die Methode nicht die einzige im gesamten Objekt sein muss, die Essenz ihrer UnabhĂ€ngigkeit voneinander. Dank Rx können ihre Ein- und Ausgabe asynchron sein und eine oder mehrere Observable darstellen, was eine weitere Dimension fĂŒr die Datenmanipulation darstellt.
Dieser Ansatz löst Ihre HÀnde und ermöglicht es Ihnen, Bildschirme nahezu beliebiger KomplexitÀt zu implementieren, wÀhrend der Code explizit und lose gekoppelt bleibt.