Skip to Content
Course content

304: Practice Exercise: Designing a Plugin System with Interfaces

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Why bother with an interface instead of just using a base class?

I get this question a lot. If you're thinking, "I could just make a BasePaymentProcessor and extend it," you're technically right, but you're painting yourself into a corner. In a real-world plugin system—say, for a payment gateway—your plugins might need to inherit from other frameworks or libraries. Since Java doesn't support multiple inheritance for classes, a base class locks the plugin author into your hierarchy.

By using an interface, you're defining a contract. You're telling the plugin developer: "I don't care who your parents are or how you're built internally, as long as you provide a processPayment(double amount) method, I can work with you."

public interface PaymentProcessor {
    boolean processPayment(double amount);
    String getProcessorName();
}

Now, whether a developer is using a legacy library for Stripe or a modern one for PayPal, they can just implement your interface and it just works.

How do I actually manage these plugins without hardcoding every single one into my main logic?

The magic happens when you stop thinking about specific classes and start thinking about a collection of the interface. You don't want your checkout code to have a giant if-else block checking if the user chose "Credit Card" or "Crypto." That's a maintenance nightmare.

Instead, I usually maintain a list or a map of the interface type. You load the plugins (via a config file, a service loader, or simple registration) and then just loop through them or pull the one you need by key. Look at how clean this keeps the "core" logic:

public class PaymentManager {
    private final Map<String, PaymentProcessor> plugins = new HashMap<>();

    public void registerPlugin(PaymentProcessor processor) {
        plugins.put(processor.getProcessorName(), processor);
    }

    public void executePayment(String method, double amount) {
        PaymentProcessor processor = plugins.get(method);
        if (processor == null) {
            throw new IllegalArgumentException("Payment method not supported!");
        }
        processor.processPayment(amount);
    }
}

Notice that PaymentManager has zero knowledge of how Stripe or PayPal actually works. It only knows the PaymentProcessor contract.

What if a plugin needs its own specific configuration that the main app doesn't know about?

This is where things get tricky. You can't put a stripeApiKey method in your general PaymentProcessor interface because the PayPal plugin wouldn't know what to do with it. I've seen people try to solve this by passing a Map<String, String> into every method, but that's messy and ruins your type safety.

The cleaner way is to handle configuration during the plugin's instantiation or through a dedicated initialize method that takes a generic configuration object. Let the plugin handle its own secrets; the main app should just be the orchestrator.

public class StripeProcessor implements PaymentProcessor {
    private String apiKey;

    // The main app doesn't call this; the plugin loader or a factory does
    public StripeProcessor(String apiKey) {
        this.apiKey = apiKey;
    }

    @Override
    public boolean processPayment(double amount) {
        System.out.println("Using API Key " + apiKey + " to charge $" + amount);
        return true;
    }

    @Override
    public String getProcessorName() {
        return "Stripe";
    }
}

By keeping the configuration separate from the interface methods, you keep your core system agnostic. The main app just sees a PaymentProcessor; it doesn't need to know that the Stripe version requires an API key while the "Cash" version requires nothing at all.




📋 Practical Task

Exercise: Implementing a Modular Text-Analysis Plugin System

Your goal is to build a small system that can analyze a string of text using multiple "Analysis Plugins." The main application should be able to run all loaded plugins against a piece of text without knowing what each plugin actually does.

  • Create an interface called TextAnalyzer with a method String analyze(String text).
  • Implement three different plugins:
    • WordCountAnalyzer: Returns the total number of words.
    • UpperCaseAnalyzer: Returns the text converted to all uppercase.
    • VowelCountAnalyzer: Returns the total number of vowels found in the text.
  • Create an AnalysisEngine class that holds a List<TextAnalyzer>. It should have a method called runAllAnalyses(String text) that iterates through all registered plugins and prints their results to the console.
  • In your main method, instantiate the engine, register your three plugins, and run the engine on a sample sentence (e.g., "Java interfaces are powerful!").
Rating
0 0

There are no comments for now.

to be the first to leave a comment.