MVP Architecture Pattern

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:

  1. 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

  2. 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

  3. 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:

  1. User interacts with the View

  2. View notifies the Presenter about the action

  3. Presenter processes the action

  4. Presenter retrieves or manipulates data in the Model

  5. Presenter formats the data

  6. 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()
    }

Last updated