lazypig
lazypig

Reputation: 747

What Design Pattern or Architecture to use to override default class Instantiation or Creation

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.

Upvotes: 1

Views: 1356

Answers (2)

user3645884
user3645884

Reputation: 1

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.

Upvotes: 0

Luiggi Mendoza
Luiggi Mendoza

Reputation: 85779

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

Upvotes: 1

Related Questions