從.NET的寵物商店到Android MVC MVP


1 一些閑話

  記得剛進公司的時候,我們除了做常規的Training Project外,每天還要上課,接受各種技術培訓和公司業務介紹。當時第一次知道QA和SQA的區別。Training Project時間其實比較緊張,給我們的就是一個英文的需求文檔。我們要做的就是數據庫設計、結構文檔、用例文檔、項目搭建、代碼編寫、單元測試,每個階段Leader會Review。除此之外,還要E-R圖、時序圖、用例圖。麻將雖小,五臟俱全。哦,對了,所有輸出必須英文完成。最后要求項目能一鍵安裝使用。

  不感興趣?請直接跳到第二部分,哈哈~

  Training Project其實就是做一個購物網站,做一個WinForm,最后用上WCF。對我來說,不是什么大問題,在學校已經做過類似的了。Scott Mitchell 60多篇的ASP.NET教程被我打印出厚厚的四本小書,代碼從頭敲了個遍。學完后就基本熟悉ASP.NET主流控件的使用,明白數據綁定、緩存以及三層架構的作用了。那么這時候,你可以達到初級的水平了。想進一步提升自己,就要懂得一定的代碼封裝、自定義控件、理解Pager的生命周期等等。關於書籍,理論方面個人推薦《你必須知道的.NET》、Jeffrey Richter《深入理解 .NET》和《Windows核心編程》。博客園內也有Scott Mitchell文章的中文翻譯:Scott Mitchell的ASP.NET2.0數據指南中文版索引。上個圖懷念一下:

  所以我的Training Project基本就是這個風格。當然,還有鼎鼎大名的PetShop。當時的水平,也就勉強明白抽象工廠,但這個寵物商店的架構圖是長這樣的:

  震住菜鳥有沒有?Talk is cheap, show me the code。廢話少說,放碼過來!代碼下載下來,光是Library就近20個,菜鳥完全有點無從下手了。你說讓我放碼,你卻退避三舍。

  扯遠了,回到我的Training Project。如果光是完成這個Training Project,倒沒什么。但是要求一個接一個。說一下印象中還算深刻的編碼規范。

  當時接受培訓的編碼規范是PHILIPS C# Coding Standard,沒聽說過?那也沒什么,微軟官網的一些Internal Coding Guidelines總要了解一下吧。我們的代碼被Leader要求得用上PHILIPS的規范。我記得我很認真地在每個接口和方法都加上注釋,該換行換行,該加括號加括號,變量命名也合乎英文語法(呵呵~你應該見過一半英文一半拼音的命名方式吧)。最后代碼Review的時候,Leader挑了一個讓我無語的地方:

/// <summary>
/// Gets product info by product id.
/// </summary>
/// <param name="id">The product id</param>
public Product GetProductById(long id);

  “你看,所有的語句結束后面應該跟一個英文的“.”,你這個The product id后面沒有。”

  我想肯定是我的代碼注釋寫得很規范,他挑不出其它的毛病來。你可能還看不出來的一點,Gets必須加s,呵呵。后來,我發現這樣的注釋簡直多余,一看方法就知道干嘛的好嗎。后來寫了點Objective-C,發現代碼基本不用寫什么注釋,方法名就是一條短語,自我解釋。有的人還把方法名寫成一條句子,也是夠離譜的。

  上面說了這么多,什么意思呢?知識儲備、動手能力很重要。代碼看過了也許你就忘了,敲過了才算學過,能總結出來才叫理解。

  • 設計模式是用來解決代碼重用的
  • 設計模式是用來隔離變化的
  • 架構是保證軟件的可用性、可擴展性、安全性

  設計模式是從編碼的層面提煉出來的一種總結,而架構則着眼於全局,對系統的高層次抽象。所以,如果你還沒有編寫過一定量的代碼(幾千行、幾萬行或十幾萬行,視個人而定),哪來的代碼重用、代碼可擴展?設計模式基本就是前人經驗的總結,有了一定的代碼基礎能更好地理解;架構則站在更高的維度,要求的就不單單是代碼經驗了,你還要懂硬件、操作系統、網絡環境等等,實踐和理論的結合。同時你還得了解技術的邊界,能做什么,不能做什么。

2 MVC

  下面終於輪到MVC和MVP登場了,剛接觸這個概念的同學可能會問:他們應該是一種設計模式吧。還真不是。那你上面還說了這么多設計模式和架構?不要緊張,這不是對比學習嘛。這個問題理清還真是需要費點力氣,還好已經有人把這個問題搞明白了:為什么MVC不是一種設計模式

  從Android的角度看一下MVC:

 

Model

  模型層就是一些基礎數據源,通常是數據庫SQLite、網絡請求的JSON、本地XML或Java對象數據。它代表了一些實體類,用來描述你的業務邏輯怎么進行組合,同時也為數據定義業務規則。

Controller

  控制器是與應用程序相關聯的動作集合,它負責處理待響應的請求。它通過界面響應用戶輸入,通過模型層處理數據,最后返回結果給界面。控制器扮演着模型和界面的粘合劑角色。

View

  界面就是各種UI組件(XML布局或Java自定義控件對象)。它只負責展示數據,同時接收控制器傳過來的結果。

  所以在Android中,activity界面就是View,本地數據或網絡數據就是Model,至於Controller嘛,看項目代碼怎么組織了。一般來說,activity可以認為是Controller,一方面它負責視圖的呈現,一方面控制業務邏輯(先從本地取緩存數據,再從服務端刷新;等等)並處理相關數據。做得好一點,無非再封裝一層BusinessLogic,activity再去調用這個BusinessLogic,從而減輕activity的代碼負擔。但也逃離不了BusinessLogic+activity就是Controller的范疇,因為兩者之間存在直接依賴,而且是依賴於具體實現。

  上圖模擬了界面可能被用戶點擊,通過事件傳遞到控制器,接着控制器發起一個網絡請求,響應結果經過轉換到了模型層,最后控制器取得模型層的數據並通知界面進行刷新。Android用到MVC的具體實現很多,如ListView,Adapter就是典型的Controller,它在數據變化的時候,就是這樣通知界面的:

adapter.notifyDataSetChanged();

  讓我們簡化上面的圖示:

  當然,這是一種理想狀態。在Android中,View和Model也有關聯的,所以更接近的圖示應該是這樣的:

3 MVP

  MVP(Model-View-Presenter),你可以把它看作MVC的一個變種,用來隔離UI、UI邏輯和業務邏輯、業務數據。

  Presenter代表界面負責處理UI事件,它也需要通過界面來獲得用戶的輸入,然后通過模型層處理數據,再返回結果給界面。跟View和Controller不同的地方在於:View和Presenter利用了接口機制,所以他們完全解耦。所以MVP比MVC更利於后期的擴展和維護,是因為它針對了接口編程。看看上面的PetShop架構圖,是不是看到了眾多的Interface?了解我放那張圖的用心良苦了吧,面向接口編程和合理的層次划分。看一個簡單的C#例子。

  接口定義:

public interface IProduct
{
    /// <summary>
    /// 獲取所有的產品信息
    /// </summary>
    /// <returns>產品信息集合</returns>
    List<Product> GetAllProducts();

    /// <summary>
    /// 通過產品編號獲取產品信息
    /// </summary>
    /// <param name="productId">產品編號</param>
    /// <returns>產品實體具體信息</returns>
    Product GetProductById(long productId);
}

  從SQL Server數據庫獲取數據

public class SQLServerProvider : IProduct
{
    public List<Product> GetAllProducts()
    {
        // TODO
    }

    public Product GetProductById(long productId)
    {
        // TODO
    }
}

  從Oracle數據獲取數據

public class OracleProvider : IProduct
{
    public List<Product> GetAllProducts()
    {
        // TODO
    }

    public Product GetProductById(long productId)
    {
        // TODO
    }
}

  使用

class Program
    {
        static void Main(string[] args)
        { 
            IProduct productProvider= new SQLServerProvider();
            productProvider.getAllProducts();

            // 或者
            IProduct productProvider= new OraclerProvider();
            productProvider.getAllProducts();
        }
    }

  只要接口不變,以后你想從SQLite、DB2獲取數據,只需要再寫個類實現IProduct接口就行了,完全不需要修改原有的類,然后在實例化的時候換一下new的對象。是不是有點開閉的意味?對擴展開放,對修改關閉。這就是設計模式中的開閉原則(OCP:Open Closed Principle)。利用接口編程,也方便了后期進行單元測試。

  回到我們的Presenter,總結一下MVP的關鍵點:

  • 用戶與View進行交互
  • View和Presenter是一對一關系
  • View持有Presenter的引用,但View對Model沒有引用 

4 MVP在Android中的實現

  有人實現了一個demo,我們來學習一下吧 。以下是類圖:

  四個接口:OnLoginFihishedListener, LoginPresenter, LoginInteractor, LoginView,兩個實現類:LoginPresenterImpl和LoginInteractorImpl,一個登錄界面LoginActivity。看起來有點費勁?我再把它抽象一下:

  代碼核心如下,注釋中的1->2->3->4->5就是具體的調用流程

public class LoginActivity extends Activity implements LoginView, View.OnClickListener {
    private LoginPresenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ......
        presenter = new LoginPresenterImpl(this);
    }

    @Override 
    public void onClick(View v) {
        // 1、調用LoginPresenterImpl進行校驗
        presenter.validateCredentials(username.getText().toString(), password.getText().toString());
    }

    @Override
    public void navigateToHome() {
        // 5、回調結果,LoginActivity作跳轉
        startActivity(new Intent(this, MainActivity.class));
        finish();
    }
}
public class LoginPresenterImpl implements LoginPresenter, OnLoginFinishedListener {
    private LoginView loginView;
    private LoginInteractor loginInteractor;

    public LoginPresenterImpl(LoginView loginView) {
        this.loginView = loginView;
        this.loginInteractor = new LoginInteractorImpl();
    }

    @Override 
    public void validateCredentials(String username, String password) {
        // 回調,通知LoginActivity,顯示加載中提示
        if (loginView != null) {
            loginView.showProgress();
        }
        // 2、開始調用LoginInteractorImpl中的方法
        loginInteractor.login(username, password, this);
    }
    
    @Override
    public void onSuccess() {
        // 4、回調,通知LoginActivity
        if (loginView != null) {
            loginView.navigateToHome();
        }
    }
}
public class LoginInteractorImpl implements LoginInteractor {
    @Override
    public void login(final String username, final String password, final OnLoginFinishedListener listener) {
        // 3、TODO,登錄邏輯。成功后回調給上層調用者
        listener.onSuccess();
    }
}

  好了,就這樣。畫圖太累了,如對你有幫助,就推薦一下吧。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM