Bird
Raised Fist0

Why Design Patterns Exist and When Not to Use Them

LLDeasyInterview freq: medium
Choose your preparation mode4 modes available
๐ŸŽฏ
Why Design Patterns Exist and When Not to Use Them
40 min ยท medium
medium

Design a Java-based system that models the rationale behind using design patterns and scenarios where they should be avoided. The system must represent multiple design patterns, their applicability, and anti-patterns, with concurrency-safe registration and querying of patterns.

  • The system supports 5 levels of pattern complexity (e.g., Creational, Structural, Behavioral, Anti-patterns, and Miscellaneous).
  • There are 4 gates representing stages of pattern adoption: Identification, Evaluation, Implementation, and Review.
  • Each pattern has a pricing structure representing its cost of adoption (time, complexity, maintenance).
  • The system must allow concurrent registration and querying of patterns by multiple threads.
  • Patterns can be extended with new attributes without modifying existing classes (Open/Closed Principle).
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define the Pattern Abstraction
Design the base abstraction for a design pattern that holds shared state such as name, complexity level, and cost. Decide between interface and abstract class.
๐Ÿ’ก Hint
Consider that interfaces in Java cannot hold instance fields; use abstract class if shared state is needed.
abstract class DesignPattern(name:String, level:PatternLevel, cost:int) - abstract because it holds shared instance STATE; interface cannot hold fields in Java
Mid: Identifies abstract class for shared state. Senior: Explains why interface cannot hold instance fields in Java and how abstract class supports state and behavior.
โš ๏ธCommon Wrong Answer
Using interface for DesignPattern with fields leads to compilation errors since interfaces cannot hold instance state in Java.
Step 2
Model Pattern Levels and Gates
Represent pattern complexity levels and adoption gates using Java constructs that prevent invalid values and enable exhaustive switch statements.
๐Ÿ’ก Hint
Use enum types for fixed sets of constants to leverage compile-time safety and IDE support.
enum PatternLevel and enum AdoptionGate - enums provide type safety, exhaustive switch, and prevent runtime typo bugs unlike Strings
Mid: Uses enums for fixed categories. Senior: Explains how enums enable exhaustive switch and prevent runtime errors from typos.
โš ๏ธCommon Wrong Answer
Using String constants for levels and gates causes typo bugs that compile but fail at runtime, and no exhaustive switch checking.
Step 3
Ensure Thread-Safe Pattern Registration
Design a thread-safe method to register new patterns concurrently without corrupting the internal collection.
๐Ÿ’ก Hint
Use synchronized method or concurrent collections with volatile references to ensure atomicity and visibility.
public synchronized boolean registerPattern(DesignPattern p) - synchronized ensures atomic check-and-add; volatile reference to pattern collection ensures visibility across threads
Mid: Uses synchronized to protect registration. Senior: Traces race condition where Thread 1 reads collection, context switch, Thread 2 modifies collection, Thread 1 overwrites changes without synchronization.
โš ๏ธCommon Wrong Answer
Not synchronizing registerPattern leads to lost updates and inconsistent pattern registry due to race conditions.
Step 4
Support Extensibility Without Modifying Existing Classes
Allow adding new attributes or behaviors to patterns without changing existing classes, adhering to the Open/Closed Principle.
๐Ÿ’ก Hint
Use inheritance or composition with abstract classes and interfaces; avoid modifying existing code.
Use abstract class DesignPattern with protected fields and allow subclassing for extensions - supports Open/Closed Principle by enabling extension without modification
Mid: Uses inheritance to extend behavior. Senior: Explains how abstract class with protected fields enables extension without breaking existing code, unlike modifying concrete classes.
โš ๏ธCommon Wrong Answer
Modifying existing concrete classes for new attributes violates Open/Closed Principle and risks regression bugs.
Step 5
Represent Pricing Structure with Immutable Objects
Model the pricing structure of pattern adoption as an immutable object to prevent accidental modification after creation.
๐Ÿ’ก Hint
Use final fields and no setters; consider making the class final to enforce immutability.
final class Pricing with final fields and no setters - immutability ensures thread-safety and prevents accidental state changes
Mid: Creates class with final fields. Senior: Explains how immutability avoids synchronization overhead and prevents subtle bugs from shared mutable state.
โš ๏ธCommon Wrong Answer
Mutable pricing objects cause inconsistent cost data in concurrent environments leading to incorrect evaluations.
๐Ÿ”Œ
API Design Challenge
Design a thread-safe API for registering and querying design patterns that supports concurrent access and extensibility.
  • Use synchronized methods or concurrent collections to ensure thread safety during registration.
  • Design clear method signatures for adding and retrieving patterns with appropriate return types.
  • Consider how to expose pattern attributes and support filtering by pattern level or adoption gate.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • abstract class DesignPattern with instance state
  • enum PatternLevel and AdoptionGate for fixed categories
  • synchronized method for thread-safe registration
  • volatile reference for visibility of shared collection
  • Open/Closed Principle adherence via subclassing
Good to Have - 30%
  • Immutable Pricing class with final fields
  • Composition relationship between registry and patterns
  • Avoidance of String constants for enums
  • Explanation of race conditions with thread interleaving
  • Use of double-checked locking with volatile if singleton used
Bonus - 10%
  • Fine-grained synchronization to reduce contention
  • Use of concurrent collections like ConcurrentHashMap
  • Detailed concurrency scenario tracing
  • Use of protected fields in abstract class for extensibility
  • Clear lifecycle ownership explanation
Using String for PatternLevel or AdoptionGate leads to typo bugs and no compile-time checks, causing runtime failures and debugging difficulty
Not synchronizing registerPattern causes race conditions and lost updates, resulting in inconsistent pattern registry state
Mutable Pricing objects cause inconsistent state in concurrent access, leading to incorrect cost evaluations and subtle bugs
Modifying existing concrete classes violates Open/Closed Principle and risks regressions, increasing maintenance cost and bugs
Using interface instead of abstract class for shared state causes compilation errors due to inability to hold instance fields
โœ…
Model Answer
โ–ผ Reveal

The system models design patterns in Java by using an abstract class to hold shared state, enums for fixed categories, and synchronized methods for thread-safe registration. It supports extensibility via subclassing and uses immutable objects for pricing to ensure thread safety and maintainability.

  • Use abstract class DesignPattern to hold shared instance state because interfaces cannot hold fields in Java
  • Define PatternLevel and AdoptionGate as enums to leverage compile-time safety and enable exhaustive switch statements
  • Implement registerPattern as a synchronized method to prevent race conditions during concurrent registrations
  • Declare the pattern collection reference as volatile to ensure visibility of updates across threads
  • Adhere to Open/Closed Principle by allowing subclassing of DesignPattern with protected fields for extensibility
  • Model Pricing as a final immutable class with final fields and no setters to guarantee thread safety and prevent accidental mutations
  • Use composition in PatternRegistry to own and manage DesignPattern instances, ensuring clear lifecycle management
  • Avoid using String constants for fixed categories to prevent runtime typo bugs and improve IDE support
  • If singleton pattern is used for PatternRegistry, apply double-checked locking with a volatile instance to ensure thread-safe lazy initialization
  • Prefer synchronized blocks or methods over coarse-grained locking to reduce contention and improve concurrency performance
  • abstract class DesignPattern holds shared instance state (name, level, cost) - interface cannot hold fields in Java
  • enum PatternLevel and enum AdoptionGate provide compile-time safety and exhaustive switch support
  • synchronized registerPattern method ensures atomic check-and-add to pattern registry preventing race conditions
  • volatile reference to pattern collection guarantees visibility of updates across threads
  • abstract class DesignPattern with protected fields supports Open/Closed Principle by enabling subclass extension without modification
  • final immutable Pricing class prevents accidental mutation and ensures thread-safety without synchronization
  • Use composition for pattern registry holding DesignPattern instances - registry lifecycle owns patterns (composition)
  • Avoid String constants for fixed categories to prevent runtime typo bugs and poor IDE support
  • Double-checked locking with volatile singleton instance if singleton pattern is used for registry
  • Use synchronized blocks or methods rather than coarse-grained locking to minimize contention

Practice

(1/5)
1. In designing the ATM system, should the Card entity be modeled as an abstract class or an interface, considering it has state (card number, expiry) and behavior (validate PIN)? Why?
easy
A. Abstract class, because Card has shared state and behavior that concrete card types inherit.
B. Interface, because Card only defines behavior and no state, allowing multiple inheritance.
C. Interface, because Java interfaces can now have default methods to hold behavior and state.
D. Abstract class, because it enforces implementation of all methods without sharing state.

Solution

  1. Step 1: Analyze Card's characteristics

    Card holds state (card number, expiry) and behavior (validate PIN), so it needs to share state and common logic.
  2. Step 2: Choose between abstract class and interface

    Abstract class supports shared state and behavior inheritance. Interface cannot hold instance state, only constants and default methods without instance fields.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Shared state + behavior -> abstract class [check]
Hint: Stateful base entity -> abstract class, not interface [check]
Common Mistakes:
2. In a multi-threaded adapter implementation, the method parkVehicle() checks if a parking spot is available and then marks it occupied. Without synchronization, which exact thread interleaving causes double-booking?
medium
A. Thread 1 reads spot as occupied, Thread 2 reads spot as free, both park simultaneously.
B. Thread 1 marks spot occupied, Thread 2 waits for Thread 1 to finish, then parks safely.
C. Thread 1 checks isAvailable() -> true, context switch before marking occupied; Thread 2 checks isAvailable() -> true, both mark occupied.
D. Thread 1 and Thread 2 both crash before marking spot, causing no booking.

Solution

  1. Step 1: Trace parkVehicle() without synchronization

    Thread 1 reads spot as free (isAvailable() -> true), then context switch occurs before marking occupied.
  2. Step 2: Thread 2 also reads spot as free and proceeds to mark occupied, causing double-booking.

    Both threads believe spot is free and park simultaneously.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Check-then-act race causes double-booking without synchronization [check]
Hint: Check-then-act gap causes double-booking without sync [check]
Common Mistakes:
3. In a multi-threaded logging framework, the Appender class has a writeLog() method that first checks if the log file is open, then writes the log. Without synchronization on writeLog(), which exact thread interleaving causes lost log entries?
medium
A. Thread 1 writes log, Thread 2 waits for Thread 1 to finish, then writes.
B. Thread 1 checks file open -> true, context switch; Thread 2 checks file open -> true; both write simultaneously causing race.
C. Thread 1 closes file after writing; Thread 2 writes before file closes.
D. Thread 1 and Thread 2 both block waiting for file lock, causing deadlock.

Solution

  1. Step 1: Identify check-then-act race condition

    Without synchronization, Thread 1 checks file open and sees true, then context switches before writing. Thread 2 also checks and sees true, both proceed to write concurrently.
  2. Step 2: Why this causes lost logs

    Concurrent writes without synchronization cause race conditions, potentially overwriting or corrupting log entries.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Check-then-act gap -> concurrent writes -> lost logs [check]
Hint: Check file open -> context switch -> both write -> race [check]
Common Mistakes:
4. In the Meeting Scheduler, the RoomBookingManager class has a bookRoom() method that checks if a room is available and then marks it booked. Without synchronizing bookRoom(), which exact sequence of events between Thread 1 and Thread 2 causes a double-booking race condition?
medium
A. Thread 1 reads availability as false; Thread 2 reads availability as true; both proceed to book.
B. Thread 1 and Thread 2 both wait on a lock, causing deadlock and no booking.
C. Thread 1 books room; Thread 2 reads availability after booking and cancels its booking.
D. Thread 1 checks isAvailable() -> true, context switch before booking; Thread 2 checks isAvailable() -> true, both book simultaneously.

Solution

  1. Step 1: Identify check-then-act gap

    Without synchronization, Thread 1 reads availability as true but is preempted before booking; Thread 2 also reads true and proceeds.
  2. Step 2: Both threads book simultaneously causing double-booking

    Because the availability check and booking are not atomic, both threads can book the same room concurrently.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Unsynchronized check-then-act -> double-booking race [check]
Hint: Check-then-act without sync -> double-booking race [check]
Common Mistakes:
5. You need to add ElectricCar support with ChargingSpots to a parking lot system using the State pattern. What is the minimum change to support this without modifying existing classes?
hard
A. Modify Vehicle base class to add isElectric flag and add charging logic in existing Spot classes
B. Create a new ElectricVehicle interface and modify all existing Vehicle subclasses to implement it
C. Add ELECTRIC_CAR to VehicleType enum and CHARGING to SpotType enum, create ElectricCar subclass, update SpotCompatibility map
D. Add charging logic inside the existing State classes handling parking spots

Solution

  1. Step 1: Identify extensibility requirements

    Adding ElectricCar and ChargingSpot requires new types without modifying existing classes to respect OCP.
  2. Step 2: Evaluate options

    Add ELECTRIC_CAR to VehicleType enum and CHARGING to SpotType enum, create ElectricCar subclass, update SpotCompatibility map adds new enum values and subclasses without changing existing classes, enabling extension. Options B, C, and D modify existing classes or interfaces, violating OCP.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    New enums and subclass -> no existing class modified [check]
Hint: Add new enums and subclasses, avoid modifying existing classes [check]
Common Mistakes:
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