creational_patterns.py

Download
python 549 lines 14.4 KB
  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()