Software architecture — MVC
by Don Dang
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
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
formula = formula + str(num)
# We will need to use eval() to evaluate equation string and do the math
total = str(eval(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)
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
Controller , following the MVC architecture for demonstration purposes.
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.
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
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
from tkinter import *
from typing import Callable...class View:
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.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:
Viewclass once initialized will also initialize a root window for our calculator, together with a
StringVarobject that will display the input data on the UI.
- There is
setup_buttonsmethod that takes care of initializing the buttons that we're using in our calculator. Note that this method will require a
controllerto be passed in when called, because the controller will hold all the logics of what would happen if the button is clicked.
setup_viewis 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_loopwhich will start showing the calculator once it's called.
Controller is the logics that binds Model and View. It accepts user input and converts it into commands for the
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:
def __init__(self, view: View, model: Model):
self.model = model
self.view = viewdef start(self) -> None:
"""Set up and start the view"""
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 == "=":
elif value == "C":
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.model.data = ""def _clear_button(self) -> None:
"""Clear out the sreen of the calculator"""
self.model.data = ""
The Controller binds the
Model and the
View together, so when initialized,
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())
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:
┃ ┣ __init__.py
┃ ┣ controllers.py
┃ ┣ models.py
┃ ┗ views.py
This concludes this post about MVC architecture, hopefully it would be helpful for your career. Happy coding.