Imagine we have interface Button{ void Click(); }
and its implementations: AndroidButton
and IOSButton
. Is it possible to create appropriate button based on input like string os = "ios"
without violating the sOlid ? Like when a new type of 'os' appeared, we would not have to change the existing code
The Open/Close Principle does not mean that you must not change the factory code. It requests you to only extend, but not modify the factory's interface and behavior.
However, implementing Abstract Factory Pattern like this will come closest to your desire:
public interface ButtonFactory {
Button createButton();
}
public class IosButtonFactory implements ButtonFactory {
public Button createButton() {
return new IosButton();
}
}
public class AndroidButtonFactory implements ButtonFactory {
public Button createButton() {
return new AndroidButton();
}
}
Now. if you have to provide a new button type, you only have to add a new implementation like this:
public class WindowsButtonFactory implements ButtonFactory {
public Button createButton() {
return new WindowsButton();
}
}
This way, your factory is free of conditional statements.
Consider that there must be a location, where a condition decides, which of the concrete implementation is used related to the provided string os
. This can be in your bootstrapper, where you configure your IoC container, or in your main method, as shown in this really neat sample.
This way, your Factory is free of conditional statements, as it was your request by your initial question.
EDIT1 Concerning @LightSoul 's 1st comment below
There are some other designs of the Factory Pattern, however, then, it's not longer an Abstract Factory, it's a simple Factory.
Here, once again, we start with the interface:
public interface ButtonFactory {
Button createButton(String token);
}
The implementation might look something like:
public class ButtonFactoryImpl implements ButtonFactory {
private final Map<String, Supplier<Button>> factoryMethods = new HashMap<>();
public ButtonFactoryImpl() {
factoryMethods.put("ios", this::createIosButton);
factoryMethods.put("android", this::createAndroidButton);
}
@Override
public Button createButton(String token) {
if (factoryMethods.containsKey(token)) {
return factoryMethods.get(token).get();
}
throw new IllegalArgumentException("Token %s not configured".formatted(token);
}
private Button createIosButton() {
return new IosButton();
}
private Button createAndroidButton() {
return new AndroidButton();
}
}
Read this article to learn why token must be a primitive datatype.
This design fits to the OpenClosed Principle since you have only to extend, but not modify code, once you introduce the WindowsButton mentioned above.
If you like, and need not to consider speed and other drawbacks, you may want to use reflection:
First, create a new annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Token {
public String value();
}
Then our factory might look like this:
public class ButtonFactoryImpl implements ButtonFactory {
@Override
public Button createButton(String token) {
for(Method method : getType().getDeclaredMethods()) {
Annotation annotation = method.getDeclaredAnnotation(Token.class);
if (annotation instanceof Token t && t.value().equals(token)) {
method.setAccessible(true);
Button button = (Button) method.invoke(this, null);
method.setAccessible(false)
return button;
}
}
throw new IllegalArgumentException("Token %s not configured".formatted(token);
}
@Token("ios")
private Button createIosButton() {
return new IosButton();
}
@Token("android")
private Button createAndroidButton() {
return new AndroidButton();
}
}
Now, you don't longer have a need to adjust any dispatch code, like switch/if or Map initializing, if you have to extend the factory with a new private method for the @Token("windows"). But, as mentioned above, reflection has some serious impacts to consider. Take care and good luck.
(Disclaimer: the code above is not tested and might require some small changes to run. It does not contain error handling for easy readability and understandability.)