'MVC the simplest example
I'm struggling to understand the MVC pattern. I've been working with MVC frameworks like ASP.NET MVC and Django, but project structure there is pretty much forced, so it really didn't help to understand how to build my own apps based on this pattern. To clear things up i decided to write the simplest example of my understanding of MVC (console program in Python) and figure out if there is anything wrong.
|- program:
|—— controller.py
|—— model.py
|—— view.py
|—— db.txt #simulates database
So this is my basic structure. What this program will do is display all people that are inside db.txt. I use db.txt(json) to simulate actual database.
controller.py
from model import Person
import view
def showAll():
#gets list of all Person objects
people_in_db = Person.getAll()
#calls view
return view.showAllView(people_in_db)
def start():
view.startView()
input = raw_input()
if input == 'y':
return showAll()
else:
return view.endView()
if __name__ == "__main__":
#running controller function
start()
view.py
from model import Person
def showAllView(list):
print 'In our db we have %i users. Here they are:' % len(list)
for item in list:
print item.name()
def startView():
print 'MVC - the simplest example'
print 'Do you want to see everyone in my db?[y/n]'
def endView():
print 'Goodbye!'
model.py
import json
class Person(object):
def __init__(self, first_name = None, last_name = None):
self.first_name = first_name
self.last_name = last_name
#returns Person name, ex: John Doe
def name(self):
return ("%s %s" % (self.first_name,self.last_name))
@classmethod
#returns all people inside db.txt as list of Person objects
def getAll(self):
database = open('db.txt', 'r')
result = []
json_list = json.loads(database.read())
for item in json_list:
item = json.loads(item)
person = Person(item['first_name'], item['last_name'])
result.append(person)
return result
So this is the scenario when the user wants to see all people in the db:
Is this approach correct?
Solution 1:[1]
The MVC architecture is very broad and can change depending on the programming language and type of application you are doing, so in this case, yes your approach can be accepted as correct.
What I have learned from static typed languages is that you define the model and views as complete separate entities, and the controller takes an instance of both model and views as parameters.
What you need to ask yourself to define if your app is MVC is the following:
- If I change something in the view do I break anything in the model?
- If I change something in the model do I break anything in the view?
- Is the controller communicating everything in both view and model so that they don't have to communicate with each other?
If nothing breaks and the controller does all of the communication then yes, your application is MVC.
You might want to look into design patterns such as Singleton, Factory and others that all use the MVC architecture and define ways to implement it.
Solution 2:[2]
Because you are working with data that needs persistence, you could try also using Repository design pattern which fits perfect in a MVC Architecture.
You can do it a little bit different with a Layered Architecture that also follows the Chain of Responsibility.
As showing in this image, the whole ideea is to have layers each one on top of the other.
Let's try to do a minimal Layered Architecture
For this: You can have:
> person.py -> This will be our model data
> repo.py -> Class for persistence layer that handles persons
> service.py -> Here you can handle business logic
> console.py -> Can be the view of your application, or presentation layer
Depending on the application, or the way you handle Presentation Layer You can also have a Controller on top of your Service.
Let's say you make a GUI in PySide, and in the design Gui.py
file, you don't want to perform any task. For that, you can have a Controller.py
that handles user input, signals and everything else. Based on the user interaction, Controller.py
will use Service.py
to perform tasks.
Let's make this simple example:
person.py -> Our Model Class
class Person:
def __init__(self, name, age, id):
self.id = id
self.name = name
self.age = age
repo.py -> Our Repository Class for Person
Here you can store to file, to memory, etc. You can also use decorator pattern to decorate a memory repository with a file repository
that can handle another persistence type.
You can maybe add 2 functions store_to_file
and load_from_file
, first called after every update, and last called when object is created to populate with data our repository. But For the moment we will keep it simple.
class PersonRepository:
def __init__(self):
self.persons = []
def add(self, person):
self.persons.append(person)
def get(self, id):
for person in self.persons:
if person.id == id:
return person
return None
def get_all(self):
return self.persons
def update(self, old_person, new_person):
self.delete(old_person.id)
self.add(new_person)
raise Exception("Person not found")
def delete(self, id):
for p in self.persons:
if p.id == id:
self.persons.remove(p)
return
raise Exception("Person not found")
service.py -> Here must lay your business logic of your application.
Every computation, filter, CRUD and everything you want to do to your model.
# A Person Service class for Person, which is a service layer for PersonRepository
class PersonService:
def __init__(self, repository):
self.repository = repository
def get(self, id):
return self.repository.get(id)
def get_all(self):
return self.repository.get_all()
def add(self, person):
self.repository.add(person)
def update(self, old_person, new_person):
self.repository.update(old_person, new_person)
def delete(self, id):
self.repository.delete(id)
# OTHER BUSINESS LOGIC CAN BE ADDED HERE
def get_by_name(self, name):
for person in self.repository.get_all():
if person.name == name:
return person
return None
# FILTER PERSON BY AGE
def filter_by_age(self, age):
persons = []
for person in self.repository.get_all():
if person.age == age:
persons.append(person)
return persons
Controller.py -> Handles Tasks from GUI
In this example it is pretty much useless since we don't have a GUI.py
, but I will keep it in case someone has to deal with it.
But if you have a GUI drawn directly, let's say in PySide, you can handle signals, tasks from GUI and everything else in Controller.py
. That way you will let your GUI.py
be JUS A GUI, nothing more.
For Now I will put the functions from Service, so our Controller.py, in this example, will be the Service.
class PersonController:
def __init__(self, service):
self.service = service
def get(self, id):
return self.service.get(id)
def get_all(self):
return self.service.get_all()
def add(self, person):
self.service.add(person)
def update(self, old_person, new_person):
self.service.update(old_person, new_person)
def delete(self, id):
self.service.delete(id)
def get_by_name(self, name):
return self.service.get_by_name(name)
def filter_by_age(self, age):
return self.service.filter_by_age(age)
# HANDLE TASKS / SIGNALS FROM GUI
console.py -> This will be our application view.
For this example, we will use a Console UI, not a Graphical UI.
Here lays our user interaction. This Should be the only class that interacts with user (eg. getting input from user, printing stuff, etc).
To be very clear, you don't have to use prints in your application, if you have prints in other classes, something is wrong.
You can communicate between layers when something is wrong using Exceptions, and catch them in your view.
# A Console class for PersonController
class Console:
def __init__(self, controller):
self.controller = controller
def get_all(self):
persons = self.controller.get_all()
for person in persons:
print(person.id, person.name, person.age)
def get(self, id):
person = self.controller.get(id)
if person is None:
print("Person not found")
else:
print(person.id, person.name, person.age)
def add(self, name, age):
person = Person(name, age, 0)
self.controller.add(person)
print("Person added")
def update(self, id, name, age):
person = Person(name, age, id)
self.controller.update(self.controller.get(id), person)
print("Person updated")
def delete(self, id):
self.controller.delete(id)
print("Person deleted")
def get_by_name(self, name):
person = self.controller.get_by_name(name)
if person is None:
print("Person not found")
else:
print(person.id, person.name, person.age)
def filter_by_age(self, age):
persons = self.controller.filter_by_age(age)
for person in persons:
print(person.id, person.name, person.age)
# Menu for user input
def run(self):
while True:
print("1. Get all persons")
print("2. Get person by id")
print("3. Add person")
print("4. Update person")
print("5. Delete person")
print("6. Get person by name")
print("7. Filter person by age")
print("8. Exit")
print("Enter your choice: ", end="")
choice = int(input())
if choice == 1:
self.get_all()
# and other functions for your menu, you got the point
main.py -> Program Startup
Note How everything gets layered on top of each other.
def main():
repository = PersonRepository()
service = PersonService(repository)
controller = PersonController(service)
console = Console(controller)
console.run()
If you also want to handle Data Validation
, which is something I recommend you to, you can also use a Validator.
Let's see how it works.
validator.py -> Data validation for Person
class Validator:
def __init__(self):
pass
def validate(self, person):
if person.name == "":
raise Exception("Name cannot be empty")
if person.age < 0:
raise Exception("Age cannot be negative")
if person.id < 0:
raise Exception("Id cannot be negative")
Now just add the validator to the service, in main and just call the validate function.
def main():
repository = PersonRepository()
validator = Validator()
service = PersonService(repository, validator)
controller = PersonController(service)
console = Console(controller)
console.run()
Now in your service.py
, just go to add, update function and call validator.validate()
method, if input data is invalid, it will raise an Exception, that can be catch in our view, to give user feedback.
service.py
def add(self, person):
validator.validate(person) # DATA VALIDATION
self.repository.add(person)
That's it, soo simple :). And one more step and we're good to go, Handle the exception in our view.
console.py
def add(self, name, age):
person = Person(name, age, 0)
try:
self.controller.add(person)
print("Person added")
except Exception as e:
print(e)
And that's it. We have an application that can do CRUD, filtering, data validation, chain of responsability, layered architecture, mvc, etc.
You have many advantages using such an architecture, for example, if you want to change the persistence layer
, to use a database
for example, you can add some logic to your repository, and everything works without any changes. Or for example, if you want to add new functionalities
, just add new methods to your service layer and that's it. Or if you want to change the GUI, you can do it and just use your service and it works without doing anything below your Presentation Layer
.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |