MVP is an architectural pattern that evolves from MVC, primarily used in developing user interfaces. It addresses some of the limitations of the MVC pattern by introducing a more decoupled approach to application design.
Core Components of MVP:
Model
Represents the data and business logic of the application
Manages data, performs computations, and handles data-related operations
Independent of the user interface
Similar to the Model in MVC
View
Responsible for displaying data to the user
Defines the UI components and layout
Passive and doesn't process data directly
Sends user actions to the Presenter
Receives display instructions from the Presenter
Presenter
Acts as an intermediary between Model and View
Contains the presentation logic
Retrieves data from the Model
Prepares and formats data for the View
Handles user interactions from the View
Updates the View based on Model changes
Key Differences from MVC:
In MVP, the Presenter fully manages the communication between Model and View
View is more passive compared to MVC
Presenter has a one-to-one relationship with the View
Direct dependency between View and Presenter (unlike MVC)
How MVP Works:
User interacts with the View
View notifies the Presenter about the action
Presenter processes the action
Presenter retrieves or manipulates data in the Model
Presenter formats the data
Presenter updates the View with processed data
Advantages of MVP:
Better separation of concerns
Improved testability (easier to unit test)
More maintainable code
Easier to modify individual components
Clearer responsibilities for each component
Class Diagram:
Code:
// Model Interface
interface Model {
fun getData(): List<String>
fun setData(data: List<String>)
fun updateState()
fun registerDataChangeListener(listener: DataChangeListener)
}
// View Interface
interface View {
fun displayData(data: List<String>)
fun showError(message: String)
fun handleUserInteraction()
fun setPresenter(presenter: Presenter)
}
// Presenter Interface
interface Presenter {
fun loadData()
fun processUserAction(action: String)
fun updateView()
}
// Data Change Listener Interface
interface DataChangeListener {
fun onDataChanged()
}
// Concrete Model Implementation
class TaskModel : Model {
private val tasks = mutableListOf<String>()
private val listeners = mutableListOf<DataChangeListener>()
override fun getData(): List<String> = tasks.toList()
override fun setData(data: List<String>) {
tasks.clear()
tasks.addAll(data)
notifyDataChangeListeners()
}
override fun updateState() {
// Simulate some state update
tasks.add("New Task ${tasks.size + 1}")
notifyDataChangeListeners()
}
override fun registerDataChangeListener(listener: DataChangeListener) {
listeners.add(listener)
}
private fun notifyDataChangeListeners() {
listeners.forEach { it.onDataChanged() }
}
}
// Concrete View Implementation
class TaskConsoleView : View, DataChangeListener {
private var presenter: Presenter? = null
private var currentTasks: List<String> = listOf()
override fun displayData(data: List<String>) {
currentTasks = data
println("Current Tasks:")
data.forEachIndexed { index, task ->
println("${index + 1}. $task")
}
}
override fun showError(message: String) {
println("Error: $message")
}
override fun handleUserInteraction() {
println("\nAvailable Actions:")
println("1. Add New Task")
println("2. Refresh Tasks")
println("3. Exit")
print("Choose an action: ")
when (readLine()?.trim()) {
"1" -> presenter?.processUserAction("add")
"2" -> presenter?.processUserAction("refresh")
"3" -> println("Exiting...")
else -> showError("Invalid action")
}
}
override fun setPresenter(presenter: Presenter) {
this.presenter = presenter
}
override fun onDataChanged() {
presenter?.updateView()
}
}
// Concrete Presenter Implementation
class TaskPresenter(
private val model: Model,
private val view: View
) : Presenter, DataChangeListener {
init {
// Register the presenter as a data change listener
model.registerDataChangeListener(this)
// Set this presenter for the view
view.setPresenter(this)
}
override fun loadData() {
// Initial data loading
val initialTasks = listOf("Setup project", "Design architecture")
model.setData(initialTasks)
}
override fun processUserAction(action: String) {
when (action) {
"add" -> {
model.updateState()
}
"refresh" -> {
view.displayData(model.getData())
}
else -> view.showError("Unknown action")
}
}
override fun updateView() {
// Update the view with current data from the model
view.displayData(model.getData())
}
override fun onDataChanged() {
// Another way to trigger view update
updateView()
}
}
// Main Application Runner
class MVPTaskManagerApp {
companion object {
@JvmStatic
fun main(args: Array<String>) {
// Create MVP components
val model = TaskModel()
val view = TaskConsoleView()
val presenter = TaskPresenter(model, view)
// Load initial data
presenter.loadData()
// Simulate user interaction loop
while (true) {
view.handleUserInteraction()
// Basic exit condition (in a real app, this would be more sophisticated)
if (view.currentTasks.size > 5) break
}
}
}
}
// Extension property to track current tasks in view
val View.currentTasks: List<String>
get() = when (this) {
is TaskConsoleView -> this.currentTasks
else -> listOf()
}