MVC Architecture pattern

MVC is a widely used architectural pattern in software design that separates an application into three interconnected components, each handling a specific aspect of the application's logic:

  1. Model

    • Represents the application's data and business logic

    • Manages the state, rules, and operations of the data

    • Independent of the user interface

    • Responsible for storing, processing, and managing data

    • Notifies Views and Controllers about any changes in its state

  2. View

    • Handles the presentation layer of the application

    • Renders the user interface and displays data to the user

    • Receives data from the Model to present

    • Typically passive, meaning it just displays information

    • Can send user actions to the Controller

  3. Controller

    • Acts as an intermediary between the Model and View

    • Processes incoming user requests

    • Manipulates the Model based on user input

    • Updates the View with new data from the Model

    • Manages the flow of data and user interactions

How MVC Works:

  1. User interacts with the View (e.g., clicking a button)

  2. Controller receives the user action

  3. Controller updates the Model based on the action

  4. Model processes the data and updates its state

  5. Model notifies the View of the changes

  6. View retrieves updated data from the Model and refreshes the display

Benefits of MVC:

  • Separates concerns, making the code more modular

  • Improves code reusability and maintainability

  • Allows parallel development of components

  • Easier to modify or replace individual components

  • Supports multiple Views for the same Model

Class Diagram

Code:

// Model Interface
interface Model {
    fun getData(): Any?
    fun setData(data: Any?)
    fun updateState()
    fun notifyObservers()
}

// View Interface
interface View {
    fun render()
    fun updateDisplay(data: Any?)
    fun handleUserInput()
}

// Controller Interface
interface Controller {
    fun processUserAction()
    fun updateModel(data: Any?)
    fun selectView()
}

// Concrete Model Implementation
class UserModel : Model {
    private var userData: MutableMap<String, Any> = mutableMapOf()
    private val observers: MutableList<View> = mutableListOf()

    override fun getData(): MutableMap<String, Any> {
        return userData
    }

    override fun setData(data: Any?) {
        if (data is Map<*, *>) {
            userData.putAll(data as Map<String, Any>)
            notifyObservers()
        }
    }

    override fun updateState() {
        // Example of updating internal state
        userData["lastUpdated"] = System.currentTimeMillis()
        notifyObservers()
    }

    override fun notifyObservers() {
        observers.forEach { it.updateDisplay(userData) }
    }

    fun addObserver(view: View) {
        observers.add(view)
    }

    fun removeObserver(view: View) {
        observers.remove(view)
    }
}

// Concrete View Implementation
class UserConsoleView : View {
    private var currentData: Map<String, Any>? = null

    override fun render() {
        println("Rendering User View")
        currentData?.let { displayUserData(it) }
    }

    override fun updateDisplay(data: Any?) {
        if (data is Map<*, *>) {
            currentData = data as Map<String, Any>
            render()
        }
    }

    override fun handleUserInput() {
        println("Handling user input...")
        // Simulate user input
        val simulatedInput = mapOf(
            "name" to "John Doe",
            "email" to "john@example.com"
        )
        // In a real app, this would be connected to actual user input
    }

    private fun displayUserData(data: Map<String, Any>) {
        println("User Data:")
        data.forEach { (key, value) ->
            println("$key: $value")
        }
    }
}

// Concrete Controller Implementation
class UserController(
    private val model: UserModel,
    private val view: UserConsoleView
) : Controller {
    init {
        // Register view as an observer of the model
        model.addObserver(view)
    }

    override fun processUserAction() {
        // Simulate processing a user action
        val newUserData = mapOf(
            "name" to "Jane Smith",
            "age" to 30,
            "email" to "jane@example.com"
        )
        updateModel(newUserData)
    }

    override fun updateModel(data: Any?) {
        // Update the model with new data
        model.setData(data)
    }

    override fun selectView() {
        // In a more complex app, this might choose between different views
        view.render()
    }

    // Demonstration method to show MVC flow
    fun rundemonstration() {
        // Initial state
        println("Initial State:")
        selectView()

        // Process user action
        println("\nProcessing User Action:")
        processUserAction()
    }
}

// Main function to demonstrate the MVC pattern
fun main() {
    // Create MVC components
    val model = UserModel()
    val view = UserConsoleView()
    val controller = UserController(model, view)

    // Run the demonstration
    controller.rundemonstration()
}

Last updated