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

- 55
- 7
-
Which code do you not want to change? The code in the factory or somewhere else? The point of the factory should be to make sure it is the only thing that needs to change when you add a new type of button. That is the S in solid. In addition you should be able to add the functionality by only adding a few lines to the factory to get your new button. That is the O in solid. The rest of you code should remain unchanged. And using a primitive like a string as paramater already makes sure that nothing else needs to know about the new button type. – obivandamme Jun 11 '23 at 21:36
-
adding a few lines to the factory violates OCP – LightSouls Jun 11 '23 at 21:48
-
There are several different Factory design patterns, some of which violate the OCP and some which do not. In particular, the [two patterns](https://stackoverflow.com/a/50786084/1371329) defined in the GoF book do _not_. – jaco0646 Jun 12 '23 at 02:00
-
I still don't get it, how you can i add new type without modifiying existing factory class? – LightSouls Jun 12 '23 at 08:34
-
Why do you think this question belongs under the `c#` and `java` tags? It seems to be a question about OOP and design patterns, and nothing to do with either of those languages specifically. – brads3290 Jun 16 '23 at 01:02
-
beacuse i am looking for the solution in these languages. not all languages has OOP, interfaces, ... – LightSouls Jun 16 '23 at 12:46
1 Answers
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.)

- 26
- 2
-
yes it's correct , my question mainly was about if statements in configuration or as you said main method.. i thought that configuration could be in factory direclty – LightSouls Jun 23 '23 at 16:44
-