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(); } }
好了,就這樣。畫圖太累了,如對你有幫助,就推薦一下吧。