1

I have a platform that I am writing that provides a default set of modules to the developer by which I am hoping that they extend to provide more custom functionality during run time. The following code snippet is the default module hierarchy:

package com.test.core;
public abstract class Module {
    public abstract void performAction();
}

package com.test.fun;
public class AModule extends Module {

    @Override
    public void performAction() {
        // Perform action A
    }
}

package com.test.fun;
public class BModule extends Module {

    @Override
    public void performAction() {
        // Perform action B
    }
}

package com.test.fun;
public class CModule extends Module {

    @Override
    public void performAction() {
        // Perform action C
    }
}

There is a module engine that will create modules at the start of the app's instance and stores these modules in a HashMap where [key=name; value=Module]. Because of the nature of my backend and app, I must use name as the key and to identify modules.

public Module createModule(String name) throws Exception {
    Module module = null;
    Class moduleClass = null;

    // Find class by name, default to using <name>Module as the class name
    // might throw exception
    String className = name = "com.test.fun." + name + "Module";
    moduleClass = Class.forName(className);

    if (moduleClass == null) {
        // quit, nothing to do
    } else {
        // create the module
        module = (QPComponent) moduleClass.getConstructor().newInstance();
    }

    return module;
}

Assuming that that AModule is a binary and I cannot update its implementation, I want to add more behaviour to the module with they key "A" such as the following.

package com.test.custom;
public class ExtendedModuleA extends AModule {
    @Override
    public void performAction() {
        super.performAction();
        // Do some more to add behaviour to AModule
    }
}

How would I have to revise the architecture to enable a developer to register their custom implementation of a module for key "A" so that when the app starts, the app will grab the custom ExtendedModuleA version instead of the default AModule version?

One way I was thinking, that doesn't seem pretty is the following:

public class ModuleRegistry {

    // Assume singleton
    HashMap<String, Class<Module>> registry;

    public ModuleRegistry() {

    }

    private void init() {
        registry = new HashMap<String, Class<Module>>();

        registry.put("A", ExtendedModuleA.class);
        // no extension for B
        registry.put("C", CModuleExtra.class);

        // developers add more entries to "register" more extended modules here for the future
    }



    public Class<Module> getExtendedModuleClass(String name) {
        return registry.get(name);  
    }
}

Is there a design pattern or tool library that can help me with this kind of problem? I am currently only thinking of this solution because I remember things like Spring or Dagger or Android's manifest were you have to register your classes in order for them to be used or picked up by the system. I am already using Dagger in my app, but my modules themselves need ObjectGraphs, so it is might have a chicken and the egg catch-22 situation.

Luiggi Mendoza
  • 85,076
  • 16
  • 154
  • 332
lazypig
  • 747
  • 1
  • 6
  • 22

2 Answers2

1

You need to check Decorator Pattern, which allows you to add dynamic behavior for classes at runtime. This works more on composition rather than inheritance, like creating a wrapper for your classes. Here's a kickoff example just to understand the main idea:

abstract class Beverage {
    protected final BigDecimal price;
    public Beverage(BigDecimal price) {
        this.price = price;
    }
    public BigDecimal getPrice() {
        return this.price;
    }
}

class Coffee extends Beverage {
    public Coffee(BigDecimal price) {
        super(price);
    }
}

class BeverageWithSugar extends Beverage {
    private Beverage beverage;
    private static final BigDecimal sugarPrice = new BigDecimal("0.15");
    public BeverageWithSugar(Beverage beverage) {
        super(sugarPrice);
        this.beverage = beverage;
    }
    @Override
    public BigDecimal getPrice() {
        //here we add the behavior dynamically
        return this.beverage.getPrice().add(sugarPrice);
    }
}

class BeverageWithChocolate extends Beverage {
    private Beverage beverage;
    private static final BigDecimal chocolatePrice = new BigDecimal("1.25");
    public BeverageWithChocolate(Beverage beverage) {
        super(chocolatePrice);
        this.beverage = beverage;
    }
    @Override
    public BigDecimal getPrice() {
        //here we add the behavior dynamically
        return this.beverage.getPrice().add(chocolatePrice);
    }
}

public class BeverageStore {
    public static void main(String[] args) {
        Coffee coffee = new Coffee(new BigDecimal("0.5"));
        //adding chocolate to our coffee
        Beverage coffeeWithChocolate = new BeverageWithChocolate(coffee);
        //adding sugar to our coffee
        Beverage coffeeWithSugar = new BeverageWithSugar(coffee);
        //adding both chocolate and sugar to our coffee
        Beverage greatCoffee = new BeverageWithChocolate(new BeverageWithSugar(coffee));
        System.out.println("Coffee price: " + coffee.getPrice());
        System.out.println("Coffee with chocolate price: " + coffeeWithChocolate.getPrice());
        System.out.println("Coffee with sugar price: " + coffeeWithSugar.getPrice());
        System.out.println("Coffee with chocolate and sugar price: " + greatCoffee.getPrice());
    }
}

Prints:

Coffee price: 0.5
Coffee with chocolate price: 1.75
Coffee with sugar price: 0.65
Coffee with chocolate and sugar price: 1.90

To check how the decorator pattern and others are used in Java framework, refer to Examples of GoF Design Patterns in Java's core libraries

Community
  • 1
  • 1
Luiggi Mendoza
  • 85,076
  • 16
  • 154
  • 332
0

You probably want to load your Modules dynamically (not at init function).

you could provide a final method in AModule class

public final String getRegistryKey() {
    return "A";
}

and implement a register method in your ModuleRegistry (I assume it is a singleton and other developers could access it during run time).

public void register(Module m) {
    // check key exist and throw exception if you need.
    registry.put(m.getRegistryKey(), m.getClass());
}

public void remove(Module m) {
    hashMap.remove(m.getRegistryKey());
}

So who ever uses your class could register or remove their modules during run time when they are needed.

  • Your answer seems to suggest that the developer will explicitly create the object and then register it in the code and that will work. However, in my case, the developer doesn't create the objects manually, they are created dynamically based on settings from the server. In your answer, the Object registers to the Name so subsequent use will use the object. In my scenario, based on the Name, a new object is created. So I need to identify which object to create based on the name rather than the other way around. What do you suggest for that? – lazypig May 20 '14 at 17:09