@shark0017
2015-12-30T16:20:17.000000Z
字数 4130
阅读 2176
模式
我们的业务里面经常会出现这么三种东西,这三个东西一定要广义理解为层,他们绝对不是狭义的类对象(因为有些语言中会有view、controller、model这样的类)。那么所谓的各种模式就是这三者的不同的组合和通信方式。
要说明白这个问题,就要知道哪些是v,哪些是m,哪些是c。
V:视图层。android中的view,比如textview,button这样的,自定义的view当然也属于此类。view层是可以独立数据而显示的,它里面没有什么程序逻辑,仅仅是做表现。
除了这些类对象外,activity、fragment算不算view呢?adapter是什么呢?如果看前面的定义,他们貌似都对对不上号,不过我们可以进行思维方式的转换,人为定义它们的意义。
M:用于封装业务逻辑相关的数据以及对数据的处理方法。M本身是完全独立的个体,并且应该能被监听到。M不应该知道view的存在,方便进行复用。
C:控制层。用来控制数据、处理view和数据的交互,它处理来自view的交互信号和数据层的改变。早期的c层是键盘和鼠标,所以早期是可以直接面向用户进行操作的,但是在移动时代慢慢变成了一个纯的控制对象。
我刚接触android的时候就听过android是MVC模式的,因为android的view层可以理解为xml层,自定义view也是较为独立的。但之后发现很多项目中在自定义view中处理了很多业务逻辑,而且在activity中做了view和controller的事情,慢慢android项目就成了下面这样:
举例:
/**
* controller 层
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // view层
final Button button = (Button) findViewById(R.id.button); // 绑定view
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// request network 做数据处理
HttpUtil.doPostAsync("http://www.baidu.com", "kale", new HttpUtil.CallBack() {
@Override
public void onRequestComplete(String result) {
button.setText(result); // 更新视图, From network
}
});
}
});
}
}
为了说明的简便,这个代码没进行网络分层(activity中写了url,无网络层)。
分析
在这段代码中我们是认为xml文件就是view层,activity做view和model的绑定和关联操作。但在这种情况下activity就会越来越臃肿,即使有fragment的加入,但还是没办法做到给activity减负的工作。
我先抛几个问题:
activity需要做view的绑定工作么?
activity需要做view的动画操作么?
activity得到网络的结果后需要做处理结果的操作么?
activity需要做不同状态下view的状态改变的操作么?
如果你觉得activity仅仅是c,那么它做了绑定视图和相应视图事件的工作,甚至还会有视图动画的操作,它就不仅仅是c了。如果你觉得它是v,它又做了很多事物操作,不仅仅是纯粹的v。为了解决activity臃肿和含义不清的矛盾,慢慢出现了mvp规范。
mvp是为了解决activity的尴尬处境而出现的,其实仅仅是把mvc做了一个逻辑比较清晰的改造,产生了清晰的封层。这是某个使用mvp的项目中activity的代码:
presenter = new AppInfoPresenter(); // p层
mShowPackageNameBtn.setOnClickListener(v -> {
v.setEnabled(false); // activity变成v层,这里控制view的相关状态
// 点击后的事情交给p做,p做完后应该给v一个回调。为了说明简单,这里是同步回调。
string name = presenter.getPackageInfo(getApplication());
mShowPackageNameBtn.setText(name); // 得到回调后更新视图
});
现在的这种方式将activity和xml文件变成了一个v,那么所有事物都是交由p做。这样的好处就是model对外层不知情,p对view不知情。于是变成了这样的一个蛋形结构:
内层对外层不知情的好处就是内层可以随意地做复用,坏处就是需要建立相互通信地机制,常见的就是各种回调。当然,你可以用Rx的方式很简单的做回调,但是我们是否真的要采用这种严格的回调方式?
比如让P对v知晓,v也知道p的存在,可以么?来看看这种方案是什么样的。
首先让activity实现某个接口比如IAppInfoP,然后让P调用这个接口对象进行交互。
Activity中的代码:
presenter = new AppInfoPresenter(); // p层
mShowPackageNameBtn.setOnClickListener(v -> {
v.setEnabled(false); // activity变成v层,这里控制view的相关状态
// 点击后的事情交给p做,不会给view回调
presenter.getPackageInfo(getApplication());
});
Presenter中的代码:
public class AppInfoPresenter extends BasePresenter<IAppInfoUi> implements IAppInfoP {
@Override
public void getPackageInfo(Context context) {
// p对v知情,直接调用v中的public方法。getView其实得到的就是activity的接口对象
getView().onGotPackageInfo(context.getPackageName());
}
}
这个的好处很明显,activity变了纯的view,而p和v的交互也不用各种回调了(将activity整体变成了一个回调接口)。那,这种方式有不会有什么问题?
p知道了v,那么p的复用性就丧失了。所以你看到了我利用接口来降低了互相知情的而造成的影响。但这样,你就必须在写view的时候定义很多接口。如果不定义接口,你就必须把activity用fragment做分割。因为业务需求是经常改变的,给p和v定义接口,每次改都好麻烦,不定义接口可以么?
好吧,看下不定义接口的情况下有没有什么问题:
Presenter:
public class Presenter {
public MainActivity mActivity;
// 省略初始化MainActivity的代码
public void loadData() {
// request network 做数据处理
HttpUtil.doPostAsync("http://www.baidu.com", "kale", (result)-> {
mActivity.fromNetwork(result); // 更新视图, From network
}
});
}
}
Activity:
@Override
protected void onCreate(Bundle savedInstanceState) {
final Presenter presenter = new Presenter(this); // presenter
button.setOnClickListener(v-> {
presenter.loadData(); // 让p不知道这是因为点击而触发的动作
}
});
}
/**
* 被presenter调用
*/
public void fromNetwork(String name) {
button.setText(name); // 更新视图, From network
}
}
用这种方式p中包含了v对象,那么不用写任何回调就能直接触发v的动作,而且不用写接口和回调,甚至还可以支持一个view有多个p的需求。但是如果你这个view被复用了(activity基本不会被复用),那p和v就必须一起变。这种方案的一大坏处就是灵活性会比较低,设计上就定死了v可以有多个p,但一个p只能对应一个v。
我们看到了上述mvp的两种实现方案,第一种灵活但是写起来复杂,第二种简单,但是灵活性不足。mvvm利用数据绑定的东西,自动化实现了第一种的回调模式,而且也不用写任何接口,同时也因为在p中操作的就是单纯的数据对象,所以不会出现p和v的关联。
上面图说明的是首先我们看到在mvvm中m、v、vm是完全独立的,viewdata是一个中间产物,是一个view的数据对象,它可以注入到任何view中。从右边来看我们操作的就是一个逻辑结构,完全不用管理view相关的东西,在view层只需要把viewdata注入到view层中就行了,这样不同的view层可以对应一个或多个viewdata,viewdata也可以对应多个view。现在蛋形结构变成了这样:
它的好处就不用说了,不用写回调,而且还可以轻松的测试。至于怎么实现,以后会说到。
参考自:
http://www.jianshu.com/p/add73330d106
http://www.jianshu.com/p/e7b6ff1bc360
http://www.jianshu.com/p/918719151e72
http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html