Learning these 10 Python patterns helps you avoid architectural mistakes as projects grow. As Hassan Nauman explains in his article “10 Python Patterns Every Developer Should Learn Before Building Big Projects”, syntax isn’t the real problem — structure is. These patterns reduce coupling, manage complexity, and help keep code maintainable and scalable over time.
- Dependency Injection – Pass dependencies in instead of hard-coding them, making code easier to test and change.
- Strategy Pattern – Replace large if/else blocks with interchangeable behaviors.
- Builder Pattern – Construct complex objects step by step without messy constructors.
- Event-Driven Pattern – Decouple components by reacting to events instead of direct calls.
- Repository Pattern – Isolate database logic from business logic.
- Mapper Pattern – Convert raw data (dicts/JSON) into clean domain objects.
- Pipeline Pattern – Process data through clear, ordered stages.
- Command Pattern – Encapsulate actions so they can be executed and undone.
- Specification Pattern – Keep complex filtering and rules readable and reusable.
- Registry Pattern – Build flexible plugin systems without hard-coded conditionals.
What Is Dependency Injection?
Dependency Injection is the foundation of maintainable architecture. In Django projects, it prevents fat views, hard-coded services, and untestable logic.
Dependency Injection means passing dependencies into a class instead of creating them inside the class. This reduces coupling and makes your code easier to test and change.
Step 1: Create the Model
from django.db import models
class Customer(models.Model):
email = models.EmailField(unique=True)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
Step 2: Create the Repository
The repository owns all database access and hides Django ORM details.
from .models import Customer
class CustomerRepository:
def get_by_id(self, pk):
return Customer.objects.get(pk=pk)
def create(self, email):
return Customer.objects.create(email=email)
def update(self, pk, email, is_active):
customer = Customer.objects.get(pk=pk)
customer.email = email
customer.is_active = is_active
customer.save()
return customer
Step 3: Create the Service
The service contains business rules and receives the repository via Dependency Injection.
class CustomerService:
def __init__(self, repository):
self.repository = repository
def register_customer(self, email):
return self.repository.create(email)
def update_customer(self, pk, email, is_active):
if not email.endswith("@example.com"):
raise ValueError("Only example.com emails are allowed")
return self.repository.update(pk, email, is_active)
Step 4: Create a Form for Validation
Forms validate input only. They do not contain business rules or persistence logic.
from django import forms
class CustomerUpdateForm(forms.Form):
email = forms.EmailField()
is_active = forms.BooleanField(required=False)
Step 5: Create Class-Based Views
At this point, clear responsibilities are established: the repository handles persistence, the service enforces business rules, and the form validates input. The views simply coordinate these pieces.
from django.views import View
from django.shortcuts import render, redirect
from django.http import Http404, JsonResponse
from .forms import CustomerUpdateForm
from .repositories import CustomerRepository
from .services import CustomerService
from .models import Customer
class RegisterCustomerView(View):
repository_class = CustomerRepository
service_class = CustomerService
def post(self, request):
repo = self.repository_class()
service = self.service_class(repo)
customer = service.register_customer(request.POST.get("email"))
return JsonResponse({"id": customer.id})
class UpdateCustomerView(View):
repository_class = CustomerRepository
service_class = CustomerService
form_class = CustomerUpdateForm
template_name = "customers/update.html"
def get(self, request, pk):
customer = Customer.objects.get(pk=pk)
form = self.form_class(initial={
"email": customer.email,
"is_active": customer.is_active,
})
return render(request, self.template_name, {"form": form})
def post(self, request, pk):
form = self.form_class(request.POST)
if not form.is_valid():
return render(request, self.template_name, {"form": form})
repo = self.repository_class()
service = self.service_class(repo)
try:
service.update_customer(
pk,
form.cleaned_data["email"],
form.cleaned_data.get("is_active", False),
)
except ValueError as e:
form.add_error(None, str(e))
return render(request, self.template_name, {"form": form})
return redirect("customer-detail", pk=pk)
Step 6: Add Views to urls.py
from django.urls import path
from .views import RegisterCustomerView, UpdateCustomerView
urlpatterns = [
path("customers/register/", RegisterCustomerView.as_view()),
path("customers/update//", UpdateCustomerView.as_view()),
]
Step 7: Testing with Dependency Injection
Because dependencies are injected, we can replace the real repository with a fake implementation that follows the same interface. This allows us to test behavior without touching the database.
class FakeCustomerRepository:
def __init__(self):
self.updated = False
def create(self, email):
return {"email": email}
def update(self, pk, email, is_active):
self.updated = True
return True
from django.test import TestCase
from django.urls import reverse
from app.views import UpdateCustomerView
class UpdateCustomerViewTest(TestCase):
def setUp(self):
self.fake_repo = FakeCustomerRepository()
UpdateCustomerView.repository_class = lambda: self.fake_repo
def test_update_customer_calls_repository(self):
response = self.client.post(
reverse("update-customer", args=[1]),
{"email": "test@example.com", "is_active": True}
)
self.assertEqual(response.status_code, 302)
self.assertTrue(self.fake_repo.updated)
What Dependency Injection Gave Us
- Clear separation of concerns
- Proper form validation without UpdateView
- Business rules isolated in services
- Simple, fast tests
Dependency Injection is not about adding layers everywhere. It is about keeping control over how your code grows.
In the next post, we will explore the Strategy Pattern and how it replaces complex conditional logic in Django projects.