MVP开发架构详解

时间:2021-04-22 21:14:47

一个好的软件,好的架构是基石,好的技术是关键。架构就像盖房子时下的地基一样,房子稳不稳取决于地基,而我们的软件好不好拓展,好不好维护,取决的便是我们软件的架构。采用架构时,没有特定的约束,按需采用,也可以自己根据开发经验总结提炼出好的架构。
业内比较常见的架构有MVC、MVP、MVVM等。MVC主要是分Model(模型)-View(视图)- Controller(控制)三个层,控制层来接收交互请求并通过协调模型和视图层对交互进行反馈,是web开发领域比较常用的架构模式。而MVP则是分为Model(模型)- View(视图)- Presenter(presenter)三个层,在MVP中,Presenter的职责主要是负责业务逻辑的处理的,View层来接收用户交互和展示结果,Model层负责模型操作与维护。MVVM模式主要分Model(模型)- View(视图)- ViewModel(视图模型)三个层,在MVVM中,ViewModel层是连接View和Model之间的纽带,同时,ViewModel层还负责所有的逻辑处理和数据处理,处理过程中如果Model层的某某数据有了变更,View层会动态刷新某某数据所对应的视图展示。而此文主要是介绍MVP开发架构。
话不多说,先上代码,以下为具体的MVP模式实现方式(PS:具体例子以点外卖下单为业务场景,用Java语言编写,模式只介绍截止本文编写日期为止最新的演进结果):

package com.chris.mvpdemo.base;

/**
* base
* Created by chris on 2017/11/16.
*/

public interface BasePresenter {
void onCreate();

void onDestroy();

void action(int actionCode, Object... actionArgs);

//.......
}
package com.chris.mvpdemo.base;

import android.support.annotation.StringRes;

/**
* base
* Created by chris on 2017/11/16.
*/

public interface BaseView<T> {
void setPresenter(T presenter);

void showMessage(String msg);

void showMessage(@StringRes int msgResId);

//.....
}

以上为base接口,制定了基本的行为。接着看:

package com.chris.mvpdemo.contract;


import com.chris.mvpdemo.base.BasePresenter;
import com.chris.mvpdemo.base.BaseView;
import com.chris.mvpdemo.model.ProductEntity;

import java.util.List;

/**
* 下单的Contract
* Created by chris on 2017/11/16.
*/

public interface OrderContract {
interface View extends BaseView<Presenter> {
/**
* 刷新购物车
*/

void refreshShoppingCart(List<ProductEntity> productEntities);
}

interface Presenter extends BasePresenter {
/**
* 添加商品
* {@link BasePresenter#action(int, Object...)}
*/

int ACTION_CODE_ADD_PRODUCT = 3;
/**
* 移除商品
* {@link BasePresenter#action(int, Object...)}
*/

int ACTION_CODE_REMOVE_PRODUCT = 4;

/**
* 操作商品
*/

void clearShoppingCart();

/**
* 重置(恢复初始化状态)
*/

void reset();
}
}

上面是Contract接口,也就是契约接口,是MVP架构的核心接口,规约了Model-View-Presenter三个层的所有行为与能力。接着看:

package com.chris.mvpdemo.presenter;


import android.content.Context;
import android.text.TextUtils;

import com.chirs.mvpdemo.R;
import com.chris.mvpdemo.App;
import com.chris.mvpdemo.callback.ShoppingCartCallback;
import com.chris.mvpdemo.contract.OrderContract;
import com.chris.mvpdemo.model.ProductEntity;
import com.chris.mvpdemo.model.ShoppingCartProcessor;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
* 下单的presenter
* Created by chris on 2017/11/16.
*/

public class OrderPresenter implements OrderContract.Presenter, ShoppingCartCallback {

private OrderContract.View view;
private Context context;

public OrderPresenter(Context context, OrderContract.View view) {
this.context = context;
this.view = view;
view.setPresenter(this);
}

@Override
public void onCreate() {
ShoppingCartProcessor.getInstance().registerCallback(this);
}

@Override
public void action(int actionCode, Object... actionArgs) {
switch (actionCode) {
default:
break;
case OrderContract.Presenter.ACTION_CODE_ADD_PRODUCT: {
if (actionArgs == null || actionArgs.length < 2 || !(actionArgs[0] instanceof ProductEntity) || !(actionArgs[1] instanceof BigDecimal)) {
return;
}
ShoppingCartProcessor.getInstance().addProductToShoppingCart((ProductEntity) actionArgs[0], (BigDecimal) actionArgs[1]);
}
break;
case OrderContract.Presenter.ACTION_CODE_REMOVE_PRODUCT: {
if (actionArgs == null || actionArgs.length < 2 || !(actionArgs[0] instanceof ProductEntity) || !(actionArgs[1] instanceof BigDecimal)) {
return;
}
ShoppingCartProcessor.getInstance().removeProductFromShoppingCart((ProductEntity) actionArgs[0], (BigDecimal) actionArgs[1]);
}
break;
}
}

@Override
public void clearShoppingCart() {
ShoppingCartProcessor.getInstance().clear();
}

@Override
public void reset() {
List<ProductEntity> productList = generateProductList();
ShoppingCartProcessor.getInstance().init(productList);
view.refreshShoppingCart(productList);
}

@Override
public void onOperatingSpecialProductSuccess(ProductEntity entity, List<ProductEntity> latestShoppingCartProducts, BigDecimal count, int operatingCode) {
StringBuilder formatStr = new StringBuilder("");
if (entity != null) {
if (!TextUtils.isEmpty(entity.getProductName())) {
formatStr.append(entity.getProductName());
}
if (count != null) {
formatStr.append(count);
}
if (!TextUtils.isEmpty(entity.getUnit())) {
formatStr.append(entity.getUnit());
}
}
switch (operatingCode) {
default:
break;
case ShoppingCartCallback.OPERATING_CODE_ADD: {
view.showMessage(context.getString(R.string.add_product_success_format, formatStr));
view.refreshShoppingCart(latestShoppingCartProducts);
}
break;
case ShoppingCartCallback.OPERATING_CODE_REMOVE: {
view.showMessage(context.getString(R.string.remove_product_success_format, formatStr));
view.refreshShoppingCart(latestShoppingCartProducts);
}
break;
}
}

@Override
public void onShoppingCartClear() {
view.refreshShoppingCart(null);
}

@Override
public void onDestroy() {
ShoppingCartProcessor.getInstance().unregisterCallback(this);
view = null;
}

private static List<ProductEntity> generateProductList() {
String[] productNames = App.getInstance().getResources().getStringArray(R.array.product_name_list);
String[] productUnits = App.getInstance().getResources().getStringArray(R.array.product_unit_list);
if (productNames != null && productNames.length > 0 && productUnits != null && productUnits.length == productNames.length) {
List<ProductEntity> productList = new ArrayList<>();
int i = 0;
for (String productName : productNames) {
ProductEntity product = new ProductEntity();
product.setCount(BigDecimal.ZERO);
product.setProductName(productName);
product.setProductId(UUID.randomUUID().toString());
product.setProductPrice(new BigDecimal(Math.random()));
product.setUnit(productUnits[i]);
productList.add(product);
i++;
}
return productList;
}
return null;
}
}

上面是presenter的具体实现,可以看到,在BasePresenter中定义了一个action的方法,这个action方法是一个灵活通用的方法,旨在减少接口中的方法定义,action 方法的入参是一个actionCode和一个可变个数的参数数组,这样能通过actionCode和参数来进行和实现不同的业务操作。接着看:

package com.chris.mvpdemo.view.fragment;


import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.chirs.mvpdemo.R;
import com.chris.mvpdemo.contract.OrderContract;
import com.chris.mvpdemo.model.ProductEntity;
import com.chris.mvpdemo.util.ToastMaster;
import com.chris.mvpdemo.view.adapter.OrderAdapter;

import java.math.BigDecimal;
import java.util.List;

/**
* 下单的fragment
* Created by chris on 2017/11/16.
*/

public class OrderFragment extends Fragment implements OrderContract.View, View.OnClickListener, OrderAdapter.Callback {

public static final String TAG = "OrderFragment";

private RecyclerView listRecyclerView;
private TextView clearBtn;
private TextView resetBtn;
private OrderAdapter orderAdapter;
private OrderContract.Presenter presenter;

public static OrderFragment newInstance() {
OrderFragment orderFragment = new OrderFragment();
return orderFragment;
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.frag_order, container, false);
listRecyclerView = view.findViewById(R.id.rv_list);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
listRecyclerView.setLayoutManager(linearLayoutManager);
orderAdapter = new OrderAdapter(getActivity());
orderAdapter.setCallback(this);
listRecyclerView.setAdapter(orderAdapter);
clearBtn = view.findViewById(R.id.tv_clear_btn);
clearBtn.setOnClickListener(this);
resetBtn = view.findViewById(R.id.tv_reset_btn);
resetBtn.setOnClickListener(this);
presenter.onCreate();
presenter.reset();
return view;
}

@Override
public void setPresenter(OrderContract.Presenter presenter) {
this.presenter = presenter;
}

@Override
public void onClick(View view) {
switch (view.getId()) {
default:
break;
case R.id.tv_clear_btn: {
presenter.clearShoppingCart();
}
break;
case R.id.tv_reset_btn: {
presenter.reset();
}
break;
}
}

@Override
public void operatingItem(int operatingCode, ProductEntity product, BigDecimal count, int position) {
switch (operatingCode) {
default:
break;
case OrderAdapter.Callback.OPERATING_ITEM_CODE_PLUS: {
presenter.action(OrderContract.Presenter.ACTION_CODE_ADD_PRODUCT, product, count);
}
break;
case OrderAdapter.Callback.OPERATING_ITEM_CODE_MINUS: {
presenter.action(OrderContract.Presenter.ACTION_CODE_REMOVE_PRODUCT, product, count);
}
break;
}
}

@Override
public void refreshShoppingCart(List<ProductEntity> productEntities) {
orderAdapter.updateDataSource(productEntities);
orderAdapter.notifyDataSetChanged();
}

@Override
public void showMessage(String msg) {
ToastMaster.showToast(getActivity(), msg);
}

@Override
public void showMessage(int msgResId) {
ToastMaster.showToast(getActivity(), msgResId);
}

@Override
public void onDestroyView() {
super.onDestroyView();
if (presenter != null) {
presenter.onDestroy();
presenter = null;
}
}
}

这是View层的实现,另外,我们将presenter的实例化放在了Activity中实现,往下看:

package com.chris.mvpdemo.view.activity;

import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;

import com.chirs.mvpdemo.R;
import com.chris.mvpdemo.presenter.OrderPresenter;
import com.chris.mvpdemo.view.fragment.OrderFragment;

/**
* 主界面
* Created by chris on 2017/11/16.
*/

public class MainActivity extends AppCompatActivity {

private OrderFragment orderFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addFragment(this, orderFragment = OrderFragment.newInstance(), R.id.fl_content, OrderFragment.TAG);
new OrderPresenter(this, orderFragment);
}

private static void addFragment(FragmentActivity activity, Fragment fragment, int position, String tag) {
if (activity == null || fragment == null) {
return;
}
FragmentManager fragmentManager = activity.getSupportFragmentManager();
if (fragmentManager == null) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (fragmentManager.isDestroyed()) {
return;
}
}
Fragment originalFragment = fragmentManager.findFragmentById(position);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (originalFragment == null) {
fragmentTransaction.add(position, fragment, tag);
} else {
fragmentTransaction.replace(position, fragment, tag);
}
fragmentTransaction.commitAllowingStateLoss();
fragmentManager.executePendingTransactions();
}
}

另外,Model层主要是实体类和数据处理器(数据操作封装的类),往下看:

package com.chris.mvpdemo.model;

import java.math.BigDecimal;

/**
* 实体类
* Created by chris on 2017/11/16.
*/

public class ProductEntity {
private String productId = "";
private String productName = "";
private BigDecimal productPrice = BigDecimal.ZERO;
private String unit = "";
private BigDecimal count = BigDecimal.ZERO;

//....

public String getProductId() {
return productId;
}

public void setProductId(String productId) {
this.productId = productId;
}

public String getProductName() {
return productName;
}

public void setProductName(String productName) {
this.productName = productName;
}

public BigDecimal getProductPrice() {
return productPrice;
}

public void setProductPrice(BigDecimal productPrice) {
this.productPrice = productPrice;
}

public BigDecimal getCount() {
return count;
}

public void setCount(BigDecimal count) {
this.count = count;
}

public String getUnit() {
return unit;
}

public void setUnit(String unit) {
this.unit = unit;
}
}
package com.chris.mvpdemo.model;

import android.text.TextUtils;

import com.chris.mvpdemo.callback.ShoppingCartCallback;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* 购物车处理器
* Created by chris on 2017/11/16.
*/

public class ShoppingCartProcessor {
private static final ShoppingCartProcessor INSTANCE = new ShoppingCartProcessor();

private List<ProductEntity> productList = new ArrayList<>();
private Set<ShoppingCartCallback> callbackSet = new HashSet<>();

private ShoppingCartProcessor() {

}

public static ShoppingCartProcessor getInstance() {
return INSTANCE;
}

public void registerCallback(ShoppingCartCallback callback) {
callbackSet.add(callback);
}

public void unregisterCallback(ShoppingCartCallback callback) {
callbackSet.remove(callback);
}

public void clear() {
productList.clear();
for (ShoppingCartCallback callback : callbackSet) {
if (callback != null) {
callback.onShoppingCartClear();
}
}
}

public void init(List<ProductEntity> productList) {
this.productList.clear();
this.productList.addAll(productList);
}

public void addProductToShoppingCart(ProductEntity product, BigDecimal count) {
if (product == null || count == null) {
return;
}
BigDecimal newCount = product.getCount().add(count);
if (newCount.compareTo(BigDecimal.ZERO) < 0) {
newCount = BigDecimal.ZERO;
}
product.setCount(newCount);
int productPosition = getProductPosition(product.getProductId());
if (productPosition != -1) {
productList.set(productPosition, product);
} else {
productList.add(product);
}
for (ShoppingCartCallback callback : callbackSet) {
if (callback != null) {
callback.onOperatingSpecialProductSuccess(product, productList, count, ShoppingCartCallback.OPERATING_CODE_ADD);
}
}
}

public void removeProductFromShoppingCart(ProductEntity product, BigDecimal count) {
if (product == null || count == null) {
return;
}
BigDecimal newCount = product.getCount().subtract(count);
if (newCount.compareTo(BigDecimal.ZERO) < 0) {
newCount = BigDecimal.ZERO;
}
product.setCount(newCount);
int productPosition = getProductPosition(product.getProductId());
if (productPosition != -1) {
productList.set(productPosition, product);
} else {
productList.add(product);
}
for (ShoppingCartCallback callback : callbackSet) {
if (callback != null) {
callback.onOperatingSpecialProductSuccess(product, productList, count, ShoppingCartCallback.OPERATING_CODE_REMOVE);
}
}
}

private int getProductPosition(String productId) {
if (TextUtils.isEmpty(productId)) {
return -1;
}
int position = 0;
for (ProductEntity productEntity : productList) {
if (TextUtils.equals(productId, productEntity.getProductId())) {
return position;
}
position++;
}
return -1;
}
}

以上就是MVP的详细实现,首先在Base接口中定义了基础的功能,然后定义了契约Contract接口,里面制定了具体的Presenter层和View层的功能与职责,最后让Presenter层和View层的具体实现类实现了Contract中定义好的对应接口。
从代码中可以看出,使用MVP架构开发层次非常清晰,代码的耦合性也非常低,但是MVP架构也还是有一定的缺陷的,下面来聊一聊它的缺陷及弥补缺陷的方式:
1.如果对于业务繁多的应用,使用MVP架构开发会增加应用开发的复杂性。由于MVP架构是使用接口抽象来衔接各层次的,来赋予各层次互相调用的能力的,所以在业务繁多的场景下,使用MVP会导致接口过多,使得开发过程变得复杂。在这种情况下,首先接口可以尽量往相同的业务方向合并,提高接口的公用性,然后有些小的视图页面(即View层),可以不用Presenter层,直接调用Model层即可,毕竟只要不影响应用性能,能够提升开发效率就行,可以视实际情况灵活使用,一切都是可以充满变化的,只要不影响应用性能和代码质量。
2.接口抽象化增加了维护成本。接口都抽象化后会发现,当需要修改某一个业务功能时,其对应层次的定义与实现都要改。
3.回调链过长,容易内存泄漏。使用了MVP架构后,现在模块层次是很清晰了,接口也抽象得很好了,但是我们的View层通常都是Activity或Fragment,我们在Presenter层里面持有了其引用,如果在Presenter层里面有子线程的后台任务,而子线程操作中又引用到了Presenter层或View层的实例,那么当View层销毁后,其内存是会无法释放的,这样就会导致内存泄漏。针对这个问题目前有两种比较常用的解决方案,第一种是在Presenter层定义onDestory方法,当View层OnDestory时也call一下Presenter层的OnDestory,call完后并将其置null,Presenter层的onDestory方法实现中先停掉后台任务,然后将View层置null,释放掉。还有一种是使用弱引用(WeakReference)来引用View层,关于弱引用的作用这里就不做详细介绍了,学过Java的同学应该都是要知道的。本文中采用的是第一种方式。
以上就是对MVP架构模式的介绍,MVP固然有其很多优点,但是我们在选择使用时也需要结合自己项目的实际情况与开发场景进行考量。