Conversation
This commit adds comprehensive implementations and documentation for five design patterns: Creational Patterns: - Prototype: Game character cloning system with deep/shallow copy examples Structural Patterns: - Facade: Home theater system with simplified interface - Proxy: Virtual, protection, and cache proxy implementations Behavioral Patterns: - State: Media player with state transitions (Stopped, Playing, Paused) - Template Method: Data processing pipeline for CSV/JSON/XML Each pattern includes: - theory.md: Comprehensive pattern documentation with benefits, use cases, Python-specific considerations, and best practices - example.py: Production-quality code with SOLID principles, type hints, comprehensive documentation, and multiple demonstrations Updated tasks.md to reflect completion of these patterns.
Summary of ChangesHello @bhrionn, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly expands the project's design pattern examples by introducing five new, thoroughly documented implementations: Prototype, Facade, Proxy, State, and Template Method. The goal is to provide clear, practical, and well-structured code examples that illustrate the application of these patterns, adhering to SOLID principles and Python best practices. This enhancement serves as a valuable resource for understanding and applying these fundamental software design concepts. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This is a great contribution that adds five well-documented design pattern examples. The code is clear, follows good practices, and the accompanying theory files are comprehensive. The demonstrations for each pattern are particularly helpful for understanding their practical application and benefits. I've provided a few suggestions to further improve the code, mainly around strengthening adherence to design principles like Dependency Inversion and improving robustness.
| """Enumeration of power states for devices.""" | ||
| ON = "on" | ||
| OFF = "off" | ||
| STANDBY = "standby" |
| def __init__( | ||
| self, | ||
| dvd_player: DVDPlayer, | ||
| projector: Projector, | ||
| screen: Screen, | ||
| amplifier: Amplifier, | ||
| lights: Lights, | ||
| streaming_device: StreamingDevice | ||
| ): |
There was a problem hiding this comment.
The docstrings for the file (line 21) and the HomeTheaterFacade class claim to demonstrate the Dependency Inversion Principle. However, the __init__ method depends on concrete subsystem classes (DVDPlayer, Projector, etc.) rather than abstractions. To better adhere to DIP, you could define abstract base classes or protocols for the subsystem components and have the facade depend on those. This would make the facade more flexible and testable, as you could pass in mock or alternative implementations.
| projector.set_input(dvd_player) | ||
| amplifier.on() | ||
| amplifier.set_dvd(dvd_player) |
There was a problem hiding this comment.
The code example in the 'Problem' section is slightly inconsistent with the example.py implementation. Here, projector.set_input() and amplifier.set_dvd() are shown taking the dvd_player object as an argument. In example.py, set_input methods take a string (e.g., 'DVD'), and the Amplifier class does not have a set_dvd method. Aligning this example with the implementation would improve clarity.
| @abstractmethod | ||
| def clone(self) -> 'GameCharacter': | ||
| """ | ||
| Clone this character (deep copy). | ||
|
|
||
| Returns: | ||
| A new instance that is a deep copy of this character | ||
| """ | ||
| pass |
There was a problem hiding this comment.
The clone method is marked as abstract here, forcing each subclass (Warrior, Mage, Archer) to implement it. However, all implementations are identical (return copy.deepcopy(self)). You could make this a concrete method in the GameCharacter base class to avoid code duplication by removing the @abstractmethod decorator and providing the implementation here. Subclasses can still override it if they need custom cloning logic, and type checkers will handle the covariant return types correctly.
| @abstractmethod | |
| def clone(self) -> 'GameCharacter': | |
| """ | |
| Clone this character (deep copy). | |
| Returns: | |
| A new instance that is a deep copy of this character | |
| """ | |
| pass | |
| def clone(self) -> 'GameCharacter': | |
| """ | |
| Clone this character (deep copy). | |
| This is a concrete implementation that can be inherited by subclasses. | |
| Returns: | |
| A new instance that is a deep copy of this character | |
| """ | |
| return copy.deepcopy(self) |
| print(f"Average time per creation: {from_scratch_time/10:.4f} seconds") | ||
| print() | ||
|
|
||
| speedup = from_scratch_time / cloning_time |
There was a problem hiding this comment.
There's a potential for a ZeroDivisionError here if cloning_time is zero. While unlikely in this example due to time.sleep(), it's good practice to guard against this, especially in performance-critical code where the duration could be negligible.
| speedup = from_scratch_time / cloning_time | |
| speedup = from_scratch_time / cloning_time if cloning_time > 0 else float('inf') |
| True if access granted, False otherwise | ||
| """ | ||
| # Write requires at least one level higher than document classification | ||
| required_level = min(self._real_document.classification.value + 1, 4) |
There was a problem hiding this comment.
The number 4 is used here as a magic number. It corresponds to the value of AccessLevel.TOP_SECRET. Using the enum member AccessLevel.TOP_SECRET.value directly would make the code more readable and maintainable, as it avoids relying on the hardcoded integer value of the enum.
| required_level = min(self._real_document.classification.value + 1, 4) | |
| required_level = min(self._real_document.classification.value + 1, AccessLevel.TOP_SECRET.value) |
| - Performance optimization | ||
| """ | ||
|
|
||
| def __init__(self, real_database: RealDatabase): |
There was a problem hiding this comment.
To better adhere to the Dependency Inversion Principle, the __init__ method of CachingDatabaseProxy should depend on the DatabaseQuery protocol abstraction rather than the concrete RealDatabase class. This would make the proxy more flexible and allow it to work with any object that conforms to the DatabaseQuery protocol.
| def __init__(self, real_database: RealDatabase): | |
| def __init__(self, real_database: DatabaseQuery): |
| proxy_time = time.time() - start | ||
| print(f"Total time: {proxy_time:.3f}s\n") | ||
|
|
||
| print(f"Speedup: {load_time/proxy_time:.2f}x faster with proxy\n") |
There was a problem hiding this comment.
|
|
||
| self._playlist = playlist | ||
| self._current_track_index = 0 | ||
| self._state = StoppedState() |
There was a problem hiding this comment.
The initial state is set directly here, but its on_enter method is not called. This is inconsistent with how subsequent state transitions are handled by set_state, which does call on_enter. This leads to the initial 'Entering StoppedState' message not being printed. To ensure consistent behavior, you should call self._state.on_enter(self) right after this line.
| # Simplified parsing for demonstration | ||
| # In real implementation, would use xml.etree or lxml | ||
| records = [] | ||
| import re |
There was a problem hiding this comment.
Pull Request Overview
This PR adds comprehensive implementations and documentation for five design patterns: Prototype (creational), Facade and Proxy (structural), and State and Template Method (behavioral). Each pattern includes detailed theory documentation covering intent, benefits, use cases, Python-specific considerations, and best practices, along with production-quality example code demonstrating SOLID principles with type hints and comprehensive demonstrations.
Key Changes
- Added complete implementation and documentation for five design patterns with real-world examples
- Each pattern includes theory.md with comprehensive pattern documentation and example.py with multiple demonstrations
- Updated tasks.md to mark the five patterns as completed
Reviewed Changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| tasks.md | Updated task checklist to mark five patterns as completed (Prototype, State, Template Method, Facade, Proxy) |
| examples/template_method_pattern/theory.md | Comprehensive documentation for Template Method pattern covering algorithm structure, hook methods, and Python-specific implementation |
| examples/template_method_pattern/example.py | Data processing pipeline implementation demonstrating CSV/JSON/XML processors with template method structure |
| examples/state_pattern/theory.md | Complete State pattern documentation explaining state transitions, context delegation, and state-specific behavior |
| examples/state_pattern/example.py | Media player implementation with Stopped, Playing, and Paused states demonstrating state transitions |
| examples/proxy_pattern/theory.md | Proxy pattern documentation covering virtual, protection, and cache proxy types with use cases |
| examples/proxy_pattern/example.py | Three proxy implementations: virtual (lazy loading), protection (access control), and cache (result caching) |
| examples/prototype_pattern/theory.md | Prototype pattern documentation explaining cloning, shallow vs deep copy, and prototype registry |
| examples/prototype_pattern/example.py | Game character cloning system demonstrating deep/shallow copy with performance comparisons |
| examples/facade_pattern/theory.md | Facade pattern documentation explaining simplified interfaces and subsystem coordination |
| examples/facade_pattern/example.py | Home theater system facade coordinating multiple components (DVD, projector, amplifier, lights) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - [ ] Abstract Factory - Provides an interface for creating families of related objects without specifying their concrete classes. | ||
| - [x] Builder - Separates complex object construction from its representation, allowing step-by-step construction. | ||
| - [ ] Prototype - Creates new objects by copying existing instances (cloning). | ||
| - [x] Prototype - Creates new objects by copying existing instances (cloning). |
There was a problem hiding this comment.
The line numbering is inconsistent. Lines 8 and 17 have their numbers included, while line 19 doesn't have a line number. This creates an inconsistent formatting pattern in the tasks list.
| - [x] State - Allows an object to alter its behavior when its internal state changes. | ||
| - [x] Strategy - Defines a family of algorithms, encapsulates each one, and makes them interchangeable. | ||
| - [ ] Template Method - Defines the skeleton of an algorithm in a base class, letting subclasses override specific steps. | ||
| - [x] Template Method - Defines the skeleton of an algorithm in a base class, letting subclasses override specific steps. |
There was a problem hiding this comment.
The line numbering is inconsistent. Lines 8 and 17 have their numbers included, while line 19 doesn't have a line number. This creates an inconsistent formatting pattern in the tasks list.
| - [x] Facade - Provides a simplified interface to a complex subsystem. | ||
| - [ ] Flyweight - Shares common state between multiple objects to save memory. | ||
| - [ ] Proxy - Provides a surrogate or placeholder to control access to another object. No newline at end of file | ||
| - [x] Proxy - Provides a surrogate or placeholder to control access to another object. No newline at end of file |
There was a problem hiding this comment.
The line numbering is inconsistent. Lines 27 and 29 have their numbers included, but the pattern is not uniform throughout the file. This creates an inconsistent formatting pattern in the tasks list.
| # Exit current state (stop audio) | ||
| self.on_exit(player) | ||
| player.move_to_previous_track() | ||
| print(f"[Player] ⏮️ Previous track: {player.current_track}") | ||
| # Re-enter state (start new audio) | ||
| self.on_enter(player) |
There was a problem hiding this comment.
In the previous_track method override, on_exit and on_enter are called manually without properly updating the state through the player's set_state method. This bypasses the state transition mechanism and could lead to inconsistent state history tracking. The method should either use set_state properly or avoid calling entry/exit hooks manually.
| - [ ] Mediator - Defines an object that encapsulates how objects interact, promoting loose coupling. | ||
| - [ ] Memento - Captures and externalizes an object's internal state for later restoration without violating encapsulation. | ||
| - [ ] State - Allows an object to alter its behavior when its internal state changes. | ||
| - [x] State - Allows an object to alter its behavior when its internal state changes. |
There was a problem hiding this comment.
The line numbering is inconsistent. Lines 8 and 17 have their numbers included, while line 19 doesn't have a line number. This creates an inconsistent formatting pattern in the tasks list.
| # Without proxy - all images loaded immediately | ||
| print("Without proxy (loading 5 images immediately):") | ||
| start = time.time() | ||
| real_images = [RealImage(f"photo{i}.jpg") for i in range(5)] |
There was a problem hiding this comment.
Variable real_images is not used.
| real_images = [RealImage(f"photo{i}.jpg") for i in range(5)] | |
| [RealImage(f"photo{i}.jpg") for i in range(5)] |
| """ | ||
|
|
||
| from abc import ABC, abstractmethod | ||
| from typing import Optional, List |
There was a problem hiding this comment.
Import of 'Optional' is not used.
| from typing import Optional, List | |
| from typing import List |
| """ | ||
|
|
||
| from abc import ABC, abstractmethod | ||
| from typing import List, Dict, Any, Optional |
There was a problem hiding this comment.
Import of 'Optional' is not used.
| from typing import List, Dict, Any, Optional | |
| from typing import List, Dict, Any |
| - Dependency Inversion: Facade depends on abstractions (component protocols) | ||
| """ | ||
|
|
||
| from abc import ABC, abstractmethod |
There was a problem hiding this comment.
Import of 'ABC' is not used.
Import of 'abstractmethod' is not used.
| from abc import ABC, abstractmethod |
| """ | ||
|
|
||
| from abc import ABC, abstractmethod | ||
| from typing import Optional, List |
There was a problem hiding this comment.
Import of 'List' is not used.
| from typing import Optional, List | |
| from typing import Optional |
This commit adds comprehensive implementations and documentation for five design patterns:
Creational Patterns:
Structural Patterns:
Behavioral Patterns:
Each pattern includes:
Updated tasks.md to reflect completion of these patterns.