Android中的MVC缘圈、MVP糠亩、MVVM架構(gòu)新的思考

前言

不管是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模式
image.png

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)

image.png

我們大量處理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
image.png

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
image.png

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 + 雙向綁定
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末曹鸠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子斥铺,更是在濱河造成了極大的恐慌彻桃,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仅父,死亡現(xiàn)場離奇詭異叛薯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)笙纤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門耗溜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人省容,你說我怎么就攤上這事抖拴。” “怎么了腥椒?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵阿宅,是天一觀的道長。 經(jīng)常有香客問我笼蛛,道長洒放,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任滨砍,我火速辦了婚禮往湿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘惋戏。我一直安慰自己领追,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布响逢。 她就那樣靜靜地躺著绒窑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舔亭。 梳的紋絲不亂的頭發(fā)上些膨,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音钦铺,去河邊找鬼订雾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛职抡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播误甚,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼缚甩,長吁一口氣:“原來是場噩夢啊……” “哼谱净!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起擅威,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤壕探,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后郊丛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體李请,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年厉熟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了导盅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡揍瑟,死狀恐怖白翻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绢片,我是刑警寧澤滤馍,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站底循,受9級特大地震影響巢株,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜熙涤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一阁苞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧灭袁,春花似錦猬错、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至软瞎,卻和暖如春逢唤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涤浇。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工鳖藕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人只锭。 一個(gè)月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓著恩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子喉誊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容