概述

(關於 MVC 在 POSA 的說明可以參考這篇,本篇是套用 MVC 於 Android 的描述和實作)
MVC 將架構分為 3 個部分,分別為 Model(模型層),View(視圖層),Controller(控制層)
View(視圖層):負責與使用者互動並顯示來自模型層的資料。
Controller(控制層):負責根據來自視圖層的互動訊息,指揮模型層更新資料或指揮視圖層重新載入模型層的資料。
Model(模型層):負責管理視圖邏輯和業務邏輯,提供網路和資料庫的監聽和修改介面。

設計理念

通常視圖邏輯發生修改的機會高於業務邏輯發生修改的機會,因此希望當這兩者發生變化時不會影響彼此(業務邏輯發生變化時不會影響視圖邏輯,視圖邏輯不會影響業務邏輯)。
在 MVC 架構中,View 依賴於 Model 和 Controller,Controller 依賴 View 和 Model,Model 完全獨立不依賴其它兩者。
View 接受使用者輸入事件並轉發給 Controller ,再根據 Controller 的指令顯示 Model 的資料。
Model 提供資料並更新資料,View 的顯示內容就是來自 Model 提供的資料,Controller 也會更新 Model 的資料。
Controller 根據來自 View 的使用者輸入事件,發出指令給 Model 更新資料並通知 View 重新載入 Model 的資料。
根據 Controller 更新 Model 資料的方式,可以分為主動和被動。因此 MVC 也可分為主動模式(Active)和被動模式(Passive)。

被動(Passive)模式

在被動模式中 ,Controller 是唯一可以更改 Model 資料的角色,而 View 只能根據 Controller 的指令被動從 Model 取得資料並顯示內容。
Controller 控制整個流程的更新過程,根據輸入事件被動的通知 Model 更改資料再通知 View 顯示資料。
流程如下:
使用者觸發 View 的輸入事件,事件轉發給 Controller,Controller 通知 Model 更改資料。當 Model 資料更改完成後,Controller 再通知 View 取得 Model 的資料並顯示內容。

被動模式實作

以簡單的亂數產生器作為範例。
首先從 View 開始。
View 就是 Activity,Activity 只有 1 個 Button 用來產生亂數,以及 1 個 TextView 用來顯示亂數。
View 會持有 Model 和 Controller 的參考,Model 參考用來取得資料並顯示內容,而 Controller 參考用來轉發使用者輸入事件。

public class MVCPassiveActivity extends AppCompatActivity implements OnClickListener {
  private Button mRollIt;
  private TextView mRollNumber;
  private MVCPassiveController mController;
  private MVCPassiveModel mModel;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.mvc_passive_activity);
    initData();
    initUI();
  }
  private void initData() {
    mModel = new MVCPassiveModel();
    mController = new MVCPassiveController(this, mModel);
  }
  private void initUI() {
    mRollIt = findViewById(R.id.mvc_passive_roll_it);
    mRollIt.setOnClickListener(this);
    mRollNumber = findViewById(R.id.mvc_passive_show_roll_number);
  }
  @Override
  public void onClick(View view) {
    int uiID = view.getId();
    switch (uiID) {
      case R.id.mvc_passive_roll_it:
        mController.rollIt();
        break;
    }
  }
  public void update() {
    mRollNumber.setText("" + mModel.getRandomNumber());
  }
}

需要注意的是在 initData 方法中初始化 Model 以及 Controller。而在 MVCPassiveController 的建構式中會去關連 View 和 Model。
在 onClick 方法中會去轉發使用者輸入事件給 Controller,而 Controller 的 rollIt 方法就是去通知 Model 更改資料。
最後在 update 方法就是從 Model 取得資料並顯示內容。
接著是 Controller ,Controller 會持有 View 和 Model 的參考並在建構式中關連起來。

public class MVCPassiveController {
  private MVCPassiveActivity mView;
  private MVCPassiveModel mModel;
  public MVCPassiveController(MVCPassiveActivity view, MVCPassiveModel model) {
    mView = view;
    mModel = model;
  }
  public void rollIt() {
    mModel.rollOnce();
    mView.update();
  }
}

在 rollIt 方法首先通知 Model 更改資料,接著再通知 View 取得 Model 的資料並顯示內容。
最後是 Model,Model 完全獨立不依賴於其它層,唯一需要注意的是實際上的 Model 應該是 repository 其中會有取得本地端和遠端資料的元件,本篇的範例相對簡單的多。

public class MVCPassiveModel {
  private int mRandomNumber;
  public void rollOnce() {
    mRandomNumber = new Random().nextInt(100);
  }
  public int getRandomNumber() {
    return mRandomNumber;
  }
}

主動(Active)模式

主動模式會應用觀察者模式(Observer Pattern) 來控制整個流程。首先 Model 就是主題(Subject),而 View 和 Controller 則都是觀察者(Observer)。
當 Model 發生變化,其註冊的觀察者(View & Controller)都會自動收到通知,Controller 仍然是通知 Model 資料變化的角色,和被動模式的差別是 Controller 不再需要通知 View 更新資料,因為當 Model 的資料發生變化時,View 會自動收到通知。
流程如下:
使用者觸發 View 的輸入事件,事件轉發給 Controller,Controller 通知 Model 更改資料。
當 Model 資料更改完成後,觀察者們(View & Controller)都會自動收到通知並進行相關處理。

主動模式實作

也是以簡單的亂數產生器作為範例。
首先建立觀察者介面,觀察者必須繼承該介面,並實作介面中的 update 方法,該方法的參數就是更新資料後的 Model。

public interface IObserver {
  public void update(MVCActiveModel mvcActiveModel);
}

接著實作 Model,Model 就是主題,會持有 1 個觀察者們的 list。對該 Model 有興趣的觀察者都會註冊成為該主題的觀察者。

public class MVCActiveModel {
  private ArrayList<IObserver> mObservers = new ArrayList<>();
  private int mRandomNumber;
  public void rollOnce() {
    mRandomNumber = new Random().nextInt(100);
    notifyObservers();
  }
  public int getRandomNumber() {
    return mRandomNumber;
  }
  public void addObserver(IObserver observer) {
    mObservers.add(observer);
  }
  public void removeObserver(IObserver observer) {
    if (mObservers.contains(observer)) {
      mObservers.remove(observer);
    }
  }
  private void notifyObservers() {
    int sizeOfObservers = mObservers.size();
    for (int i = 0; i < sizeOfObservers; ++i) {
      mObservers.get(i).update(this);
    }
  }
}

notifyObservers 方法就是通知所有的觀察者,主題(Model)的資料已經發生變化,觀察者會收到通知,並自行決定如何處理已變化的資料。
而 rollOnce 方法就是改變資料並通知觀察者們的觸發點。
接著是 Controller,Controller 也是觀察者之一,因此需要繼承 IObserver。
而在建構式中會將自己註冊到 Model 的 Observer list 中。

public class MVCActiveController implements IObserver {
  private MVCActiveActivity mView;
  private MVCActiveModel mModel;
  public MVCActiveController(MVCActiveActivity view, MVCActiveModel model) {
    mView = view;
    mModel = model;
    mModel.addObserver(this);
  }
  public void rollIt() {
    mModel.rollOnce();
  }
  @Override
  public void update(MVCActiveModel model) {
  }
}

最後是 View,View 也是觀察者所以需要繼承 IObserver,並實作 update 方法,在該方法中取得更新後的 Model 並顯示其資料內容。
而在 initData 方法內會將自己註冊為 Model 的觀察者。

public class MVCActiveActivity extends AppCompatActivity implements OnClickListener, IObserver {
  //Main UI
  private Button mRollIt;
  private TextView mRollNumber;
  private MVCActiveController mController;
  private MVCActiveModel mModel;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.mvc_active_activity);
    initData();
    initUI();
  }
  private void initData() {
    mModel = new MVCActiveModel();
    mModel.addObserver(this);
    mController = new MVCActiveController(this, mModel);
  }
  private void initUI() {
    mRollIt = findViewById(R.id.mvc_active_roll_it);
    mRollIt.setOnClickListener(this);
    mRollNumber = findViewById(R.id.mvc_active_show_roll_number);
  }
  @Override
  public void onClick(View view) {
    int uiID = view.getId();
    switch (uiID) {
      case R.id.mvc_active_roll_it:
        mController.rollIt();
        break;
    }
  }
  @Override
  public void update(MVCActiveModel model) {
    mRollNumber.setText("" + model.getRandomNumber());
  }
}

變化

如果希望 Controller 完全不包含 Android 的視圖元素,可以建立一個 IView 介面並讓Activity 繼承它,而 Controller 持有對象的型別從 Activity 改為 IView,而原本對 Activity的操作都改為對 IView 的操作,如下
IView.java

public interface IView {
  public void update();
}

MVCPassiveActivity.java

public class MVCPassiveActivity extends AppCompatActivity implements OnClickListener, IView {
...
  @Override
  public void update() {
    mRollNumber.setText("" + mModel.getRandomNumber());
  }
}

MVCPassiveController.java

public class MVCPassiveController {
  private IView mView;
  public MVCPassiveController(IView view, MVCPassiveModel model) {
    mView = view;
    ...
  }
  public void rollIt() {
    ...
    mView.update();
  }
}

以上為MVC Pattern in Android。


以下為 MVP 相關內容
http://34.80.81.192/?p=6021
http://34.80.81.192/?p=5987