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.
fill_row
Create Derived Circle Class Extending Shape
We define a 'Circle' class that inherits from 'Shape' and overrides the 'draw' method to provide specific behavior.
💡 This step shows how inheritance allows specialization by overriding base class methods.
Line:class Circle extends Shape {
@Override
public void draw() {
// draw circle
}
}
💡 Inheritance enables code reuse but tightly couples Circle to Shape's interface.
setup
Add Renderer Interface for Composition
We define a 'Renderer' interface with a 'render' method. This interface will be used to compose rendering behavior into shapes.
💡 Introducing an interface separates rendering behavior from shape hierarchy, enabling flexible composition.
Line:interface Renderer {
void render();
}
💡 This step sets up the composition approach by defining a role that can be delegated to.
fill_row
Implement Renderer with VectorRenderer
We create 'VectorRenderer' class implementing 'Renderer' interface, providing vector-based rendering logic.
💡 This concrete implementation shows how different rendering strategies can be composed.
We define 'Circle' class extending 'Shape' and using the composed 'Renderer' to draw itself.
💡 This step demonstrates how composition allows shape classes to reuse rendering behavior flexibly.
Line:class Circle extends Shape {
public Circle(Renderer renderer) {
super(renderer);
}
@Override
public void draw() {
renderer.render();
}
}
💡 Composition enables changing rendering behavior at runtime by passing different Renderer instances.
insert
Instantiate VectorRenderer and Compose with Circle
We create an instance of 'VectorRenderer' and pass it to a new 'Circle' instance, composing rendering behavior.
💡 This step shows how composition allows runtime binding of behavior implementations.
Line:Renderer renderer = new VectorRenderer();
Shape circle = new Circle(renderer);
💡 Composition provides flexibility to change rendering without modifying shape classes.
traverse
Call draw() on Circle Using Inheritance
We invoke the 'draw' method on the Circle instance that inherits from Shape, executing the overridden method.
💡 This step shows how polymorphism works in inheritance to call the correct method.
Line:circle.draw();
💡 Inheritance binds behavior at compile time, limiting flexibility to change rendering dynamically.
traverse
Call draw() on Circle Using Composition
We invoke the 'draw' method on the Circle instance composed with a Renderer, which delegates rendering to the Renderer instance.
💡 This step highlights how composition delegates behavior dynamically to the composed object.
Line:circle.draw();
💡 Composition allows changing rendering behavior by passing different Renderer implementations at runtime.
prune
Summary: Why Favor Composition Over Inheritance
We conclude by highlighting that composition offers greater flexibility, easier maintenance, and better separation of concerns compared to inheritance.
💡 This final step synthesizes the visualized differences into a clear design guideline.
💡 Favoring composition reduces tight coupling and allows behavior to be changed at runtime without modifying class hierarchies.
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.
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. In a large software system, when would applying the Interface Segregation Principle (ISP) be most beneficial?
easy
A. When clients depend on interfaces that contain methods they do not use, causing unnecessary implementation burden.
B. When all clients require the exact same set of methods, so a single fat interface simplifies design.
C. When you want to enforce a strict inheritance hierarchy with minimal interfaces.
D. When you want to reduce the number of interfaces to simplify the codebase.
Solution
Step 1: Understand ISP's goal
ISP aims to prevent clients from depending on methods they don't use, avoiding fat interfaces that force unnecessary implementations.
Step 2: Analyze options
When clients depend on interfaces that contain methods they do not use, causing unnecessary implementation burden. correctly identifies the scenario where ISP helps by splitting fat interfaces. When all clients require the exact same set of methods, so a single fat interface simplifies design. describes a scenario where ISP is less needed. When you want to enforce a strict inheritance hierarchy with minimal interfaces. confuses inheritance hierarchy with interface segregation. When you want to reduce the number of interfaces to simplify the codebase. incorrectly assumes fewer interfaces always simplify design, ignoring interface misuse.
Final Answer:
Option A -> Option A
Quick Check:
ISP is about splitting interfaces to avoid forcing clients to implement unused methods.
Hint: ISP splits interfaces so clients only depend on what they use.
Common Mistakes:
Believing fewer interfaces always mean better design
Thinking ISP applies when all clients use all methods
Confusing ISP with inheritance hierarchy rules
2. When a new feature is added by extending a class hierarchy following the Open/Closed Principle, what is the typical sequence of events when the system executes a method call on the new subclass instance?
easy
A. The base class method is always executed first, then the subclass method overrides it afterward
B. The system duplicates the base class code inside the subclass to avoid modifying the base
C. The subclass method is invoked directly due to polymorphism, without modifying base class code
D. Both base and subclass methods execute sequentially because the base class is modified to call the subclass
Solution
Step 1: Understand polymorphic dispatch
Method calls on subclass instances invoke the subclass's overridden method directly.
Step 2: Base class code remains unchanged
The base class is closed for modification; no changes are made to call subclass methods explicitly.
Step 3: Why other options are incorrect
The base class method is always executed first, then the subclass method overrides it afterward incorrectly suggests base method runs first then subclass; Both base and subclass methods execute sequentially because the base class is modified to call the subclass implies base class modification; The system duplicates the base class code inside the subclass to avoid modifying the base suggests code duplication, violating DRY and OCP.
Final Answer:
Option C -> Option C
Quick Check:
Polymorphism enables extension without modifying base code.
The Director calls build_pool expecting the pool to be added, but due to missing implementation, it is not.
Final Answer:
Option D -> Option D
Quick Check:
Missing pool addition causes incomplete luxury house [OK]
Hint: Empty build_pool method -> missing part added [OK]
Common Mistakes:
Confusing constructor initialization
Ignoring missing method implementation
4. What is a common trade-off or limitation when applying Dependency Inversion Principle (DIP) with heavy use of Dependency Injection frameworks?
medium
A. It always improves runtime performance by reducing object creation overhead.
B. It eliminates the need for interfaces or abstractions entirely.
C. It can increase complexity and reduce code readability due to indirect dependencies and configuration.
D. It guarantees compile-time safety without any runtime errors.
Solution
Step 1: Understand DIP and DI trade-offs
While DIP and DI improve modularity, heavy use of DI frameworks can add complexity and obscure dependencies.
Step 2: Analyze options
It can increase complexity and reduce code readability due to indirect dependencies and configuration. correctly identifies increased complexity and reduced readability as a trade-off. It always improves runtime performance by reducing object creation overhead. is false; DI can add runtime overhead. It eliminates the need for interfaces or abstractions entirely. is wrong; DIP requires abstractions. It guarantees compile-time safety without any runtime errors. is incorrect; runtime errors can still occur due to misconfiguration.
Final Answer:
Option C -> Option C
Quick Check:
DI frameworks improve flexibility but can complicate understanding and debugging.
Hint: DI improves modularity but can complicate code and configs.
Common Mistakes:
Assuming DI always improves performance
Believing DIP removes need for interfaces
Thinking DI guarantees no runtime errors
5. If the Composite pattern iterator is extended to allow reusing leaf components multiple times during traversal (e.g., shared leaves), which modification is necessary to ensure correct iteration without infinite loops?
hard
A. No change needed; the existing iterator handles reuse naturally.
B. Modify the iterator to push children in original order instead of reversed order.
C. Add a visited set to track and skip already visited components during iteration.
D. Convert the iterator to a recursive traversal to handle reuse correctly.
Solution
Step 1: Understand reuse implications
Reusing leaves means the same component can appear multiple times, risking infinite loops.
Step 2: Identify solution to prevent infinite loops
Tracking visited components prevents revisiting the same node repeatedly during iteration.
Final Answer:
Option C -> Option C
Quick Check:
Visited set avoids infinite loops with shared components [OK]
Hint: Track visited nodes to handle shared components safely [OK]
Common Mistakes:
Assuming no changes needed
Changing push order does not fix reuse loops
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.