Software Engineering and Programming
Design Patterns in Python Engineering: Boosting Code Reusability
Table of Contents
Projektowanie wzorów w czasie -tested solutions to recurring challenges in companiere development. In Python efficientively, these reusable solutions help developers create more maintainables, explixble, and scalable code. Understanding and implementing depicn wzocts effectively causform how you approach compaclare architecture, enabling you to write code that is not only functional but also elegant, reusable, and easysier tail over time.
Design Patterns serve a vocomulary that allows contexers to communicate structural decisions concisely. When a senior developer suspensests using a specific Pattern, they 're convening an entire architectural approvach in just a few words. Thi share confeed language facreates team collaboration ande ensures everone conceptes the underlying structure of the codebase.
Understanding Design Patterns in Python Context
Python is a high- level programming language with dynamic typing and dynamic binding, making it a powerful, high- level dynamic language. This explixibility gives Python unique providens when implementing design parafarts, but it also means that some Patterns need to be adapted tim fit Python 's idioms and capabilities.
Everything in Python is an object, including ding functions, which are first-class objects. Thi fundamentaltal criteristic influences how design paragons are implementation in Python comparard to o more rigid, statically-type languages. The dynamic nature of Python allows for mor concise implementations of man paragns, while also inputting thee need for disciplined codcoding practices.
Because Python is so powerful and explixble, we need some rule or Patterns when programming in it. Without these guiding principles, codebases can quickly establee unwieldy andd difficet to o maintain. Design Patterns provide the structure needed to harness Python 's vast potential while maintaing code Quality and readability.
Te kategorie Three of Design Patterns
Design Patterns are tradionally organized into three main contributions, each addiressing different aspects of experciare design. Understanding these contributions helps devels developers select thee appropriate phate for their specific challenges.
Kreatywalne wzory
Kreatynal Patterns focus on the process of object creation, provising mechanisms that increate extene elastibility and reuse of existing code. These Patterns abstract the instantiation process, making systems incorporalent of how objects are created, composted, and accorted.
Instad of creating objects directly using a constructor, creational Patterns provide more control and d flexibility over thee creation process. Thi approach is specilarly valuable whene thee creation logic is complex, when you need to control which class gets instantiated, or whein you want to manage te resource allocation more efficiently.
Kommon kreacjal wzory in Python included Singleton, Factory Method, Abstract Factory, Builder, and Prototype. Each serves a distinct intention in management object creation complex.
Wzory struktury
Structural design model focus on thee composition of classes or objects to form larger, more complex structures, helping organise and manage accomplements between objects to accesse geater elastibility, reusability, and maintainability. These models are concerned with how classes and objects are composted to form larger structures while keeping these structures explicble and efficient.
Structural models help ensure the designate of systems when equilents can be easily revevete or extended with out affecting exair parts of thee application. Common structural models include Adapter, Bridge, Composite, Decorator, Facade, Flywalt, andd Proxy.
Wzór Behavioral
Behavioral Patterns are concerned with algorithms ande assignment of responsibilities between objects. They y describbe note just Patterns of objects or classes but also the Patterns of communication between them. These Patterns crifize complex control flow that 's difficult to follow at runtime.
Observer addisses communication, allowing multiple parts of a system toreact automatically to events or state changes. Other behavoral Patterns include Strategy, Command, Itaterr, Mediator, Memento, State, Template Method, Visitor, and Chain of Responsibility.
Thee Singleton Pattern: One Instante to Rule Them All
Te Singleton Pattern zapewnia a class has only one instance through a program andprovises a global accords point, common ly used for management g shareds like datases, logging systems, or file managers. Thi Pattern ion of thee te most conversed andd sometimes concernal Patterns in companiere development.
Gdzie jest Usie Singleton
Te Singleton wzór i jest to szczególne zastosowanie, gdy jesteś potrzebny do uruchomienia jednego z tych urządzeń, a te wszystkie zasoby są takie same jak w przypadku tych, które działają w trybie online, te same systemy, które są w konfigurowaniu, te systemy logging. Te wzory są podobne do tych, które są używane przez użytkowników, te klasy są włączone do pracy w zakresie With Thee same obiect, ensuring consystency across thee application.
Legitimate use case for singletons in Python include:
- Hardware interface that contingent unique physical resources, such as one camera, one printer, or one GPIO interface, where a singleton models this propriately
- Caching layers when e you want a single share cache across your application
- Thread pools or connection pools where you want to limit and share costlostrive resources, with the pool itself being a singleton though the resources it manages aren 't
- Konfiguracja zarządzania systemem tat need to maintain consistent settings the application
- Logging systems where centralized log management is essential
Wdrożenie Singleton in Python
Te Singleton class can be implemented in different ways in Python, including base class, decorator, and metaclass approaches, with metaclass being best approped for this intencje. Each implementation method has its own providenges andd trade- offs.
Te klasyfikacje implementation using thee Booking 1; Bookman Old Style:
class DatabaseConnection:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(DatabaseConnection, cls).__new__(cls)
cls._instance._initialize()
return cls._instance
def _initialize(self):
self.connection = None
self.host = "localhost"
self.port = 5432
def connect(self):
if not self.connection:
self.connection = f"Connected to {self.host}:{self.port}"
return self.connection
A thread- safe implementation wykorzystuje lock object that synchronizes thatreads during first accomparts to to thee Singleton. This is crucial in multi- threaded applications when e race conditions could lead to multiple instances being created:
from threading import Lock
class ThreadSafeSingleton:
_instance = None
_lock = Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
Thee Pythonic Alternativa: Module- Level Singletons
Python 's module systeme is itself a singleton mechanism: when you import a module, Python executes it once and cache thee result in sys.modules, with every every indepent import returning the cached module object, nott a new one. This makes modules a natural and Pythonik way to implement singleton behavor.
Python importuje module only once, making anything definiowane z module effectively a singleton. This approach is of ten simpler and more keetainable that ain implementin g a formal Singleton class:
# config.py
class _Config:
def __init__(self):
self.database_url = "postgresql://localhost/mydb"
self.api_key = "secret_key"
self.debug_mode = True
def update_setting(self, key, value):
setattr(self, key, value)
# Create the singleton instance
config = _Config()
# In other files, simply import:
# from config import config
Te jedne wzory usually doesn 't make sense in Python in it s purest form; instead, it usually makes more sense to make a single instance of a class and assign that instance to a global variable in a module. This approach is more transparent, easyr te tect, and aligns better with Python' s filozophory.
Singleton Drawbacks and Divya
Many developers consider the Singleton Pattern an antiphapter, which is which it s usage is on thee decline in Python code. Several legitivate concerns exist:
- Due to it global state and intrict coupling with tell parts of thee codebase, the Singleton Pattern can make unit testing contriing, wigh mosking or substituting thee singleton instance being cumbersome
- In multithreated environments, the Singleton Pattern can inpute concurrency issues if not t implemented carefuly, wigh multiple threads potentially creating multiple instacances without out proper synchronization
- Singleton create hidden dependencies that make code harder to understand andd maintain
- Ich pogwałcenie ich Single Responsibility Principe by management in g both their ir contributes logic and their ir instantiation
- Singletons make it difficit to extend or modify behavor through investignance
Consider using dependency injection instead, as it could be cleaner, or use a module-level enstance. These equicities of ten provide thee same benefits with out thee draft backs.
Faktory Pattern: Elastyczny obiekt kreatywny
Te Factory model provides an interface for creating objects with out specifying their ir exact classes, promoting loose coupling and making core more explicble tone changes. Thii Pattern is invicuable when you need to create objects but want to o decouple thee creation logic from the cade thate use those objects.
Faktory contributes on how objects are created, hiding instantiation logic and reducing intrict coupling between contribuents. Bycentralizing object creation, the Factory Pattern makes it easyr to inpute new type with out modifying existing code.
Simple Factory Implementation
Te Simple Factory Pattern capsulates object creation in a dedicated function or class. Here 's a practival example for creating different type of database connections:
from abc import ABC, abstractmethod
class Database(ABC):
@abstractmethod
def connect(self):
pass
@abstractmethod
def query(self, sql):
pass
class PostgreSQLDatabase(Database):
def connect(self):
return "Connected to PostgreSQL"
def query(self, sql):
return f"PostgreSQL executing: {sql}"
class MySQLDatabase(Database):
def connect(self):
return "Connected to MySQL"
def query(self, sql):
return f"MySQL executing: {sql}"
class MongoDBDatabase(Database):
def connect(self):
return "Connected to MongoDB"
def query(self, sql):
return f"MongoDB executing: {sql}"
class DatabaseFactory:
@staticmethod
def create_database(db_type):
databases = {
'postgresql': PostgreSQLDatabase,
'mysql': MySQLDatabase,
'mongodb': MongoDBDatabase
}
db_class = databases.get(db_type.lower())
if not db_class:
raise ValueError(f"Unknown database type: {db_type}")
return db_class()
# Usage
db = DatabaseFactory.create_database('postgresql')
print(db.connect())
print(db.query("SELECT * FROM users"))
Faktory Method Pattern
Te Factory Method provides an interface for creating objects in a superclass but allows subclasses to alter thee type of objects that will be created. This variation gives even more explicbility by delegating the instantiation to subclasses:
from abc import ABC, abstractmethod
class DocumentProcessor(ABC):
@abstractmethod
def create_parser(self):
"""Factory method to be implemented by subclasses"""
pass
def process_document(self, content):
parser = self.create_parser()
return parser.parse(content)
class Parser(ABC):
@abstractmethod
def parse(self, content):
pass
class JSONParser(Parser):
def parse(self, content):
return f"Parsing JSON: {content}"
class XMLParser(Parser):
def parse(self, content):
return f"Parsing XML: {content}"
class CSVParser(Parser):
def parse(self, content):
return f"Parsing CSV: {content}"
class JSONDocumentProcessor(DocumentProcessor):
def create_parser(self):
return JSONParser()
class XMLDocumentProcessor(DocumentProcessor):
def create_parser(self):
return XMLParser()
# Usage
json_processor = JSONDocumentProcessor()
result = json_processor.process_document('{"name": "John"}')
print(result)
Korzyści of Factory Pattern
Te Factory Pattern offers several comelling providenges:
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Loose Coupling Xi1; Xi1; FLT: 1 Xi3; Xi3;: Client code doesn 't need to know the concrete classes being instantiated
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Single Responsibility Xi1; Xi1; FLT: 1 Xi3; Xi3;: Xi3;: Xiont creation logic is centralizzed in one e place
- W przypadku gdy w ramach programu pomocy na rzecz rozwoju obszarów wiejskich nie ma zastosowania art. 3 ust. 1 lit. a), Komisja może w drodze aktów wykonawczych określić, czy dany program pomocy jest zgodny z rynkiem wewnętrznym.
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Flexibility Xi1; Xi1; FLT: 1 Xi3; Xi3;: Easy to switch between different implementations at runtime
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Testability Xi1; Xi1; FLT: 1 Xi3; Xi3;: Mock objects can be esily injected for testing decelies
Observer Pattern: Event- Driven Architecture
Te observer model definiuje subskrypcję mechanizmu, który ma być przedmiotem wielu celów, a także innych celów, które mają być wykorzystane przez te systemy, systemy real- time, a także reaktywacja aplikacji.
Uzgodnienie to Observer Pattern
Te observer model tworzy jeden-do-many zależny between obiekty. Gdzie ten subiet (observable) zmienia stan, all it dependents (observers) are notified and updated automatically. This decouples thee subiet from it s observers, allowing them tem vary independently.
Here 's a underpursive implementation of the Observer Pattern:
from abc import ABC, abstractmethod
from typing import List
class Observer(ABC):
@abstractmethod
def update(self, subject):
"""Receive update from subject"""
pass
class Subject(ABC):
def __init__(self):
self._observers: List[Observer] = []
def attach(self, observer: Observer):
"""Attach an observer to the subject"""
if observer not in self._observers:
self._observers.append(observer)
def detach(self, observer: Observer):
"""Detach an observer from the subject"""
try:
self._observers.remove(observer)
except ValueError:
pass
def notify(self):
"""Notify all observers about an event"""
for observer in self._observers:
observer.update(self)
class StockPrice(Subject):
def __init__(self, symbol: str, price: float):
super().__init__()
self._symbol = symbol
self._price = price
@property
def symbol(self):
return self._symbol
@property
def price(self):
return self._price
@price.setter
def price(self, new_price: float):
if new_price != self._price:
self._price = new_price
self.notify()
class StockDisplay(Observer):
def __init__(self, name: str):
self._name = name
def update(self, subject: StockPrice):
print(f"{self._name}: {subject.symbol} is now ${subject.price:.2f}")
class StockAlert(Observer):
def __init__(self, threshold: float):
self._threshold = threshold
def update(self, subject: StockPrice):
if subject.price > self._threshold:
print(f"ALERT: {subject.symbol} exceeded ${self._threshold}! Current: ${subject.price:.2f}")
# Usage
apple_stock = StockPrice("AAPL", 150.00)
display1 = StockDisplay("Display 1")
display2 = StockDisplay("Display 2")
alert = StockAlert(160.00)
apple_stock.attach(display1)
apple_stock.attach(display2)
apple_stock.attach(alert)
apple_stock.price = 155.50
apple_stock.price = 162.00
Real- WorldAplikacje
Te Observer Pattern is extensively used in modern communare development:
- Xi1; Xi1; FLT: 0 Xi3; Xi3; GUI Event Handling Xi1; Xi1; FLT: 1 Xi3; Xi3;: Button clicks, mouse movements, ande keyboard events
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Model- View- Controller (MVC) Xi1; Xi1; FLT: 1 Xi3; Xi3;: Views observe models for data changes
- Real- time Data Feeds Real1; Real- time Data Feeds Real1; FLT 1 Real1; FLT 3; FLT Prices, weatherr updates, social media notifications
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Logging Systems Xi1; Xi1; FLT: 1 Xi3; Xi3;: Multiple log handlers observing application events
- Xiv1; Xiv1; FLT: 0 Xiv3; Xiv3; Publish- Subscribe Systems Xiv1; Xiv1; FLT: 1 Xiv3; Xiv3;: Message queues andd event buses
Decorator Pattern: Extending Functionality Dynamically
Thee Decorator Pattern pozwala adding behavor to an object - logging, caching, uwierzytelniation, retrying - bez modyfikacji tych klasówek obiektowych oraz bez podklasyfikacji. This Pattern provides a flexible ble entertivive to subclassing for extending functiality.
Python Decorators vs Decorator Pattern
Python 's @ decorator syntax and the Decorator design pattern are conceptually thee same - both wrap behavor around an existing callable without out modifying it, with Python' s syntax making thee Pattern a language-nativie factuure. This makes Python specilarly well-approvement.fr implementation g decorator functionyality.
Here 's an example using Python' s decorator syntax:
import time
import functools
def timing_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time:.4f} seconds")
return result
return wrapper
def cache_decorator(func):
cache = {}
@functools.wraps(func)
def wrapper(*args):
if args in cache:
print(f"Returning cached result for {args}")
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
def retry_decorator(max_attempts=3):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
print(f"Attempt {attempt + 1} failed: {e}. Retrying...")
return None
return wrapper
return decorator
@timing_decorator
@cache_decorator
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
@retry_decorator(max_attempts=3)
def unreliable_api_call():
import random
if random.random() < 0.7:
raise ConnectionError("API temporarily unavailable")
return "Success!"
# Usage
print(fibonacci(10))
print(fibonacci(10)) # This will use cached result
Class- Based Decorator Pattern
Te tradycjonal Decorator model can also be implemented using classes, which is useful for more complex concluos:
from abc import ABC, abstractmethod
class Coffee(ABC):
@abstractmethod
def cost(self):
pass
@abstractmethod
def description(self):
pass
class SimpleCoffee(Coffee):
def cost(self):
return 2.00
def description(self):
return "Simple coffee"
class CoffeeDecorator(Coffee):
def __init__(self, coffee: Coffee):
self._coffee = coffee
def cost(self):
return self._coffee.cost()
def description(self):
return self._coffee.description()
class MilkDecorator(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 0.50
def description(self):
return self._coffee.description() + ", milk"
class SugarDecorator(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 0.25
def description(self):
return self._coffee.description() + ", sugar"
class WhippedCreamDecorator(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 0.75
def description(self):
return self._coffee.description() + ", whipped cream"
# Usage
coffee = SimpleCoffee()
print(f"{coffee.description()}: ${coffee.cost():.2f}")
coffee_with_milk = MilkDecorator(coffee)
print(f"{coffee_with_milk.description()}: ${coffee_with_milk.cost():.2f}")
fancy_coffee = WhippedCreamDecorator(SugarDecorator(MilkDecorator(SimpleCoffee())))
print(f"{fancy_coffee.description()}: ${fancy_coffee.cost():.2f}")
Plan strategiczny: Zamiana Algorithms
Te strategie Plan pozwala you tu zdefiniować a family of algorytmy, encapsulate each one, and make te interchangeable at runtime, deleging behavor to separate strategy classes or functions instead of writing large conditional blocks. Thi modeln is essential for writting explicble, maintainable cade that cade adaft to different requiments.
Te strategiczne wzory definiują rodzinne algorytmy of, puts each of them into a separate class, and makes their ir objects interchangeable. This enables selectin algorytmy at runtime based on configuration.
Wdrożenie Strategii Wzorca
Here 's a undercompute example demonstranting the Strategy Pattern for payment processing:
from abc import ABC, abstractmethod
from typing import Protocol
class PaymentStrategy(Protocol):
def pay(self, amount: float) -> str:
"""Process payment and return confirmation"""
...
class CreditCardPayment:
def __init__(self, card_number: str, cvv: str, expiry: str):
self.card_number = card_number
self.cvv = cvv
self.expiry = expiry
def pay(self, amount: float) -> str:
# Simulate payment processing
masked_card = f"****-****-****-{self.card_number[-4:]}"
return f"Paid ${amount:.2f} using Credit Card {masked_card}"
class PayPalPayment:
def __init__(self, email: str):
self.email = email
def pay(self, amount: float) -> str:
return f"Paid ${amount:.2f} using PayPal account {self.email}"
class CryptocurrencyPayment:
def __init__(self, wallet_address: str, currency: str = "BTC"):
self.wallet_address = wallet_address
self.currency = currency
def pay(self, amount: float) -> str:
return f"Paid ${amount:.2f} using {self.currency} to wallet {self.wallet_address[:10]}..."
class BankTransferPayment:
def __init__(self, account_number: str, routing_number: str):
self.account_number = account_number
self.routing_number = routing_number
def pay(self, amount: float) -> str:
masked_account = f"****{self.account_number[-4:]}"
return f"Paid ${amount:.2f} via bank transfer from account {masked_account}"
class ShoppingCart:
def __init__(self):
self.items = []
self.payment_strategy = None
def add_item(self, item: str, price: float):
self.items.append({'item': item, 'price': price})
def set_payment_strategy(self, strategy: PaymentStrategy):
self.payment_strategy = strategy
def calculate_total(self) -> float:
return sum(item['price'] for item in self.items)
def checkout(self) -> str:
if not self.payment_strategy:
raise ValueError("Payment strategy not set")
total = self.calculate_total()
return self.payment_strategy.pay(total)
# Usage
cart = ShoppingCart()
cart.add_item("Laptop", 999.99)
cart.add_item("Mouse", 29.99)
cart.add_item("Keyboard", 79.99)
# Pay with credit card
cart.set_payment_strategy(CreditCardPayment("1234567890123456", "123", "12/25"))
print(cart.checkout())
# Change strategy to PayPal
cart.set_payment_strategy(PayPalPayment("[email protected]"))
print(cart.checkout())
# Change strategy to Cryptocurrency
cart.set_payment_strategy(CryptocurrencyPayment("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"))
print(cart.checkout())
Pithonik Strategy with Functions
Pierwszy klas Pythona działa allow for a more concise implementation of thee Strategy Pattern:
def discount_none(total):
return total
def discount_percentage(percentage):
def apply_discount(total):
return total * (1 - percentage / 100)
return apply_discount
def discount_fixed_amount(amount):
def apply_discount(total):
return max(0, total - amount)
return apply_discount
def discount_bulk(threshold, discount_percentage):
def apply_discount(total):
if total >= threshold:
return total * (1 - discount_percentage / 100)
return total
return apply_discount
class Order:
def __init__(self, total: float):
self.total = total
self.discount_strategy = discount_none
def set_discount_strategy(self, strategy):
self.discount_strategy = strategy
def calculate_final_price(self):
return self.discount_strategy(self.total)
# Usage
order = Order(100.00)
print(f"No discount: ${order.calculate_final_price():.2f}")
order.set_discount_strategy(discount_percentage(10))
print(f"10% discount: ${order.calculate_final_price():.2f}")
order.set_discount_strategy(discount_fixed_amount(15))
print(f"$15 off: ${order.calculate_final_price():.2f}")
order.set_discount_strategy(discount_bulk(50, 20))
print(f"Bulk discount: ${order.calculate_final_price():.2f}")
Builder Pattern: Constructing Complex Objects
Thee Builder Pattern lets you construct complex objects step by step, allowing you toe produce different type andrepresents of an object using thee same construction code. Thies modeln is specilarly useful when an object requires numerus configuation options or when thee construction process involves multiple steps.
When to Usie Builder Pattern
Thee Builder Pattern shines in presenos where:
- Obiekty konstrukcyjne wymagają męskiego opcjonalnego parametru
- Te konstruction process must follow a specific sequence
- You need to create different represents of thee same object
- Parametry konstrukcyjne stworzyłyby kwotowanie; texoscoding constructor quenquentin; anty-wzorzec
- Object creation involves complex logic that should be separated frem the object itself
Wdrożenie projektu Builder Pattern in Python
Jest praktycznym implementacją for building database query objects:
from typing import List, Optional
from dataclasses import dataclass, field
@dataclass
class Query:
table: str
columns: List[str]
where_clauses: List[str]
joins: List[str]
order_by: Optional[str]
limit: Optional[int]
offset: Optional[int]
def to_sql(self) -> str:
# Select clause
cols = ", ".join(self.columns) if self.columns else "*"
sql = f"SELECT {cols} FROM {self.table}"
# Joins
if self.joins:
sql += " " + " ".join(self.joins)
# Where clause
if self.where_clauses:
sql += " WHERE " + " AND ".join(self.where_clauses)
# Order by
if self.order_by:
sql += f" ORDER BY {self.order_by}"
# Limit and offset
if self.limit:
sql += f" LIMIT {self.limit}"
if self.offset:
sql += f" OFFSET {self.offset}"
return sql
class QueryBuilder:
def __init__(self):
self._table: Optional[str] = None
self._columns: List[str] = []
self._where_clauses: List[str] = []
self._joins: List[str] = []
self._order_by: Optional[str] = None
self._limit: Optional[int] = None
self._offset: Optional[int] = None
def table(self, table_name: str) -> 'QueryBuilder':
self._table = table_name
return self
def select(self, *columns: str) -> 'QueryBuilder':
self._columns.extend(columns)
return self
def where(self, condition: str) -> 'QueryBuilder':
self._where_clauses.append(condition)
return self
def join(self, join_clause: str) -> 'QueryBuilder':
self._joins.append(join_clause)
return self
def order_by(self, column: str, direction: str = "ASC") -> 'QueryBuilder':
self._order_by = f"{column} {direction}"
return self
def limit(self, limit: int) -> 'QueryBuilder':
self._limit = limit
return self
def offset(self, offset: int) -> 'QueryBuilder':
self._offset = offset
return self
def build(self) -> Query:
if not self._table:
raise ValueError("Table name is required")
return Query(
table=self._table,
columns=self._columns,
where_clauses=self._where_clauses,
joins=self._joins,
order_by=self._order_by,
limit=self._limit,
offset=self._offset
)
def reset(self) -> 'QueryBuilder':
self.__init__()
return self
# Usage
builder = QueryBuilder()
# Build a simple query
query1 = (builder
.table("users")
.select("id", "name", "email")
.where("age > 18")
.where("status = 'active'")
.order_by("name", "ASC")
.limit(10)
.build())
print(query1.to_sql())
# Build a complex query with joins
builder.reset()
query2 = (builder
.table("orders")
.select("orders.id", "users.name", "products.title", "orders.total")
.join("INNER JOIN users ON orders.user_id = users.id")
.join("INNER JOIN products ON orders.product_id = products.id")
.where("orders.status = 'completed'")
.where("orders.total > 100")
.order_by("orders.created_at", "DESC")
.limit(20)
.offset(10)
.build())
print(query2.to_sql())
Korzyści z interfejsu Fluent
Te wzory Builder wdrażają fluent interface (metod chaining), w których provides serela providages:
- Xiv1; Xiv1; FLT: 0 Xiv3; Xiv3; Readability Xiv1; Xiv1; FLT: 1 Xiv3; Xiv3;: Code reads like natural language
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Flexibility Xi1; Xi1; FLT: 1 Xi3; Xi3;: Easy to add or remove configuation steps
- Suma: 1; Suma: 1; Suma: 1; Suma: 0; Suma: 3; Suma: 3; Suma: 1; Suma: 1; Suma: 1; Suma: 0; Suma: 3; Suma: 3; Suma: 0; Suma: 3; Suma: 3; Suma: 1; Suma: 1; Suma: Suma: 1; Suma: Suma: Suma: 1; Suma: Suma: Suma: Suma: Suma: Suma: 3; Suma: Suma: Suma: Suma:
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Validation Xi1; Xi1; FLT: 1 Xi3; Xi3;: Construction logic can validate the object before creation
- Reusability Reimability Reimable Reimability Reimages 1; FLT Reimable 3; FLT 3; FLT 3; FLT Reimade;: Builders can be reused to create multiple similar objects
Adapter Pattern: Making Incompatible Interface Work Together
Te Adapter Pattern pozwala obiektom with incompatible interface too collaborate. This structural Pattern acts a bridge between two incompatible intefaces, eabling classes to work together thatt could not t other wise due te incompatible interfaces.
Adapter Method is a structural design model that allows you tu two incompatible interfaces work together b y creating a bridge between them. This is especilarly valuable when n integrating three-party libraries, legacy code, or external API into your application.
Real- World Adapter Wdrażanie mentationa
Consider a consideo where you need to integrate multiple payment gateways with different interfaces:
from abc import ABC, abstractmethod
# Target interface that our application expects
class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, amount: float, currency: str) -> dict:
pass
# Adaptee 1: Stripe API (incompatible interface)
class StripeAPI:
def create_charge(self, amount_cents: int, currency_code: str, source: str):
return {
'id': 'ch_stripe_123',
'amount': amount_cents,
'currency': currency_code,
'status': 'succeeded'
}
# Adaptee 2: PayPal API (different incompatible interface)
class PayPalAPI:
def make_payment(self, total: float, currency_type: str, account: str):
return {
'transaction_id': 'pp_456',
'total_amount': total,
'currency': currency_type,
'state': 'approved'
}
# Adaptee 3: Square API (yet another incompatible interface)
class SquareAPI:
def charge_card(self, money_amount: dict, card_token: str):
return {
'payment_id': 'sq_789',
'amount_money': money_amount,
'status': 'COMPLETED'
}
# Adapter for Stripe
class StripeAdapter(PaymentProcessor):
def __init__(self, stripe_api: StripeAPI):
self.stripe = stripe_api
def process_payment(self, amount: float, currency: str) -> dict:
# Convert dollars to cents for Stripe
amount_cents = int(amount * 100)
# Call Stripe API with adapted parameters
result = self.stripe.create_charge(
amount_cents=amount_cents,
currency_code=currency.upper(),
source='tok_visa'
)
# Adapt the response to our standard format
return {
'success': result['status'] == 'succeeded',
'transaction_id': result['id'],
'amount': amount,
'currency': currency,
'provider': 'Stripe'
}
# Adapter for PayPal
class PayPalAdapter(PaymentProcessor):
def __init__(self, paypal_api: PayPalAPI):
self.paypal = paypal_api
def process_payment(self, amount: float, currency: str) -> dict:
result = self.paypal.make_payment(
total=amount,
currency_type=currency.upper(),
account='[email protected]'
)
return {
'success': result['state'] == 'approved',
'transaction_id': result['transaction_id'],
'amount': amount,
'currency': currency,
'provider': 'PayPal'
}
# Adapter for Square
class SquareAdapter(PaymentProcessor):
def __init__(self, square_api: SquareAPI):
self.square = square_api
def process_payment(self, amount: float, currency: str) -> dict:
money_amount = {
'amount': int(amount * 100),
'currency': currency.upper()
}
result = self.square.charge_card(
money_amount=money_amount,
card_token='cnon:card-token'
)
return {
'success': result['status'] == 'COMPLETED',
'transaction_id': result['payment_id'],
'amount': amount,
'currency': currency,
'provider': 'Square'
}
# Client code that works with the unified interface
class PaymentService:
def __init__(self, processor: PaymentProcessor):
self.processor = processor
def charge_customer(self, amount: float, currency: str = 'USD'):
result = self.processor.process_payment(amount, currency)
if result['success']:
print(f"✓ Payment successful via {result['provider']}")
print(f" Transaction ID: {result['transaction_id']}")
print(f" Amount: {result['amount']} {result['currency']}")
else:
print(f"✗ Payment failed")
return result
# Usage - all payment gateways work through the same interface
stripe_processor = StripeAdapter(StripeAPI())
paypal_processor = PayPalAdapter(PayPalAPI())
square_processor = SquareAdapter(SquareAPI())
# Process payments using different providers
service = PaymentService(stripe_processor)
service.charge_customer(99.99)
service = PaymentService(paypal_processor)
service.charge_customer(149.99)
service = PaymentService(square_processor)
service.charge_customer(199.99)
Template Method Pattern: Definiing Algorithm Skelemotes
Te Template Method definiuje te szkielety, które mają być algorytmem in thee superclass but lets subclasses override specific steps of thee algorytm with out changing it structure. This behavoral pattern is excellent for enforming a consistent process while allowing customization at specific points.
Template Method Implementation
from abc import ABC, abstractmethod
import time
class DataProcessor(ABC):
"""Template class defining the data processing algorithm"""
def process(self, data):
"""Template method defining the algorithm structure"""
print("Starting data processing pipeline...")
# Step 1: Validate
if not self.validate(data):
raise ValueError("Data validation failed")
# Step 2: Extract
extracted = self.extract(data)
# Step 3: Transform
transformed = self.transform(extracted)
# Step 4: Load
result = self.load(transformed)
# Step 5: Cleanup (optional hook)
self.cleanup()
print("Data processing completed successfully")
return result
@abstractmethod
def validate(self, data) -> bool:
"""Validate input data - must be implemented by subclasses"""
pass
@abstractmethod
def extract(self, data):
"""Extract relevant data - must be implemented by subclasses"""
pass
@abstractmethod
def transform(self, data):
"""Transform data - must be implemented by subclasses"""
pass
@abstractmethod
def load(self, data):
"""Load processed data - must be implemented by subclasses"""
pass
def cleanup(self):
"""Optional hook method - can be overridden by subclasses"""
print("Performing default cleanup...")
class CSVDataProcessor(DataProcessor):
def validate(self, data) -> bool:
print("Validating CSV data...")
return isinstance(data, str) and len(data) > 0
def extract(self, data):
print("Extracting CSV data...")
lines = data.strip().split('n')
headers = lines[0].split(',')
rows = [line.split(',') for line in lines[1:]]
return {'headers': headers, 'rows': rows}
def transform(self, data):
print("Transforming CSV data...")
headers = data['headers']
rows = data['rows']
return [dict(zip(headers, row)) for row in rows]
def load(self, data):
print(f"Loading {len(data)} CSV records...")
return data
def cleanup(self):
print("Cleaning up CSV processing resources...")
class JSONDataProcessor(DataProcessor):
def validate(self, data) -> bool:
print("Validating JSON data...")
import json
try:
json.loads(data)
return True
except:
return False
def extract(self, data):
print("Extracting JSON data...")
import json
return json.loads(data)
def transform(self, data):
print("Transforming JSON data...")
if isinstance(data, list):
return [self._flatten_dict(item) for item in data]
return [self._flatten_dict(data)]
def _flatten_dict(self, d, parent_key='', sep='_'):
items = []
for k, v in d.items():
new_key = f"{parent_key}{sep}{k}" if parent_key else k
if isinstance(v, dict):
items.extend(self._flatten_dict(v, new_key, sep=sep).items())
else:
items.append((new_key, v))
return dict(items)
def load(self, data):
print(f"Loading {len(data)} JSON records...")
return data
class XMLDataProcessor(DataProcessor):
def validate(self, data) -> bool:
print("Validating XML data...")
return data.strip().startswith('')
def extract(self, data):
print("Extracting XML data...")
# Simplified XML parsing
return {'xml_content': data}
def transform(self, data):
print("Transforming XML data...")
return {'processed_xml': data['xml_content']}
def load(self, data):
print("Loading XML data...")
return data
def cleanup(self):
print("Cleaning up XML processing resources...")
print("Closing XML parser...")
# Usage
csv_data = """name,age,city
John,30,New York
Jane,25,Los Angeles
Bob,35,Chicago"""
json_data = '''[
{"name": "John", "age": 30, "address": {"city": "New York"}},
{"name": "Jane", "age": 25, "address": {"city": "Los Angeles"}}
]'''
xml_data = "John30"
# Process CSV
csv_processor = CSVDataProcessor()
csv_result = csv_processor.process(csv_data)
print(f"CSV Result: {csv_result}n")
# Process JSON
json_processor = JSONDataProcessor()
json_result = json_processor.process(json_data)
print(f"JSON Result: {json_result}n")
# Process XML
xml_processor = XMLDataProcessor()
xml_result = xml_processor.process(xml_data)
print(f"XML Result: {xml_result}")
When to Usie Design Patterns
Knowing when two use design paragons is cucial for effective districade design, specilarly whether you meether recurring design problems that have well-established solutions, as designn paragons provide tested and proven approvaches to establishn establishare destablin provenges.
Adresate Use Cases
Usie design wzorzec to promote code reusability, flexibility, and maintainability, as they help in structuring code in a way that makes it easyr t modify andd extend as requirements evolvne. Consider implementing Patterns when:
- You face a problem that matches a known Pattern 's intent
- Thee Pattern provides clear benefits over a simpler solution
- Ty, drużyna, rozumiesz ten wzór i masz maintain it
- Te wszystkie skomplikowane i usprawiedliwione elastyczne metody są bardziej elastyczne.
- Chcesz, żeby ktoś się do ciebie zbliżył?
When NOT to Usie Patterns
To jest to, co jest proste, to jest poprawne rozwiązanie tego problemu - czasami jest to dobrze miejsce design parafine, ale often it 's just a functionon and a dict. Avoid Patterns when:
- A simpler solution would work juszt as well
- You 're applicying Patterns for the sake of using Patterns
- Te wzory dodają niepotrzebne kompleksy to prostefuroward code
- Ty jesteś przyjacielem, więc to jest wzór i dokument.
- Ten problem nie jest prawdziwy.
Each Pattern has its own trade-offs, and you need to a Pattern mone attention te why you 're choosing a certain pattern than tu how toimplement it. The decision to use a Pattern should be contron by thee problem at hand, nott by a desire to demonte knowdge of Patterns.
Anty- Patterns to Avoid in Python
Nie all Patterns make sense in Python 's ecosystem. Python modules are already singletons - every module is imported only once once, so explacit singleton classes add unnecessary completity, with better confictetives being module-level variables or dependency injection.
Wzór That Don 't Fit Python Well
- BEN1; BEN1; FLT: 0 = 3; BEN3; God Object = 1; BEN1; FLT: 1 = 3; BEN3; FLT:: Centralizies too much logic in a single class, makes code harder to tect andd maintain, with the better incorporativa being to split functionality into smaller, cohesiva classes
- Reference: 1; Deep Invesignace Hieragies Reference 1; Deep Invesignace (FLT: 1 Designation 3; Designace 3; Deep invesignace trees make code brittle, so prefer composition and delegtion
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Unnecessary Abstractions Xi1; Xi1; FLT: 1 Xi3; Xi3;: Python 's duck typing often eliminates the need for complex interface hierieraries
Modern Python Pattern Rozważania
In modern Python (3.8 +), prefer Protocol for structural subtyping as it doesn 't require explire incompaniance, witch classes satisfying a Protocol juset by having thee right methods, while using ABC when you want to enforcement incompaniene andd provide default implementations.
Using Protocols for Duck Typing
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> str:
...
class Circle:
def draw(self) -> str:
return "Drawing a circle"
class Square:
def draw(self) -> str:
return "Drawing a square"
def render(shape: Drawable) -> None:
print(shape.draw())
# Both Circle and Square satisfy the Drawable protocol
# without explicit inheritance
render(Circle())
render(Square())
Projektowanie Wzory in Production Python Wnioski
In 2026, Python sits at t te intersection of AI and machine learning, scalable web product development, and enterprise data incorporationg, with its dominance reflecting a structural insociage that insomering teams dicovered years ago: Python lets you move faster, integrate more easily, and build systems that are maintainable by teams, nott just by thee original author.
Framework- Specific Patterns
Django restauses thee most complete Python framework for teams building products with multi- user accessis, complex data models, adimen interfaces, and authentiation systems. Django extensively uses patterns like:
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Model- View- Template (MVT) Xi1; Xi1; FLT: 1 Xi3; Xi3;: Django 's variation of MVC
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Active Record Xi1; Xi1; FLT: 1 Xi3; Xi3;: Django 's ORM models
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Template Method Xi1; Xi1; FLT: 1 Xi3; Xi3;: Widok z podstawówki klasycznej
- Xiv1; Xiv1; FLT: 0 Xiv3; Xiv3; Middleware Chain Xiv1; Xiv1; FLT: 1 Xiv3; Xiv3;: Requect / response processing
FastaPI leverages modern Python features andd patterns:
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Dependency Injection Xi1; Xi1; FLT: 1 Xi3; Xi3;: Built- in DI system
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Decorator Pattern Xi1; Xi1; FLT: 1 Xi3; Xi3;: Dekoratory Route
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Strategy Pattern Xi1; Xi1; FLT: 1 Xi3; Xi3;: Pluggable uwierzytelniation
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Factory Pattern Xi1; Xi1; FLT: 1 Xi3; Xi3;: Response model creation
Wzory architektoniczne
In 2026, the consensus among experimenced Python incorporaing teams is clearer: start with a well-structured monolith, decopose into services when specific boundaries emerge from real usage, as a monolith is nott a failure mode.
Instagram ramn on a Django monolith well pact 100 million users before demoposing specific high-load functions into services, with th te key being building thee monolith with services demoposition in mind mrem the startt through gh clear module boundaries, minimal cross- module coupling, and event- courn communication Patterns.
Testing Design Patterns
Design Patterns should make code more testable, note less. Here are strategies for testing Pattern implementations:
Testing Strategy Pattern
import unittest
from unittest.mock import Mock
class TestPaymentStrategies(unittest.TestCase):
def test_credit_card_payment(self):
strategy = CreditCardPayment("1234567890123456", "123", "12/25")
result = strategy.pay(100.00)
self.assertIn("Credit Card", result)
self.assertIn("100.00", result)
def test_paypal_payment(self):
strategy = PayPalPayment("[email protected]")
result = strategy.pay(50.00)
self.assertIn("PayPal", result)
self.assertIn("[email protected]", result)
def test_shopping_cart_with_different_strategies(self):
cart = ShoppingCart()
cart.add_item("Item 1", 50.00)
# Test with credit card
cart.set_payment_strategy(CreditCardPayment("1234", "123", "12/25"))
result1 = cart.checkout()
# Test with PayPal
cart.set_payment_strategy(PayPalPayment("[email protected]"))
result2 = cart.checkout()
self.assertIsNotNone(result1)
self.assertIsNotNone(result2)
Testing Observer Pattern
class TestObserverPattern(unittest.TestCase):
def test_observer_notification(self):
stock = StockPrice("AAPL", 150.00)
observer = Mock(spec=Observer)
stock.attach(observer)
stock.price = 155.00
observer.update.assert_called_once_with(stock)
def test_multiple_observers(self):
stock = StockPrice("GOOGL", 2800.00)
observer1 = Mock(spec=Observer)
observer2 = Mock(spec=Observer)
stock.attach(observer1)
stock.attach(observer2)
stock.price = 2850.00
observer1.update.assert_called_once()
observer2.update.assert_called_once()
def test_detach_observer(self):
stock = StockPrice("MSFT", 300.00)
observer = Mock(spec=Observer)
stock.attach(observer)
stock.detach(observer)
stock.price = 310.00
observer.update.assert_not_called()
Rozważanie wydajności
Choć design wzory improwizować Code organization i zachować taktowność, they can ne inpute performance overhead if not t implemented carhely. Consider these optimization strategies:
Inicjalizacja lenistwa
Te instalacje i są kreowane tylko wtedy, gdy te get _ instance () methods i s called for thee first time, ensuring that resources are allocated only when need. This is specilarly important for resource- intensive objects:
class DatabaseConnection:
_instance = None
_initialized = False
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not DatabaseConnection._initialized:
self._connect()
DatabaseConnection._initialized = True
def _connect(self):
# Expensive connection operation
print("Establishing database connection...")
self.connection = "Connected"
Caching Decorator Results
from functools import lru_cache
@lru_cache(maxsize=128)
def expensive_computation(n):
"""Cached using built-in LRU cache"""
print(f"Computing for {n}...")
return sum(range(n))
# First call computes
result1 = expensive_computation(1000)
# Second call returns cached result
result2 = expensive_computation(1000)
Bett Practices for Design Patterns in Python
Design model powinien uprościć, nie komplikować, as Python design model are nott about copying texbook diagrams - they y are about solving real problems elegantly. Follow these guidelines for effective Pattern implementation:
1. Komposition Over Dziedzictwo
Python 's dynamic nature makes composition pyllarly powerful. Instad of deep insurance hieraries, compose objects from smaller, focused contents:
# Instead of inheritance
class EmailNotifier:
def send(self, message):
print(f"Sending email: {message}")
class SMSNotifier:
def send(self, message):
print(f"Sending SMS: {message}")
# Use composition
class NotificationService:
def __init__(self):
self.notifiers = []
def add_notifier(self, notifier):
self.notifiers.append(notifier)
def notify(self, message):
for notifier in self.notifiers:
notifier.send(message)
# Usage
service = NotificationService()
service.add_notifier(EmailNotifier())
service.add_notifier(SMSNotifier())
service.notify("Important update!")
2. Use Type Hints for Clarity
Type hints make Pattern implementations more explacit and enable better IDE support:
from typing import Protocol, List
from abc import abstractmethod
class PaymentMethod(Protocol):
def process(self, amount: float) -> bool:
...
class PaymentProcessor:
def __init__(self, methods: List[PaymentMethod]):
self.methods = methods
def charge(self, amount: float) -> bool:
for method in self.methods:
if method.process(amount):
return True
return False
3. Wzór dokumentu Usage
Zawsze dokumentuje, co jest w twoim stylu i dlaczego:
class DataExporter:
"""
Uses the Strategy pattern to support multiple export formats.
This allows adding new export formats without modifying existing code,
following the Open/Closed Principle.
Example:
exporter = DataExporter(JSONExportStrategy())
exporter.export(data)
"""
def __init__(self, strategy: ExportStrategy):
self._strategy = strategy
def export(self, data):
return self._strategy.export(data)
4. Keep It Simple
Nie ma zbyt wielu powodów.
# Simple is often better
def calculate_discount(price, customer_type):
discounts = {
'regular': 0,
'premium': 0.10,
'vip': 0.20
}
return price * (1 - discounts.get(customer_type, 0))
# Only introduce Strategy pattern when you need:
# - Runtime strategy switching
# - Complex discount logic
# - Multiple discount calculation methods
Resources for Learning Design Patterns
Tu deepen you understang of design paraptes in Python, explore these valuable resources:
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Refactoring.Gru Xi1; Xi1; FLT: 1 Xi3; Xi3;: ComXive design design paragon catalog with Python examples at Xi1; Xi1; FLT: 2 XI3; Xi3; https: / / refactoring.guru / design- Patterns / python Xion1; XI1; FLT: 3 XIN3; XIN3;
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Python Patterns Guide Xi1; Xi1; FLT: 1 Xi3; Xi3;: Brandon Rhodes Xifs; Evolving guide at Xi1; Xif1; FLT: 2 XI3; Xif3; Xif3; https: / / python- Patterns.guide / Xif1; Xif1; FLT: 3 XIf3; XIf3; XIF;
- Xiv1; Xiv1; FLT: 0 Xiv3; Xiv3; GitHub Python Patterns Xi1; Xiv1; FLT: 1 Xiv3; Xiv3; FLT: Viv3; FLT: 2 XI3; Xiv3; https: / / github.com / faif / python- Patterns Xiv1; XiV1; FLT: 3 XI3; Xiv3; XIv3;
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Real Python Xi1; Xi1; FLT: 1 Xi3; Xi3;: Practical tutorials on Python design Patterns at Xi1; Xi1; FLT: 2 XI3; Xi3; https: / / realpython.com Xion1; Xi1; FLT: 3 Xion3; Xion3;
- Xi1; Xi1; FLT: 0 XI3; Xi3; GeeksforGeeks Python Design Patterns Xi1; Xi1; FLT: 1 XI3; XI3;: Compagsive tutorials at Xi1; XI1; FLT: 2 XI3; XI3; https: / / www.geeksforgeeks.org / python- designs / XI1; XI1; FLT: 3 XI3; XI3; XI3;
Konkluzja
Projektowanie wzorów are powerful tools in a Python engineer 's toolkit, ale ich powinien być właściwy model sądowy. Strategie, Faktory, i Observer wzory z tego appear do geteer in well-architected Python systemy, With Strategy focusing og selecting and d swapping algorytmy harthms, Faktory difficingin g oun how obiects are created, and Observer addisponsing communication, witch conforming these differentions helping you exapesse the ript facin based on whether ir your eyar aboube about iour behavoun, creatin, our communicion, our communicinging.
Te Key to successfuly using design model in Python is understanding both thee Patterns themselves and Python 's unique criterics. Python' s dynamic typing, first-class functions, and powerful standard library often allow for simpler implementations than in statically-type languages. Always prioritize code clarity and maintainability over paragon purity.
Remember that Patterns are means to ann end in themselves. Thee goal is to write code that is easy to understand, tect, and modify. When a pattern helps achieve that goal, use it. When a simpler solution suffices, embrace too simplicity. As you gain experimence, you 'll develop an intuition for when wzorce add value and wheren they add unnecesary complyty.
By mastering design wzocts andd understang when to applicy them, you 'll be better equipped to build robust, scalable Python applications that stand the tect of time and d evolving requirements.