MVP模式和Clean模式

    從類圖上來看,MVP都是一個業務一個Presenter,每一個Presenter都是一個接口,它還包含了View的接口,用於定於和View相關的行爲,而後Activity等業務類實現View的接口,由於UI有關的操做只能在UI線程。數據庫

    採用MVP模式,和View相關的接口都要由業務類實現,天然,業務類自己就會有數量不小的方法,而邏輯相關的接口能夠放在Presenter裏面,而後由一個PresenterImpl去實現。編程

    說實話,雖然這樣看上去確實將Activity變成各類View和Presenter組合的模塊,可是控件的聲明仍是在Activity,因此Activity的職責就變成了View的方法實現和控件聲明。網絡

    若是咱們只是單純的想要在MVP模式下輸出一句Hello World到TextView上,是一件不太容易的事情,固然,MVP自己就不是用來解決這種微小場景。框架

    是否有更加簡單的模式可以應付全部場景?答案是幾乎沒有的,都是要結合實際場景,在大的框架模式不變的狀況下,容許作小的調整,好比一個應用總體的框架是MVP模式,但一些實在是細微的場景,有必要也使用嗎?ide

    MVP模式並非插拔式的,雖說真正在執行邏輯處理的是Presenter,而Activity只是提供對應的方法實現,可是假設要移除這塊業務,要清理的東西也是不少的,可是它能夠實現必定程度的複用,只要實現了View的接口,經過對應的Presenter,就能夠擁有這些接口定義的行爲協議,可是也僅僅只是行爲協議,具體的實現仍是要本身編寫。佈局

    這所謂的複用,就是將業務類做爲一個系統運做的部件,只要可以知足部件的要求,系統就能立刻運轉起來。測試

    MVP爲了保證業務類更好的成爲部件,它就必須儘量的解耦,如和UI相關的接口在View接口裏,而邏輯相關的方法在Presenter裏,而且還會多一層PresenterImpl,每一個View的接口方法在Presenter哪一個方法裏面調用,都是在這裏進行處理,從而將業務類打形成一個清晰的部件,它和其餘部件之間沒有任何關係。優化

    固然,咱們也能夠將業務類上升爲PresenterImpl,這樣就能減小不少類的編寫,不過並不建議這樣作,由於業務類若是成爲PresenterImpl,那麼若是出現相似功能的業務,就須要到這個業務類複製粘貼相關的代碼,若是將這些邏輯鎖在一個PresenterImpl裏,只要聲明這個PresenterImpl就能使用了。this

    每一個業務就算類似,也可能存在必定的差別,爲了保證結構通用,是要儘可能避免引入這些差別,差別就要隔離在對應的類裏面,這就致使可能會有一些AbstractPresenter的產生,而後每一個業務的Presenter在繼承AbstractPresenter的通用行爲基礎上,再實現本身的行爲。編碼

    這是不可避免的,若是真的想要結構上清晰,提取通用業務是必定要的,而這部分工做每每會帶來類數量的增長,由於它只是通用類,具體業務也會有本身對應的業務類。

    咱們能夠這麼理解,MVP就是合理的規定,哪部分代碼應該寫在那裏,而誰應該寫哪部分代碼的一個約定,這也是MVC,MVVM等框架模式都具有的能力。

    在咱們以往認知中,依賴倒置原則要求咱們面向抽象編程,每一個部分都應該是和抽象打交道,而不是具體的實現,實現是會變的,可是抽象是幾乎不會變的。

    只要好好遵循這個原則,都能寫出很好的代碼,結構清晰,而且具備良好的維護性,不管MVP,MVC,仍是MVVM,本質都是這個原則的實現產物。

    MVP相比MVC來講,它有一個優點:它的Presenter是能夠測試的。

    單純的Presenter是沒有和View有任何關聯的,有關聯的是PresenterImpl,因此咱們若是隻是測試Presenter,是並不牽扯到UI,而Android的測試中,涉及到UI都是很麻煩的事情。

    對Presenter的測試,就和傳統的接口測試是同樣的。

    對於Android開發人員來講,因爲xml佈局文件的存在,View的繪製並不僅是單純的代碼編寫,咱們但願佈局也可以複用。

    Android爲佈局的複用提供了必定的支持,像是include的使用,就能把一個佈局,切割成幾個佈局的組合。

    咱們能夠把每一個佈局按照自己必定的功能,劃分爲幾個include,而後每一個include都有本身的邏輯處理類,好比說,咱們能夠定義一個ViewController,這個ViewController負責View的初始化和對應功能實現,那麼一個Activity自己就只是多個ViewController的組合類,而Activity的佈局,也是多個include的組合。

    若是劃分得足夠清楚,能夠經過組合不一樣的ViewController來實現不一樣的界面組合。

    這種模式在定義上,和MVC是一致的,ViewController就是Controller,Android的Activity自己在Android的機制裏面其實也是Controller,只不過對於開發者來講,接觸到的是Activity,因此就在Activity上作功夫了。

    在咱們以往的依賴關係或者結構設計上,都是相似樹狀的結構,經過賦予每一個類依賴,實現或者繼承的關係,將他們鏈接到一塊兒。

    這種結構就是依賴關係樹。

    之因此是依賴關係樹這種形狀,很大關係是由於咱們的設計,實質上只有兩種:自頂而下或者自下而上,咱們會先肯定一個點,而後從這個點開始延伸出和其餘點的關係,從而不斷輻射出去。

    Clean模式在依賴關係上,是畫一個同心圓。

    

    Clean模式的解釋是,依賴應該是從外到內,所以在實現上,率先實現內層,再實現外層,而每一層都會把內部那一層徹底包裹起來,造成一個相似洋蔥的結構。

    咱們在實現上,一般都會實現最核心的那一層,好比說,咱們想實現一個功能,輸出Hello World,那麼首先要實現的就是輸出Hello World的方法,不過咱們這個方法後面可能不僅是輸出Hello World,所以就抽象成輸出傳入的String的方法,而後咱們再編寫外部傳入String的方法,有多是鍵盤輸入,有多是其餘輸入等等。。。這樣一層一層寫下去,一直寫到觸發這個需求的地方,需求的最初位置,也是依賴的起點。

    因此咱們日常的作法和Clean模式對於依賴的理解是沒啥區別的,固然,也不能說Clean模式就是畫個圓就說是新的東西,它自己更增強調的是,乾淨。

    Clean,就是乾淨,而什麼樣的程度是乾淨,乾淨又能作到什麼呢?

    所謂的乾淨,是由於Clean模式的依賴是從外到內,所以內部對外部是無感知的,就像咱們剝洋蔥,每剝掉一層,裏面依然仍是完整的,只不過變小的洋蔥。

    Clean模式是如何作到這樣的獨立性呢?

    在Clean模式中,DB,Web,Devcices等數據來源是最外層,這個沒毛病,數據輸入都是任何一個系統的起點,可是它把UI也放在了最外層。而後再進一步的層級是Controllers,GateWays和Presenters等,按照依賴從外到內的設計思想,這些層級也是最早接觸這些數據來源和UI,接着的層級就是Use Cases,也就是用戶場景,最裏面就是Entities,這個能夠理解爲用戶場景中的實體。

    UI變成了一個獨立的層級,和DB,Web等並列,而DB,Web這些層級在設計上,本來就具有自測的能力,而且它應該也是最早被測試的,由於它們是框架提供的能力。這樣一層層下去,每一個內層在測試的時候,只須要了解它的內層,不知道它的外層。

    咱們好奇的是,UI怎麼就變成了一個獨立的層級?

    這裏的UI應該是各類組件,前面有關MVP的討論中也提到,PresenterImpl確實是須要從外部傳入View接口的實現類,因此UI做爲最外層的依賴,也是沒問題的。

    在Android中,通常的層級並不會超過三層。實現層,也就是框架層,像是Web,DB等,就是Clean模式的外層,而中間層是接口適配層,負責鏈接實現層和內層的業務邏輯層。

    在Clean模式中,業務邏輯層,也就是內層,應該是對外層毫無感知的,因此咱們測試業務邏輯層,徹底能夠跳過框架層。

    咱們很常見的作法就是在Activity中調用網絡接口來獲取數據,而後在對應的回調中將該數據展現到對應的控件上,若是按照Clean模式,這時候不該該是直接就將數據和控件進行綁定,中間要有一個接口適配層,將這二者獨立開來。

    任什麼時候候,兩個獨立的部分都不該該直接交互,而是要經過一個抽象,依賴倒置原則在這裏的產物就是接口適配層。

    對於Android,Clean模式的要求就是業務邏輯層徹底不能持有外層的引用,也就是說,內層提供一個它須要的數據模型,而接口適配層負責將框架層傳遞過來的數據轉化爲業務邏輯層須要的數據模型,而後再傳給業務邏輯層,這就是適配層的工做。

    所以,在Android中,Clean模式的內層必須暴露接口,以便外部傳遞須要的數據,而外層是知道這些數據模型,能夠跳過內層,組裝這些數據模型進行測試。

    沒法應用到實際場景的模式都是假模式,所以咱們如今趕忙開始試試Clean模式在代碼上是如何表現的。

    咱們能夠簡單點,假設一個用戶場景:獲取到某個來源的字符串,而後顯示到TextView上。

    從最核心的內層開始編寫。

    最核心的內層就是用戶場景,而且它是與外層毫無關聯的,可是它須要暴露一個回調,負責和上層打交道,因此它接受一個字符串,而後輸出這個字符串:

public class Interactor {
    private Callback mCallback;

    public Interactor(Callback callback) {
        mCallback = callback;
    }

    public void run(String info) {
        if (mCallback != null) {
            mCallback.showInput(info);
        }
    }

    public interface Callback {
        void showInput(String info);
    }
}

    對於MVP模式來講,最核心的部分就是Interactor,它負責將外部的Model轉換成ViewModel。

    在MVP中,Presenter是不直接操做View和Model的,它要作的工做就是把ViewModel傳給View。Model並不等於ViewModel,Model是原始的數據,而ViewModel是視圖數據,好比說一個登錄頁面上的密碼和用戶名,這些數據的集合就是一個ViewModel。

    Interactor既然做爲最內層,它就不該該直接和最外層拿Model,因此咱們須要一箇中間層Converter。

    咱們假設這裏的場景是從User模型中獲取name字段,這就是Interactor的數據來源,那麼Converter的職責就是從接受到的User模型中取出name傳給Interactor。

public class Converter {

public void converterNameToInteractor(Callback callback, User user) {
new Interactor(callback).run(user.getName());
}
}

    依賴是從外到內的,因此Interactor不能直接從外層設置Callback,它不能和外層有任何交集,這部分工做都在中間層Converter。

    Clean模式向咱們作出了保證,Interactor是能夠測試的,並且應該是能夠獨立於Android系統,在任何JVM機器上測試的代碼,由於它已經隔離了外層的依賴,而這部分隔離的標準就是Interactor不依賴於任何框架的庫和包,它依賴的是Java的庫和包。

    按照Clean模式的原則,咱們這裏的Converter有個問題:它知道了User這個Model。

    這個有什麼問題呢?User是外層的數據模型,而Converter從這個Model提取出Interactor須要的name這個String,而後Interactor經過Callback將name這個ViewModle傳遞給外層UI顯示。

    這個過程徹底沒有問題,可是Clean模式要求內層不能知道外層的東西,中間層的Converter如今知道了外層的User,這個是要剝離的。

    咱們只要將User這個參數修改成String就能夠了。

    這樣就會產生一個疑問:Converter不是將外層的數據模型轉換爲內層的數據模型嗎?那麼User.getName這個操做放在Converter不是應該的嗎?

    道理上是這樣的沒錯,可是爲了保證依賴上的乾淨,好比說,咱們如今要測試Converter,可是有了User這個依賴,就要知道外層了,就不能獨立測試了,並且咱們這裏只是一個簡單的參數,假設咱們的Interactor須要的是不一樣類型的多個字段,它本身自己可能會爲此提供一個Model,那麼Converter的職責就是接受須要的字段,而後組裝這個Model。

    爲了獨立,犧牲了便利,這在代碼設計上是很常見的考慮,可是咱們也要權衡一下,這犧牲的便利和換來的獨立,哪一個更加劇要。

    接下來咱們只要讓Activity實現Callback接口就能夠了。

public class MainActivity extends AppCompatActivity implements Interactor.Callback{
private TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

textView = (TextView) findViewById(R.id.text);
Converter converter = new Converter();
User user = new User("張三");
converter.converterNameToInteractor(this, user.getName());
}

@Override
public void showInput(String info) {
textView.setText(info);
}
}

     Activity就是外層,它這裏有數據的來源,UI的展現。

     Clean模式大概就是這樣的結構,咱們能夠看到依賴確實是一步一步傳遞過去的,每一層均可以獨立於外層進行測試。

     這一路下來,Presenter去了哪裏?

     固然,這裏咱們仍是能夠抽取出Presenter:

public class Presenter implements Interactor.Callback{
    private View mView;

    public Presenter(View view){
        this.mView = view;
    }

    @Override
    public void showInput(String info) {
        if(mView != null) {
            mView.showText(info);
        }
    }

    public interface View{
        void showText(String text);
    }
}

     而後Activity再修改爲這樣:

public class MainActivity extends AppCompatActivity implements Presenter.View{
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.text);
        Presenter presenter = new Presenter(this);
        presenter.showInput(new User("張三").getName());
    }

    @Override
    public void showText(String text) {
        textView.setText(text);
    }
}

    實際上,Converter和Presenter是並列的,前面的例子中,Converter其實是把Presenter的工做也作了,不是一個單純的轉換,是有一個分配數據的操做,因此咱們若是要加入Converter這個層級,必須保證它就僅僅只是一個數據轉換的類。

    如今咱們修改Converter:

public class Converter {

    public String converterName(String name) {
        return name;
    }
}

   而後Activity再這樣修改:

public class MainActivity extends AppCompatActivity implements Presenter.View{
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.text);
        Presenter presenter = new Presenter(this);
        Converter converter = new Converter();
        User user = new User("張三");
        presenter.showInput(converter.converterName(user.getName()));
    }

    @Override
    public void showText(String text) {
        textView.setText(text);
    }
}

    如今只是由於Converter要轉換的類型只是String的單一字段,若是是自定義類型,就不會顯得這麼小題大作了。

    Clean模式的依賴是從外到內,那麼外層可否跳過中間層,直接接觸內層呢?好比說,咱們這裏的Activity乾脆就實現Callback這個接口?

    最好不要這樣作,咱們要保證乾淨,就要完全的隔離,保證每剝掉一層,內部仍是完整的。

    不過基於Java的語言特性,內層提供的接口,誰均可以實現,而後只要由中間層傳過來就好了,也是可以保證依賴從外到內的原則,若是真的想要嚴格隔離,咱們這裏利用訪問權限來控制,將中間層和內層放在同一個包裏,內層的接口都只有包訪問權限,不一樣包的外層天然就沒法訪問到了。

    命名空間在隔離上是可以發揮特別好的做用,所以咱們要作好包的管理。

    在不少實際的編碼工做,若是嚴格按照外層-->中間層-->內層,內層-->中間層-->外層這種訪問順序,能夠預計,中間層的類的數量可能會爆炸,好比內層不能直接訪問數據庫這樣的外層,那麼它就會經過一箇中間層來獲取數據,有可能只是簡單的查詢。

    固然,咱們能夠優化中間層,好比對某個表的操做能夠合併到一個類裏面,這樣就會減小不少中間層。

    嚴格並非壞事,規矩仍是要遵照的,咱們能作的靈活,就是中間層的管理,可是外層和內層,是必須保證必定是隔離開來的。       

相關文章
相關標籤/搜索