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 InterfaceinterfaceModel {fungetData(): List<String>funsetData(data: List<String>)funupdateState()funregisterDataChangeListener(listener: DataChangeListener)}// View InterfaceinterfaceView {fundisplayData(data: List<String>)funshowError(message: String)funhandleUserInteraction()funsetPresenter(presenter: Presenter)}// Presenter InterfaceinterfacePresenter {funloadData()funprocessUserAction(action: String)funupdateView()}// Data Change Listener InterfaceinterfaceDataChangeListener {funonDataChanged()}// Concrete Model ImplementationclassTaskModel : Model {privateval tasks =mutableListOf<String>()privateval listeners =mutableListOf<DataChangeListener>()overridefungetData(): List<String> = tasks.toList()overridefunsetData(data: List<String>) { tasks.clear() tasks.addAll(data)notifyDataChangeListeners() }overridefunupdateState() {// Simulate some state update tasks.add("New Task ${tasks.size +1}")notifyDataChangeListeners() }overridefunregisterDataChangeListener(listener: DataChangeListener) { listeners.add(listener) }privatefunnotifyDataChangeListeners() { listeners.forEach { it.onDataChanged() } }}// Concrete View ImplementationclassTaskConsoleView : View, DataChangeListener {privatevar presenter: Presenter? =nullprivatevar currentTasks: List<String> =listOf()overridefundisplayData(data: List<String>) { currentTasks =dataprintln("Current Tasks:")data.forEachIndexed { index, task ->println("${index +1}. $task") } }overridefunshowError(message: String) {println("Error: $message") }overridefunhandleUserInteraction() {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") } }overridefunsetPresenter(presenter: Presenter) {this.presenter = presenter }overridefunonDataChanged() { presenter?.updateView() }}// Concrete Presenter ImplementationclassTaskPresenter(privateval model: Model,privateval 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) }overridefunloadData() {// Initial data loadingval initialTasks =listOf("Setup project", "Design architecture") model.setData(initialTasks) }overridefunprocessUserAction(action: String) {when (action) {"add"-> { model.updateState() }"refresh"-> { view.displayData(model.getData()) }else-> view.showError("Unknown action") } }overridefunupdateView() {// Update the view with current data from the model view.displayData(model.getData()) }overridefunonDataChanged() {// Another way to trigger view updateupdateView() }}// Main Application RunnerclassMVPTaskManagerApp {companionobject {@JvmStaticfunmain(args: Array<String>) {// Create MVP componentsval model =TaskModel()val view =TaskConsoleView()val presenter =TaskPresenter(model, view)// Load initial data presenter.loadData()// Simulate user interaction loopwhile (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 viewval View.currentTasks: List<String>get() =when (this) {is TaskConsoleView ->this.currentTaskselse->listOf() }