1"""
2Creational Design Patterns
3
4Patterns for object creation mechanisms:
51. Singleton - Ensure only one instance exists
62. Factory Method - Create objects without specifying exact class
73. Abstract Factory - Create families of related objects
84. Builder - Construct complex objects step by step
9"""
10
11from abc import ABC, abstractmethod
12from typing import Dict, Optional, List
13from enum import Enum
14
15
16# =============================================================================
17# 1. SINGLETON PATTERN
18# Ensure a class has only one instance and provide global access to it
19# =============================================================================
20
21print("=" * 70)
22print("1. SINGLETON PATTERN")
23print("=" * 70)
24
25
26class SingletonMeta(type):
27 """
28 Metaclass for Singleton pattern.
29 Thread-safe implementation.
30 """
31 _instances: Dict[type, object] = {}
32
33 def __call__(cls, *args, **kwargs):
34 if cls not in cls._instances:
35 instance = super().__call__(*args, **kwargs)
36 cls._instances[cls] = instance
37 return cls._instances[cls]
38
39
40class DatabaseConnection(metaclass=SingletonMeta):
41 """
42 Database connection that should exist only once.
43 Uses Singleton to ensure single connection pool.
44 """
45
46 def __init__(self):
47 self.connection_string = "postgresql://localhost:5432/mydb"
48 self.pool_size = 10
49 print(f"Initializing database connection to {self.connection_string}")
50
51 def query(self, sql: str) -> str:
52 return f"Executing: {sql}"
53
54
55class ConfigManager(metaclass=SingletonMeta):
56 """
57 Application configuration manager.
58 Should be singleton to ensure consistent config across app.
59 """
60
61 def __init__(self):
62 self.settings: Dict[str, str] = {}
63 print("Loading configuration...")
64
65 def set(self, key: str, value: str):
66 self.settings[key] = value
67
68 def get(self, key: str) -> Optional[str]:
69 return self.settings.get(key)
70
71
72# =============================================================================
73# 2. FACTORY METHOD PATTERN
74# Define interface for creating objects, let subclasses decide which class
75# =============================================================================
76
77print("\n" + "=" * 70)
78print("2. FACTORY METHOD PATTERN")
79print("=" * 70)
80
81
82# Product interface
83class Document(ABC):
84 """Abstract document that can be created"""
85
86 @abstractmethod
87 def open(self) -> str:
88 pass
89
90 @abstractmethod
91 def save(self, content: str) -> str:
92 pass
93
94
95# Concrete products
96class PDFDocument(Document):
97 def open(self) -> str:
98 return "Opening PDF document..."
99
100 def save(self, content: str) -> str:
101 return f"Saving to PDF: {content}"
102
103
104class WordDocument(Document):
105 def open(self) -> str:
106 return "Opening Word document..."
107
108 def save(self, content: str) -> str:
109 return f"Saving to DOCX: {content}"
110
111
112class TextDocument(Document):
113 def open(self) -> str:
114 return "Opening text document..."
115
116 def save(self, content: str) -> str:
117 return f"Saving to TXT: {content}"
118
119
120# Creator (factory)
121class DocumentCreator(ABC):
122 """
123 Abstract creator declares factory method.
124 Subclasses override to change product type.
125 """
126
127 @abstractmethod
128 def create_document(self) -> Document:
129 """Factory method - subclasses implement this"""
130 pass
131
132 def new_document(self, content: str) -> str:
133 """
134 Business logic that uses the factory method.
135 This code works with any document type!
136 """
137 doc = self.create_document()
138 doc.open()
139 return doc.save(content)
140
141
142# Concrete creators
143class PDFCreator(DocumentCreator):
144 def create_document(self) -> Document:
145 return PDFDocument()
146
147
148class WordCreator(DocumentCreator):
149 def create_document(self) -> Document:
150 return WordDocument()
151
152
153class TextCreator(DocumentCreator):
154 def create_document(self) -> Document:
155 return TextDocument()
156
157
158# =============================================================================
159# 3. ABSTRACT FACTORY PATTERN
160# Create families of related objects without specifying concrete classes
161# =============================================================================
162
163print("\n" + "=" * 70)
164print("3. ABSTRACT FACTORY PATTERN")
165print("=" * 70)
166
167
168# Abstract products
169class Button(ABC):
170 @abstractmethod
171 def render(self) -> str:
172 pass
173
174
175class Checkbox(ABC):
176 @abstractmethod
177 def render(self) -> str:
178 pass
179
180
181# Concrete products - Windows family
182class WindowsButton(Button):
183 def render(self) -> str:
184 return "Rendering Windows style button"
185
186
187class WindowsCheckbox(Checkbox):
188 def render(self) -> str:
189 return "Rendering Windows style checkbox"
190
191
192# Concrete products - Mac family
193class MacButton(Button):
194 def render(self) -> str:
195 return "Rendering Mac style button"
196
197
198class MacCheckbox(Checkbox):
199 def render(self) -> str:
200 return "Rendering Mac style checkbox"
201
202
203# Concrete products - Linux family
204class LinuxButton(Button):
205 def render(self) -> str:
206 return "Rendering Linux style button"
207
208
209class LinuxCheckbox(Checkbox):
210 def render(self) -> str:
211 return "Rendering Linux style checkbox"
212
213
214# Abstract factory
215class GUIFactory(ABC):
216 """Abstract factory for creating families of UI elements"""
217
218 @abstractmethod
219 def create_button(self) -> Button:
220 pass
221
222 @abstractmethod
223 def create_checkbox(self) -> Checkbox:
224 pass
225
226
227# Concrete factories
228class WindowsFactory(GUIFactory):
229 def create_button(self) -> Button:
230 return WindowsButton()
231
232 def create_checkbox(self) -> Checkbox:
233 return WindowsCheckbox()
234
235
236class MacFactory(GUIFactory):
237 def create_button(self) -> Button:
238 return MacButton()
239
240 def create_checkbox(self) -> Checkbox:
241 return MacCheckbox()
242
243
244class LinuxFactory(GUIFactory):
245 def create_button(self) -> Button:
246 return LinuxButton()
247
248 def create_checkbox(self) -> Checkbox:
249 return LinuxCheckbox()
250
251
252# Client code
253class Application:
254 """
255 Application works with factories and products through abstract interfaces.
256 Doesn't know about concrete classes!
257 """
258
259 def __init__(self, factory: GUIFactory):
260 self.factory = factory
261
262 def create_ui(self) -> List[str]:
263 button = self.factory.create_button()
264 checkbox = self.factory.create_checkbox()
265 return [button.render(), checkbox.render()]
266
267
268# =============================================================================
269# 4. BUILDER PATTERN
270# Construct complex objects step by step
271# =============================================================================
272
273print("\n" + "=" * 70)
274print("4. BUILDER PATTERN")
275print("=" * 70)
276
277
278class Computer:
279 """Complex object with many optional parts"""
280
281 def __init__(self):
282 self.cpu: Optional[str] = None
283 self.ram: Optional[int] = None
284 self.storage: Optional[str] = None
285 self.gpu: Optional[str] = None
286 self.os: Optional[str] = None
287 self.peripherals: List[str] = []
288
289 def __str__(self) -> str:
290 parts = [
291 f"CPU: {self.cpu}",
292 f"RAM: {self.ram}GB",
293 f"Storage: {self.storage}",
294 ]
295 if self.gpu:
296 parts.append(f"GPU: {self.gpu}")
297 if self.os:
298 parts.append(f"OS: {self.os}")
299 if self.peripherals:
300 parts.append(f"Peripherals: {', '.join(self.peripherals)}")
301 return "Computer:\n " + "\n ".join(parts)
302
303
304class ComputerBuilder:
305 """
306 Builder for constructing Computer step by step.
307 Provides fluent interface.
308 """
309
310 def __init__(self):
311 self.computer = Computer()
312
313 def set_cpu(self, cpu: str) -> 'ComputerBuilder':
314 self.computer.cpu = cpu
315 return self
316
317 def set_ram(self, ram: int) -> 'ComputerBuilder':
318 self.computer.ram = ram
319 return self
320
321 def set_storage(self, storage: str) -> 'ComputerBuilder':
322 self.computer.storage = storage
323 return self
324
325 def set_gpu(self, gpu: str) -> 'ComputerBuilder':
326 self.computer.gpu = gpu
327 return self
328
329 def set_os(self, os: str) -> 'ComputerBuilder':
330 self.computer.os = os
331 return self
332
333 def add_peripheral(self, peripheral: str) -> 'ComputerBuilder':
334 self.computer.peripherals.append(peripheral)
335 return self
336
337 def build(self) -> Computer:
338 """Return the constructed computer"""
339 return self.computer
340
341
342class ComputerType(Enum):
343 """Predefined computer configurations"""
344 GAMING = "gaming"
345 OFFICE = "office"
346 WORKSTATION = "workstation"
347
348
349class ComputerDirector:
350 """
351 Director knows how to build specific configurations.
352 Encapsulates common building recipes.
353 """
354
355 @staticmethod
356 def build_gaming_pc() -> Computer:
357 return (ComputerBuilder()
358 .set_cpu("Intel i9-13900K")
359 .set_ram(32)
360 .set_storage("2TB NVMe SSD")
361 .set_gpu("NVIDIA RTX 4090")
362 .set_os("Windows 11")
363 .add_peripheral("Gaming Keyboard")
364 .add_peripheral("Gaming Mouse")
365 .add_peripheral("144Hz Monitor")
366 .build())
367
368 @staticmethod
369 def build_office_pc() -> Computer:
370 return (ComputerBuilder()
371 .set_cpu("Intel i5-12400")
372 .set_ram(16)
373 .set_storage("512GB SSD")
374 .set_os("Windows 11 Pro")
375 .add_peripheral("Wireless Keyboard")
376 .add_peripheral("Wireless Mouse")
377 .build())
378
379 @staticmethod
380 def build_workstation() -> Computer:
381 return (ComputerBuilder()
382 .set_cpu("AMD Threadripper PRO")
383 .set_ram(128)
384 .set_storage("4TB NVMe SSD")
385 .set_gpu("NVIDIA RTX A6000")
386 .set_os("Ubuntu 22.04 LTS")
387 .add_peripheral("Mechanical Keyboard")
388 .add_peripheral("Professional Mouse")
389 .add_peripheral("4K Monitor")
390 .add_peripheral("Graphics Tablet")
391 .build())
392
393
394# =============================================================================
395# DEMONSTRATIONS
396# =============================================================================
397
398def demonstrate_singleton():
399 print("\n[SINGLETON DEMONSTRATION]")
400 print("-" * 50)
401
402 # Create two "instances" - should be the same object
403 db1 = DatabaseConnection()
404 db2 = DatabaseConnection()
405
406 print(f"db1 is db2: {db1 is db2}") # True!
407 print(f"db1 id: {id(db1)}, db2 id: {id(db2)}")
408
409 # Configuration manager
410 config1 = ConfigManager()
411 config1.set("theme", "dark")
412
413 config2 = ConfigManager() # Same instance!
414 print(f"Config theme: {config2.get('theme')}") # Gets "dark"
415
416 print("\nUse cases:")
417 print(" • Database connection pools")
418 print(" • Configuration managers")
419 print(" • Logging services")
420 print(" • Cache managers")
421
422
423def demonstrate_factory_method():
424 print("\n[FACTORY METHOD DEMONSTRATION]")
425 print("-" * 50)
426
427 # Client code works with abstract creator
428 creators = [PDFCreator(), WordCreator(), TextCreator()]
429
430 for creator in creators:
431 result = creator.new_document("Hello, World!")
432 print(result)
433
434 print("\nUse cases:")
435 print(" • Framework extension points")
436 print(" • Plugin systems")
437 print(" • When exact type isn't known until runtime")
438 print(" • Delegating instantiation to subclasses")
439
440
441def demonstrate_abstract_factory():
442 print("\n[ABSTRACT FACTORY DEMONSTRATION]")
443 print("-" * 50)
444
445 # Determine platform (simulated)
446 import platform
447 os_name = platform.system()
448
449 # Select appropriate factory
450 if os_name == "Windows":
451 factory = WindowsFactory()
452 elif os_name == "Darwin": # macOS
453 factory = MacFactory()
454 else:
455 factory = LinuxFactory()
456
457 # Application uses factory without knowing concrete types
458 app = Application(factory)
459 ui_elements = app.create_ui()
460
461 print(f"Platform: {os_name}")
462 for element in ui_elements:
463 print(f" {element}")
464
465 print("\nUse cases:")
466 print(" • Cross-platform UI toolkits")
467 print(" • Database drivers (SQL/NoSQL families)")
468 print(" • Creating themed UI elements")
469 print(" • Multiple product families")
470
471
472def demonstrate_builder():
473 print("\n[BUILDER DEMONSTRATION]")
474 print("-" * 50)
475
476 # Build custom computer
477 custom = (ComputerBuilder()
478 .set_cpu("AMD Ryzen 9 7950X")
479 .set_ram(64)
480 .set_storage("1TB NVMe SSD")
481 .set_gpu("AMD RX 7900 XTX")
482 .set_os("Arch Linux")
483 .add_peripheral("Mechanical Keyboard")
484 .build())
485
486 print("Custom Build:")
487 print(custom)
488
489 # Use director for predefined configs
490 print("\nGaming PC:")
491 gaming = ComputerDirector.build_gaming_pc()
492 print(gaming)
493
494 print("\nOffice PC:")
495 office = ComputerDirector.build_office_pc()
496 print(office)
497
498 print("\nUse cases:")
499 print(" • Complex object construction")
500 print(" • Many optional parameters")
501 print(" • Step-by-step construction")
502 print(" • Different representations of same object")
503
504
505def print_summary():
506 print("\n" + "=" * 70)
507 print("CREATIONAL PATTERNS SUMMARY")
508 print("=" * 70)
509
510 print("""
511SINGLETON
512 Purpose: Ensure only one instance exists
513 When: Global state, shared resources
514 Benefits: Controlled access, lazy initialization
515 Drawbacks: Global state, hard to test
516
517FACTORY METHOD
518 Purpose: Create objects without specifying exact class
519 When: Don't know exact types beforehand
520 Benefits: Loose coupling, extensibility
521 Drawbacks: More classes needed
522
523ABSTRACT FACTORY
524 Purpose: Create families of related objects
525 When: Multiple product families
526 Benefits: Consistency, isolation from concrete classes
527 Drawbacks: Difficult to add new products
528
529BUILDER
530 Purpose: Construct complex objects step by step
531 When: Many construction steps, optional parts
532 Benefits: Fluent API, flexible construction
533 Drawbacks: More code, complexity
534
535CHOOSING THE RIGHT PATTERN:
536 • Singleton: Need exactly ONE instance
537 • Factory Method: Don't know exact product type
538 • Abstract Factory: Need families of related products
539 • Builder: Complex construction with many options
540""")
541
542
543if __name__ == "__main__":
544 demonstrate_singleton()
545 demonstrate_factory_method()
546 demonstrate_abstract_factory()
547 demonstrate_builder()
548 print_summary()