Bird
Raised Fist0

Design a Plugin Architecture - Plugin Interface, Registry, Loader, Dependency

LLDhardInterview freq: medium
Choose your preparation mode4 modes available
๐ŸŽฏ
Design a Plugin Architecture - Plugin Interface, Registry, Loader, Dependency
40 min ยท medium
medium

You are tasked with designing a plugin architecture for a Java application. Plugins can be loaded dynamically at runtime, may depend on other plugins, and must be registered and retrieved safely in a concurrent environment.

  • Plugins implement a common Plugin interface with lifecycle methods.
  • Plugins can declare dependencies on other plugins by unique IDs.
  • A PluginRegistry manages plugin registration and lookup.
  • A PluginLoader loads plugins dynamically from external sources (e.g., JAR files).
  • The system must resolve dependencies before activating plugins.
  • Concurrent registration and retrieval of plugins must be thread-safe.
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define the Plugin Interface
Design the Plugin interface that all plugins must implement, including lifecycle methods and a method to declare dependencies.
๐Ÿ’ก Hint
Use a Java interface for behavior abstraction; interfaces cannot hold instance state but can have default methods since Java 8.
Use a Java interface Plugin with methods: void initialize(), void shutdown(), List<String> getDependencies(); interface chosen because plugins vary widely and no shared instance state is needed.
Mid: Names the Plugin interface with lifecycle methods. Senior: Explains why interface is chosen over abstract class due to lack of shared instance state and flexibility for multiple inheritance.
โš ๏ธCommon Wrong Answer
Using an abstract class for Plugin with no shared state leads to unnecessary inheritance constraints and reduces flexibility for plugins to extend other classes.
Step 2
Design the PluginRegistry
Design a thread-safe PluginRegistry to register, retrieve, and check plugin availability by unique plugin IDs.
๐Ÿ’ก Hint
Use ConcurrentHashMap<String, Plugin> to store plugins; synchronize methods that perform check-then-act operations to avoid race conditions.
PluginRegistry uses private final ConcurrentHashMap<String, Plugin> plugins; public synchronized boolean registerPlugin(String id, Plugin p) to atomically check and add plugin; volatile keyword not needed due to ConcurrentHashMap but synchronized needed for atomic check-and-set.
Mid: Uses ConcurrentHashMap for thread-safe storage. Senior: Explains exact race condition where two threads check plugin absence simultaneously and both register, causing duplicate entries without synchronized registerPlugin.
โš ๏ธCommon Wrong Answer
Using HashMap without synchronization causes race conditions leading to corrupted internal state and lost plugins.
Step 3
Implement PluginLoader with Dependency Resolution
Design PluginLoader to load plugins dynamically and resolve dependencies before activation.
๐Ÿ’ก Hint
Use a topological sort algorithm to resolve dependencies; detect cycles to prevent deadlocks; use synchronized blocks or locks to ensure thread-safe loading.
PluginLoader uses synchronized loadPlugin(String id) method that performs depth-first search with a visited set to detect cycles; dependency graph stored as Map<String, List<String>>; synchronized ensures no concurrent partial loading of same plugin.
Mid: Implements dependency resolution with topological sort. Senior: Explains how synchronized loadPlugin prevents interleaved partial loads causing inconsistent plugin states and how cycle detection prevents infinite recursion.
โš ๏ธCommon Wrong Answer
Ignoring dependency cycles leads to StackOverflowError at runtime; no synchronization causes partially loaded plugins visible to other threads.
Step 4
Design Plugin Dependency Management
Design how plugins declare and manage dependencies, and how the system enforces dependency contracts.
๐Ÿ’ก Hint
Use List<String> getDependencies() in Plugin interface; PluginRegistry verifies dependencies exist before activation; consider using immutable collections to prevent modification after declaration.
Plugin interface method List<String> getDependencies() returns unmodifiableList to prevent runtime mutation; PluginRegistry checks dependencies presence atomically before activation using synchronized methods.
Mid: Uses getDependencies() to declare dependencies. Senior: Explains why returning unmodifiableList prevents runtime bugs from dependency mutation and how synchronized dependency checks prevent race conditions where dependencies are removed mid-activation.
โš ๏ธCommon Wrong Answer
Returning mutable dependency lists allows plugins to change dependencies at runtime causing inconsistent states and hard-to-debug errors.
Step 5
Ensure Extensibility and Maintainability
Design the architecture to allow adding new plugin types and loaders without modifying existing code.
๐Ÿ’ก Hint
Use interface-based design and dependency inversion principle; PluginLoader can be an interface with multiple implementations; PluginRegistry depends on Plugin interface only.
Define PluginLoader as an interface with method loadPlugin(String id); concrete loaders implement this interface; PluginRegistry depends only on Plugin interface and PluginLoader interface, enforcing Dependency Inversion Principle (SOLID).
Mid: Uses interfaces for extensibility. Senior: Explains how Dependency Inversion Principle enables adding new loaders without modifying PluginRegistry and how interface segregation avoids forcing plugins to implement unused methods.
โš ๏ธCommon Wrong Answer
Hardcoding PluginLoader implementation in PluginRegistry violates Dependency Inversion Principle, making extension difficult and requiring code changes for new loaders.
๐Ÿ”Œ
API Design Challenge
Design a plugin architecture that supports dynamic loading, dependency resolution, and thread-safe plugin management in Java.
  • Use a Java interface for the Plugin abstraction with lifecycle and dependency methods.
  • Use ConcurrentHashMap and synchronized methods in PluginRegistry to ensure thread safety.
  • Implement dependency resolution with topological sort and cycle detection in PluginLoader.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • Plugin interface with lifecycle and dependency methods, no instance state
  • Thread-safe PluginRegistry using ConcurrentHashMap and synchronized registerPlugin
  • PluginLoader with synchronized loadPlugin and dependency resolution with cycle detection
  • Plugins declare dependencies via unmodifiable List<String>
Good to Have - 30%
    Bonus - 10%
      โœ…
      Model Answer
      โ–ผ Reveal

      This design defines a Java Plugin interface with lifecycle and dependency methods, a thread-safe PluginRegistry using ConcurrentHashMap and synchronized registration to avoid race conditions, and a PluginLoader interface with implementations that load plugins dynamically with dependency resolution via topological sort and cycle detection. Plugins declare dependencies via unmodifiable lists to prevent runtime mutation. The architecture follows SOLID principles to ensure extensibility and maintainability.

      • Use Java interface for Plugin to allow flexible implementations without shared instance state.
      • Use ConcurrentHashMap for thread-safe plugin storage and synchronized registerPlugin to prevent race conditions during check-then-act.
      • Implement dependency resolution with topological sort and cycle detection in synchronized loadPlugin method to avoid deadlocks and inconsistent states.
      • Return unmodifiable List<String> from getDependencies() to prevent runtime mutation bugs.
      • Define PluginLoader as an interface to allow multiple implementations and adhere to Dependency Inversion Principle.
      • Synchronize dependency checks and plugin activation to prevent race conditions where dependencies might be removed mid-activation.
      • Plugin interface defines lifecycle methods and dependency declaration with no instance state, enabling flexible plugin implementations.
      • PluginRegistry uses ConcurrentHashMap for thread-safe storage and synchronized registerPlugin for atomic check-and-set to prevent race conditions.
      • PluginLoader implements synchronized loadPlugin with topological sort for dependency resolution and cycle detection to prevent deadlocks and infinite recursion.
      • Plugins declare dependencies via unmodifiable List<String> to prevent runtime mutation bugs.
      • PluginRegistry verifies dependencies atomically before activation using synchronized methods to avoid inconsistent states.
      • PluginLoader is an interface allowing multiple implementations, enforcing Dependency Inversion Principle for extensibility.
      • Use of synchronized methods and volatile is carefully chosen to ensure thread safety without unnecessary blocking.
      • Cycle detection in dependency graph prevents StackOverflowError and deadlocks during plugin loading.
      • Returning unmodifiable collections prevents accidental mutation of plugin dependencies at runtime.
      • Interface-based design allows adding new plugin types and loaders without modifying existing classes, adhering to Open/Closed Principle.

      Practice

      (1/5)
      1. In designing the Amazon Locker Service, should the Package entity be modeled as an abstract class or an interface, and why?
      easy
      A. Abstract class, because interfaces cannot have any methods related to package handling.
      B. Abstract class, because Package has shared state like tracking info and common behavior.
      C. Interface, because Package only defines behaviors and allows multiple inheritance of behavior.
      D. Interface, because Package needs to hold mutable state for OTP and expiry.

      Solution

      1. Step 1: Analyze Package state and behavior

        Package has shared mutable state such as OTP, expiry time, and tracking info, which requires fields and common method implementations.
      2. Step 2: Abstract class vs interface suitability

        Abstract classes can hold state and provide default behavior, interfaces cannot hold mutable state (prior to Java 8 default methods, but still no fields). Therefore, abstract class fits better.
      3. Final Answer:

        Option B -> Option B
      4. Quick Check:

        Shared mutable state -> abstract class needed [check]
      Hint: Stateful entities -> abstract class; stateless behaviors -> interface [check]
      Common Mistakes:
      2. The current parking lot design uses a FeeStrategy interface for pricing. To add a WeekendFeeStrategy without modifying existing classes, which SOLID principle is being applied and what is the minimum required change?
      medium
      A. Open-Closed Principle; create a new WeekendFeeStrategy class implementing FeeStrategy without changing existing code.
      B. Dependency Inversion Principle; inject WeekendFeeStrategy into existing classes by changing their constructors.
      C. Single Responsibility Principle; modify FeeStrategy interface to add weekend method.
      D. Liskov Substitution Principle; override existing fee methods to handle weekends.

      Solution

      1. Step 1: Identify the SOLID principle for extensibility

        Open-Closed Principle (OCP) states software entities should be open for extension but closed for modification.
      2. Step 2: Apply OCP by adding new strategy class

        Implement WeekendFeeStrategy as a new class implementing FeeStrategy interface, no changes to existing classes needed.
      3. Final Answer:

        Option A -> Option A
      4. Quick Check:

        New class implements interface -> existing code unchanged -> OCP [check]
      Hint: Add new strategy class, no existing code change -> OCP [check]
      Common Mistakes:
      3. The system needs to support ElectricCar vehicles that require ChargingSpots. What is the minimum change to the current design to add this feature without modifying existing classes?
      hard
      A. Add charging logic inside Vehicle class and extend FareCalculator to include charging fees.
      B. Modify Vehicle class to include a boolean isElectric field and add charging logic inside existing methods.
      C. Create a new ElectricCar class and modify ParkingLot to handle charging spots explicitly.
      D. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility map externally.

      Solution

      1. Step 1: Identify extensibility without modifying existing classes

        Adding new enums and subclasses externally allows extension without changing existing code, preserving OCP.
      2. Step 2: Update SpotCompatibility map externally

        Maintaining compatibility mapping outside existing classes avoids class explosion or god-class logic.
      3. Final Answer:

        Option D -> Option D
      4. Quick Check:

        New enums + subclass + external compatibility update -> minimal change [check]
      Hint: Add enums + subclass + external map update, no existing class edits [check]
      Common Mistakes:
      4. To add support for ElectricCar objects requiring ChargingSpots in the existing LRU Cache design (which models Vehicle and Spot types), what is the minimum change that respects extensibility and avoids modifying existing classes?
      hard
      A. Modify Vehicle base class to include a boolean isElectric field and add logic in Spot class to check this field.
      B. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility map; no existing classes modified.
      C. Create a new ElectricCar class and a new ChargingSpot class, then modify the Cache class to handle these new types explicitly.
      D. Add a new method in Vehicle interface for charging compatibility and implement it in ElectricCar; modify Spot class accordingly.

      Solution

      1. Step 1: Identify extensibility requirements

        We want to add ElectricCar and ChargingSpot without modifying existing classes to respect OCP.
      2. Step 2: Evaluate options

        Modify Vehicle base class to include a boolean isElectric field and add logic in Spot class to check this field. modifies existing Vehicle class, violating OCP. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility map; no existing classes modified. adds enum values and compatibility map without changing existing classes, preserving extensibility.
      3. Step 3: Confirm minimal change

        Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility map; no existing classes modified. adds new enum constants and new subclass, updating compatibility map externally, no existing class code changed.
      4. Final Answer:

        Option A -> Option A
      5. Quick Check:

        New enums and subclass added, existing classes untouched [check]
      Hint: Add enums and subclass, avoid modifying existing classes [check]
      Common Mistakes:
      5. The Blackjack game needs to support ElectricCar players who require ChargingSpot betting spots. What is the minimum design change to support this without modifying existing classes?
      hard
      A. Add ELECTRIC_CAR to VehicleType, CHARGING to SpotType, create ElectricCar extending Vehicle, and update compatibility map externally
      B. Modify Vehicle and Spot classes to include electric-specific logic and fields
      C. Create a new ElectricCarVehicle and ChargingSpot classes inheriting from existing ones and modify base classes to support them
      D. Add electric-specific flags and logic inside existing Vehicle and Spot classes

      Solution

      1. Step 1: Identify extensibility without modifying existing classes

        Adding new enum values and subclasses externally allows extension without changing existing code, respecting OCP.
      2. Step 2: Why other options violate OCP or cause class explosion

        Options B and D modify existing classes, violating OCP; Create a new ElectricCarVehicle and ChargingSpot classes inheriting from existing ones and modify base classes to support them creates new classes but requires base class changes.
      3. Final Answer:

        Option A -> Option A
      4. Quick Check:

        Extend enums + subclass + external compatibility map -> minimal change [check]
      Hint: Extend enums + subclass + external map -> no existing class change [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