✓ The Open/Closed Principle is implemented by defining an abstract base class and extending it with new subclasses without modifying existing code.
This insight is hard to see from code alone because the principle is about design intent and extensibility, which is clearer when visualized step-by-step.
✓ Client code depends on abstractions (the Shape interface) rather than concrete implementations, enabling polymorphism.
Seeing the client class interact only with the abstract class clarifies how extension is possible without modification.
✓ Adding new functionality (like the Triangle class) requires no changes to existing classes or client code, demonstrating maintainability.
The visualization shows how new subclasses integrate seamlessly, which is difficult to grasp by reading code alone.
Practice
(1/5)
1. You are designing a system for different types of vehicles where some can fly and some can float on water. Which design approach best supports adding new capabilities like flying or floating without modifying existing vehicle classes?
easy
A. Use inheritance to create subclasses like FlyingCar and FloatingCar from Vehicle
B. Use a single Vehicle class with flags indicating if it can fly or float
C. Use composition by creating separate capability classes like FlyBehavior and FloatBehavior and compose them with Vehicle
D. Use inheritance and override methods in each subclass to add flying or floating behavior
Solution
Step 1: Identify the problem with inheritance here
Inheritance leads to a combinatorial explosion of subclasses (FlyingCar, FloatingCar, FlyingFloatingCar), making the design rigid and hard to maintain.
Step 2: Understand composition benefits
Composition allows attaching behaviors dynamically via separate classes (FlyBehavior, FloatBehavior), promoting flexibility and easier extension.
Step 3: Analyze other options
Options A and C rely on inheritance, causing tight coupling and poor scalability. Use a single Vehicle class with flags indicating if it can fly or float uses flags, which leads to complex conditional logic and violates single responsibility.
Final Answer:
Option C -> Option C
Quick Check:
Composition supports adding new capabilities without modifying existing classes, enhancing maintainability and flexibility.
Hint: Composition lets you add capabilities by combining behaviors, inheritance forces rigid hierarchies.
Common Mistakes:
Assuming inheritance is always better for code reuse
Using flags instead of behaviors leads to messy conditionals
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. What is the time complexity of computing the cost() method when stacking k decorators on a core object using the Decorator Pattern with dynamic behavior injection?
medium
A. O(1) because each decorator adds a fixed cost
B. O(k) because each decorator delegates the call to the next one
C. O(k^2) because each decorator calls all previous decorators recursively
D. O(log k) because decorators form a balanced tree structure
Solution
Step 1: Identify call chain length
Each decorator's cost() calls the wrapped object's cost(), forming a chain of length k.
Step 2: Calculate total calls
Cost computation requires traversing all k decorators once, so time complexity is O(k).
Final Answer:
Option B -> Option B
Quick Check:
Each decorator adds constant work, total linear in k [OK]
Hint: Decorator calls chain length equals number of decorators [OK]
Common Mistakes:
Assuming O(1) because cost is a simple addition
Mistaking recursive calls as quadratic
5. Examine the following buggy code implementing the Template Method Pattern. Which line contains the subtle bug that breaks the pattern's intended behavior?
medium
A. Line overriding prepare_recipe in Tea subclass
B. Line defining abstract method brew in base class
C. Line calling add_condiments inside prepare_recipe base method
D. Line overriding customer_wants_condiments in Tea subclass
Solution
Step 1: Identify overridden methods
Tea overrides prepare_recipe, which breaks the template method pattern by duplicating and changing the algorithm flow.
Step 2: Understand impact
Overriding the template method in subclass bypasses the base class skeleton, causing inconsistent behavior and code duplication.
Final Answer:
Option A -> Option A
Quick Check:
Template method must not be overridden by subclasses [OK]