Software Architecture Patterns
Mohamad Abuzaid 1 year ago
mohamad-abuzaid #general

Architecture Patterns (MVC-MVP-MVVM)

With Android/Kotlin sample code.


Note: In this article I am using Android/Kotlin for reference and sample code. However, the concept behind these architecture patterns remains the same for all programming languages or frameworks.

In software development, architecture patterns are general solutions to common problems that occur when designing software systems. These patterns provide a way to organize and structure the code, making it more maintainable, testable, and scalable. Examples of architecture patterns include the Model-View-Controller (MVC) pattern, the Model-View-Presenter (MVP) pattern and the Model-View-ViewModel (MVVM) pattern. These patterns help developers to build and maintain large and complex software systems by providing a clear and consistent way to organize the code.

--------------------

Model-View-Controller (MVC)

Model-View-Controller (MVC)

The (MVC) pattern is a way to separate the code for the user interface (UI) from the code that handles the business logic and data.

The Model represents the data and the business logic of the application. It holds the state of the application and handles any updates to that state. The Model is typically implemented as a set of classes or objects that represent the data and the business logic.

class TaskModel {
    var taskList = mutableListOf<String>()

    fun addTask(task: String) {
        taskList.add(task)
    }

    fun removeTask(index: Int) {
        taskList.removeAt(index)
    }
}


The View represents the UI of the application. It is responsible for displaying the data from the Model and handling any user interactions. In Android, the View is typically implemented as an XML layout file and a corresponding Java class.

The Controller is responsible for coordinating the Model and the View. It receives user input from the View and updates the Model accordingly. In Android, the Controller is typically implemented as the Activity or Fragment class.

class MainActivity : AppCompatActivity() {

    lateinit var adapter: ArrayAdapter<String>
    lateinit var taskModel: TaskModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        taskModel = TaskModel()

        adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, taskModel.taskList)
        taskListView.adapter = adapter

        addButton.setOnClickListener {
            val task = taskEditText.text.toString()
            taskModel.addTask(task)
            adapter.notifyDataSetChanged()
        }

        taskListView.setOnItemClickListener { _, _, position, _ ->
            taskModel.removeTask(position)
            adapter.notifyDataSetChanged()
        }
    }
}


The Model TaskModel contains the state of the application which is a list of tasks, and it has methods to add and remove tasks from the list. The View MainActivity is responsible for displaying the data from the Model and handling any user interactions. It uses an ArrayAdapter to display the list of tasks, and it sets up listeners for the add and remove buttons.

MVC pattern helps to separate the concerns, making it easier to test and maintain the code. The Model does not depend on the View, so it can be tested separately. The View does not depend on the Model, so it can be easily replaced. The Controller is the glue that holds the Model and the View together, so it can be easily modified to change the behavior of the application.

Note: In Android, the Activity acts as the Controller, coordinating the Model and the View by updating the Model when the user interacts with the UI and updating the View when the Model changes. That is why some MVC implementations in Android might not have clear separation of concerns, making the code harder to understand and maintain.


Pros & Cons

The Model-View-Controller (MVC) pattern has several advantages and disadvantages:

Pros:

  • Separation of concerns: MVC separates the code for the user interface (View) from the code that handles the business logic and data (Model). This makes it easier to test and maintain the code, as changes to one part of the system don’t affect the other parts.
  • Reusability: The Model and the View can be reused in other parts of the application or in other applications.
  • Modularity: MVC makes it easy to add new features to the application by creating new Models, Views, and Controllers.
  • Flexibility: MVC can handle complex and changing requirements by allowing different parts of the system to be developed and modified independently.

Cons:

  • Complexity: MVC can add complexity to the codebase, as it requires a clear separation of concerns and communication between the Model, View, and Controller.
  • Overhead: MVC can add overhead to the development process, as it requires more classes and interfaces to be created.
  • Tight coupling: If not implemented properly, the tight coupling between the Model, View, and Controller can make it difficult to change or extend the application.
  • Event-based communication can be hard to understand and reason about.
  • It might not be the best fit for small or simple applications.

--------------------

Model-View-Presenter (MVP)

Model-View-Presenter (MVP)

The Model-View-Presenter (MVP) pattern is a variation of the Model-View-Controller (MVC) pattern that is designed to improve the maintainability and testability of the code.

The Model represents the data and the business logic of the application, similar to MVC. It holds the state of the application and handles any updates to that state. The Model is typically implemented as a set of classes or objects that represent the data and the business logic.

class TaskModel {
    var taskList = mutableListOf<String>()

    fun addTask(task: String) {
        taskList.add(task)
    }

    fun removeTask(index: Int) {
        taskList.removeAt(index)
    }
}


The View represents the UI of the application, similar to MVC. It is responsible for displaying the data from the Model and handling any user interactions. In Android, the View is typically implemented as an XML layout file and a corresponding Java or Kotlin class.

interface TaskView {
    fun updateTaskList(taskList: List<String>)
    fun showError(message: String)
}

class MainActivity : AppCompatActivity(), TaskView {

    lateinit var presenter: TaskPresenter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val taskModel = TaskModel()
        presenter = TaskPresenter(this, taskModel)

        adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, taskModel.taskList)
        taskListView.adapter = adapter

        addButton.setOnClickListener {
            val task = taskEditText.text.toString()
            presenter.addTask(task)
        }

        taskListView.setOnItemClickListener { _, _, position, _ ->
            presenter.removeTask(position)
        }
    }

    override fun updateTaskList(taskList: List<String>) {
        adapter.clear()
        adapter.addAll(taskList)
        adapter.notifyDataSetChanged()
    }

    override fun showError(message: String) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
    }
}


The Presenter acts as the middle man between the Model and the View. It receives user input from the View, updates the Model accordingly, and tells the View to update itself based on the new Model data. The Presenter also reacts to changes in the Model by updating the View. In MVP pattern the Presenter is responsible for handling the logic that glues the Model and the View together.

class TaskPresenter(private val view: TaskView, private val model: TaskModel) {
    fun addTask(task: String) {
        if (task.isEmpty()) {
            view.showError("Task cannot be empty.")
        } else {
            model.addTask(task)
            view.updateTaskList(model.taskList)
        }
    }

    fun removeTask(index: Int) {
        model.removeTask(index)
        view.updateTaskList(model.taskList)
    }
}


The Model TaskModel contains the state of the application which is a list of tasks, and it has methods to add and remove tasks from the list. The View MainActivity is responsible for displaying the data from the Model and handling any user interactions. It uses an ArrayAdapter to display the list of tasks, and it sets up listeners for the add and remove buttons. The Presenter TaskPresenter acts as the middle man between the Model and the View, it receives user input from the View, updates the Model accordingly, and tells the View to update itself based on the new Model data. The View communicates with the Presenter through an interface TaskView that defines the methods that the Presenter can call on the View.

Similar to (MVC), MVP pattern helps to separate the concerns, making it easier to test and maintain the code. The Model does not depend on the View, so it can be tested separately. The View does not depend on the Model, so it can be easily replaced. The Presenter is the glue that holds the Model and the View together, so it can be easily modified to change the behavior of the application.


Pros & Cons

The Model-View-Presenter (MVP) pattern has several advantages and disadvantages when compared to the Model-View-Controller (MVC) pattern:

Pros:

  • Separation of concerns: MVP improves on the separation of concerns in MVC by clearly separating the logic that handles the business logic and data (Model) from the logic that handles the user interface (View) and the coordination between them (Presenter)
  • Testability: MVP makes it easier to write automated unit tests for the Model and the Presenter, as they are decoupled from the framework and can be tested in isolation.
  • Flexibility: MVP allows different parts of the system to be developed and modified independently, similar to MVC.
  • Loose Coupling: MVP allows for the View and the Model to be loosely coupled, which makes it easier to change or extend the application without affecting the other parts.

Cons:

  • Complexity: MVP can add complexity to the codebase, as it requires a clear separation of concerns and communication between the Model, View, and Presenter.
  • Overhead: MVP can add overhead to the development process, as it requires more classes and interfaces to be created.
  • It might not be the best fit for small or simple applications.

-------------------

Model-View-ViewModel (MVVM)

Model-View-ViewModel (MVVM)

The Model-View-ViewModel (MVVM) pattern is a variation of the Model-View-Controller (MVC) pattern that is designed to improve the maintainability and testability of the code. It’s heavily inspired by the MVP pattern and it’s based on the separation of concerns principle.

The Model represents the data and the business logic of the application, similar to MVC and MVP. It holds the state of the application and handles any updates to that state. The Model is typically implemented as a set of classes or objects that represent the data and the business logic.

class TaskModel {
    var taskList = mutableListOf<String>()

    fun addTask(task: String) {
        taskList.add(task)
    }

    fun removeTask(index: Int) {
        taskList.removeAt(index)
    }
}


The View represents the UI of the application, similar to MVC and MVP. It is responsible for displaying the data from the Model and handling any user interactions. In Android, the View is typically implemented as an XML layout file and a corresponding Java or Kotlin class.

class MainActivity : AppCompatActivity() {

    lateinit var binding: ActivityMainBinding
    lateinit var viewModel: TaskViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        val taskModel = TaskModel()
        viewModel = TaskViewModel(taskModel)
        binding.viewModel = viewModel

        binding.addButton.setOnClickListener {
            val task = binding.taskEditText.text.toString()
            viewModel.addTask(task)
        }

        binding.taskListView.setOnItemClickListener { _, _, position, _ ->
            viewModel.removeTask(position)
        }
    }
}


The ViewModel is a new component in MVVM. It’s a non-UI component that acts as a bridge between the Model and the View. It’s responsible for exposing the data from the Model in a format that’s appropriate for the View to consume, and it also handles the logic for the View. It’s also responsible for handling the state of the UI and any commands that are issued by the View.

class TaskViewModel(private val model: TaskModel) {
    val taskList = ObservableArrayList<String>().apply {
        addAll(model.taskList)
    }

    fun addTask(task: String) {
        if (task.isEmpty()) {
            // Show error message
        } else {
            model.addTask(task)
            taskList.add(task)
        }
    }

    fun removeTask(index: Int) {
        model.removeTask(index)
        taskList.removeAt(index)
    }
}


The Model TaskModel contains the state of the application which is a list of tasks, and it has methods to add and remove tasks from the list. The View MainActivity is responsible for displaying the data from the Model and handling any user interactions. It uses Data binding to bind the View to the ViewModel, and it sets up listeners for the add and remove buttons. The ViewModel TaskViewModel acts as a bridge between the Model and the View, it receives user input from the View, updates the Model accordingly, and updates the View based on the new Model data. It’s also responsible for handling the state of the UI and any commands that are issued by the View. The ViewModel exposes the data from the Model in a format that’s appropriate for the View to consume, it also handles the logic for the View.

Again, similar to the previous (MVC) and (MVP), MVVM pattern helps to separate the concerns, making it easier to test and maintain the code. The Model does not depend on the View, so it can be tested separately. The View does not depend on the Model, so it can be easily replaced. The ViewModel is the glue that holds the Model and the View together, so it can be easily modified to change the behavior of the application.Model-View-ViewModel (MVVM).


Pros & Cons

The Model-View-ViewModel (MVVM) pattern has several advantages and disadvantages when compared to the Model-View-Controller (MVC) and Model-View-Presenter (MVP) pattern:

Pros:

  • Separation of concerns: MVVM improves on the separation of concerns in MVC and MVP by clearly separating the logic that handles the business logic and data (Model) from the logic that handles the user interface (View) and the coordination between them (ViewModel)
  • Testability: MVVM makes it easier to write automated unit tests for the Model and the ViewModel, as they are decoupled from the Android framework and can be tested in isolation.
  • Flexibility: MVVM allows different parts of the system to be developed and modified independently, similar to MVC and MVP.
  • Loose Coupling: MVVM allows for the View and the Model to be loosely coupled, which makes it easier to change or extend the application without affecting the other parts.
  • Data Binding: MVVM uses data binding to bind the View to the ViewModel, which reduces the amount of boilerplate code and makes it easier to keep the View and the ViewModel in sync.

Cons:

  • Complexity: MVVM can add complexity to the codebase, as it requires a clear separation of concerns and communication between the Model, View, and ViewModel.
  • Overhead: MVVM can add overhead to the development process, as it requires more classes and interfaces to be created.
  • It might not be the best fit for small or simple applications.
  • Data binding can be hard to debug and it might add complexity to the codebase.

-----------------

How To choose the best Architecture?


Choosing the best architecture for your project depends on several factors, including the size and complexity of the project, the development team’s experience and expertise, and the project’s specific requirements and constraints. Here are some general guidelines to help you choose the best architecture for your project:


  1. Understand the problem: Before choosing an architecture, it’s important to understand the problem that your project is trying to solve, and to have a clear understanding of the requirements and constraints of the project.
  2. Consider the size and complexity of the project: For small and simple projects, a simple architecture such as MVC might be sufficient. For larger and more complex projects, a more robust architecture such as MVP or MVVM might be more appropriate.
  3. Consider the development team’s experience and expertise: If the development team is experienced with a certain architecture, it might be wise to stick with that architecture, as it will make the development process more efficient and reduce the risk of introducing bugs.
  4. Consider the project’s scalability: The architecture you choose should be scalable and should be able to handle the growth and changing requirements of the project.
  5. Consider the testability: The architecture you choose should make it easy to write automated unit tests and should make it easy to test the different parts of the project in isolation.
  6. Consider the maintainability: The architecture you choose should make it easy to maintain the codebase over time and should make it easy to add new features and fix bugs.
  7. Consider the community and resources: The architecture you choose should have a strong community and a good number of resources, such as tutorials, forums, and libraries, available.


Overall, the best architecture for your project will depend on the specific needs and constraints of your project. Additionally, it’s not always necessary to choose a specific architecture, you can always adopt certain principles or elements of different architectures to suit your needs. It’s always a good idea to evaluate your project’s requirements and constraints regularly, and make adjustments to your architecture as necessary.

1
571
Kotlin's Interoperability with Java (2/3)

Kotlin's Interoperability with Java (2/3)

1675112374.jpg
Mohamad Abuzaid
7 months ago
Dependency Injection-quick overview

Dependency Injection-quick overview

1675112374.jpg
Mohamad Abuzaid
1 year ago
Kotlin's Interoperability with Java (1/3)

Kotlin's Interoperability with Java (1/3)

1675112374.jpg
Mohamad Abuzaid
7 months ago
Design Patterns - [3] Behavioral

Design Patterns - [3] Behavioral

1675112374.jpg
Mohamad Abuzaid
1 year ago
Introduction to Kotlin Functional Programming (3/3)

Introduction to Kotlin Functional Programming (3/3)

1675112374.jpg
Mohamad Abuzaid
7 months ago