Java Factory pattern

118 Views Asked by At

I have module A and module B each one have some specific services and i have a module C that don't have any dependencies to A and B so what i want is to call services from modules A and B and I want to acheive that using factory pattern.

in module C i have created this interface and it will be a common interface to be implemented in modules A and B

public interface ThirdPartyCallsStrategy {
    void apply(String orderId);
}

And this is example of factory class and will provide the appropriate instance needed based on type parameter

@Component
@AllArgsConstructor
public class CoreCallsFactory {

    private static final String A_PACKAGE = "com.testA.service";
    private static final String A_CLASS = "TestClassA";
    
    private static final String B_PACKAGE = "com.testB.service";
    private static final String B_CLASS = "TestClassA";
    
    @SneakyThrows
    public ThirdPartyCallsStrategy createThirdPartyCallsStrategy(String type) {
        Class<?> clazz;
        if (type.equals("A")) {
            clazz = Class.forName(String.format("%s.%s", A_PACKAGE , A_CLASS ));
            return (ThirdPartyCallsStrategy) clazz.getDeclaredConstructor().newInstance();
        }

        if (type.equals("B")) {
            clazz = Class.forName(String.format("%s.%s", B_PACKAGE , B_CLASS ));
            return (ThirdPartyCallsStrategy) clazz.getDeclaredConstructor().newInstance();
        }

        return null;
    }
}

ThirdPartyCallsStrategy is implemented in different services in different modules

And finally in module C, i make the instanciation through java reflection but this will be at runtime and it can through exceptions later at runtime and not at compile time

public String checkStatusAndRedirect(String orderId, String type) {
    boolean status = checkStatus(orderId);
    StringBuilder paymentResultUrl = new StringBuilder(frontUrl + "/public/payment/status?success=");
    
    if (status) {

        coreCallsFactory.createThirdPartyCallsStrategy(type).apply(orderId);
        paymentResultUrl.append(Boolean.TRUE);
    } else {
        paymentResultUrl.append(Boolean.FALSE);
    }
    return paymentResultUrl.toString();
}

So what i need is a cleaner way to change this implementation.

2

There are 2 best solutions below

2
ControlAltDel On

The best way to do this is like:

public interface FactoryProducer<T> {
  public T create(String param);
}

public class Factory<T> {
  Map producers = new HashMap<string, FactoryProducer<T>>();
  public void registerFactory(String key, FactoryProducer<T> producer) {
    producers.put(key, producer);
  }
  public FactoryProducer<T> getFactory(String key) {
    return producers.get(key);
  }
}

This way you can build FactoryProducers and Factories and keep everything decoupled.

1
Tianhong Yu On

Use Java SPI.

Define the interface com.modelc.ThirdPartyCallsStrategy in model C.

public interface ThirdPartyCallsStrategy {
    void apply(String orderId);

    String type();
}

Implement ThirdPartyCallsStrategy in model A and model B.

public class AStrategy implements ThirdPartyCallsStrategy {
    public void apply(String orderId) {
        // Do something here.
    }
    public String type() {
        return "A";
    }
}
public class BStrategy implements ThirdPartyCallsStrategy {
    public void apply(String orderId) {
        // Do something different here.
    }
    public String type() {
        return "B";
    }
}

Register your implements in META-INF.

In the file of model A resources/META-INF/services/com.modelc.ThirdPartyCallsStrategy:

com.modela.AStrategy

In the file of model B resources/META-INF/services/com.modelc.ThirdPartyCallsStrategy:

com.modelb.BStrategy

Create your factory in model C.

public class CoreCallsFactory {
    private final Map<String, ThirdPartyCallsStrategy> strategyMap;

    public CoreCallsFactory() {
        strategyMap = new HashMap<>();
        ServiceLoader<ThirdPartyCallsStrategy> strategies = ServiceLoader.load(ThirdPartyCallsStrategy.class);
        for (ThirdPartyCallsStrategy strategy : strategies) {
            strategyMap.put(strategy.type(), strategy);
        }
    }

    public ThirdPartyCallsStrategy createThirdPartyCallsStrategy(String type) {
        ThirdPartyCallsStrategy strategy = strategyMap.get(type);
        if (strategy == null) {
            throw new IllegalArgumentException("There is no strategy for this type: " + type);
        }
        return strategy;
    }
}

model A and B depend on the interface in model C, but model C don't know model A or B.