Model-view-presenter,簡稱MVP,是電腦軟體設計工程中一種對針對MVC模式,再審議後所延伸提出的一種軟體設計模式。
描述
M-Model,資料層
V-View,介面顯示層
P-Presenter,中介者,連線Model和View層
結構圖如下:
從圖中可以看到,View可以和Presenter進行雙向通訊,Presenter和Model進行單方面通訊,而View和Model是不能直接進行通訊的。
那麼這個通訊是什麼意思呢?我個人理解就是是否持有對方的例項,是否能夠呼叫對方的方法。
所以,這裡就很清晰了。我們在程式碼中想要實現這個架構的話,其實也很簡單,就是讓View和Presenter相互持有以及讓Presenter持有Model的物件。
我們用簡單的程式碼演示一下:
public class View {
private Presenter presenter;
}
public class Presenter {
private View view;
private Model model;
}
public class model {}複製程式碼
我們的示例程式碼就實現了我們上面所要求的那些:讓View和Presenter雙向繫結、讓Presenter持有Model的引用。
好,到現在,你已經初步瞭解了MVP的規則(也可以叫做套路)。
有的同學可能會問,這樣寫的程式碼量變多了,為什麼要這樣寫?這個我們後邊再講。
Android中的MVP
既然已經瞭解了MVP的基本規則,那麼我們就來看一下如何在Android中實現這個架構。
首先,我們先要了解一下Android中的M、V、P分別都代表著什麼。
View
在Android中,顯示的操作是交給Activity或者Fragment的。所以理所當然的,Activity\Fragment就是MVP中的View了。
既然把Activity或者Fragment當作View層,即顯示層,那麼它的工作應該就是顯示操作,不應該有其他型別的操作,例如解析資料等等。所以你會發現View中的方法大多就是showXXX()
,hideXXX()
等等。
在我的理解中,一切的邏輯操作都應該在Presenter中處理,但是,實際情況中,有些特別小的邏輯也可以在View中直接實現。
Presenter
這裡的Presenter就是處理各種邏輯。例如這樣一個場景:從伺服器獲取資料,對資料進行處理,然後顯示在介面上。相信開發者們都遇到過這樣的需求。那麼在我們的MVP架構中是如何實現的呢?
流程就和圖中顯示的一樣,Model提供資料、Presenter處理資料、View顯示操作
而且命令Model提供資料和命令View顯示資料這兩個操作都應該在Presenter中進行,所以你會發現,MVP的核心就是Presneter中,其他兩個部分都很單一,就像是工具類一樣,任人呼叫。
從這個角度出發的話,Presenter必須要持有Model和View的引用,因為只有這樣才能命令它們執行某個操作。
那為什麼View還要持有Presneter的引用呢?其實原因很簡單,因為在Android中大多數事務都是從View中產生的。例如:你想要新增一條資料,點選了那個+
號按鈕,無形中就產生了一個事務。
也就是說很多邏輯都是從View中產生,然後命令Presenter去處理資料(比如檢驗資料是否合理),再然後Presenter再命令Model新增資料。所以,View中也必須持有Presenter的引用,也就是說View和Presenter相互持有。
Model
Model就很簡單了,根據我們上邊的描述,就知道Model就相當於一個包裝後的資料庫,執行著與資料相關的操作。
另外,我上面說的把Activity、Fragment當作View層只是一種實現方法。還有一種實現方法就是把Activity\Fragment當作Presenter,將UI操作抽到delegate中作為View層(例如T-MVP
)。
實現
接下來我們就在Android中完完整整地實現MVP架構。
專案的包結構
注意在這裡我們採取和Google一樣的思路,根據功能進行分包。如圖就是我的專案包結構:
- model 沒錯就是MVP中的M
- local
- LocalResponse 本地資料來源,一般就是本地資料庫的一些操作
- remote
- RemoteResponse 遠端資料來源,伺服器的一些操作
- Response 資料來源介面,這裡宣告瞭所有對資料的操作
- ResponseImpl Response的實現類,持有localResponse和RemoteResponse的引用。
- local
- sign_in 這個就是我們的登入功能
- SignInActivity 我們這裡的Activity所起的作用就是託管Fragment
- SignInFragment MVP中的V
- SignInContract 內有兩個介面,分別是View介面和Presenter介面。
- SignInPresenter MVP中的P
- util 工具包
- BasePresenter Presenter的基類
- BaseView View的基類
- SingleFragmentActivity 一個抽象類,把Activity託管一個Fragment的操作封裝起來了。
程式碼
SingleFragmentActivity
public abstract class SingleFragmentActivity extends AppCompatActivity {
protected abstract Fragment createFragment();
protected void init() {
}
/**
* 可以設定一個只有FrameLayout的xml檔案作為預設的Fragment容器,
* 若子類有更好的容器可以重寫該方法,若沒有則可以直接用預設的容器
* 假設佈局檔名字為activity_fragment.xml
*/
@LayoutRes
protected abstract int getLayoutResId();
@IdRes
protected abstract int getFragmentContainerId();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResId());
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(getFragmentContainerId());
if (null == fragment) {
fragment = createFragment();
fm.beginTransaction()
.add(getFragmentContainerId(), fragment)
.commit();
}
init();
}
}複製程式碼
BaseView
public interface BaseView<T> {
void setPresenter(T t);
}複製程式碼
BasePresenter
public interface BasePresenter {
void start();
void destroy();
}複製程式碼
SignInActivity
public class SignInActivity extends SingleFragmentActivity {
@Override
protected int getLayoutResId() {
return R.layout.sign_in_act;
}
@Override
protected int getFragmentContainerId() {
return R.id.fragment_container;
}
@Override
protected Fragment createFragment() {
return SignInFragment.newInstance();
}
}複製程式碼
SignInContract
public interface SignInContract {
interface Presenter extends BasePresenter {
void signIn(String username, String passwd, Action<Integer> action);
void signIn(String username, String passwd);
}
interface View extends BaseView<Presenter> {
String getUsername();
String getPasswd();
void showToast(String message);
}
}複製程式碼
SignInFragment
public class SignInFragment extends Fragment implements SignInContract.View {
private static String TAG = "SignInFragment";
private SignInContract.Presenter mPresenter;
private View rootView;
private EditText mEtUsername;
private EditText mEtPasswd;
private Button mBtnSignIn;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = new SignInPresenter(this,
new ResponseImpl(new LocalResponse(), new RemoteResponse()));
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.sign_in_frag, container, false);
mEtPasswd = (EditText) rootView.findViewById(R.id.et_username);
mEtUsername = (EditText) rootView.findViewById(R.id.et_passwd);
mBtnSignIn = (Button) rootView.findViewById(R.id.btn_sign_in);
initEvent();
return rootView;
}
private void initEvent() {
mBtnSignIn.setOnClickListener(v ->
mPresenter.signIn(getUsername(), getPasswd(),
result_code -> {
if (result_code == 0) {
showToast(getString(R.string.sign_in_success));
} else {
showToast(getString(R.string.sign_in_fail));
}
}));
}
@Override
public void onResume() {
super.onResume();
if (mPresenter != null) {
mPresenter.start();
}
}
@Override
public void onDestroy() {
if (mPresenter != null) {
mPresenter.destroy();
}
super.onDestroy();
}
@Override
public void setPresenter(SignInContract.Presenter presenter) {
mPresenter = presenter;
}
@Override
public String getUsername() {
return mEtUsername.getText().toString();
}
@Override
public String getPasswd() {
return mEtPasswd.getText().toString();
}
@Override
public void showToast(String message) {
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
}
/**
*
* @return 返回一個指定的Fragment例項
*/
public static Fragment newInstance() {
return new SignInFragment();
}
}複製程式碼
public class SignInPresenter implements SignInContract.Presenter {
private static String TAG = "SignInPresenter";
private SignInContract.View mView;
private ResponseImpl mResponse;
public SignInPresenter(SignInContract.View view, ResponseImpl response) {
mView = view;
mView.setPresenter(this);
mResponse = response;
}
@Override
public void start() {
Log.i(TAG, "start: ");
}
@Override
public void destroy() {
Log.i(TAG, "destroy: ");
}
@Override
public void signIn(String username, String passwd, Action<Integer> action) {
mResponse.signIn(username, passwd, action);
}
@Override
public void signIn(String username, String passwd) {
mResponse.signIn(username, passwd, integer -> {
if(integer == 0) {
mView.showToast("Success");
} else {
mView.showToast("fail");
}
});
}
}複製程式碼
限於篇幅,Model的程式碼我們就不貼了,大家可以去我的Github上去看完整的demo。
值得注意的地方,我們的BasePresenter中提供了兩個方法start()
和destroy()
,目的就是要把Presenter的生命週期和Fragment的生命週期繫結在一起。另外,可以改進的地方是Response的實現類可以改成單例模式,以及為ResponseImpl傳入一個Context
引數,因訪問資料庫操作和訪問網路操作都是需要Context
的。
MVP存在的問題
最大的問題就是MVP需要建立太多的介面和實現類。
針對這個問題,有一種解決方法就是使用Android Studio的Template自動生成需要的類和介面,但是這種方法只是解放了雙手,並沒有減少程式碼量。
另外就是,如果某個介面的邏輯很簡單,那麼可以直接把省略掉Presenter;如果這個介面與資料無關,那麼可以把Model也省略掉。
Presenter可以重用,所以不需要每個Activity或者Fragment都為其分配一套MVP,可以幾個Activity或者Fragment共用一個Presenter。
想要了解更多的話,請移步本人的學習筆記,如果覺得有幫助的話,請點一個star✨。