Bird
Raised Fist0
Interview Prepoop-design-patternsmediumAmazonGoogleMicrosoftFlipkartSwiggyRazorpayCRED

Composition vs Inheritance - Favour Composition, Why & When

Choose your preparation mode3 modes available

Start learning this pattern below

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

Define Base Shape Class Using Inheritance

We start by defining a base class 'Shape' with a method 'draw'. This sets up the inheritance approach where specific shapes will extend this base class.

💡 Initializing the base class is crucial because it establishes the common interface for all shapes in the inheritance model.
Line:class Shape { public void draw() {} }
💡 This step shows how inheritance begins with a common superclass defining shared behavior.
📊
Composition vs Inheritance - Favour Composition, Why & When - Watch the Algorithm Execute, Step by Step
Watching this visualization helps you understand the structural and behavioral differences between inheritance and composition without needing to read dense theory. You see how design decisions affect class relationships and extensibility.
Step 1/10
·Active fillAnswer cell
Demonstrates inheritance base class setup
Shape
+draw()
Shows subclass extending base class
Shape
+draw()
Circle
+draw()
Circle Shape (1:1)
Defines interface for composition
«interface»Renderer
+render()
Implements Renderer interface for composition
«interface»Renderer
+render()
VectorRenderer
+render()
VectorRenderer Renderer (1:1)
Shows composition by embedding Renderer
«interface»Renderer
+render()
Shape
#renderer: Renderer
+Shape()
+draw()
Shape Renderer (1:1)
Circle uses composition via Shape
«interface»Renderer
+render()
Shape
#renderer: Renderer
+Shape()
+draw()
Circle
+Circle()
+draw()
Shape Renderer (1:1)Circle Shape (1:1)
Shows runtime composition of behavior
«interface»Renderer
+render()
VectorRenderer
+render()
Shape
#renderer: Renderer
+Shape()
+draw()
Circle
+Circle()
+draw()
VectorRenderer Renderer (1:1)Shape Renderer (1:1)Circle Shape (1:1)
Demonstrates polymorphic method call in inheritance
Shape
+draw()
Circle
+draw()
Circle Shape (1:1)
Shows dynamic delegation in composition
Shape
#renderer: Renderer
+draw()
Circle
+draw()
VectorRenderer
+render()
Shape Renderer (1:1)Circle Shape (1:1)VectorRenderer Renderer (1:1)
Summarizes composition benefits over inheritance
Shape
#renderer: Renderer
+draw()
Circle
+draw()
«interface»Renderer
+render()
VectorRenderer
+render()
Circle Shape (1:1)Shape Renderer (1:1)VectorRenderer Renderer (1:1)

Key Takeaways

Composition decouples behavior from class hierarchies, enabling flexible and dynamic behavior changes.

This insight is hard to see from code alone because the delegation and runtime binding are implicit; visualization makes it explicit.

Inheritance tightly couples subclasses to base class interfaces, limiting extensibility and increasing fragility.

Seeing the fixed inheritance arrows and overridden methods side-by-side clarifies why inheritance can be restrictive.

Using interfaces and composition promotes separation of concerns and adheres to SOLID principles.

Visualizing interfaces and composition relationships helps understand how design principles are applied in practice.

Practice

(1/5)
1. You need to create different types of vehicles (cars, bikes) that share a common interface, and sometimes you want to create entire families of related vehicles (e.g., electric car + electric bike) ensuring consistency. Which design pattern best fits this requirement?
easy
A. Builder Pattern, because it constructs complex objects step-by-step.
B. Abstract Factory Pattern, because it creates families of related objects ensuring consistency.
C. Factory Pattern, because it creates simple objects based on a type parameter.
D. Singleton Pattern, because it ensures only one instance of each vehicle type.

Solution

  1. Step 1: Understand the requirement for families of related objects

    The problem states the need to create related vehicles (e.g., electric car and electric bike) that belong to a family and must be consistent.
  2. Step 2: Match pattern to requirement

    Abstract Factory is designed to create families of related objects, ensuring that the created objects are compatible and consistent.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Abstract Factory creates related object families, Factory creates single objects [OK]
Hint: Families of related objects -> Abstract Factory [OK]
Common Mistakes:
  • Confusing Factory with Abstract Factory
  • Using Builder for simple object creation
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

  1. 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.
  2. Step 2: Recognize MRO linearization

    MRO uses a specific linearization (like C3 linearization) that merges parent classes and their ancestors in a consistent order.
  3. 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.
  4. Final Answer:

    Option A -> Option A
  5. 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. What is the time complexity of processing a payment using the optimal strategy pattern implementation with dependency injection, assuming the payment method is already selected?
medium
A. O(n) where n is the number of payment methods
B. O(log n) due to searching the strategy in a factory method
C. O(1) constant time since the strategy is injected and pay() is a direct call
D. O(n^2) because of nested conditional checks inside the strategy classes

Solution

  1. Step 1: Identify the operation performed during payment

    Once the strategy is injected, calling pay() is a direct method call without iteration or recursion.
  2. Step 2: Analyze complexity of the pay() method

    The pay() method executes a simple print statement, which is O(1).
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Direct method call with no loops or recursion is constant time [OK]
Hint: Injected strategy enables direct O(1) method call [OK]
Common Mistakes:
  • Assuming factory lookup or conditionals happen at payment time
4. If you want to allow decorators to be added and removed dynamically at runtime (e.g., undo last added decorator), which modification to the Decorator Pattern implementation is most appropriate?
hard
A. Modify the base class to include flags for each decorator feature
B. Use inheritance to create new subclasses for every add/remove combination
C. Store decorators in a stack data structure and update the wrapped object reference on add/remove
D. Cache all possible decorator combinations in a lookup table

Solution

  1. Step 1: Understand dynamic add/remove requirement

    Need to add or remove decorators at runtime without subclass explosion or code changes.
  2. Step 2: Identify suitable data structure and pattern modification

    Using a stack to track decorators allows pushing and popping decorators, updating the wrapped object reference accordingly.
  3. Step 3: Evaluate other options

    Inheritance and flags lead to rigidity; caching all combinations is infeasible for many decorators.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    Stack structure supports dynamic decorator management efficiently [OK]
Hint: Stack supports dynamic add/remove of decorators [OK]
Common Mistakes:
  • Trying to use inheritance for dynamic behavior
  • Modifying base class breaks open-closed principle
5. In a legacy system, a class handles both business logic and logging. You want to refactor it following SRP, but the logging code is tightly intertwined with business logic. What is the best approach to refactor this while respecting SRP?
hard
A. Ignore SRP in this case because legacy code should not be refactored.
B. Leave logging inside the class because extracting it would break existing functionality and increase risk.
C. Merge business logic and logging into a utility class to centralize all cross-cutting concerns.
D. Extract logging into a separate class and replace logging calls with calls to this new class, even if it requires modifying many places.

Solution

  1. Step 1: Identify responsibilities

    Business logic and logging are separate reasons to change.
  2. Step 2: Refactor strategy

    Extract logging into its own class to isolate changes related to logging.
  3. Step 3: Address tight coupling

    Though intertwined, refactoring calls to the new logging class improves maintainability and respects SRP.
  4. Step 4: Evaluate other options

    Options B and D avoid refactoring, risking future issues; C increases coupling by merging unrelated concerns.
  5. Final Answer:

    Option D -> Option D
  6. Quick Check:

    Extract and isolate responsibilities even if it requires effort -> SRP compliance.
Hint: Separate concerns even if intertwined; refactor stepwise.
Common Mistakes:
  • Avoiding refactoring due to perceived risk.
  • Merging unrelated concerns for convenience.
  • Ignoring SRP in legacy code.