Bird
Raised Fist0
Interview Prepoop-design-patternseasyAmazonGoogleMicrosoftFlipkartRazorpayCREDSwiggy

Single Responsibility Principle - One Class, One Reason to Change

Choose your preparation mode3 modes available
▶
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.
📊
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. Given the following Python code using the Builder pattern, what is the output of print(house) after constructing a basic house with the Director?
easy
A. House parts: Walls, Roof
B. House parts: Walls, Roof, Pool
C. House parts: Roof, Walls
D. House parts: Pool

Solution

  1. Step 1: Trace Director.construct_basic_house()

    The Director calls build_walls() and build_roof() on the builder, adding "Walls" and "Roof" to the house parts.
  2. Step 2: Check the final house parts list

    The house parts list contains ["Walls", "Roof"]. The pool is not added in this method.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Only walls and roof are added in basic house construction [OK]
Hint: Basic house adds walls and roof only [OK]
Common Mistakes:
  • Assuming pool is added by default
  • Mixing order of parts
2. You need to ensure that a class has only one instance throughout the application lifecycle, and this instance must be lazily initialized in a thread-safe manner without incurring synchronization overhead on every access. Which design approach best fits this requirement?
easy
A. Use a simple static variable without synchronization and rely on the language's memory model.
B. Use a synchronized method that creates the instance on first call, locking every time.
C. Create the instance eagerly at class loading time to avoid synchronization issues.
D. Implement double-checked locking with a volatile instance variable to minimize synchronization overhead.

Solution

  1. Step 1: Understand the problem constraints

    The instance must be lazily initialized and thread-safe, but synchronization overhead should be minimized.
  2. Step 2: Evaluate each approach

    Synchronized method locks on every call, causing overhead. Eager initialization is thread-safe but not lazy. Unsynchronized static variable risks multiple instances in multithreaded contexts. Double-checked locking with volatile ensures lazy init, thread safety, and minimal locking.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Double-checked locking balances thread safety and performance [OK]
Hint: Double-checked locking minimizes synchronization overhead [OK]
Common Mistakes:
  • Assuming synchronized method is efficient enough
  • Believing eager initialization is always best
  • Ignoring volatile keyword necessity
3. Which of the following statements about using inheritance to model different vehicle types in the parking lot system is INCORRECT?
medium
A. Inheritance allows sharing common vehicle attributes and behaviors in a base Vehicle class
B. Using inheritance for vehicle types can lead to rigid designs that are hard to extend with new types
C. Inheritance is always the best approach to add new vehicle types without modifying existing code
D. Polymorphism enables treating all vehicle types uniformly when allocating parking spots

Solution

  1. Step 1: Review inheritance benefits

    Inheritance supports code reuse and polymorphism for uniform handling.
  2. Step 2: Recognize limitations

    Inheritance hierarchies can become rigid and hard to extend, violating open-closed principle.
  3. Step 3: Identify incorrect statement

    Claiming inheritance is always best ignores alternatives like composition or interfaces that improve extensibility.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    Inheritance is not always best for extensibility [OK]
Hint: Inheritance can cause rigidity; prefer composition for extensibility [OK]
Common Mistakes:
  • Assuming inheritance is always the best design choice
  • Ignoring polymorphism benefits
4. In a large-scale system where behaviors need to be dynamically changed at runtime, what is a key design consideration when using composition to avoid pitfalls that inheritance-based designs face?
hard
A. Use deep inheritance hierarchies to capture all behavior variations upfront.
B. Ensure that composed behavior objects are immutable to prevent side effects.
C. Avoid using interfaces and rely on concrete classes to reduce complexity.
D. Design behavior interfaces and use delegation so behaviors can be swapped without modifying the main object.

Solution

  1. Step 1: Understand dynamic behavior change

    To change behaviors at runtime, the design must support swapping behavior implementations easily.
  2. Step 2: Analyze options

    Ensure that composed behavior objects are immutable to prevent side effects. is not practical; immutability limits dynamic changes. Avoid using interfaces and rely on concrete classes to reduce complexity. increases coupling and reduces flexibility. Use deep inheritance hierarchies to capture all behavior variations upfront. contradicts favoring composition and leads to rigid designs.
  3. Step 3: Confirm best practice

    Design behavior interfaces and use delegation so behaviors can be swapped without modifying the main object. promotes defining behavior interfaces and delegating calls, enabling runtime swapping without modifying main objects.
  4. Final Answer:

    Option D -> Option D
  5. Quick Check:

    Delegation with interfaces is key to flexible, dynamic composition.
Hint: Use interfaces and delegation to swap behaviors dynamically, not inheritance or concrete classes.
Common Mistakes:
  • Thinking immutability aids dynamic behavior changes
  • Relying on inheritance for runtime flexibility
5. In a language that supports multiple inheritance with MRO, what happens if two parent classes define a method with the same name, but one parent class inherits from the other (forming a diamond), and the child class overrides that method? How does the MRO affect which method is called when the child class method calls super()?
hard
A. super() follows the MRO linearization, calling the next method in the MRO sequence, which may skip some classes.
B. super() calls the method from the immediate parent class only, ignoring the diamond structure.
C. super() calls all parent methods with the same name in parallel, combining their effects.
D. super() always calls the method from the base class at the top of the diamond first.

Solution

  1. Step 1: Understand super() in multiple inheritance

    super() does not simply call the immediate parent but follows the MRO linearization.
  2. Step 2: MRO linearization

    MRO creates a linear order of classes to avoid ambiguity and duplication, so super() calls the next method in this order.
  3. Step 3: Eliminate incorrect options

    super() calls the method from the immediate parent class only, ignoring the diamond structure is incorrect because super() is not limited to immediate parent. super() calls all parent methods with the same name in parallel, combining their effects is wrong because super() does not call methods in parallel. super() always calls the method from the base class at the top of the diamond first is incorrect because super() does not always start at the base class.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    super() respects MRO linearization, ensuring consistent method calls in diamond inheritance.
Hint: super() = next in MRO chain
Common Mistakes:
  • Thinking super() calls only immediate parent
  • Believing super() calls all parents simultaneously
  • Assuming super() always calls base class first
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard