作为开发人员,我们不应该仅仅满足于实现功能需求,还应该实现易于维护的代码。在这篇文章里,我们将简单地介绍一下Model-View-Presenter结构,并用一个简单的成绩登记系统来作为例子。
- MVP和MVC的区别
- MVP的优势
- MVP的实现
- Sample
MVP和MVC的区别
MVC模式(Model–view–controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
- 控制器(Controller)- 负责转发请求,对请求进行处理。
- 视图(View) - 界面设计人员进行图形界面设计。
- 模型(Model) - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。
Model-view-presenter (MVP) 是使用者界面设计模式的一种,被广范用于便捷自动化单元测试和在呈现逻辑中改良分离关注点(separation of concerns)。
- Model 定义使用者界面所需要被显示的资料模型,一个模型包含着相关的业务逻辑。
- View 视图为呈现使用者界面的终端,用以表现来自 Model 的资料,和使用者命令路由再经过 Presenter 对事件处理后的资料。
- Presenter 包含着元件的事件处理,负责检索 Model 取得资料,和将取得的资料经过格式转换与 View 进行沟通。<a href="https://zh.wikipedia.org/zh-cn/MVC" target="_blank"> [ 维基百科 ]
MVP是建立在MVC的基础上的。Presenter和Controller功能差不多,它们都是负责Model和View之间的交流的。 Controller没有像Presenter那样如此严格地管理Model和View.
在MVC模型里,View层可以直接从Model取数据。在MVP模型里,View是完全被动的,数据是Presenter提供的。MVC中,Controller可以被多个View共享。在MVP中,View和Presenter有着一对一的关系。
MVP的优势
正是如上的区别,使MVP能更好地分离关注点。使对象更小,Model和View之间的依赖更少。把三个核心层区分开,也提高了可测试性。
MVP的实现
- Model:数据层
- View:UI层,展示从Presenter接收的数据,接受用户输入。在安卓里,Activities,Fragments和android.view.View作为MVP 中的View。
- Presenter:对UI层上的操作作出响应,在Model上执行业务逻辑,并将执行的结果传递给View。
Sample
下面来举个例子说明一下吧。老师可以登录该系统登记学生成绩。以下代码主要体现成绩登记部分。
基本接口和实现
Interface RequiredViewOps
/**
* View mandatory methods. Available to Presenter
* Presenter -> View
*/
interface RequiredViewOps {
void showToast(String msg);
void showAlert(String msg);
// any other ops
}
Interface PresenterOps
/**
* Operations offered from Presenter to View
* View -> Presenter
*/
interface PresenterOps{
void onConfigurationChanged(RequiredViewOps view);
void onDestroy(boolean isChangingConfig);
void addGrade(Grade grade);
// any other ops to be called from View
}
Interface RequiredPresenterOps
/**
* Operations offered from Presenter to Model
* Model -> Presenter
*/
interface RequiredPresenterOps {
void onAddGrade(Grade grade);
void onError(String errorMsg);
// Any other returning operation Model -> Presenter
}
Interface ModelOps
/**
* Model operations offered to Presenter
* Presenter -> Model
*/
interface ModelOps {
void insertGrade(Grade grade);
void onDestroy();
// Any other data operation
}
GradePresenter Class
public class GradePresenter
implements RequiredPresenterOps,PresenterOps {
// Layer View reference
private RequiredViewOps mView;
// Layer Model reference
private ModelOps mModel;
// Configuration change state
private boolean mIsChangingConfig;
public GradePresenterRequiredViewOps mView) {
this.mView = mView;
this.mModel = new MainModel(this);
}
/**
* Sent from Activity after a configuration changes
* @param view View reference
*/
@Override
public void onConfigurationChanged(RequiredViewOps view) {
this.mView =view;
}
/**
* Receives {@link MainActivity#onDestroy()} event
* @param isChangingConfig Config change state
*/
@Override
public void onDestroy(boolean isChangingConfig) {
mView = null;
mIsChangingConfig = isChangingConfig;
if ( !isChangingConfig ) {
mModel.onDestroy();
}
}
/**
* Called by user interaction from {@link MainActivity}
* creates a new Note
*/
@Override
public void addGrade(Grade grade) {
mModel.insertGrade(grade);
}
/**
* Called from {@link MainModel}
* when a Note is inserted successfully
*/
@Override
public void onAddGrade(Grade grade){
mView.get().showToast("New grade added");
}
/**
* receive errors
*/
@Override
public void onError(String errorMsg) {
mView.get().showAlert(errorMsg);
}
}
GradeModel Class
public class GradeModel implements ModelOps {
// Presenter reference
private RequiredPresenterOps mPresenter;
public MainModel(RequiredPresenterOps mPresenter) {
this.mPresenter = mPresenter;
}
/**
* Sent from {@link MainPresenter#onDestroy(boolean)}
* Should stop/kill operations that could be running
* and aren't needed anymore
*/
@Override
public void onDestroy() {
// destroying actions
}
// Insert grade in DB
@Override
public void insertGrade(Grade grade) {
// data business logic
// ...
mPresenter.onAddGrade(grade);
}
}
生命周期的管理
我们需要添加第四个元素StateMaintainer,用来管理在Activity生命周期变化的时候,Presenter和Model的状态变化。下图展示了简化版的MVP在Activity生命周期的状态。
StateMaintainer Class
public class StateMaintainer {
protected final String TAG = getClass().getSimpleName();
private final String mStateMaintenerTag;
private final WeakReference<FragmentManager> mFragmentManager;
private StateMngFragment mStateMaintainerFrag;
/**
* Constructor
* @param fragmentManager FragmentManager reference
* @param stateMaintainerTAG the TAG used to insert the state maintainer fragment
*/
public StateMaintainer(FragmentManager fragmentManager, String stateMaintainerTAG) {
mFragmentManager = new WeakReference<>(fragmentManager);
mStateMaintenerTag = stateMaintainerTAG;
}
/**
* Create the state maintainer fragment
* @return true: the frag was created for the first time
* false: recovering the object
*/
public boolean firstTimeIn() {
try {
// Recovering the reference
mStateMaintainerFrag = (StateMngFragment)
mFragmentManager.get().findFragmentByTag(mStateMaintenerTag);
// Creating a new RetainedFragment
if (mStateMaintainerFrag == null) {
Log.d(TAG, "Creating a new RetainedFragment " + mStateMaintenerTag);
mStateMaintainerFrag = new StateMngFragment();
mFragmentManager.get().beginTransaction()
.add(mStateMaintainerFrag, mStateMaintenerTag).commit();
return true;
} else {
Log.d(TAG, "Returns a existent retained fragment existente " + mStateMaintenerTag);
return false;
}
} catch (NullPointerException e) {
Log.w(TAG, "Error firstTimeIn()");
return false;
}
}
/**
* Insert Object to be preserved during configuration change
* @param key Object's TAG reference
* @param obj Object to maintain
*/
public void put(String key, Object obj) {
mStateMaintainerFrag.put(key, obj);
}
/**
* Insert Object to be preserved during configuration change
* Uses the Object's class name as a TAG reference
* Should only be used one time by type class
* @param obj Object to maintain
*/
public void put(Object obj) {
put(obj.getClass().getName(), obj);
}
/**
* Recovers saved object
* @param key TAG reference
* @param <T> Class type
* @return Objects
*/
@SuppressWarnings("unchecked")
public <T> T get(String key) {
return mStateMaintainerFrag.get(key);
}
/**
* Verify the object existence
* @param key Obj TAG
*/
public boolean hasKey(String key) {
return mStateMaintainerFrag.get(key) != null;
}
/**
* Save and manages objects that show be preserved
* during configuration changes.
*/
public static class StateMngFragment extends Fragment {
private HashMap<String, Object> mData = new HashMap<>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Grants that the frag will be preserved
setRetainInstance(true);
}
/**
* Insert objects
* @param key reference TAG
* @param obj Object to save
*/
public void put(String key, Object obj) {
mData.put(key, obj);
}
/**
* Insert obj using class name as TAG
* @param object obj to save
*/
public void put(Object object) {
put(object.getClass().getName(), object);
}
/**
* Recover obj
* @param key reference TAG
* @param <T> Class
* @return Obj saved
*/
@SuppressWarnings("unchecked")
public <T> T get(String key) {
return (T) mData.get(key);
}
}
}
MainActivity Activity (View layer)
public class MainActivity extends AppCompatActivity
implements RequiredViewOps {
protected final String TAG = getClass().getSimpleName();
// Responsible to maintain the Objects state
// during changing configuration
private final StateMaintainer mStateMaintainer =
new StateMaintainer( this.getFragmentManager(), TAG );
// Presenter operations
private PresenterOps mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startMVPOps();
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
}
/**
* Initialize and restart the Presenter.
* This method should be called after {@link Activity#onCreate(Bundle)}
*/
public void startMVPOps() {
try {
if ( mStateMaintainer.firstTimeIn() ) {
Log.d(TAG, "onCreate() called for the first time");
initialize(this);
} else {
Log.d(TAG, "onCreate() called more than once");
reinitialize(this);
}
} catch ( InstantiationException | IllegalAccessException e ) {
Log.d(TAG, "onCreate() " + e );
throw new RuntimeException( e );
}
}
/**
* Initialize relevant MVP Objects.
* Creates a Presenter instance, saves the presenter in {@link StateMaintainer}
*/
private void initialize( RequiredViewOps view )
throws InstantiationException, IllegalAccessException{
mPresenter = new MainPresenter(view);
mStateMaintainer.put(PresenterOps.class.getSimpleName(), mPresenter);
}
/**
* Recovers Presenter and informs Presenter that occurred a config change.
* If Presenter has been lost, recreates a instance
*/
private void reinitialize( RequiredViewOps view)
throws InstantiationException, IllegalAccessException {
mPresenter = mStateMaintainer.get(PresenterOps.class.getSimpleName() );
if ( mPresenter == null ) {
Log.w(TAG, "recreating Presenter");
initialize( view );
} else {
mPresenter.onConfigurationChanged( view );
}
}
// Show AlertDialog
@Override
public void showAlert(String msg) {
// show alert Box
}
// Show Toast
@Override
public void showToast(String msg) {
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show;
}
}