1"""
2SOLID Principles Demonstration
3
4SOLID is an acronym for five design principles that make software more:
5- Understandable
6- Flexible
7- Maintainable
8
9S - Single Responsibility Principle
10O - Open/Closed Principle
11L - Liskov Substitution Principle
12I - Interface Segregation Principle
13D - Dependency Inversion Principle
14"""
15
16from abc import ABC, abstractmethod
17from typing import List, Protocol
18from dataclasses import dataclass
19from enum import Enum
20
21
22# =============================================================================
23# S - SINGLE RESPONSIBILITY PRINCIPLE (SRP)
24# A class should have only ONE reason to change
25# =============================================================================
26
27print("=" * 70)
28print("S - SINGLE RESPONSIBILITY PRINCIPLE")
29print("=" * 70)
30
31
32# ❌ BEFORE: God class with multiple responsibilities
33class UserManagerBad:
34 """This class violates SRP - it does TOO MUCH"""
35
36 def __init__(self, username: str, email: str):
37 self.username = username
38 self.email = email
39
40 def save_to_database(self):
41 """Responsibility 1: Database operations"""
42 print(f"Saving {self.username} to database...")
43
44 def send_welcome_email(self):
45 """Responsibility 2: Email sending"""
46 print(f"Sending welcome email to {self.email}...")
47
48 def generate_report(self) -> str:
49 """Responsibility 3: Report generation"""
50 return f"User Report: {self.username}"
51
52 # Problem: If email system changes, database logic changes,
53 # or reporting format changes, we modify the SAME class!
54
55
56# ✅ AFTER: Each class has a single responsibility
57@dataclass
58class User:
59 """Only responsible for user data"""
60 username: str
61 email: str
62
63
64class UserRepository:
65 """Single responsibility: Database operations"""
66
67 def save(self, user: User):
68 print(f"Saving {user.username} to database...")
69
70
71class EmailService:
72 """Single responsibility: Email sending"""
73
74 def send_welcome_email(self, user: User):
75 print(f"Sending welcome email to {user.email}...")
76
77
78class ReportGenerator:
79 """Single responsibility: Report generation"""
80
81 def generate_user_report(self, user: User) -> str:
82 return f"User Report: {user.username}"
83
84
85# =============================================================================
86# O - OPEN/CLOSED PRINCIPLE (OCP)
87# Open for extension, closed for modification
88# =============================================================================
89
90print("\n" + "=" * 70)
91print("O - OPEN/CLOSED PRINCIPLE")
92print("=" * 70)
93
94
95# ❌ BEFORE: Must modify class to add new shapes
96class AreaCalculatorBad:
97 """Violates OCP - must modify to add new shapes"""
98
99 def calculate_area(self, shape_type: str, **kwargs) -> float:
100 if shape_type == "circle":
101 return 3.14159 * kwargs["radius"] ** 2
102 elif shape_type == "rectangle":
103 return kwargs["width"] * kwargs["height"]
104 elif shape_type == "triangle":
105 return 0.5 * kwargs["base"] * kwargs["height"]
106 # Adding a new shape requires MODIFYING this method!
107 else:
108 raise ValueError(f"Unknown shape: {shape_type}")
109
110
111# ✅ AFTER: Extensible through inheritance/protocols
112class Shape(ABC):
113 """Abstract base - closed for modification"""
114
115 @abstractmethod
116 def area(self) -> float:
117 pass
118
119
120class Circle(Shape):
121 """Extends Shape without modifying it"""
122
123 def __init__(self, radius: float):
124 self.radius = radius
125
126 def area(self) -> float:
127 return 3.14159 * self.radius ** 2
128
129
130class Rectangle(Shape):
131 def __init__(self, width: float, height: float):
132 self.width = width
133 self.height = height
134
135 def area(self) -> float:
136 return self.width * self.height
137
138
139class Triangle(Shape):
140 def __init__(self, base: float, height: float):
141 self.base = base
142 self.height = height
143
144 def area(self) -> float:
145 return 0.5 * self.base * self.height
146
147
148class AreaCalculator:
149 """Closed for modification - works with any Shape"""
150
151 def calculate_total_area(self, shapes: List[Shape]) -> float:
152 return sum(shape.area() for shape in shapes)
153
154
155# =============================================================================
156# L - LISKOV SUBSTITUTION PRINCIPLE (LSP)
157# Subtypes must be substitutable for their base types
158# =============================================================================
159
160print("\n" + "=" * 70)
161print("L - LISKOV SUBSTITUTION PRINCIPLE")
162print("=" * 70)
163
164
165# ❌ BEFORE: Square violates LSP
166class RectangleBad:
167 def __init__(self, width: float, height: float):
168 self.width = width
169 self.height = height
170
171 def set_width(self, width: float):
172 self.width = width
173
174 def set_height(self, height: float):
175 self.height = height
176
177 def area(self) -> float:
178 return self.width * self.height
179
180
181class SquareBad(RectangleBad):
182 """Violates LSP - changes behavior unexpectedly"""
183
184 def set_width(self, width: float):
185 self.width = width
186 self.height = width # Breaks rectangle's assumption!
187
188 def set_height(self, height: float):
189 self.height = height
190 self.width = height # Breaks rectangle's assumption!
191
192
193# ✅ AFTER: Proper abstraction respects LSP
194class ShapeLSP(ABC):
195 @abstractmethod
196 def area(self) -> float:
197 pass
198
199
200class RectangleLSP(ShapeLSP):
201 def __init__(self, width: float, height: float):
202 self._width = width
203 self._height = height
204
205 def area(self) -> float:
206 return self._width * self._height
207
208
209class SquareLSP(ShapeLSP):
210 """Independent class - doesn't inherit problematic behavior"""
211
212 def __init__(self, side: float):
213 self._side = side
214
215 def area(self) -> float:
216 return self._side ** 2
217
218
219# =============================================================================
220# I - INTERFACE SEGREGATION PRINCIPLE (ISP)
221# Clients should not depend on interfaces they don't use
222# =============================================================================
223
224print("\n" + "=" * 70)
225print("I - INTERFACE SEGREGATION PRINCIPLE")
226print("=" * 70)
227
228
229# ❌ BEFORE: Fat interface forces unnecessary implementation
230class WorkerBad(ABC):
231 """Fat interface - not all workers do everything"""
232
233 @abstractmethod
234 def work(self):
235 pass
236
237 @abstractmethod
238 def eat(self):
239 pass
240
241 @abstractmethod
242 def sleep(self):
243 pass
244
245
246class HumanWorkerBad(WorkerBad):
247 def work(self):
248 print("Human working...")
249
250 def eat(self):
251 print("Human eating...")
252
253 def sleep(self):
254 print("Human sleeping...")
255
256
257class RobotWorkerBad(WorkerBad):
258 def work(self):
259 print("Robot working...")
260
261 def eat(self):
262 raise NotImplementedError("Robots don't eat!") # Forced to implement!
263
264 def sleep(self):
265 raise NotImplementedError("Robots don't sleep!") # Forced to implement!
266
267
268# ✅ AFTER: Segregated interfaces
269class Workable(Protocol):
270 """Small, focused interface"""
271
272 def work(self):
273 pass
274
275
276class Eatable(Protocol):
277 def eat(self):
278 pass
279
280
281class Sleepable(Protocol):
282 def sleep(self):
283 pass
284
285
286class HumanWorker:
287 """Implements only needed interfaces"""
288
289 def work(self):
290 print("Human working...")
291
292 def eat(self):
293 print("Human eating...")
294
295 def sleep(self):
296 print("Human sleeping...")
297
298
299class RobotWorker:
300 """Only implements what makes sense"""
301
302 def work(self):
303 print("Robot working...")
304
305
306# =============================================================================
307# D - DEPENDENCY INVERSION PRINCIPLE (DIP)
308# Depend on abstractions, not concretions
309# =============================================================================
310
311print("\n" + "=" * 70)
312print("D - DEPENDENCY INVERSION PRINCIPLE")
313print("=" * 70)
314
315
316# ❌ BEFORE: High-level module depends on low-level module
317class MySQLDatabase:
318 """Low-level module (concrete implementation)"""
319
320 def save(self, data: str):
321 print(f"Saving to MySQL: {data}")
322
323
324class UserServiceBad:
325 """High-level module depends on concrete MySQL"""
326
327 def __init__(self):
328 self.db = MySQLDatabase() # Tight coupling!
329
330 def save_user(self, username: str):
331 self.db.save(username)
332 # Can't easily switch to PostgreSQL or MongoDB!
333
334
335# ✅ AFTER: Both depend on abstraction
336class Database(Protocol):
337 """Abstraction (interface)"""
338
339 def save(self, data: str):
340 pass
341
342
343class MySQLDatabaseGood:
344 def save(self, data: str):
345 print(f"Saving to MySQL: {data}")
346
347
348class PostgreSQLDatabase:
349 def save(self, data: str):
350 print(f"Saving to PostgreSQL: {data}")
351
352
353class MongoDBDatabase:
354 def save(self, data: str):
355 print(f"Saving to MongoDB: {data}")
356
357
358class UserService:
359 """High-level module depends on abstraction"""
360
361 def __init__(self, database: Database):
362 self.db = database # Depends on interface, not concrete class!
363
364 def save_user(self, username: str):
365 self.db.save(username)
366
367
368# =============================================================================
369# DEMONSTRATION
370# =============================================================================
371
372def demonstrate_solid():
373 print("\n" + "=" * 70)
374 print("SOLID PRINCIPLES DEMONSTRATION")
375 print("=" * 70)
376
377 # Single Responsibility
378 print("\n[SRP] Single Responsibility Principle:")
379 user = User("alice", "alice@example.com")
380 repo = UserRepository()
381 email_service = EmailService()
382 report_gen = ReportGenerator()
383
384 repo.save(user)
385 email_service.send_welcome_email(user)
386 print(report_gen.generate_user_report(user))
387
388 # Open/Closed
389 print("\n[OCP] Open/Closed Principle:")
390 shapes = [
391 Circle(5),
392 Rectangle(4, 6),
393 Triangle(3, 8)
394 ]
395 calculator = AreaCalculator()
396 total = calculator.calculate_total_area(shapes)
397 print(f"Total area: {total:.2f}")
398
399 # Liskov Substitution
400 print("\n[LSP] Liskov Substitution Principle:")
401 shapes_lsp: List[ShapeLSP] = [
402 RectangleLSP(4, 6),
403 SquareLSP(5)
404 ]
405 for shape in shapes_lsp:
406 print(f"Area: {shape.area()}")
407
408 # Interface Segregation
409 print("\n[ISP] Interface Segregation Principle:")
410 human = HumanWorker()
411 robot = RobotWorker()
412
413 human.work()
414 human.eat()
415 robot.work()
416 # robot.eat() # Not forced to implement!
417
418 # Dependency Inversion
419 print("\n[DIP] Dependency Inversion Principle:")
420 mysql_service = UserService(MySQLDatabaseGood())
421 postgres_service = UserService(PostgreSQLDatabase())
422 mongo_service = UserService(MongoDBDatabase())
423
424 mysql_service.save_user("alice")
425 postgres_service.save_user("bob")
426 mongo_service.save_user("charlie")
427
428
429def print_summary():
430 print("\n" + "=" * 70)
431 print("SOLID PRINCIPLES SUMMARY")
432 print("=" * 70)
433
434 print("""
435[S] Single Responsibility Principle
436 ✓ One class = one reason to change
437 ✓ Easier to understand and maintain
438 ✓ Better testability
439
440[O] Open/Closed Principle
441 ✓ Open for extension (add new features)
442 ✓ Closed for modification (don't change existing code)
443 ✓ Use abstraction and polymorphism
444
445[L] Liskov Substitution Principle
446 ✓ Subtypes must be substitutable for base types
447 ✓ Don't violate base class contracts
448 ✓ Prefer composition over problematic inheritance
449
450[I] Interface Segregation Principle
451 ✓ Many small interfaces > one large interface
452 ✓ Clients use only what they need
453 ✓ Reduces coupling
454
455[D] Dependency Inversion Principle
456 ✓ Depend on abstractions, not concretions
457 ✓ High-level modules independent of low-level details
458 ✓ Enables easy swapping of implementations
459
460BENEFITS OF SOLID:
461 • More maintainable code
462 • Easier to test
463 • Better extensibility
464 • Reduced coupling
465 • Improved reusability
466""")
467
468
469if __name__ == "__main__":
470 demonstrate_solid()
471 print_summary()