在Android开发的初始阶段,学习者会以最终创建一个MainActivity类的方式编写代码,该类包含应用程序的所有实现逻辑(现实世界中的业务逻辑)。这种应用程序开发方法导致Android活动与UI和应用程序数据处理机制紧密相关。此外,这在维护和扩展此类移动应用程序方面造成了困难。为了避免在可维护性,可读性,可伸缩性和应用程序重构方面出现此类问题,开发人员更喜欢定义分隔良好的代码层。通过应用软件体系结构模式,人们可以组织应用程序的代码以分离关注点。 MVP(模型-视图-演示者)体系结构是最受欢迎的体系结构模式之一,可有效地组织项目。
MVP(模型-视图-演示者)作为传统MVC(模型-视图-控制器)体系结构模式的替代品出现。使用MVC作为软件体系结构,开发人员最终面临以下困难:
- 大多数核心业务逻辑都驻留在Controller中。在应用程序的生命周期中,此文件会变得越来越大,并且变得难以维护代码。
- 由于UI和数据访问机制紧密耦合,因此Controller和View层属于同一活动或片段。这会导致在更改应用程序功能时出现问题。
- 由于要测试的大部分零件都需要Android SDK组件,因此很难对不同层进行单元测试。
MVP模式克服了MVC的这些挑战,并提供了一种构造项目代码的简便方法。 MVP之所以被广泛接受,是因为它提供了模块化,可测试性以及更干净和可维护的代码库。它由以下三个组件组成:
- 型号:用于存储数据的层。它负责处理域逻辑(实际业务规则)以及与数据库和网络层的通信。
- 查看: UI(用户界面)层。它提供数据的可视化并跟踪用户的操作,以便通知Presenter。
- 演示者:从模型中获取数据并应用UI逻辑来决定要显示的内容。它管理视图的状态,并根据用户从视图中输入的通知执行操作。
MVP架构的要点
- View-Presenter和Presenter-Model之间的通信是通过接口(也称为Contract)进行的。
- 一个Presenter类一次管理一个View,即Presenter和View之间存在一对一的关系。
- Model和View类不了解彼此的存在。
MVP架构示例
为了展示MVP架构模式在项目上的实现,这里是一个单活动android应用程序的示例。通过从模型中随机选择,应用程序将在View(Activity)上显示一些字符串。 Presenter类的作用是使应用程序的业务逻辑远离活动。以下是此android应用程序的完整分步实施。请注意,我们将同时使用Java和Kotlin语言来实现该项目。
Note: Following steps are performed on Android Studio version 4.0
步骤1:创建一个新项目
- 单击文件,然后单击新建=>新建项目。
- 选择清空活动
- 选择语言作为Java/科特林
- 根据需要选择最小的SDK。
步骤2:修改String.xml文件
此文件中列出了活动中使用的所有字符串。
XML
GfG | MVP Architecture
Display Next Course
MVP Architecture Pattern
GeeksforGeeks Computer Science Online Courses
Course Description
XML
Java
public interface Contract {
interface View {
// method to display progress bar
// when next random course details
// is being fetched
void showProgress();
// method to hide progress bar
// when next random course details
// is being fetched
void hideProgress();
// method to set random
// text on the TextView
void setString(String string);
}
interface Model {
// nested interface to be
interface OnFinishedListener {
// function to be called
// once the Handler of Model class
// completes its execution
void onFinished(String string);
}
void getNextCourse(Contract.Model.OnFinishedListener onFinishedListener);
}
interface Presenter {
// method to be called when
// the button is clicked
void onButtonClick();
// method to destroy
// lifecycle of MainActivity
void onDestroy();
}
}
Kotlin
interface Contract {
interface View {
// method to display progress bar
// when next random course details
// is being fetched
fun showProgress()
// method to hide progress bar
// when next random course details
// is being fetched
fun hideProgress()
// method to set random
// text on the TextView
fun setString(string: String?)
}
interface Model {
// nested interface to be
interface OnFinishedListener {
// function to be called
// once the Handler of Model class
// completes its execution
fun onFinished(string: String?)
}
fun getNextCourse(onFinishedListener: OnFinishedListener?)
}
interface Presenter {
// method to be called when
// the button is clicked
fun onButtonClick()
// method to destroy
// lifecycle of MainActivity
fun onDestroy()
}
}
Java
import android.os.Handler;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
public class Model implements Contract.Model {
// array list of strings from which
// random strings will be selected
// to display in the activity
private List arrayList = Arrays.asList(
"DSA Self Paced: Master the basics of Data Structures and Algorithms to solve complex problems efficiently. ",
"Placement 100: This course will guide you for placement with theory,lecture videos, weekly assignments " +
"contests and doubt assistance.",
"Amazon SDE Test Series: Test your skill & give the final touch to your preparation before applying for " +
"product based against like Amazon, Microsoft, etc.",
"Complete Interview Preparation: Cover all the important concepts and topics required for the interviews. " +
"Get placement ready before the interviews begin",
"Low Level Design for SDE 1 Interview: Learn Object-oriented Analysis and Design to prepare for " +
"SDE 1 Interviews in top companies"
);
@Override
// this method will invoke when
// user clicks on the button
// and it will take a delay of
// 1200 milliseconds to display next course detail
public void getNextCourse(final OnFinishedListener listener) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
listener.onFinished(getRandomString());
}
}, 1200);
}
// method to select random
// string from the list of strings
private String getRandomString() {
Random random = new Random();
int index = random.nextInt(arrayList.size());
return arrayList.get(index);
}
}
Kotlin
import android.os.Handler
import java.util.*
class Model : Contract.Model {
// array list of strings from which
// random strings will be selected
// to display in the activity
private val arrayList =
Arrays.asList(
"DSA Self Paced: Master the basics of Data Structures and Algorithms to solve complex problems efficiently. ",
"Placement 100: This course will guide you for placement with theory,lecture videos, weekly assignments " +
"contests and doubt assistance.",
"Amazon SDE Test Series: Test your skill & give the final touch to your preparation before applying for " +
"product based against like Amazon, Microsoft, etc.",
"Complete Interview Preparation: Cover all the important concepts and topics required for the interviews. " +
"Get placement ready before the interviews begin",
"Low Level Design for SDE 1 Interview: Learn Object-oriented Analysis and Design to prepare for " +
"SDE 1 Interviews in top companies"
)
// this method will invoke when
// user clicks on the button
// and it will take a delay of
// 1200 milliseconds to display next course detail
override fun getNextCourse(onFinishedListener: Contract.Model.OnFinishedListener?) {
Handler().postDelayed({ onFinishedListener!!.onFinished(getRandomString) }, 1200)
}
// method to select random
// string from the list of strings
private val getRandomString: String
private get() {
val random = Random()
val index = random.nextInt(arrayList.size)
return arrayList[index]
}
}
Java
public class Presenter implements Contract.Presenter, Contract.Model.OnFinishedListener {
// creating object of View Interface
private Contract.View mainView;
// creating object of Model Interface
private Contract.Model model;
// instantiating the objects of View and Model Interface
public Presenter(Contract.View mainView, Contract.Model model) {
this.mainView = mainView;
this.model = model;
}
@Override
// operations to be performed
// on button click
public void onButtonClick() {
if (mainView != null) {
mainView.showProgress();
}
model.getNextCourse(this);
}
@Override
public void onDestroy() {
mainView = null;
}
@Override
// method to return the string
// which will be displayed in the
// Course Detail TextView
public void onFinished(String string) {
if (mainView != null) {
mainView.setString(string);
mainView.hideProgress();
}
}
}
Kotlin
// instantiating the objects of View and Model Interface
// creating object of View Interface
// creating object of Model Interface
class Presenter(
private var mainView: Contract.View?,
private val model: Contract.Model) : Contract.Presenter,
Contract.Model.OnFinishedListener {
// operations to be performed
// on button click
override fun onButtonClick() {
if (mainView != null) {
mainView!!.showProgress()
}
model.getNextCourse(this)
}
override fun onDestroy() {
mainView = null
}
// method to return the string
// which will be displayed in the
// Course Detail TextView
override fun onFinished(string: String?) {
if (mainView != null) {
mainView!!.setString(string)
mainView!!.hideProgress()
}
}
}
Java
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import static android.view.View.GONE;
public class MainActivity extends AppCompatActivity implements Contract.View {
// creating object of TextView class
private TextView textView;
// creating object of Button class
private Button button;
// creating object of ProgressBar class
private ProgressBar progressBar;
// creating object of Presenter interface in Contract
Contract.Presenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// assigning ID of the TextView
textView = findViewById(R.id.textView);
// assigning ID of the Button
button = findViewById(R.id.button);
// assigning ID of the ProgressBar
progressBar = findViewById(R.id.progressBar);
// instantiating object of Presenter Interface
presenter = new Presenter(this, new Model());
// operations to be performed when
// user clicks the button
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.onButtonClick();
}
});
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.onDestroy();
}
@Override
// method to display the Course Detail TextView
public void showProgress() {
progressBar.setVisibility(View.VISIBLE);
textView.setVisibility(View.INVISIBLE);
}
@Override
// method to hide the Course Detail TextView
public void hideProgress() {
progressBar.setVisibility(GONE);
textView.setVisibility(View.VISIBLE);
}
@Override
// method to set random string
// in the Course Detail TextView
public void setString(String string) {
textView.setText(string);
}
}
Kotlin
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity(), Contract.View {
// creating object of TextView class
private var textView: TextView? = null
// creating object of Button class
private var button: Button? = null
// creating object of ProgressBar class
private var progressBar: ProgressBar? = null
// creating object of Presenter interface in Contract
var presenter: Presenter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// assigning ID of the TextView
textView = findViewById(R.id.textView)
// assigning ID of the Button
button = findViewById(R.id.button)
// assigning ID of the ProgressBar
progressBar = findViewById(R.id.progressBar)
// instantiating object of Presenter Interface
presenter = Presenter(this, Model())
// operations to be performed when
// user clicks the button
this.button!!.setOnClickListener(View.OnClickListener { presenter!!.onButtonClick() })
}
override fun onResume() {
super.onResume()
}
override fun onDestroy() {
super.onDestroy()
presenter!!.onDestroy()
}
// method to display the Course Detail TextView
override fun showProgress() {
progressBar!!.visibility = View.VISIBLE
textView!!.visibility = View.INVISIBLE
}
// method to hide the Course Detail TextView
override fun hideProgress() {
progressBar!!.visibility = View.GONE
textView!!.visibility = View.VISIBLE
}
// method to set random string
// in the Course Detail TextView
override fun setString(string: String?) {
textView!!.text = string
}
}
步骤3:使用activity_main.xml文件
打开activity_main.xml文件,并添加一个Button,一个TextView以显示该字符串以及一个Progress Bar,以使应用程序具有动态感。以下是用于设计适当活动布局的代码。
XML格式
步骤4:为模型,视图和演示者定义合同接口文件
为了在View-Presenter和Presenter-Model之间建立通信,需要一个接口。该接口类将包含所有抽象方法,这些方法稍后将在View,Model和Presenter类中定义。
Java
public interface Contract {
interface View {
// method to display progress bar
// when next random course details
// is being fetched
void showProgress();
// method to hide progress bar
// when next random course details
// is being fetched
void hideProgress();
// method to set random
// text on the TextView
void setString(String string);
}
interface Model {
// nested interface to be
interface OnFinishedListener {
// function to be called
// once the Handler of Model class
// completes its execution
void onFinished(String string);
}
void getNextCourse(Contract.Model.OnFinishedListener onFinishedListener);
}
interface Presenter {
// method to be called when
// the button is clicked
void onButtonClick();
// method to destroy
// lifecycle of MainActivity
void onDestroy();
}
}
科特林
interface Contract {
interface View {
// method to display progress bar
// when next random course details
// is being fetched
fun showProgress()
// method to hide progress bar
// when next random course details
// is being fetched
fun hideProgress()
// method to set random
// text on the TextView
fun setString(string: String?)
}
interface Model {
// nested interface to be
interface OnFinishedListener {
// function to be called
// once the Handler of Model class
// completes its execution
fun onFinished(string: String?)
}
fun getNextCourse(onFinishedListener: OnFinishedListener?)
}
interface Presenter {
// method to be called when
// the button is clicked
fun onButtonClick()
// method to destroy
// lifecycle of MainActivity
fun onDestroy()
}
}
步骤5:创建模型类
创建一个名为Model的新类,以分隔所有字符串数据和获取这些数据的方法。该类将不知道View类的存在。
Java
import android.os.Handler;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
public class Model implements Contract.Model {
// array list of strings from which
// random strings will be selected
// to display in the activity
private List arrayList = Arrays.asList(
"DSA Self Paced: Master the basics of Data Structures and Algorithms to solve complex problems efficiently. ",
"Placement 100: This course will guide you for placement with theory,lecture videos, weekly assignments " +
"contests and doubt assistance.",
"Amazon SDE Test Series: Test your skill & give the final touch to your preparation before applying for " +
"product based against like Amazon, Microsoft, etc.",
"Complete Interview Preparation: Cover all the important concepts and topics required for the interviews. " +
"Get placement ready before the interviews begin",
"Low Level Design for SDE 1 Interview: Learn Object-oriented Analysis and Design to prepare for " +
"SDE 1 Interviews in top companies"
);
@Override
// this method will invoke when
// user clicks on the button
// and it will take a delay of
// 1200 milliseconds to display next course detail
public void getNextCourse(final OnFinishedListener listener) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
listener.onFinished(getRandomString());
}
}, 1200);
}
// method to select random
// string from the list of strings
private String getRandomString() {
Random random = new Random();
int index = random.nextInt(arrayList.size());
return arrayList.get(index);
}
}
科特林
import android.os.Handler
import java.util.*
class Model : Contract.Model {
// array list of strings from which
// random strings will be selected
// to display in the activity
private val arrayList =
Arrays.asList(
"DSA Self Paced: Master the basics of Data Structures and Algorithms to solve complex problems efficiently. ",
"Placement 100: This course will guide you for placement with theory,lecture videos, weekly assignments " +
"contests and doubt assistance.",
"Amazon SDE Test Series: Test your skill & give the final touch to your preparation before applying for " +
"product based against like Amazon, Microsoft, etc.",
"Complete Interview Preparation: Cover all the important concepts and topics required for the interviews. " +
"Get placement ready before the interviews begin",
"Low Level Design for SDE 1 Interview: Learn Object-oriented Analysis and Design to prepare for " +
"SDE 1 Interviews in top companies"
)
// this method will invoke when
// user clicks on the button
// and it will take a delay of
// 1200 milliseconds to display next course detail
override fun getNextCourse(onFinishedListener: Contract.Model.OnFinishedListener?) {
Handler().postDelayed({ onFinishedListener!!.onFinished(getRandomString) }, 1200)
}
// method to select random
// string from the list of strings
private val getRandomString: String
private get() {
val random = Random()
val index = random.nextInt(arrayList.size)
return arrayList[index]
}
}
步骤6:创建Presenter类
此类的方法包含核心业务逻辑,这些逻辑将决定要显示什么以及如何显示。它触发View类对UI进行必要的更改。
Java
public class Presenter implements Contract.Presenter, Contract.Model.OnFinishedListener {
// creating object of View Interface
private Contract.View mainView;
// creating object of Model Interface
private Contract.Model model;
// instantiating the objects of View and Model Interface
public Presenter(Contract.View mainView, Contract.Model model) {
this.mainView = mainView;
this.model = model;
}
@Override
// operations to be performed
// on button click
public void onButtonClick() {
if (mainView != null) {
mainView.showProgress();
}
model.getNextCourse(this);
}
@Override
public void onDestroy() {
mainView = null;
}
@Override
// method to return the string
// which will be displayed in the
// Course Detail TextView
public void onFinished(String string) {
if (mainView != null) {
mainView.setString(string);
mainView.hideProgress();
}
}
}
科特林
// instantiating the objects of View and Model Interface
// creating object of View Interface
// creating object of Model Interface
class Presenter(
private var mainView: Contract.View?,
private val model: Contract.Model) : Contract.Presenter,
Contract.Model.OnFinishedListener {
// operations to be performed
// on button click
override fun onButtonClick() {
if (mainView != null) {
mainView!!.showProgress()
}
model.getNextCourse(this)
}
override fun onDestroy() {
mainView = null
}
// method to return the string
// which will be displayed in the
// Course Detail TextView
override fun onFinished(string: String?) {
if (mainView != null) {
mainView!!.setString(string)
mainView!!.hideProgress()
}
}
}
步骤7:在MainActivity文件中定义View的功能
View类负责根据Presenter层触发的更改来更新UI。 View将使用模型提供的数据,并在活动中进行适当的更改。
Java
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import static android.view.View.GONE;
public class MainActivity extends AppCompatActivity implements Contract.View {
// creating object of TextView class
private TextView textView;
// creating object of Button class
private Button button;
// creating object of ProgressBar class
private ProgressBar progressBar;
// creating object of Presenter interface in Contract
Contract.Presenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// assigning ID of the TextView
textView = findViewById(R.id.textView);
// assigning ID of the Button
button = findViewById(R.id.button);
// assigning ID of the ProgressBar
progressBar = findViewById(R.id.progressBar);
// instantiating object of Presenter Interface
presenter = new Presenter(this, new Model());
// operations to be performed when
// user clicks the button
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.onButtonClick();
}
});
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.onDestroy();
}
@Override
// method to display the Course Detail TextView
public void showProgress() {
progressBar.setVisibility(View.VISIBLE);
textView.setVisibility(View.INVISIBLE);
}
@Override
// method to hide the Course Detail TextView
public void hideProgress() {
progressBar.setVisibility(GONE);
textView.setVisibility(View.VISIBLE);
}
@Override
// method to set random string
// in the Course Detail TextView
public void setString(String string) {
textView.setText(string);
}
}
科特林
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity(), Contract.View {
// creating object of TextView class
private var textView: TextView? = null
// creating object of Button class
private var button: Button? = null
// creating object of ProgressBar class
private var progressBar: ProgressBar? = null
// creating object of Presenter interface in Contract
var presenter: Presenter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// assigning ID of the TextView
textView = findViewById(R.id.textView)
// assigning ID of the Button
button = findViewById(R.id.button)
// assigning ID of the ProgressBar
progressBar = findViewById(R.id.progressBar)
// instantiating object of Presenter Interface
presenter = Presenter(this, Model())
// operations to be performed when
// user clicks the button
this.button!!.setOnClickListener(View.OnClickListener { presenter!!.onButtonClick() })
}
override fun onResume() {
super.onResume()
}
override fun onDestroy() {
super.onDestroy()
presenter!!.onDestroy()
}
// method to display the Course Detail TextView
override fun showProgress() {
progressBar!!.visibility = View.VISIBLE
textView!!.visibility = View.INVISIBLE
}
// method to hide the Course Detail TextView
override fun hideProgress() {
progressBar!!.visibility = View.GONE
textView!!.visibility = View.VISIBLE
}
// method to set random string
// in the Course Detail TextView
override fun setString(string: String?) {
textView!!.text = string
}
}
输出
MVP架构的优势
- android组件中没有概念关系
- 由于应用程序的模型,视图和演示者层是分离的,因此易于代码维护和测试。
MVP架构的缺点
- 如果开发人员不遵循单一职责原则来破坏代码,则Presenter层倾向于扩展为庞大的全知类。