zoukankan      html  css  js  c++  java
  • MVP架构学习

    MVP架构学习

    M:数据层(数据库,文件,网络等...)
    V:UI层(Activity,Fragment,View以及子类,Adapter以及子类)
    P:中介,关联UI层和数据层,因为V和M是相互看不到对方的,简单而言就是不能相互持有对方的引用
    MVP只是一种思想,不要把它认为是一种规范,要学会灵活用户,下面就带大家走进MVP模式的学习

    需求

    需求很简单,我们就做一个简单的登录功能,当点击界面上的Login按钮,会向后台发送登录请求

    布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout 
    	xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="xdysite.cn.testdemo.MainActivity">
    
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="login"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:onClick="login"/>
    </android.support.constraint.ConstraintLayout>
    

    注:按钮点击会调用login方法

    方案1

    本方案中给出了最朴素的实现方式

    public class MainActivity extends AppCompatActivity {
        static final String TAG = "MainActivity";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        public void login(View v) {
            OkHttpClient client = new OkHttpClient();
            RequestBody body = new FormBody.Builder().add("username", "admin").add("password", "12345").build();
            Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build();
            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    Log.i(TAG, "onFailure: " + call.toString());
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    Log.i(TAG, "onResponse: " + response.body().string());
                }
            });
        }
    }
    

    上图中当我们点击login按钮的时候会调用MainActivity中的login方法,在login方法中会向服务器发送请求,并等待返回结果(这里使用了OKHttp,使用异步的方式发送请求)。

    小结

    这种设计简单明了,直观具体,但是它有个局限性,它将所有的功能全部在一个类中完成,那只适合单人作战。我们想象一下,按照上面的方案,如果我们让一个人写界面(布局文件),让一个人写登录功能(Activity),那么写登录功能的人某一天将login函数改为了login2了,但他忘记告诉了写界面的人,那是不是就是出现了问题。即使他告诉了写界面的人说“你将界面的上的login换为login2”,人家愿不愿换还是一回事呢!!!

    方案2

    本方案中引入MVP思想,对上面的设计优化一下。

    Model层

    Model我们就让其与服务器打交道,来实现登录功能的逻辑,我们实现了一个LoginModel类。

    public class LoginModel {
    
        void login(String username, String password, final OnResultListener listener) {
            OkHttpClient client = new OkHttpClient();
            RequestBody body = new FormBody.Builder().add("username", username).add("password", password).build();
            Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build();
            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    listener.onResult(response.body().string());
                }
            });
        }
    
        public interface OnResultListener {
            void onResult(String result);
        }
    }
    

    在LoginModel类对外暴露了一个login的方法来供别人调用,传入的参数为用户名、密码和监听器。监听器作用是当服务器返回结果时调用。
    监听器是LoginModel类的内部接口,需要调用者去实现该接口。

    View层

    View层就是我们的Activity和布局文件。View层将持有的P层的引用。下面是改造后的MainActivity类

    public class MainActivity extends AppCompatActivity {
        static final String TAG = "MainActivity";
        LoginPresenter mLoginPresener;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mLoginPresener = new LoginPresenter(this);
        }
    
        public void login(View v) {
        	mLoginPresener.login("admin", "12345");
    	}
    
    	public void showResult(String result) {
        	Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
    	}
    }
    

    在MainActivity类中持有了P层(LoginPresenter)的引用,当用户点击登录时,它会调用P层的login方法,并且这里还提供了一个showResult来显示登录结果(成功/失败)

    Presenter层

    在P层充当中介的身份,它同时需了解V层和M层的情况,因此P层将会持有V层和M层的引用。

    public class LoginPresenter {
        LoginModel mLoginModel = new LoginModel();
        MainActivity mLoginView;
    
        public LoginPresenter(MainActivity loginView) {
        	mLoginView = loginView;
        }
    
       	public void login(String usernanem, String password) {
       		mLoginModel.login(usernanem, password, new LoginModel.OnResultListener() {
            	@Override
            	public void onResult(String result) {
                	mLoginView.showResult(result);
        		}
        	});
    	}
    }
    

    在LoginPresenter类中同时持有了MainActivity的引用和LoginModel的引用,当在MainActivity中调用LoginPresenter的login方法时,LoginPresener会调用LoginModel中的login方法,然后在回调中还是调用MainActivity的showResult方法。这样LoginPresenter就完成了中介的职责。

    小结

    通过MVP我们将界面的处理和与服务器交互逻辑分离的开来,如果View层代码被修改了,那么M层的代码将不会受任何影响,反之依然。这样就解决了方案一种出现的争端。这是MVP最简单的运用了,说它简单那么存在不完善的地方。就拿V和P来说,LoginPresenter中调用了MainActivity的showResult方法,当这个方法被改名的话,在LoginPresenter中也要做同样的修改。而且在创建LoginPresenter时也只能接受MainActivity对象,当你的老板有天说我们的登录换成LoginFragment了,那你又要再写一个接受LoginFragment对象的LoginPresenter了。下来我们继续优化上的设计

    方案3

    为了更好的解耦,那么我们将会在View层引入接口类,接口的一大特性就是解耦。因为接口意味着规范,接口中的方法必须要实现,而且接口类一旦确定,那么很少发生修改了。

    Model层

    Model层的代码我们一点都不动

    public class LoginModel {
    
        void login(String username, String password, final OnResultListener listener) {
            OkHttpClient client = new OkHttpClient();
            RequestBody body = new FormBody.Builder().add("username", username).add("password", password).build();
            Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build();
            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    listener.onResult(response.body().string());
                }
            });
        }
    
        public interface OnResultListener {
            void onResult(String result);
        }
    }
    

    View层

    在View层中我们定义了接口类LoginView,目的就是为了让V和P解耦。

    接口类
    public interface LoginView {
        void showResult(String result);
    }
    
    具体类
    public class MainActivity extends AppCompatActivity implements LoginView {
        static final String TAG = "MainActivity";
        LoginPresenter mLoginPresener;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mLoginPresener = new LoginPresenter(this);
        }
    
        public void login(View v) {
            mLoginPresener.login("admin", "12345");
        }
    
        @Override
        public void showResult(String result) {
            Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
        }
    }
    

    具体类实现了LoginView接口,则表明showResult方法不是MainActivity所有了,它属于接口类了,成为了一种规范。

    Presenter

    P层现在持有的不是一个具体的View层对象了,它是面向接口的。不管你是Activity还是Fragment,只要你实现了LoginView接口就好。而且它也不用担心View层胡乱改的问题了,只要你实现LoginView这个接口。

    public class LoginPresenter {
        LoginModel mLoginModel = new LoginModel();
        LoginView mLoginView;
    
        public LoginPresenter(LoginView loginView) {
          mLoginView = loginView;
        }
    
        public void login(String usernanem, String password) {
            mLoginModel.login(usernanem, password, new LoginModel.OnResultListener() {
                @Override
                public void onResult(String result) {
                    mLoginView.showResult(result);
                }
            });
        }
    }
    
    小结

    我们将V和P通过接口的方式进行解耦了,我们在MVP架构上又往前走了一步。但是,如果仔细研究代码的话,我们会发现有内存泄露的问题。当网络请求发出去后,如果我们立马关闭Activity,那么Activity会得到释放吗?答案是不会,这个留给大家去思考。

    补充

    补充部分是对方案3的小优化,主要解决Activity引起的内存泄露的问题。

    对Presenter优化

    引入attcheView和detachView方法来绑定视图和解除视图,现在这两个方法都比较单薄,但是我们在这个方法中可以根据业务需要添加别的逻辑了。

    public class LoginPresenter {
        LoginModel mLoginModel = new LoginModel();
        LoginView mLoginView;
    
        public void login(String usernanem, String password) {
            mLoginModel.login(usernanem, password, new LoginModel.OnResultListener() {
                @Override
                public void onResult(String result) {
                    if (mLoginView != null)
                    mLoginView.showResult(result);
                }
            });
        }
    
        public void attcheView(LoginView loginView) {
            mLoginView = loginView;
        }
    
        public void detachView() {
            mLoginView = null;
        }
    }
    
    对MainActivity进行改进
    public class MainActivity extends AppCompatActivity implements LoginView {
        static final String TAG = "MainActivity";
        LoginPresenter mLoginPresener;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mLoginPresener = new LoginPresenter();
            mLoginPresener.attcheView(this);
        }
    
        public void login(View v) {
            mLoginPresener.login("admin", "12345");
        }
    
        @Override
        public void showResult(String result) {
            Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mLoginPresener.detachView();
    		mLoginPresener = null;
        }
    }
    

    在onCreate中我们创建了LoginPresenter对象,并将自己与其绑定。当Acitvity要销毁时,我们就解除绑定,这样就防止发送内存泄露了。

    方案4

    前面的方案将View层进行了处理,使用了接口来使用V和P进行解耦,能不能做进一步处理,比如使用泛型。P层不用关心V层具体是什么类型。甚至对V层和P层之间的数据交互也做泛型处理。这样的话耦合程度更小了。

    通用接口

    将View层和Presenter层做接口化处理,当然Model层也能做接口化处理,由于时间有限,这里只实现前两个。

    View接口
    public interface IView<D> {
        void showResult(D result);
    }
    

    D是表示数据,这样showResult可以处理任意类型的数据了

    prestener接口
    public interface IPresenter<D, V extends IView<D>> {
        void attcheView(V view);
    
        void detachView();
    }
    

    在Presenter中我们对V也做了泛型处理,这样Presenter可以既可以绑定Activity,又可以绑定Fragment

    通用抽象类

    抽象类是对接口做了一点点实现,这个看个人需求了,如果你感觉类太多的话可以把接口剔除掉,直接使用抽象类来做。

    Presenter抽象类
    public abstract class AbsPresenter<D, V extends IView<D>> implements IPresenter<D, V> {
        private V mView;
    
        @Override
        public void attcheView(V view) {
            mView = view;
        }
    
        @Override
        public void detachView() {
            mView = null;
        }
    
        public V getView() {
            return mView;
        }
    }
    

    AbsPresenter类对Presenter接口做了些实现

    Activity抽象类
    public abstract class BaseActivity<D, V extends IView<D>, P extends AbsPresenter<D, V>> extends AppCompatActivity{
        private P presenter;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            if (presenter == null) {
                presenter = bindPresenter();
                presenter.attcheView((V)this);
            }
        }
    
        public abstract P bindPresenter();
    
    	public  P fetchPresenter() {
    		return presenter;
    	}
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (presenter != null)
                presenter.detachView();
        }
    }
    

    BaseActivity类对将视图的绑定与解除抽了出来

    具体实现

    前面的两部分很抽象了,可以应对各种场景了,下面我们就应用到登录场景中。

    首先是LoginModel
    其实Model和Presenter直接也可以解耦的,可以定义一个Model接口出来,而Presenter持有Model接口的引用即可。但是由于时间关系。我们直接上Model了

    public class LoginModel {
    
        public void login(String username, String password, final OnResultListener listener) {
            OkHttpClient client = new OkHttpClient();
            RequestBody body = new FormBody.Builder().add("username", username).add("password", password).build();
            Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build();
            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    listener.onResult(response.body().string());
                }
            });
        }
    
        public interface OnResultListener {
            void onResult(String result);
        }
    }
    

    下来是View

    public interface LoginView extends MvpView<String>{
    
    }
    
    public class MainActivity extends BaseActivity<String, LoginView, LoginPresenter> implements LoginView{
    
        @Override
        public LoginPresenter bindPresenter() {
            return new LoginPresenter();
        }
    
    
        @Override
        public void showResult(final String result) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
                }
            });
        }
    
    	 public void login(View v) {
        	LoginPresenter presenter = fetchPresenter();
        	if (presenter != null)
            	presenter.login("admin", "12345");
    	}
    }
    

    定义了LoginView接口,并在MainActivity中对泛型做了具体化处理,比如数据类型指定为String类型。而且我们发现MainActivity中的代码更少了。

    最后是Presenter

    public class LoginPresenter extends AbsPresenter<String, LoginView> {
        LoginModel mLoginModel = new LoginModel();
    
        public void login(String username, String password) {
            mLoginModel.login(username, password, new LoginModel.OnResultListener() {
                @Override
                public void onResult(String result) {
                    MvpView<String> loginView = getView();
                    if (loginView != null)
                        loginView.showResult(result);
                }
            });
        }
    }
    

    总结

    我们通过递进的方式,一步一步对MVP架构进行完善,而且这是MVP架构中的一种体现方式。其核心思想急就是分离,这种思想在方案2已经体现出来了,所以不要拘泥于某种模版或规范,要灵活运用。

  • 相关阅读:
    luogu P3327 [SDOI2015]约数个数和
    生成函数
    luogu P4318 完全平方数
    SP5971 LCMSUM
    luogu P2522 [HAOI2011]Problem b
    UOJ #82. 【UR #7】水题生成器
    CF1147F Zigzag Game
    CF1106F Lunar New Year and a Recursive Sequence
    1114: 逆序
    1113: 递归调用的次数统计(函数专题)
  • 原文地址:https://www.cnblogs.com/xidongyu/p/7192186.html
Copyright © 2011-2022 走看看