
Im Gegensatz zum provokanten Titel ist dies keine neue Architektur, sondern ein Versuch, einfache und bewährte Praktiken in Newspeak zu übersetzen, das von der modernen Android-Community gesprochen wird
Einführung
In letzter Zeit ist es schmerzhaft geworden zu sehen, was in der Welt der Entwicklung für mobile Plattformen passiert. Die architektonische Astronautik boomt, jeder Hipster sieht es als seine Pflicht an, eine neue Architektur zu entwickeln und anstelle von zwei Zeilen eine einfache Aufgabe zu lösen und mehrere modische Frameworks einzufügen.
Diese Websites enthalten Tutorials zu trendigen Frameworks und anspruchsvollen Architekturen, aber es gibt nicht einmal eine bewährte Methode für Android REST-Clients. Dies ist zwar einer der häufigsten Anwendungsfälle. Ich möchte, dass die normale Herangehensweise an die Entwicklung auch an die Massen geht. Deshalb schreibe ich diesen Artikel
Warum sind bestehende Lösungen schlecht?
Im Großen und Ganzen ist das Problem von neuem MVP, VIPER und dergleichen genau dasselbe, ihre Autoren wissen nicht, wie sie entwerfen sollen. Und ihre Anhänger - umso mehr. Und deshalb verstehen sie wichtige und offensichtliche Dinge nicht. Und sie beschäftigen sich mit konventioneller
Überentwicklung .
1. Die Architektur sollte einfach sein
Je einfacher desto besser. Dies macht es verständlicher, zuverlässiger und flexibler. Jeder Dummkopf kann sich neu komplizieren und eine Reihe von Abstraktionen machen, aber um es einfach zu machen, müssen Sie sorgfältig überlegen.
2. Überentwicklung ist schlecht
Sie müssen nur dann eine neue Abstraktionsebene hinzufügen, wenn die alte keine Probleme löst. Nach dem Hinzufügen einer neuen Ebene sollte das System verständlicher und
weniger Code sein. Wenn Sie danach beispielsweise drei Dateien anstelle einer Datei hatten und das System komplizierter wurde, haben Sie einen Fehler gemacht und auf einfache Weise
Müll geschrieben .
Fans von MVP schreiben beispielsweise in ihren Artikeln selbst im Klartext, dass MVP dumm zu einer erheblichen
Komplikation des Systems führt. Und sie rechtfertigen es damit, dass es so
flexibel und
leichter zu warten ist . Wie wir jedoch aus Absatz 1 wissen, schließen sich diese Dinge gegenseitig aus.
Schauen Sie sich zum Beispiel VIPER zum Beispiel das Diagramm in
diesem Artikel an.
Und das ist für jeden Bildschirm! Es tut meinen Augen weh. Ich sympathisiere besonders mit denen, die bei der Arbeit gegen ihren Willen damit umgehen müssen. Denjenigen, die es selbst vorgestellt haben, sympathisiere ich mit
etwas anderen Gründen.
Neuer Ansatz
Hey, ich möchte auch einen modischen Namen . Daher wird die vorgeschlagene Architektur als
RESS -
R equest,
E vent,
S creen,
S torage bezeichnet. Die Buchstaben und Namen sind so dumm detailliert, um ein lesbares Wort zu erhalten. Nun, um keine Verwechslung mit den bereits verwendeten Namen zu schaffen. Nun, mit REST im Einklang.
Machen Sie sofort eine Reservierung, diese Architektur ist für REST-Clients. Für andere Arten von Anwendungen wird es wahrscheinlich nicht funktionieren.

1. Lagerung
Data Warehouse (mit anderen Worten Modell, Repository). Diese Klasse speichert Daten und verarbeitet sie (speichert, lädt, fügt sie der Datenbank hinzu usw.), sowie alle Daten vom REST-Service werden zuerst hier abgerufen, analysiert und hier gespeichert.
2. Bildschirm
Der Anwendungsbildschirm, im Fall von Android, ist dies Ihre Aktivität. Mit anderen Worten, es ist ein normaler ViewController wie Apples MVC.
3. Anfrage
Die Klasse, die für das Senden von Anforderungen an den REST-Service sowie für das Empfangen von Antworten und das Benachrichtigen über die Antwort anderer Systemkomponenten verantwortlich ist.
4. Ereignis
Die Verbindung zwischen den anderen Komponenten. Beispielsweise sendet Request ein Ereignis über die Antwort des Servers an diejenigen, die ihn abonnieren. Und Storage sendet ein Ereignis über Datenänderungen.
Das Folgende ist ein Beispiel für eine vereinfachte Implementierung. Der Code wurde mit Annahmen geschrieben und nicht überprüft, sodass möglicherweise Syntaxfehler und Tippfehler auftreten
Anfragepublic class Request { public interface RequestListener { default void onApiMethod1(Json answer) {} default void onApiMethod2(Json answer) {} } private static class RequestTask extends AsyncTask<Void, Void, String> { public RequestTask(String methodName) { this.methodName = methodName; } private String methodName; @Override protected String doInBackground(Void ... params) { URL url = new URL(Request.serverUrl + "/" + methodName); HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection();
Lagerung public class DataStorage { public interface DataListener { default void onData1Changed() {} default void onData2Changed() {} } private static MyObject1 myObject1 = null; private static List<MyObject2> myObjects2 = new ArrayList<>(); public static void registerListener(DataListener listener) { listeners.add(listener); } public static void unregisterListener(DataListener listener) { listeners.remove(listener); } public static User getMyObject1() { return myObject1; } public static List<MyObject2> getMyObjects2() { return myObjects2; } public static Request.RequestListener listener = new Request.RequestListener() { private T fromJson<T>(Json answer) {
Bildschirm public class MyActivity extends Activity implements DataStorage.DataListener { private Button button1; private Button button2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); button1.setOnClickListener((View) -> { Request.apiMethod1(); }); button2.setOnClickListener((View) -> { Request.apiMethod2(); }); updateViews(); } @Override protected void onPause() { super.onPause(); DataStorage.unregisterListener(this); } @Override protected void onResume() { super.onResume(); DataStorage.registerListener(this); updateViews(); } private void updateViews() { updateView1(); updateView2(); } private void updateView1() { Object1 data = DataStorage.getObject1();
Die App public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); Request.registerListener(DataStorage.listener); } }
Dieselbe Shemka, aber in Bezug auf RESS, zum Verständnis Das funktioniert folgendermaßen: Wenn Sie auf die Schaltfläche klicken, zuckt die gewünschte Methode für die Anforderung. Die Anforderung sendet eine Anforderung an den Server, verarbeitet die Antwort und benachrichtigt zuerst DataStorage. DataStorage analysiert die Antwort und speichert die Daten zu Hause zwischen. Die Anforderung benachrichtigt dann den aktuell aktiven Bildschirm. Der Bildschirm nimmt Daten aus dem DataStorage und aktualisiert die Benutzeroberfläche.
Bildschirmzeichen und Abmeldungen von Mittelmäßigkeit in onResume bzw. onPause. Zusätzlich zu onResume wird auch die Benutzeroberfläche aktualisiert. Was gibt es? Benachrichtigungen kommen nur in der aktuell aktiven Aktivität, keine Probleme bei der Verarbeitung der Anforderung im Hintergrund oder beim Drehen der Aktivität. Die Aktivitäten werden immer auf dem neuesten Stand sein. Die Benachrichtigung erreicht die Hintergrundaktivität nicht und wenn Sie in den aktiven Zustand zurückkehren, werden die Daten aus dem DataStorage übernommen. Daher gibt es keine Probleme, wenn Sie den Bildschirm drehen und die Aktivität neu erstellen.
Und dafür reicht die Standard-API aus dem Android SDK.
Fragen und Antworten für zukünftige Kritik
1. Was ist der Gewinn?
Echte Einfachheit, Flexibilität, Wartbarkeit, Skalierbarkeit und ein Minimum an Abhängigkeiten. Sie können jederzeit einen bestimmten Teil des Systems komplizieren, wenn Sie dies benötigen. Zu viele Daten? Teilen Sie den DataStorage vorsichtig in mehrere Teile auf. Riesenservice-REST-API? Machen Sie ein paar Anfragen. Ist Listering zu einfach, umständlich und unmodern? Nimm den EventBus. Siehst du einen Friseursalon auf der HttpConnection schief an? Nehmen Sie Retrofit. Mutige Aktivität mit ein paar Fragmenten? Stellen Sie sich einfach vor, dass jedes Fragment Screen ist, oder teilen Sie es in Unterklassen auf.
2. AsyncTask ist ein schlechter Mann, nimm mindestens Retrofit!
Huh? Und welche Probleme verursacht es in diesem Code? Speicherlecks? Nein, hier speichert AsyncTask keine Links zu Aktivierungen, sondern nur einen Link zu einer statischen Methode. Die Antwort ist verloren? Nein, die Antwort kommt immer zum statischen DataStorage, bis die Anwendung beendet wird. Versuchen Sie, die Aktivität in der Pause zu aktualisieren? Nein, Benachrichtigungen werden nur in der aktiven Aktivität angezeigt.
Und wie kann Retrofit hier helfen? Schau einfach
hier . Der Autor nahm RxJava, Retrofit und formt immer noch Krücken, um ein Problem zu lösen, das RESS einfach nicht hat.
3. Bildschirm ist der gleiche ViewController! Logik und Präsentation müssen getrennt werden, arrr!
Lass dieses Mantra schon fallen. Ein typischer Client für einen REST-Service ist eine große Ansicht für die Serverseite. Ihre gesamte Geschäftslogik besteht darin, den richtigen Status für eine Schaltfläche oder ein Textfeld festzulegen. Was wirst du dort teilen? Sagen wir, es wird einfacher zu warten sein? 3 Dateien mit 3 Tonnen Code pflegen, statt 1 Datei mit 1 Tonne einfacher? Ok Und wenn wir Aktivität mit 5 Fragmenten haben? Dies sind bereits 3 x (5 + 1) = 18 Dateien.
Die Trennung in Controller und View erzeugt in solchen Fällen einfach eine Menge bedeutungslosen Codes. Es ist Zeit, dies zuzugeben. Das Hinzufügen von Funktionen zu einem Projekt mit MVP macht besonders viel Spaß: Möchten Sie einen Button-Handler hinzufügen? Ok, korrigieren Sie Presenter, Activity und View-Interface. In RESS werde ich dazu ein paar Codezeilen in eine einzige Datei schreiben.
Aber in großen Projekten wächst ViewController furchtbar? Sie haben also keine großen Projekte gesehen. Ihr REST-Client für die nächste Site mit 5.000 Zeilen ist ein kleines Projekt, und 5.000 Zeilen dort nur, weil auf jedem Bildschirm 5 Klassen vorhanden sind. Wirklich große Projekte auf RESS mit über 100 Bildschirmen und mehreren Teams von 10 Personen fühlen sich großartig an. Machen Sie einfach ein paar Anfragen und Speicher. Und Bildschirm für fett gedruckte Bildschirme enthalten zusätzlichen Bildschirm für große Benutzeroberflächenelemente, z. B. dieselben Fragmente. Ein Projekt auf einem MVP der gleichen Größenordnung wird einfach in einer Reihe von Präsentatoren, Schnittstellen, Aktivierungen, Fragmenten und nicht offensichtlichen Verbindungen ertrinken. Und der Übergang zu VIPER wird in der Regel dazu führen, dass das gesamte Team eines Tages ausscheidet.
Fazit
Ich hoffe, dieser Artikel wird viele Entwickler ermutigen, ihre Ansichten zur Architektur zu überdenken, keine Abstraktionen zu erstellen und einfachere und bewährte Lösungen zu betrachten.