对Android-MVP架构模式的理解与初尝试

通常,如果你是一名面向对象的开发者,或多或少都了解和接触过大名鼎鼎的“MVC”模式。

到了Android移动端上,因为其自身的某些特性。于是,从“MVC”模式里又衍生出了一种新的模式,既“MVP”模式。


关于其二者的特点,从根本来说,十分相似:

  • Controller/Presenter负责接受数据,并命令View或Model做出相应的修改;
  • Model负责封装应用程序的数据模型及业务逻辑。
  • View负责应用程序的显示。
而二者之间的最大的区别在于:
  • MVP:View并不直接使用Model,它们之间的通信是通过Presenter(MVC中的Controller)来进行的,所有的交互都发生Presenter内部。
  • MVC:允许View跳过Controller,直接对Model进行访问


那么,到了Android开发当中,为什么MVP模式会显得比MVC模式更加合适,我们加以分析。


以传统的JavaWeb开发而言,其对应于MVC模式来说,通常表现为:

  • Servlet或者Filter作为Controller;
  • JSP作为View;
  • POJO + 封装BIZ的Action类作为Model。


我们按照同样的思想换算到Android当中,则应该表现为:

  • Activity作为Controller;
  • 布局xml文件作为View;
  • POJO + BIZ作为Model。


这时,细心的人可能已经注意到了,强行把Activity作为Controller似乎并不那么合理。

因为在Android当中:Activity除了负责接收用户的输入之外,还承担着加载界面布局,实例化控件,绑定监听事件等等工作。


你之所以选择架构模式,根本就是为了最大程度的对代码结构进行解耦和模块分离。

所以,如果出现Activity既像View,又像Controller这种不伦不类的情况,我们自然应该避免其发生。


也正是基于此,所以在衍生出的MVP模式中,选择这样做。

  • 将Activity作为View,只负责界面显示和用户交互。
  • Model依然保持本色。
  • 而建立Presenter负责View与Model的交互。


这个周末抽空花了一点时间,尝试了一下所谓的MVP模式。多多少少有些体会,在此记录。


首先,既然要使用到MVP模式,自然要了解为什么使用它。如果没有好处,我们用它搞毛呢?

简单归纳,其好处为:

  • 易于维护
  • 易于测试
  • 松耦合度
  • 复用性高
  • 健壮稳定
个人尝试用这种模式搭建项目结构,发现的确逻辑条理和项目结构更加清晰,会使得后期更容易进行维护工作。

不过相对于平常来说,经分离之后,类文件,接口文件反而相对却变多了。

所以个人感觉如果是规模较小,逻辑较为简单的项目,其实反而没有使用的必要。


话入正题,所谓的M-V-P,对应 来说:

M - Model数据模型(业务逻辑层)

既是主要处理伴随数据模型的业务逻辑层。


V - View 视图(表现层)

更基本的来说,也就是界面,负责显示数据及与用户进行交互。


P - Presenter 表示器。

个人认为可以理解为一个中转站,也就是M与P的连接枢纽。

其行为表现为:接收“V”上反馈的指令,分发给“M”进行处理,最后将处理的结果再反馈在“V”上。


知道了较为书面化的概念,结合一点生活中熟悉的事物,希望能帮助我们更好的进行理解。

我们这样想象,你开了一家小餐馆,那么对应来说:

V - 餐馆门市。

既消费者双眼所见的,门市既是整个“餐馆系统”的视图。


M - 餐馆厨房。

用户在“V”里,既是在“餐馆”内产生了交互行为“点餐”,“厨房”负责处理该业务逻辑,最终产生要反馈给用户的结果“菜肴”。


P - 餐馆店小二。

通常来说,食客来到餐馆,不会直接开始寻找厨房,然后直接告诉厨师自己所点的菜肴。而厨师也不会在完成菜品之后,再亲自上菜。

所以,你需要一个“店小二”,既MVP里的“P”。这里你就能十分形象的理解到“表示器P”其充当的角色,既一个“枢纽”的角色。


分析一下:

  1. 食客点餐完毕。
  2. 将菜单交至店小二;
  3. 店小二将菜单送到厨房;
  4. 厨房根据菜单做好菜肴,通知店小二;
  5. 店小二将菜肴上至对应餐桌。

对应来说:

  1. 用户在android设备上(既“V”)进行了操作;
  2. “V”接受到交互,将用户的行为传递给表示器“P”;
  3. 表示器收到反馈行为,通知对应的业务逻辑处理器;
  4. “M”收到,进行处理,将处理得到的结果返回给“P”;
  5. “P”得到结果,将结果返回给“V”,即反馈给用户;

这样加以对应,是否会对于理解MVP模式有所帮助?


话已至此,对于所谓的MVP,我们基本已经有了一定的了解,接着,就根据一个简单的例子,来加深自己的印象和理解。
以我在另一篇描述MVP模式的博客里看到的需求为例,假设我们现在有一个“验证登录”的需求,要实现以下效果:



接着,开始着手搭建项目,编写代码。

1、餐厅开业之前,我们要先搞定门市。 OK,所以第一步,我们完成登录界面的布局文件的编写。

2、门市搞定了,为了生意,接着我们进行“装修”。没问题,那我们接着进行View的编写工作,完成装修(加载布局,实例化控件,绑定监听事件)。于是这时的LoginActivity应该是这样的:
public class LoginActivity extends Activity implements ILoginView {
	private EditText userName, passWord;
	private Button login, clearInput;
	private ProgressBar loginProgress;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_login);
		// Init view
		initView();

	}

    private void initView() {
	userName = (EditText) this.findViewById(R.id.user_name);
	passWord = (EditText) this.findViewById(R.id.password);

	login = (Button) this.findViewById(R.id.btn_login);
	clearInput = (Button) this.findViewById(R.id.btn_clear);

	login.setOnClickListener(new View.OnClickListener() {

		@Override
		public void onClick(View v) {
			//need to do..
		}
	});

	clearInput.setOnClickListener(new View.OnClickListener() {

		@Override
		public void onClick(View v) {
			//need to do..
		}
	});
	
	loginProgress = (ProgressBar) this.findViewById(R.id.login_loading);
}

	}
}

3.门市的装修工作终于完成了,我们送了一口气。这个时候,为了将来餐馆能够有条不紊的顺利营业。 此时,也许我们就应该开始着手考虑,当我们的餐馆正式开始营业后,可能与消费产生哪些交互行为?从而定下一些好的营业行为规范了!
  
  对应于程序中来说,既是我们该View中需要与用户发生交互的一切行为。所以,对应我们“验证登录”的需求来说,接口的定义可能就类似于:
public interface ILoginView {

	// 获取用户输入用户名
	String getUserNameInput();

	// 获取用户输入密码
	String getPassWordInput();

	// 登录成功,跳转界面
	void toAppMainActivity();

	// 登录失败,提示用户
	void showLoginFailedInfo();

	// 清空输入记录
	void clearInputValue();
	
	// 提示正在登录
	void showLoginLoading();
	
	// 隐藏正在登录提示
	void hideLoginLoading();
}

4、规范我们已经定下了,通知小秘,去把经营规范打印出来,给我挂在餐馆里最显眼的地方!
所以我们此时要做的也是一样,既然View的接口已经声明完毕,此时我们就应该让Activity去实现接口,去落实老板定下的规范。
所以此时的Activity类经过完善,变成了下面这样:
public class LoginActivity extends Activity implements ILoginView {

	private EditText userName, passWord;
	private Button login, clearInput;
	private ProgressBar loginProgress;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_login);
		// Init view
		initView();

	}

	private void initView() {
		userName = (EditText) this.findViewById(R.id.user_name);
		passWord = (EditText) this.findViewById(R.id.password);

		login = (Button) this.findViewById(R.id.btn_login);
		clearInput = (Button) this.findViewById(R.id.btn_clear);

		login.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// need to do..
			}
		});

		clearInput.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// need to do..
			}
		});
		
		loginProgress = (ProgressBar) this.findViewById(R.id.login_loading);
	}

	// implements interface method
	@Override
	public String getUserNameInput() {
		return userName.getEditableText().toString();
	}

	@Override
	public String getPassWordInput() {
		return passWord.getEditableText().toString();
	}

	@Override
	public void toAppMainActivity() {
		Intent intent = new Intent(LoginActivity.this, MainActivity.class);
		startActivity(intent);

	}

	@Override
	public void showLoginFailedInfo() {
		Toast.makeText(this, "Sorry,Plase check your input!", Toast.LENGTH_LONG).show();
	}

	@Override
	public void clearInputValue() {
		userName.setText("");
		passWord.setText("");
	}

	@Override
	public void showLoginLoading() {
		loginProgress.setVisibility(View.VISIBLE);
	}

	@Override
	public void hideLoginLoading() {
		loginProgress.setVisibility(View.INVISIBLE);
	}
}

5、这个时候对于店铺的打理已经基本进行完毕了,这个时候我们应该着手考虑一下餐厅的业务逻辑了,例如最关键的:找个好厨师~
没问题,也就是说此时我们应该开始着手于Model层的打架工作了。
在这里,对应于验证登录的业务需求,我们首先建立我们的数据模型:
public class User {

	private String userName;
	private String passWord;

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getPassWord() {
		return passWord;
	}

	public void setPassWord(String passWord) {
		this.passWord = passWord;
	}

}
数据模型搞定之后,也就该完成针对于此数据模型进行处理的业务逻辑类了。在我们的例子中,只存在一个业务逻辑,既是验证登录。首先,我们依旧是首先定义下规范:
public interface ILoginModel {

	void doLogin(String userName, String passWord, ILoginListener loginListener);
}
接着,根据规范去定义具体的处理办法:
public class LoginModel implements ILoginModel {

	@Override
	public void doLogin(final String userName, final String passWord, final ILoginListener loginListener) {
		new Thread(new Runnable() {

			@Override
			public void run() {
				// 模拟耗时操作
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				// 验证登录
				if (userName.equals("tsr") && passWord.equals("123")) {
					User user = new User();
					user.setUserName(userName);
					user.setPassWord(passWord);
					loginListener.loginSuccess(user);
				} else {
					loginListener.loginFailed();
				}

			}
		}).start();
	}

}
一切工作都在有条不紊的进行当中,唯一值得注意的是,在此我们定义了一个监听接口,用以监听登录结果,并根据结果做出对应操作。
public interface ILoginListener {

	void loginSuccess(User user);

	void loginFailed();
}

6、当我们工作至此处时,厨房部门已经被我们打点完毕了。此时,万事俱备,只欠东风。我们只差一个关键的“店小二”了。
是的,当View层和Model层的搭建工作都已经完成,我们就只差一个Presenter来联系它们了。

你可以试着这样考虑,既然Presenter将作为View和Model的链接枢纽,那么肯定它是能够同时访问二者的。
这对应于程序来说,也就是,在Presenter类里,将必然存在View和Model的一个实例,从而你才能够调用他们的方法,完成交互。

而同时,就好比你作为餐馆老板招聘一名店小二,你肯定会明确的做出要求:既店小二开始上班以后,他的工作内容是什么。
所以在Presenter类里,你还应该根据实际,定义相应的行为。
就如同,餐馆老板在餐厅营业应为里规定了有“点餐”这个营业行为,那么当有顾客进行了“点餐”这个行为时,就应该调用“店小二”执行“收、取菜单”的动作。
对应我们本例当中,既是当有用户点击了“登录”按钮时,View就知道调用Presenter里的对应方法,再由Presenter去调用model里对应的方法。

所以,我们最终的Presenter类的定义可能如下:
public class LoginPresenter {

	private ILoginModel loginModel;
	private ILoginView loginView;
	private Handler mHandler;

	public LoginPresenter(ILoginView loginView) {
		this.loginModel = new LoginModel();
		this.loginView = loginView;
		mHandler = new Handler();
	}

	public void doLogin() {
		loginView.showLoginLoading();
		
		loginModel.doLogin(loginView.getUserNameInput(), loginView.getPassWordInput(), new ILoginListener() {

			@Override
			public void loginSuccess(User user) {
				mHandler.post(new Runnable() {

					@Override
					public void run() {
						loginView.hideLoginLoading();
						loginView.toAppMainActivity();

					}
				});

			}

			@Override
			public void loginFailed() {
				mHandler.post(new Runnable() {

					@Override
					public void run() {
						loginView.hideLoginLoading();
						loginView.showLoginFailedInfo();

					}
				});

			}
		});
	}

	public void clearInputValue() {
		loginView.clearInputValue();
	}
}

7、到了这里你的餐厅完全已经做好开始营业的准备了!唯一的工作,记得为你的“店小二”办理入职手续。
对应到我们的程序来说,既是将Presenter的实例添加到View当中,并且在各个按钮的监听事件里,安排Presenter进行对应的行为。
于是,最终的Activity变成了:
public class LoginActivity extends Activity implements ILoginView {

	private LoginPresenter mPresenter;
	private EditText userName, passWord;
	private Button login, clearInput;
	private ProgressBar loginProgress;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_login);
		// Init presenter
		mPresenter = new LoginPresenter(this);
		// Init view
		initView();

	}

	private void initView() {
		userName = (EditText) this.findViewById(R.id.user_name);
		passWord = (EditText) this.findViewById(R.id.password);

		login = (Button) this.findViewById(R.id.btn_login);
		clearInput = (Button) this.findViewById(R.id.btn_clear);

		login.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				mPresenter.doLogin();
			}
		});

		clearInput.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				mPresenter.clearInputValue();
			}
		});
		
		loginProgress = (ProgressBar) this.findViewById(R.id.login_loading);
	}

	// implements interface method
	@Override
	public String getUserNameInput() {
		return userName.getEditableText().toString();
	}

	@Override
	public String getPassWordInput() {
		return passWord.getEditableText().toString();
	}

	@Override
	public void toAppMainActivity() {
		Intent intent = new Intent(LoginActivity.this, MainActivity.class);
		startActivity(intent);

	}

	@Override
	public void showLoginFailedInfo() {
		Toast.makeText(this, "Sorry,Plase check your input!", Toast.LENGTH_LONG).show();
	}

	@Override
	public void clearInputValue() {
		userName.setText("");
		passWord.setText("");
	}

	@Override
	public void showLoginLoading() {
		loginProgress.setVisibility(View.VISIBLE);
	}

	@Override
	public void hideLoginLoading() {
		loginProgress.setVisibility(View.INVISIBLE);
	}
}

至此,我们已经完全采用M-V-P的架构模式来搭建完成了“登录验证”的小项目。
对此我们加以总结,其实可以得出:对于MVP模式的构建,其规律为:
  • Activity现在只编写最基本的功能代码,如:加载布局,实例化控件,绑定监听等。
  • 将与用户发生交互的界面操作,都抽象到View接口中,然后由Activity实现接口,进而具体实现。
  • 需要对用户的行为进行处理或反馈的方法(例如上面我们的代码中登录与清楚按钮的点击事件处理),都抽离出来放到Presenter当中。
  • 需要对针对于数据模型进行操作的方法(例如将用户输入的数据进行持久化存储或需要在服务器进行处理等)都放在业务逻辑类,即Model当中。
  • Presenter从View接收数据,调用Model进行处理;从Model获取处理的结果,调用View反馈到界面。
来看一下最终的代码结构变成了什么样,是不是条理有变得更加清晰:






  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值