RESS-移动应用程序的新架构



与具有挑衅性的标题相反,这不是新架构,而是尝试将简单且经过时间考验的实践转换为Newspeak,这是现代Android社区所说的

引言


最近,查看移动平台开发世界中正在发生的事情变得很痛苦。 建筑航天学正蓬勃发展,每个潮人都认为自己有责任提出一种新的体系结构,并解决一个简单的任务,而不是用两条线,而是插入几个时尚的框架。

这些网站上都填充了有关流行框架和复杂架构的教程,但对于Android REST客户端来说,甚至没有最佳实践。 尽管这是最常见的应用案例之一。 我也希望普通的发展方法也能适用于大众。 因此,我正在写这篇文章

为什么现有解决方案不好?


总的来说,新式的MVP,VIPER等问题完全相同,他们的作者不知道如何设计。 和他们的追随者-甚至更多。 因此,他们不了解重要和显而易见的事情。 他们从事传统的过度工程

1.架构应该简单


越简单越好。 这使得它更易于理解,更可靠,更灵活。 任何傻瓜都可以将其重新复杂化并做出一堆抽象,但是要简单地做到这一点,您需要仔细考虑。

2.过度设计是不好的


仅当旧的不能解决问题时,才需要添加新的抽象级别。 添加新级别后,系统应该变得更易于理解,并且代码更少 。 例如,如果在那之后只有三个而不是一个文件,并且系统变得更加复杂,那么您就犯了一个错误,并且如果以一种简单的方式编写了垃圾文件

例如,MVP的拥护者在自己的文章中以纯文本形式写道,MVP愚蠢地导致系统的严重复杂化 。 他们证明了它如此灵活易于维护这一事实是合理的。 但是,从第一段我们知道,这些是互斥的。

现在关于VIPER,例如,看一下本文中的图表。

方案
图片

这适用于每个屏幕! 伤了我的眼睛。 我特别同情那些在工作中不得不违背自己意愿的人。 对于那些自己介绍它的人,我表示同情。

新方法


嘿,我也想要一个时髦的名字 。 因此,所提出的体系结构称为RESS-请求,排放,Screen, S torage。 字母和名称如此愚蠢,以得到可读的单词。 好吧,以免与已经使用的名称混淆。 好吧,借助REST。

立即进行预订,此体系结构适用于REST客户端。 对于其他类型的应用程序,它可能无法正常工作。



1.储存


数据仓库(即模型,存储库)。 此类存储数据并对其进行处理(保存,加载,添加到数据库等),以及REST服务中的所有数据首先到达此处,进行解析并存储在此处。

2.屏幕


对于Android,应用程序屏幕是您的活动。 换句话说,它是常规的ViewController,例如Apple的MVC。

3.要求


一个类,负责将请求发送到REST服务,以及接收响应并通知系统其他组件的响应。

4.活动


其他组件之间的链接。 例如,“请求”向订阅服务器的人发送有关服务器响应的事件。 然后,存储发送有关数据更改的事件。

以下是简化实现的示例。 该代码是根据假设编写的,未经检查,因此可能存在语法错误和错别字

索取
public 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(); // ... //      // ... return result; } @Override protected void onPostExecute(String result) { // ... //  JSON  result // ... Requestr.onHandleAnswer(methodName, json); } } private static String serverUrl = "myserver.com"; private static List<OnCompleteListener> listeners = new ArrayList<>(); private static void onHandleAnswer(String methodName, Json json) { for(RequestListener listener : listeners) { if(methodName.equals("api/method1")) listener.onApiMethod1(json); else if(methodName.equals("api/method2")) listener.onApiMethod2(json); } } private static void makeRequest(String methodName) { new RequestTask(methodName).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } public static void registerListener(RequestListener listener) { listeners.add(listener); } public static void unregisterListener(RequestListener listener) { listeners.remove(listener); } public static void apiMethod1() { makeRequest("api/method1"); } public static void onApiMethod2() { makeRequest("api/method2"); } } 


贮藏
 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) { // ... //    JSON // ... return objectT; } @Override public void onApiMethod1(Json answer) { myObject1 = fromJson(answer); for(RequestListener listener : listeners) listener.data1Changed(); } @Override public void onApiMethod2(Json answer) { myObject2 = fromJson(myObjects2); for(RequestListener listener : listeners) listener.data2Changed(); } }; } 


萤幕
 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(); // ... //     // ... } private void updateView2() { List<Object2> data = DataStorage.getObjects2(); // ... //     // ... } @Override public void onData1Changed() { updateView1(); } @Override public void onData2Changed() { updateView2(); } } 


该应用程序
 public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); Request.registerListener(DataStorage.listener); } } 


相同的shemka,但就RESS而言,用于理解


它的工作方式如下:单击该按钮时,所需的Request方法将被抽动,Request将请求发送到服务器,处理响应,并首先通知DataStorage。 DataStorage解析响应并在家中缓存数据。 然后,请求会通知当前活动的Screen,Screen从DataStorage中获取数据并更新UI。

屏幕标志和取消订阅分别来自onResume和onPause中的平庸。 除了onResume之外,它还更新UI。 它有什么作用? 通知仅出现在当前活动的活动中,而在后台处理请求或打开活动没有问题。 活动将始终是最新的。 通知将不会到达后台活动,并且返回活动状态时,将从DataStorage中获取数据。 因此,旋转屏幕并重新创建活动时没有问题。

对于所有这些,Android SDK中的默认api就足够了。

未来批评的问答


1.什么是利润?


真正的简单性,灵活性,可维护性,可伸缩性和最少的依赖关系。 如果需要,您总是可以使系统的特定部分复杂化。 数据太多? 轻轻将DataStorage分成几部分。 庞大的服务REST API? 提出一些要求。 侦听器是否过于简单,笨拙且不合时宜? 乘坐EventBus。 在HttpConnection上的理发店里看起来很ask跷? 好吧,进行改造。 带有一堆碎片的大胆活动? 只需考虑每个片段都是Screen,或将其分为子类即可。

2. AsyncTask是一个坏人,至少要进行改造!


?? 这段代码会导致什么问题? 内存泄漏? 不,这里AsyncTask不存储指向激活的链接,而仅存储指向静态方法的链接。 答案不见了吗? 不,答案始终是静态数据存储,直到应用程序被杀死。 尝试在暂停时刷新活动? 不,通知仅出现在活动的活动中。

改造如何在这里提供帮助? 看看这里 。 作者使用RxJava,Retrofit并雕刻了拐杖来解决RESS根本没有的问题。

3.屏幕是相同的ViewController! 需要将逻辑和表示分开,arrr!


已删除此咒语。 REST服务的典型客户端是服务器端的一大视角。 您所有的业务逻辑都是为按钮或文本字段设置正确的状态。 您打算在那里分享什么? 说会更容易维护吗? 用3吨代码维护3个文件,而不是用1吨轻松维护1个文件? 好啦 如果我们有5个片段的活动? 这已经是3 x(5 +1)= 18个文件。

在这种情况下,将其分离为Controller和View只会产生一堆毫无意义的代码,是时候接受它了。 使用MVP向项目添加功能特别有趣:是否要添加按钮处理程序? 好的,修复Presenter,Activity和View界面。 为此,在RESS中,我将在一个文件中编写几行代码。

但是在大型项目中,ViewController的增长非常厉害吗? 因此,您还没有看到大型项目。 您的下一个站点有5,000行的REST客户端是一个小项目,那里有5,000行是因为每个屏幕上都有5个类。 拥有100多个屏幕的RESS上的真正大型项目,以及几个由10个人组成的团队,感觉很棒。 只需提出一些请求和存储。 用于粗体屏幕的屏幕包含用于大型UI元素(例如,相同的片段)的其他屏幕。 具有相同规模的MVP的项目只会淹没在许多演示者,界面,激活,片段和不明显的连接中。 过渡到VIPER通常会使整个团队有一天退出。

结论


我希望本文能够鼓励许多开发人员重新考虑他们对体系结构的看法,而不是产生抽象概念,而是研究更简单且经过时间考验的解决方案。

Source: https://habr.com/ru/post/zh-CN424081/


All Articles