Skip to content

Commit 541f3a8

Browse files
Add patterns_toolkit with reusable templates and real-world examples
- Reusable templates: Observer, Singleton, Null Object, Specification - Real-world examples: Library System, Exam Platform, Inventory Management - All examples include passing doctests
1 parent 7ffb182 commit 541f3a8

7 files changed

Lines changed: 609 additions & 0 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"""
2+
Example: Singleton Pattern Applied to an Online Exam Platform
3+
==============================================================
4+
This example shows how the Singleton pattern can be used for
5+
a centralized exam configuration that is shared across all
6+
parts of the platform.
7+
8+
This directly relates to an Online Exam Platform project,
9+
demonstrating how design patterns integrate into real-world applications.
10+
"""
11+
12+
from __future__ import annotations
13+
from typing import Any, Dict
14+
15+
16+
class Singleton:
17+
_instance = None
18+
19+
def __new__(cls, *args: Any, **kwargs: Any):
20+
if cls._instance is None:
21+
cls._instance = super().__new__(cls)
22+
return cls._instance
23+
24+
25+
class ExamConfig(Singleton):
26+
"""
27+
Centralized exam configuration.
28+
All modules (timer, grading, question loader) share the same config.
29+
"""
30+
31+
_initialized: bool = False
32+
33+
def __init__(self) -> None:
34+
if self._initialized:
35+
return
36+
self._settings: Dict[str, Any] = {
37+
"time_limit_minutes": 60,
38+
"passing_score": 50,
39+
"shuffle_questions": True,
40+
"show_results_immediately": False,
41+
"max_attempts": 3,
42+
}
43+
self._initialized = True
44+
45+
def get(self, key: str) -> Any:
46+
return self._settings.get(key)
47+
48+
def set(self, key: str, value: Any) -> None:
49+
self._settings[key] = value
50+
51+
52+
class ExamTimer:
53+
"""Timer module that reads time limit from the shared config."""
54+
55+
def get_time_limit(self) -> str:
56+
config = ExamConfig()
57+
minutes = config.get("time_limit_minutes")
58+
return f"Exam time limit: {minutes} minutes"
59+
60+
61+
class ExamGrader:
62+
"""Grading module that reads passing score from the shared config."""
63+
64+
def check_result(self, score: int) -> str:
65+
config = ExamConfig()
66+
passing = config.get("passing_score")
67+
if score >= passing:
68+
return f"Score {score}/{100}: PASSED (minimum: {passing})"
69+
return f"Score {score}/{100}: FAILED (minimum: {passing})"
70+
71+
72+
class QuestionLoader:
73+
"""Question module that checks shuffle setting from the shared config."""
74+
75+
def load_questions(self) -> str:
76+
config = ExamConfig()
77+
shuffle = config.get("shuffle_questions")
78+
if shuffle:
79+
return "Loading questions in random order..."
80+
return "Loading questions in original order..."
81+
82+
83+
def main():
84+
"""
85+
>>> timer = ExamTimer()
86+
>>> grader = ExamGrader()
87+
>>> loader = QuestionLoader()
88+
89+
# All modules read from the SAME config instance
90+
>>> timer.get_time_limit()
91+
'Exam time limit: 60 minutes'
92+
93+
>>> grader.check_result(75)
94+
'Score 75/100: PASSED (minimum: 50)'
95+
96+
>>> grader.check_result(30)
97+
'Score 30/100: FAILED (minimum: 50)'
98+
99+
>>> loader.load_questions()
100+
'Loading questions in random order...'
101+
102+
# Admin changes the config - all modules see the change immediately
103+
>>> admin_config = ExamConfig()
104+
>>> admin_config.set("time_limit_minutes", 90)
105+
>>> admin_config.set("passing_score", 60)
106+
>>> admin_config.set("shuffle_questions", False)
107+
108+
>>> timer.get_time_limit()
109+
'Exam time limit: 90 minutes'
110+
111+
>>> grader.check_result(55)
112+
'Score 55/100: FAILED (minimum: 60)'
113+
114+
>>> loader.load_questions()
115+
'Loading questions in original order...'
116+
"""
117+
118+
119+
if __name__ == "__main__":
120+
import doctest
121+
122+
doctest.testmod()
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
"""
2+
Example: Specification Pattern Applied to an Inventory Management System
3+
=========================================================================
4+
This example shows how the Specification pattern can be used to create
5+
flexible, composable filters for inventory queries.
6+
7+
This directly relates to an Inventory Management System project,
8+
demonstrating how design patterns integrate into real-world applications.
9+
"""
10+
11+
from __future__ import annotations
12+
from abc import abstractmethod
13+
from typing import List
14+
15+
16+
# --- Specification Base ---
17+
18+
19+
class Specification:
20+
@abstractmethod
21+
def is_satisfied_by(self, candidate) -> bool:
22+
pass
23+
24+
def and_(self, other: Specification) -> AndSpec:
25+
return AndSpec(self, other)
26+
27+
def or_(self, other: Specification) -> OrSpec:
28+
return OrSpec(self, other)
29+
30+
def not_(self) -> NotSpec:
31+
return NotSpec(self)
32+
33+
34+
class AndSpec(Specification):
35+
def __init__(self, one: Specification, other: Specification) -> None:
36+
self._one = one
37+
self._other = other
38+
39+
def is_satisfied_by(self, candidate) -> bool:
40+
return self._one.is_satisfied_by(candidate) and self._other.is_satisfied_by(candidate)
41+
42+
43+
class OrSpec(Specification):
44+
def __init__(self, one: Specification, other: Specification) -> None:
45+
self._one = one
46+
self._other = other
47+
48+
def is_satisfied_by(self, candidate) -> bool:
49+
return self._one.is_satisfied_by(candidate) or self._other.is_satisfied_by(candidate)
50+
51+
52+
class NotSpec(Specification):
53+
def __init__(self, wrapped: Specification) -> None:
54+
self._wrapped = wrapped
55+
56+
def is_satisfied_by(self, candidate) -> bool:
57+
return not self._wrapped.is_satisfied_by(candidate)
58+
59+
60+
# --- Inventory Domain ---
61+
62+
63+
class InventoryItem:
64+
"""A product in the inventory system."""
65+
66+
def __init__(
67+
self, name: str, category: str, quantity: int, price: float, supplier: str
68+
) -> None:
69+
self.name = name
70+
self.category = category
71+
self.quantity = quantity
72+
self.price = price
73+
self.supplier = supplier
74+
75+
def __repr__(self) -> str:
76+
return f"{self.name} (qty: {self.quantity})"
77+
78+
79+
# --- Inventory Specifications ---
80+
81+
82+
class LowStockSpec(Specification):
83+
"""Items with quantity below a minimum threshold."""
84+
85+
def __init__(self, minimum: int = 10) -> None:
86+
self._minimum = minimum
87+
88+
def is_satisfied_by(self, candidate: InventoryItem) -> bool:
89+
return candidate.quantity < self._minimum
90+
91+
92+
class InCategorySpec(Specification):
93+
"""Items belonging to a specific category."""
94+
95+
def __init__(self, category: str) -> None:
96+
self._category = category
97+
98+
def is_satisfied_by(self, candidate: InventoryItem) -> bool:
99+
return candidate.category == self._category
100+
101+
102+
class PriceAboveSpec(Specification):
103+
"""Items priced above a certain amount."""
104+
105+
def __init__(self, amount: float) -> None:
106+
self._amount = amount
107+
108+
def is_satisfied_by(self, candidate: InventoryItem) -> bool:
109+
return candidate.price > self._amount
110+
111+
112+
class FromSupplierSpec(Specification):
113+
"""Items from a specific supplier."""
114+
115+
def __init__(self, supplier: str) -> None:
116+
self._supplier = supplier
117+
118+
def is_satisfied_by(self, candidate: InventoryItem) -> bool:
119+
return candidate.supplier == self._supplier
120+
121+
122+
class OutOfStockSpec(Specification):
123+
"""Items with zero quantity."""
124+
125+
def is_satisfied_by(self, candidate: InventoryItem) -> bool:
126+
return candidate.quantity == 0
127+
128+
129+
def main():
130+
"""
131+
>>> inventory = [
132+
... InventoryItem("Laptop", "electronics", 25, 1200.0, "TechCorp"),
133+
... InventoryItem("Mouse", "electronics", 3, 25.0, "TechCorp"),
134+
... InventoryItem("Desk", "furniture", 8, 350.0, "OfficePlus"),
135+
... InventoryItem("Chair", "furniture", 0, 200.0, "OfficePlus"),
136+
... InventoryItem("Notebook", "stationery", 150, 3.0, "PaperWorld"),
137+
... InventoryItem("Pen", "stationery", 5, 1.5, "PaperWorld"),
138+
... ]
139+
140+
# Find low stock items (below 10 units)
141+
>>> low_stock = LowStockSpec(10)
142+
>>> [i for i in inventory if low_stock.is_satisfied_by(i)]
143+
[Mouse (qty: 3), Desk (qty: 8), Chair (qty: 0), Pen (qty: 5)]
144+
145+
# Find out-of-stock items
146+
>>> out_of_stock = OutOfStockSpec()
147+
>>> [i for i in inventory if out_of_stock.is_satisfied_by(i)]
148+
[Chair (qty: 0)]
149+
150+
# Find low stock electronics (combining two rules)
151+
>>> low_stock_electronics = low_stock.and_(InCategorySpec("electronics"))
152+
>>> [i for i in inventory if low_stock_electronics.is_satisfied_by(i)]
153+
[Mouse (qty: 3)]
154+
155+
# Find expensive items from TechCorp
156+
>>> expensive_techcorp = PriceAboveSpec(100).and_(FromSupplierSpec("TechCorp"))
157+
>>> [i for i in inventory if expensive_techcorp.is_satisfied_by(i)]
158+
[Laptop (qty: 25)]
159+
160+
# Find items that need reordering: low stock OR out of stock
161+
>>> needs_reorder = low_stock.or_(out_of_stock)
162+
>>> [i for i in inventory if needs_reorder.is_satisfied_by(i)]
163+
[Mouse (qty: 3), Desk (qty: 8), Chair (qty: 0), Pen (qty: 5)]
164+
165+
# Find furniture that is NOT out of stock
166+
>>> available_furniture = InCategorySpec("furniture").and_(out_of_stock.not_())
167+
>>> [i for i in inventory if available_furniture.is_satisfied_by(i)]
168+
[Desk (qty: 8)]
169+
"""
170+
171+
172+
if __name__ == "__main__":
173+
import doctest
174+
175+
doctest.testmod()

0 commit comments

Comments
 (0)