Jump into concepts and practice - no test required
or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
▶
Steps
setup
Initial Class with Multiple Responsibilities
We start with a single class 'Employee' that handles both employee data and report generation. This violates the Single Responsibility Principle because it has more than one reason to change.
💡 Identifying a class with multiple responsibilities is the first step to applying SRP.
Line:class Employee:
def __init__(self, name, id):
self.name = name
self.id = id
def generate_report(self):
# generates report
💡 A class should have only one reason to change; here, 'Employee' mixes data and reporting.
compare
Identify Responsibilities in Employee Class
We analyze the 'Employee' class and identify two distinct responsibilities: managing employee data and generating reports.
💡 Separating concerns requires recognizing distinct responsibilities within a class.
💡 Delegation does not violate SRP if the class focuses on one reason to change.
compare
Verify Single Responsibility for ReportGenerator
We confirm that 'ReportGenerator' is solely responsible for report generation, with one reason to change.
💡 Each class should have a clear, single responsibility to minimize coupling and improve maintainability.
Line:# ReportGenerator responsibility:
# - Generate reports for employees
💡 Isolating responsibilities reduces complexity and improves code clarity.
reconstruct
Summary: SRP Achieved by Refactoring
The design now has two classes, each with a single responsibility: 'Employee' manages data, 'ReportGenerator' handles reporting. This design follows the Single Responsibility Principle.
💡 Refactoring to SRP improves code maintainability and reduces the risk of bugs when requirements change.
Line:# Final design:
# Employee: data management
# ReportGenerator: report generation
💡 SRP leads to cleaner, more modular, and easier to maintain code.
reconstruct
Read Final Answer: SRP Enforced
The final design clearly separates responsibilities, ensuring each class has one reason to change, fulfilling the Single Responsibility Principle.
💡 Understanding the final design confirms the effectiveness of SRP refactoring.
Line:# SRP enforced: Employee and ReportGenerator have distinct responsibilities
💡 The final class diagram is the answer to applying SRP correctly.
class Employee:
# STEP 1: Initial class with multiple responsibilities
def __init__(self, name, id): # STEP 1
self.name = name # STEP 1
self.id = id # STEP 1
# STEP 5: Add ReportGenerator instance
self.report_generator = ReportGenerator() # STEP 5
# STEP 6: Delegate report generation
def generate_report(self): # STEP 6
return self.report_generator.generate(self) # STEP 6
class ReportGenerator:
# STEP 3: New class for report generation
def generate(self, employee): # STEP 3
# generate report for employee
return f"Report for {employee.name} (ID: {employee.id})" # STEP 3
📊
Single Responsibility Principle - One Class, One Reason to Change - Watch the Algorithm Execute, Step by Step
Watching the step-by-step refactoring helps you understand why SRP matters and how to identify and separate responsibilities in class design.
Step 1/10
·Active fill★Answer cell
Initial design violates SRP by combining data and reporting.
Employee
−name: string
−id: string
+__init__()
+generate_report()
Highlighting responsibilities within the class.
Employee
−name: string
−id: string
+__init__()
+generate_report()
Introduced ReportGenerator class to separate concerns.
Employee
−name: string
−id: string
+__init__()
ReportGenerator
+generate()
ReportGenerator → Employee (1:1)
Removed report generation from Employee to enforce SRP.
Employee
−name: string
−id: string
+__init__()
ReportGenerator
+generate()
ReportGenerator → Employee (1:1)
Added composition to delegate report generation.
Employee
−name: string
−id: string
−report_generator: ReportGenerator
+__init__()
ReportGenerator
+generate()
Employee ◆ ReportGenerator (1:1)
Added delegation method to Employee.
Employee
−name: string
−id: string
−report_generator: ReportGenerator
+__init__()
+generate_report()
ReportGenerator
+generate()
Employee ◆ ReportGenerator (1:1)
Confirmed SRP adherence after refactoring.
Employee
−name: string
−id: string
−report_generator: ReportGenerator
+__init__()
+generate_report()
ReportGenerator
+generate()
Employee ◆ ReportGenerator (1:1)
Confirmed SRP adherence for ReportGenerator.
Employee
−name: string
−id: string
−report_generator: ReportGenerator
+__init__()
+generate_report()
ReportGenerator
+generate()
Employee ◆ ReportGenerator (1:1)
Final design respects Single Responsibility Principle.
Employee
−name: string
−id: string
−report_generator: ReportGenerator
+__init__()
+generate_report()
ReportGenerator
+generate()
Employee ◆ ReportGenerator (1:1)
SRP applied successfully; design is modular and maintainable.
Employee
−name: string
−id: string
−report_generator: ReportGenerator
+__init__()
+generate_report()
ReportGenerator
+generate()
Employee ◆ ReportGenerator (1:1)
Key Takeaways
✓ A class should have only one reason to change, which means it should have a single responsibility.
This insight is hard to see from code alone because multiple responsibilities can be hidden inside methods or fields.
✓ Separating responsibilities into different classes improves maintainability and reduces coupling.
Visualizing the separation helps understand how SRP leads to modular design.
✓ Delegation allows a class to expose functionality without violating SRP by forwarding tasks to dedicated classes.
Seeing delegation in the diagram clarifies how responsibilities remain separated while preserving interface usability.
Practice
(1/5)
1. In the object-oriented design of a Snake and Ladder game, which component is primarily responsible for managing the state transitions of a player's position after a dice roll?
easy
A. The Dice class, since it generates the number that determines movement
B. The Player class, as it holds the current position and updates it directly
C. The Board class, because it contains the snakes and ladders and applies their effects
D. The GameController class, which orchestrates the game flow and updates player positions accordingly
Solution
Step 1: Understand the role of Dice
The Dice only generates a random number; it does not manage state transitions.
Step 2: Consider Player class responsibilities
Player holds position but should not decide how to update it considering snakes or ladders.
Step 3: Analyze Board class role
Board knows snakes and ladders but does not manage player state transitions directly.
Step 4: Role of GameController
GameController coordinates dice roll, queries Board for snakes/ladders, and updates Player position accordingly.
Final Answer:
Option D -> Option D
Quick Check:
GameController centralizes state transitions, ensuring separation of concerns.
Hint: GameController orchestrates state changes, not Dice or Player alone [OK]
Common Mistakes:
Thinking Dice manages player position
Assuming Player updates position without Board's input
Believing Board directly changes player state
2. When a class inherits from multiple classes that have a method with the same name, describe the step-by-step process the Method Resolution Order (MRO) uses to determine which method is called.
easy
A. MRO uses a linearization algorithm that merges the order of parents and their ancestors to find the method.
B. MRO searches the first parent class fully before moving to the next parent class.
C. MRO always calls the method from the last parent class listed in the inheritance.
D. MRO randomly picks the method from any parent class that defines it.
Solution
Step 1: Understand naive search
MRO does not simply search the first parent class fully before moving to the next; it uses a more sophisticated approach.
Step 2: Recognize MRO linearization
MRO uses a specific linearization (like C3 linearization) that merges parent classes and their ancestors in a consistent order.
Step 3: Eliminate incorrect options
MRO always calls the method from the last parent class listed in the inheritance is incorrect because the last parent is not always chosen; order and ancestors matter. MRO randomly picks the method from any parent class that defines it is incorrect because MRO is deterministic, not random.
Final Answer:
Option A -> Option A
Quick Check:
MRO merges inheritance hierarchies to find the correct method in a predictable order.
Hint: MRO = deterministic linearization of inheritance graph
Common Mistakes:
Assuming simple left-to-right search suffices
Believing last parent always overrides
Thinking method choice is random
3. Given the following Python code using deep copy, what will be printed after modifying the copy's scores list?
A. Original scores: [10, 20]
Copy scores: [10, 20]
After modifying copy scores:
Original scores: [10, 20, 30]
Copy scores: [10, 20, 30]
B. Original scores: [10, 20]
Copy scores: [10, 20]
After modifying copy scores:
Original scores: [10, 20]
Copy scores: [10, 20, 30]
C. Original scores: [10, 20]
Copy scores: [10, 20]
After modifying copy scores:
Original scores: [10, 20]
Copy scores: [10, 20]
D. Original scores: [10, 20]
Copy scores: [10, 20, 30]
After modifying copy scores:
Original scores: [10, 20]
Copy scores: [10, 20, 30]
Solution
Step 1: Trace initial print statements
Both original.scores and copy_obj.scores start as [10, 20], so first two prints show identical lists.
Step 2: Trace modification and final prints
copy_obj.scores.append(30) modifies only the copy's scores list because deep copy created a new list. Original remains [10, 20]. Final prints reflect this separation.
Final Answer:
Option B -> Option B
Quick Check:
Deep copy prevents shared nested list -> original unchanged [OK]
Hint: Deep copy isolates nested mutable objects [OK]
Common Mistakes:
Assuming append affects original due to shared reference
4. Examine the following buggy CompositeIterator code snippet. Which line contains the subtle bug that causes incorrect traversal order?
medium
A. Line initializing self.stack without reversing children.
B. Line checking hasNext() before popping from stack.
C. Line popping component from stack.
D. Line returning the component after processing.
Solution
Step 1: Identify stack initialization issue
Stack is initialized with children in original order, not reversed, causing traversal order reversal.
Step 2: Confirm impact on traversal order
Without reversing, popping from stack yields children in reverse order, breaking expected traversal.
Final Answer:
Option A -> Option A
Quick Check:
Reversing children on stack initialization fixes traversal order [OK]
Hint: Stack must reverse children to preserve traversal order [OK]
Common Mistakes:
Forgetting to reverse children on stack push
Misplacing hasNext() check
5. Suppose you want to implement a prototype pattern for an object that contains references to other objects which themselves may reference back to the original object (cyclic references). Which approach correctly handles deep copying in this scenario?
hard
A. Use a deep copy implementation with memoization to track already copied objects and avoid infinite recursion
B. Use a naive recursive deep copy without memoization, which will eventually copy all objects
C. Use shallow copy to avoid recursion issues, accepting shared nested references
D. Serialize the object to JSON and deserialize it, which naturally handles cyclic references
Solution
Step 1: Understand cyclic references problem
Naive recursion without tracking copied objects causes infinite recursion on cycles.
Step 2: Evaluate solutions
Memoization tracks already copied objects, preventing infinite loops and ensuring correct deep copy. Shallow copy shares references, breaking independence. JSON serialization cannot handle cycles and will fail.
Final Answer:
Option A -> Option A
Quick Check:
Memoization prevents infinite recursion in cyclic graphs [OK]
Hint: Memoization is essential for deep copying cyclic object graphs [OK]
Common Mistakes:
Ignoring cycles causes infinite recursion or stack overflow