前言
不管是iOS開發(fā)虐骑、Android開發(fā)、甚至是游戲開發(fā)赎线,MVX都是說到爛而且被普遍大眾接受的設(shè)計(jì)框架廷没,可這個(gè)框架一直以來都有很多誤區(qū)。寫此文的目的是方便自己整理一下MVX的思路垂寥,也提供大家思考颠黎。
架構(gòu)介紹
常用的設(shè)計(jì)架構(gòu)就幾個(gè),MVC滞项、MVP狭归、MVVM。軟件開發(fā)的套路無非就幾個(gè)步驟文判,畫界面过椎、拿數(shù)據(jù)、更新界面戏仓。而在這個(gè)過程中程序員為了不把所有的操作都塞到一個(gè)類文件去寫代碼疚宇,引申出MVX的架構(gòu)設(shè)計(jì)。
目的:解耦
1赏殃、MVC模式
Model:數(shù)據(jù)的獲取敷待、操作
更新:數(shù)據(jù)更新后,反饋給UI顯示通知View更新數(shù)據(jù)
View:看得見的東西
用戶事件:通知Controller點(diǎn)擊了按鈕
Controller:負(fù)責(zé)數(shù)據(jù)處理
數(shù)據(jù)操作:通知Model進(jìn)行數(shù)據(jù)操作仁热,比如從網(wǎng)絡(luò)上獲取數(shù)據(jù)
傳統(tǒng)意義上的MVC模式應(yīng)該是上述的部分的描述榜揖,但在移動開發(fā)中我們會對MVC產(chǎn)生了很多誤解。因?yàn)樵趇OS和Android這樣的框架下面開發(fā)抗蠢,我們基本只需要關(guān)心ViewController/Activity的實(shí)現(xiàn)就可以举哟。
大眾觀點(diǎn)認(rèn)為:
View:XML布局文件;Model:數(shù)據(jù)的操作類迅矛;Controller:處理數(shù)據(jù)炎滞、業(yè)務(wù)和UI。更像圖下的結(jié)構(gòu)
我們大量處理View的邏輯只能寫在Activity中诬乞,這樣Activity就充當(dāng)了View和Controller兩個(gè)角色,直接導(dǎo)致Activity中的代碼大爆炸钠导。
代碼例子:
public class MVCActivity extends AppCompatActivity {
EditText editText1;
EditText editText2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
editText1 = findViewById(R.id.data1View);
editText2 = findViewById(R.id.data2View);
//數(shù)據(jù)操作
String[] data = Model.getData();
//UI更新
editText1.setText(data[0]);
editText2.setText(data[1]);
}
}
一般來說震嫉,為了解決這個(gè)問題我們就開始引入MVP模式了.....等一下!D凳簟票堵!
既然MVP模式是為了把業(yè)務(wù)邏輯操作抽離出去作為一個(gè)Presenter,那我可以不可以把各種findViewById和setText的東西抽離出去呢逮栅?
所以MVC模式又變成了:
Model:沒變
View:xml布局+新建View類
public class MVCView extends LinearLayout implements MVCActivity.IView{
EditText editText1;
EditText editText2;
Button button;
public MVCView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
editText1 = findViewById(R.id.data1View);
editText2 = findViewById(R.id.data2View);
button = findViewById(R.id.saveButton);
}
@Override
public void showData(String[] data){
//更新
editText1.setText(data[0]);
editText2.setText(data[1]);
}
@Override
public void listenButton(OnClickListener listener) {
button.setOnClickListener(listener);
}
}
繼承自LinerLayout悴势,因?yàn)樵瓉淼牟季质鞘褂肔inerLayout窗宇,注意xml的根節(jié)點(diǎn)就變成我新建的View類了~
<c.damon.makemyprogress.mvx.mvc.MVCView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mvc_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/data1View"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/data2View"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/saveButton"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="Save"/>
</c.damon.makemyprogress.mvx.mvc.MVCView>
Controller:activity
public class MVCActivity extends AppCompatActivity {
interface IView {
void showData(String[] data);
void listenButton(View.OnClickListener listener);
}
IView mView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
mView = findViewById(R.id.mvc_view);
//數(shù)據(jù)操作
String[] data = Model.getData();
//UI操作
mView.showData(data);
mView.listenButton(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("123123123");
}
});
}
}
讀到這里,相信你已經(jīng)可以感受到這種寫法可以把大部分的UI邏輯解耦出去了特纤。
2军俊、MVP
Model: 數(shù)據(jù)的獲取、操作捧存。
View: 對應(yīng)于Activity和XML粪躬,負(fù)責(zé)View的繪制以及與用戶的交互。
Presenter: 負(fù)責(zé)完成View與Model間的交互和業(yè)務(wù)邏輯昔穴。
代碼例子:
Model:沒變
View:Activtiy+xml
public class MVPActivity extends AppCompatActivity implements Presenter.IView{
EditText editText1;
EditText editText2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvp);
editText1 = findViewById(R.id.data1View);
editText2 = findViewById(R.id.data2View);
new Presenter(this).load();
}
@Override
public void showData(String[] data) {
editText1.setText(data[0]);
editText2.setText(data[1]);
}
}
Presenter:新建一個(gè)Presenter類
public class Presenter {
interface IView {
void showData(String[] data);
}
IView mView;
Presenter(IView mView){
this.mView = mView;
}
public void load() {
//數(shù)據(jù)操作
String[] data = Model.getData();
mView.showData(data);
}
}
從思想上來講镰官,MVP跟MVC竟然是沒區(qū)別的,唯一的區(qū)別是結(jié)構(gòu)上的吗货,一個(gè)是把界面邏輯拆出去泳唠,另外一個(gè)是把業(yè)務(wù)邏輯拆出去。
在MVP中宙搬,V層與P層還是有一定的耦合度笨腥。一旦V層某個(gè)UI元素更改,那么對應(yīng)的IView接口就必須得改害淤,并且IView接口方法太多也太臃腫扇雕,如果這一層也能解耦就更好了。
MVVM
Model: 數(shù)據(jù)的獲取窥摄、操作镶奉。
View: 對應(yīng)于Activity和XML,負(fù)責(zé)View的繪制以及與用戶的交互崭放。
ViewModel: 負(fù)責(zé)完成View與Model間的交互和業(yè)務(wù)邏輯哨苛。
思想竟然又是一樣的,所以 MVVM = MVP + 雙向綁定工具币砂。
數(shù)據(jù)的雙向綁定:通俗點(diǎn)的理解是表象數(shù)據(jù)與內(nèi)存數(shù)據(jù)建峭,不管哪個(gè)發(fā)生變化,另外一個(gè)也同時(shí)發(fā)生變化决摧。比如我有一個(gè)EditTextView,在我設(shè)置一些值之后亿蒸,與之對應(yīng)的數(shù)據(jù)模型也同時(shí)發(fā)生變化。
好神奇的功能掌桩,相當(dāng)于把MPV中的showData()方法省掉了边锁。具體怎么實(shí)現(xiàn)呢?
代碼例子(偽代碼):
Model:沒變
View:Activtiy+xml
public class MVVMActivity extends AppCompatActivity {
EditText editText1;
EditText editText2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvvm);
editText1 = findViewById(R.id.data1View);
editText2 = findViewById(R.id.data2View);
new ViewModel(new ViewBinder(),editText1,editText2).load();
}
}
ViewModel:新建一個(gè)ViewModel類
public class ViewModel {
String datum0, datum1;
ViewModel(ViewBinder binder, EditText editText0, EditText editText1) {
binder.bind(editText0, datum0);
binder.bind(editText1, datum1);
}
public void load() {
//數(shù)據(jù)操作
String[] data = Model.getData();
datum0.setValue(data[0]);
datum1.setValue(data[1]);
}
}
ViewBinder工具類(偽代碼):
public class ViewBinder {
void bind(final EditText editText, final String datum) {
editText.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
if (!Objects.equals(s.toString(), datum.getValue())) {
datum.setValue(s.toString());
}
}
});
datum.setOnChangeListener(new OnChangeListener() {
@Override
public void onChange(String newValue) {
if (!Objects.equals(newValue, editText.getText().toString())) {
editText.setText(newValue);
}
}
});
}
}
ViewBinder的本質(zhì)是一個(gè)監(jiān)聽器波岛,內(nèi)部通過互相監(jiān)聽的方式去實(shí)現(xiàn)數(shù)據(jù)的雙向綁定茅坛,而DataBinding這個(gè)庫已經(jīng)幫我們做好ViewBinder所做的事情。
修改一下Activity類
public class MVVMActivity extends AppCompatActivity {
EditText editText1;
EditText editText2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvvm);
editText1 = findViewById(R.id.data1View);
editText2 = findViewById(R.id.data2View);
ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
binding.setHandlers(new Handler());
new ViewModel(binding).load();
//new ViewModel(new ViewBinder(),editText1,editText2).load();
}
}
修改一下ViewModel類
public class ViewModel {
ActivityMvvmBinding binding;
public ViewModel(ActivityMvvmBinding binding) {
this.binding = binding;
}
public void load() {
String[] strs = Model.getData();
binding.setDm(new DataModel(strs[0], strs[1]));
}
}
此處就不介紹DataBinding的用法了~大家可以參考其它資料
總結(jié)
我們重新總結(jié)一下MVC则拷、MVP贡蓖、MVVM中的職責(zé)
MVC | MVP | MVVM | |
---|---|---|---|
Model | 數(shù)據(jù)操作 | 數(shù)據(jù)操作 | 數(shù)據(jù)操作 |
View | View+XML布局 | Activity+XML布局 | Activity+XML布局 |
X | Activity | Presenter | ViewModel + 雙向綁定 |