編輯:關於Android編程
Android開發領域近一到兩年內有一種架構模式逐步流行起來,就是今天我們要談的MVP模式。聽名字感覺好厲害的樣子,Most Valuable Player? 不,別誤會,這個MVP不是NBA的最有價值球員。而是Model View Presenter。區別於我們開發中傳統的Model View Controller(MVC)。這次我要說的不是怎麼寫MVP,因為網上的此類文章已足夠大家學習了,那麼我要說的是為什麼MVP寫成那個樣子。(首先聲明一下:本人經驗並不是很豐富,只是看過點書而已,對於技術的理解,也只個人的拙見,望各位大牛輕噴!)
好,我們閒言少敘,步入正題。
首先,講編程的話,不舉例子總感覺不知所雲似的,程序員也最忌諱手懶,好吧,那我們簡單的提一個最最容易的需求:設計一個顯示未來幾天天氣的列表,要求是從服務器請求數據,請求之前彈出加載進度條,請求到數據之後隱藏進度條並顯示數據。(我想每個Android開發者都是從這樣的例子走出來的,其實Android開發也基本就是這樣,哈哈。) 有人就會說了,so easy 嘛!
直接就要上代碼:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 這裡假裝顯示進度條
showProgress();
requestWeathers();
}
private void showProgress() {
// 假裝顯示進度條
}
private void requestWeathers() {
// ...一系列異步請求
}
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
// 這裡假裝拿到數據
// 假裝解析數據
// 假裝隱藏進度條
// 假裝刷新頁面
super.handleMessage(msg);
}
};
}
恩,看起來很好嘛,沒什麼不對啊。其實如果你是一個初級(本人也是^_^)而且沒想過要跳出初級的開發者,這樣寫也無可厚非,因為功能已經實現,運行的沒有問題,老板也很高興並不會扣你錢。但如果這時又讓你在這個頁面加載一些別的數據呢,又讓你加上動畫效果,或者又讓你將天氣展示方式改一改等等。。。總之,產品狗不是白叫的,你懂的。這樣隨著時間的推移,需求的變化,你會發現你的上帝Activity已經雜亂不堪了,每一次修改都是一段痛苦的折磨。相信這種情況也確實發生在許多開發者身上。好,接下來我們來談一談如何分析、設計一下我們的代碼來更好的擁抱可愛的產品提出的變化。
其實如果你想在技術上,編程思想,以及職業道德(哈哈,扯得有點歪)上有更高的追求,我想你在拿到需求的時候不應該只想著如何實現,更重要的,你應該根據你掌握的知識盡可能的設計出你針對該需求的解決方案,而這個解決方案不能僅僅是如何去實現,因為實現只是編程中最基礎的東西,更重要的是,如何組織好這些實現,就像建築一樣,你沒有很好的設計,再好的磚瓦,如果亂造,即使建成了,也只是豆腐渣工程。好,那麼有人會問了,你說設計,那怎麼下手呢。(我強調一下,上面所舉得例子,確實不需要怎麼設計,只是用來講解的,勿噴!)
說到設計,那我們就得從面向對象說起了,我們知道Android是用Java開發,而Java是一門已經經歷了三十年風雨的很成熟的面向對象語言,Java學起來不難,但是要達到精通,感覺真的不是一朝一夕的事。在Java界有一本被稱為聖經的書《Think in Java》(java編程思想),這是我買的第二本關於java的書,當時買的時候只是因為網上很多人說這本書是java的聖經,買來之後也只是看了看前兩章,後來也是偶爾翻翻而已。直到後來看過幾章《Android源碼設計模式解析與實戰》,看到裡面講的什麼面向對象六大原則,各種設計模式,這時我突然發現其中的思想貌似都能在《Think in Java》的第一章《對象導論》中找到對應的解釋或者說相應的概括,也突然明白了一些“聖經”的含義和分量。
在講面向對象之前還要提一個概念:抽象。因為這是對編程核心思想的最精簡的概括。所謂抽象,就是在問題空間(通常指業務,或者說需求或者實際問題)和解空間(機器,或說計算機或者代碼)之間建立關聯。而面向對象就是讓我們可以將在問題空間中的元素及其在解空間中的表示稱為對象的一種編程方式。這種思想的實質是:程序可以通過添加新類型的對象使自身使用於某個特定問題。因此,當你在閱讀描述解決方案的代碼同時,也是在閱讀問題的表述。(以上幾句是Think in Java原文,經典的描述了面向對象的思想,需好好理解)。那麼,我們在使用面向對象的方式編程時,主要的挑戰就是,如何在問題空間中的元素和解空間的對象之間建立一對一的映射關系。那麼處理好這樣的映射,也就成了設計一個好的程序的關鍵所在。(不知道大家有沒有明白一些^_^)
好,了解了面向對象的相關知識之後,我們回到上面的例子中。首先根據需求我們做一個簡單的抽象描述即:操作頁面,再操作數據,最後根據數據操作頁面。其實這也就是我們上面說的問題空間(一種業務的描述,只是我們做了個簡單的抽象)了。那麼接下來我們要做的就是在解空間,也就是我們的代碼,建立與上面的問題空間的映射了。由於我們已經知道上面的寫法有很多弊端,隨著需求的變化和增減,代碼邏輯會愈發混亂,頁面和數據的操作錯綜復雜,難以梳理。所以為了解決這個問題我們對代碼進行分層,依據的是設計原則之一:單一職責原則,這裡對其進行一個簡單解釋:《Android源碼設計模式解析與實戰》中是這樣描述的:就一個類而言,應該僅有一個引起它變化的原因,簡單來說,一個類中應該是一組相關性很高的函數、數據的封裝。但在介紹的後面也提到,單一職責原則的劃分界限並不是那麼清晰,最大的問題就是對職責的定義。每個人都會有自己的理解。而我這裡就將類內部的操作內容定義為類的職責了,即操作頁面在一個類中,操作數據則在另一個類中,那麼這就會出現一個問題:頁面和數據存在邏輯關系,也就是要有交互,那這個交互處理要放在哪呢,頁面中還是數據中?好像都不合適,於是中介類就誕生了。好了,簡單的分析之後是,時候走一波代碼了;
MainActivity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
// 中介
private Presenter presenter;
private TextView mTvRequestData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mTvRequestData = (TextView) findViewById(R.id.tv_request_data);
assert mTvRequestData != null;
mTvRequestData.setOnClickListener(this);
// 實例化中介
presenter = new Presenter(this);
}
@Override
public void onClick(View v) {
// 發起請求
presenter.request();
}
public void onResponse(String data) {
// 請求回的數據
mTvRequestData.setText(data);
}
}
Presenter:
@SuppressWarnings(“all”)
public class Presenter {
private MainActivity activity;
private Model model;
public Presenter(MainActivity activity) {
this.activity = activity;
// 實例化數據層
model = new Model();
}
public void request() {
// 調用數據層,發起請求
model.requestData(new RequestCallBack() {
@Override
public void onResponse(String data) {
// 請求回結果調用Activity中的接收結果的方法
activity.onResponse(data);
}
});
}
}
Model:
public class Model {
public void requestData(RequestCallBack callBack) {
// TODO: 2016/12/17 這裡假裝請求數據,返回結果之後接口回調
callBack.onResponse("這是請求回的數據");
}
}
有人看過之後可能會問了,恩?你這也不是MVP啊?我知道,我也沒說這是MVP,但是這樣的確解決了我們之前的問題,分出了清晰的層次:Activity(或者Fragment)只做頁面的操作,Model中只做數據的操作,Presenter中處理二者的交互。不知道你曾經是否這樣寫或者想這樣寫過代碼來解決上帝類無比臃腫的問題。
但是如果一段時間之後,產品(狗)突然心血來潮要你來改這段你很滿意的代碼,這時候你再去看這些代碼,我想就會產生一個問題:在你看Presenter中的邏輯的時候你不知道(或者說已經忘了)activity的引用是以什麼形式存在的,因為activity的功能太多了。換句話說:你在寫這段代碼時,本來對應的問題空間是:操作數據,操作頁面,二者交互。但是現在你的“操作頁面”的類不只是能操作頁面,還有很多別的功能,而在這段代碼中你現在卻很不幸的忘了它都用來做哪些工作了,這樣的話,邏輯就有些不好弄了,因為說實話Activity能做的事,你都不一定能說的全。關於這一塊的問題,我想最好舉個例子來說明一下:不知道大家有沒有看過沈騰演的《一念天堂》,主角沈默在不同的場景扮演著不同的角色,演繹著多樣的人生。其實他在我們編程中就好比Activity這樣一個有著很多功能或者說任務的對象,而他所處的場景或者公司,就是我們的問題空間。他在自己的飯店,是老板,能做飯,能指揮員工干活,他在入職的一個詐騙公司,是一個工作努力,善於騙錢的員工。在不同的場景,他有自己的職責,承擔著相應的工作。但他不能在或者說不應該在他的詐騙公司中炒菜做飯,也不能在飯店進行詐騙工作。影射到我們的開發中就是:Activity(或者Fragment)在加載數據、展示數據這一問題空間中,除了負責視圖的操作之外不應該在負責其他的工作,那麼就可以根據接口隔離原則將Activity(或者Fragment)的操作視圖的功能分離出來(注意這裡的分離不是指分隔出來,而是分離出一個抽象的角色),即:將接口粒度最小化。這樣在我們的問題空間中層次就更加清晰了。(就先說這麼多吧,最近工作很忙,寫了好多天,可能感覺有點亂。。。大家有興趣的就將就看看吧,後期如果有時間我再重新寫一篇)
※效果 ※使用方法 package com.fancyy.calendarweight; import java.util.ArrayList; import j
玩過自定義View的小伙伴都知道,在View的繪制過程中,有一個類叫做Path,Path可以幫助我們實現很多自定義形狀的View,特別是配合xfermode屬性來使用的時
前言 本文實現的效果:文本框輸入為空時顯示輸入的圖標;不為空時顯示清空的圖標,此時點擊清空圖標能清空文本框內輸入文字。正文 一、實現效果 二、實現代碼 綁定事件 復
一對一雙向關聯映射(六)上篇博文一對一單向關聯映射(五),我們介紹了一對一的單向關聯映射,單向是指只能從人(Person)這端加載身份證端(IdCard),但是反過來,不