Software architecture — MVC

by Don Dang

Programming languages e.g. Python, Go, Javascript, etc can be used to write scripts to hack together something quickly, and of course, they are great for that purpose. And it is perfectly fine to write scripts that are used only once, for example, to quickly clean up some junk files or folders. However, as soon as the code is not used only once, or it will be used by others, for example, writing library code to solve a particular problem. We have to think about the Software architecture of the code.

We can think of the code organization as a stack of three layers:

  • In the lowest layer, is choosing some particular syntax elements or solutions for specific problems and, for example, using a for-loop or a while-loop? Which algorithm to use for sorting a list? Should data be stored in an array or dictionary? And so on.
  • In the middle layer are the Software Design Principle and Design Pattern, such as using functions or classes, using appropriate design patterns to avoid code duplication and be more maintainable.
  • And on the top level is Software Architecture, which defines the overall philosophy or approach of how the code works and how it solves the main problem.

For example, in the case of the Python web framework Django, under the hood, there are many design patterns and solutions used to solve particular problems. But there is also an overarching approach that’s Django’s way of doing things. For example, Django expects you to write Templates for representing your content; it provides you tools to work with data more easily. In the case of Django, its software architecture is called Model-View-Template architecture (MVT), which could be considered as a variant of the Model-View-Controller (MVC) architecture.

Model-View-Controller (MVC) architecture splits the software into three main parts.

  • The Model deals with the data/database, and it is independent of the UI.
  • The View is the presentation of the model in a particular format such as a chart, diagram, table, etc.
  • The Controller is the logic that binds Model and View. It accepts user input and converts it into commands for the Model or View.

MVC is one of the techniques to achieve the Separation of Concern (SOC) design principle, which simply tells you not to write your program as one solid block, instead, break up the code into chunks that are finalized tiny pieces of the system each able to complete a simple distinct job. Keeping these pieces separate make it easier for developers to perform tasks independently without affecting others and the code would be more maintainable.

MVC has been widely adopted as a design for web applications. However, it was originally developed for desktop computing. So in this post, we will take a look at the implementation of MVC architecture with a simple desktop application example — Calculator application made with Python and Tkinter.

Let’s first look at the code from the first version of the calculator. We simply import the Tkinter library and then initiate a root window which will be the main window for our calculator. A variable formula will be used as a global variable to hold our calculation string and display it to the calculator UI. There is also the calculation Label which does the displaying job of the calculator.

We also have three main functions that track down if the user clicks to the calculator’s buttons and respond accordingly.

And after that, we define all the buttons that our calculator will have together with the displayed text on them and the corresponding function that will be called when they are clicked. Finally we start the application by calling root.mainloop() method.

from tkinter import *root = Tk()formula=""
equation = StringVar()
calculation = Label(root, textvariable=equation)equation.set("0")calculation.grid(columnspan=4)#Creating buttons & functionsdef button_press(num):
# We create a gloabl variable that will be updated whenever the button is pressed
global formula
formula = formula + str(num)
equation.set(formula)
def equal_button():
# We will need to use eval() to evaluate equation string and do the math
global formula
total = str(eval(formula))
equation.set(total)
formula=""
def clear_button():
global formula
formula = ""
equation.set(formula)
button_1 = Button(root, text="1", command=lambda: button_press(1))
button_1.grid(row=1, column=0)
button_2 = Button(root, text="2", command=lambda: button_press(2))
button_2.grid(row=1, column=1)
... # And so onbutton_plus = Button(root, text="+", command=lambda: button_press("+"))
button_plus.grid(row=1, column=3)
... # And so onbutton_clear = Button(root, text="C", command=clear_button)
button_clear.grid(row=4, column=1)
root.mainloop()

And when we run this script, we will have our little calculator look like this:

This is all good if you want to quickly build this calculator up and feel excellent about your programming skills. However, suppose you start thinking about scaling this calculator into something more, for example. In that case, a scientific calculator with a lot more buttons and functions, then putting them all in one script would be a complete mess, and debugging would be a nightmare.

And so, one of the solutions for our little calculator is to refactor the code, following the MVC architecture. We will split the script into 3 main parts; each part is represented as a Python class for this simple example. However, you can also put each part into its module. Splitting them into their modules would be preferred, especially when scaling this simple calculator into a scientific calculator.

We will name the classes exactly as Model, View, and Controller , following the MVC architecture for demonstration purposes.

The Model

First of all, the Model, by definition, deals with the data/database and it is independent of the UI. For our little calculator, we don't use any kind of database, however, if we do, the Model is where we store all the database related code. Take a look at our application, the only that can be considered as data is the formula string, because it is used to store input and also to display to the UI. So, while defining the Model class, we will also rename it to input_str, perhaps it would be more meaningful.

class Model:
def __init__(self):
self.input_str: str = ""
@property
def data(self) -> str:
return self.input_str
@data.setter
def data(self, value) -> None:
self.input_str = value

To make it a bit more OOP style, we will use setter and getter methods to get/set the input_str from the Model, and called it data.

The View

Next step, we will construct our View, and again, by definition, it is the presentation of the Model in a particular format such as a chart, diagram, table, etc. Because we are using Tkinter to construct our UI, so everything that is related to Tkinter should be included in the View class.

from tkinter import *
from typing import Callable
...class View:
def __init__(self):
self.root = Tk()
self.equation = StringVar()
def set_equation(self, value: str) -> None:
self.equation.set(value)
def setup_view(self, callback: Callable) -> None:
calculation = Label(self.root, textvariable=self.equation)
self.set_equation("0")
calculation.grid(columnspan=4)
self.setup_buttons(callback)
def setup_buttons(self, callback: Callable) -> None:
button_1 = Button(self.root, text="1", command=lambda: callback("1"))
button_1.grid(row=1, column=0)
button_2 = Button(self.root, text="2", command=lambda: callback("2"))
button_2.grid(row=1, column=1)
... # and so ondef start_main_loop(self) -> None:
self.root.mainloop()
  • The View class once initialized will also initialize a root window for our calculator, together with a StringVar object that will display the input data on the UI.
  • There is setup_buttons method that takes care of initializing the buttons that we're using in our calculator. Note that this method will require a controller to be passed in when called, because the controller will hold all the logics of what would happen if the button is clicked.
  • The setup_view is the method that takes care of initializing everything, including the buttons and the grid. Once this method is called, our calculator is ready to be shown.
  • The last method is start_main_loop which will start showing the calculator once it's called.

The Controller

Controller is the logics that binds Model and View. It accepts user input and converts it into commands for the Model or View. Base on that definition, we can see that our button click handler should belong to the Controller more than to the View. Those functions don't have any thing to do with showing the data to the UI, but instead, they are preparing the data for the UI to show. In general, they will access to the data in the Model, depends on what user clicked, they will get or set the data accordingly, then feed that data to the View to display. To keep the Controller separate from the View, we will create a single method that will be passed to the View as the click handler callback. Thus, we have our Controller class defined like this:

class Controller:
def __init__(self, view: View, model: Model):
self.model = model
self.view = view
def start(self) -> None:
"""Set up and start the view"""
self.view.setup_view(self.button_click_handler)
self.view.start_main_loop()
def button_click_handler(self, value: str) -> None:
"""Redirect to the suitable handler function base on the value of the clicked button"""
if value == "=":
self._equal_button()
elif value == "C":
self._clear_button()
else:
self._button_pressed(value)
def _button_pressed(self, num: str) -> None:
"""Add the value of the clicked button to the equation"""
self.model.data += str(num)
self.view.set_equation(self.model.data)
def _equal_button(self) -> None:
"""Evaluate the equation and show the result"""
total = str(eval(self.model.data))
self.view.set_equation(total)
self.model.data = ""
def _clear_button(self) -> None:
"""Clear out the sreen of the calculator"""
self.model.data = ""
self.view.set_equation(self.model.data)

The Controller binds the Model and the View together, so when initialized, Model and View instances need to be passed in. We define the start method in the Controller, which will call the setup_view method from the View and then start the view’s main loop. We are going to use the Controller as following:

if __name__ == "__main__":
controller = Controller(View(), Model())
controller.start()

And so, we have completed refactoring our calculator using MVC architecture. For now, by looking at the classes, you will quickly know where to put new codes if you want to scale this calculator up. For example, you need more buttons, put them in the View, if the new buttons are connected the new new functions, put the new functions in the Controller. If you want to have a mechanism to store the calculation in a database, put the code in the Model. You can find the full code for the new version here.

And ideally, you would have your project structured like this for better code management:

src
┣ calculator
┃ ┣ __init__.py
┃ ┣ controllers.py
┃ ┣ models.py
┃ ┗ views.py
┗ main.py

This concludes this post about MVC architecture, hopefully it would be helpful for your career. Happy coding.

Anders is a Finnish IT company, whose mission is sustainable software development with the greatest colleagues of all time.