Design Patterns and MVC
Introduction to Software Engineering (CSSE 1001)
Model View Controller
The Model-View-Controller (MVC) is a design pattern that separates an application into three interconnected components:
- Model: Stores the data and business rules of the application; responsible for every state change.
- View: Some (usually visual) representation of the data without altering it.
- Controller: Receives user input (commands), tells the Model what to do, then selects a View to present the result.
Anatomy of MVC
| Layer | Core question | Owns | MUST NOT |
|---|---|---|---|
| Model | What is the data? | Data & rules | print, parse args, call input() |
| View | How is info shown? | Formatting & rendering | Change state, validate data |
| Controller | What happens next? | Workflow & commands | Store raw data, format output |
Model knows nothing about how (or whether) the state is being displayed to a user. View knows nothing about where the data comes from, and doesn’t store the data / state within itself; it just displays what it’s told to display.
MVC Flow
- User issues a command.
- Controller parses input and calls the appropriate Model method.
- Model mutates state and returns domain data.
- Controller selects a View and passes the data.
- View formats the data and decides how to display it.
- Control returns to the main loop, waiting for the next user action.
Why MVC?
Modularity – we can swap new models, views, and controllers without breaking the application.
Testability – each component can be tested in isolation.
Separation of concerns – each component has a distinct responsibility.
Exercise
Implement a minimal todo-list manager using the MVC design pattern.
Task+TodoList→ Model (domain)
TodoView→ View (presentation only)
TodoApp+ run-loop → Controller (input parsing & orchestration)
Model
class Task:
def __init__(self, title: str) -> None:
self._title = title
self._done = False
class TodoList:
def __init__(self) -> None:
self._tasks = []
def add(self, title: str) -> Task:
task = Task(title)
self._tasks.append(task)
return task
def all(self) -> list[Task]:
return list(self._tasks)View
class TodoView:
def show_task(self, task: Task) -> None:
mark: str = "/" if task._done else "X"
print(f"[{mark}] {task._title}")
def show_list(self, tasks: list[Task]) -> None:
print("\nMy Tasks\n--------")
for t in tasks:
self.show_task(t)Controller
class TodoApp:
def __init__(self, todo: TodoList, view: TodoView) -> None:
self._todo = todo
self._view = view
def handle(self, command: str) -> None:
parts = command.strip().split(maxsplit=1)
if not parts:
return
cmd: str = parts[0]
if cmd == "add" and len(parts) == 2:
self._todo.add(parts[1])
print("Task added!")
elif cmd == "list":
self._view.show_list(self._todo.all())
else:
print("Commands: add <title>, list, quit")Controller (run-loop)
app = TodoApp(TodoList(), TodoView())
while True:
user_cmd: str = input("$")
if user_cmd.strip() in ("quit", "exit"):
break
app.handle(user_cmd)
# Sample session
$add Work on A2
Task added!
$add buy groceries
Task added!
$list
My Tasks
--------
[X] Work on A2
[X] buy groceriesMVC Flow (example)
User issues a command (e.g., add Buy groceries).
Controller parses input and calls the appropriate Model method (
TodoList.add).Model mutates state and returns domain data (
Task).Controller selects a View (e.g.,
show_task) and passes the data.View formats the data and pushes it to stdout.
Control returns to the main loop, waiting for the next user action.
Summary
MVC is a pattern, which is typically used in web/GUI applications.
Separation of concerns makes code more organised.
Easier to test, maintain, and extend.
Provides a solid foundation for future enhancements.
Numerous variants of MVC exist. In this course we use a variant called Apple MVC, which enforces a strict separation of the model and view.