Factory Pattern Without any `if`
Yeah... Dude, I've seen a lot of accidents in my rear view mirror! (: And I'm grateful my first job was in a huge software company where I kept hearing those two solid basics: - Generic - Configurable You got these two - There's basically nothing left to do. Generic: Code you can extend without modifying it. For example, a file system engine copy and sort "Items". We can extend and introduce a special engine that can copy video file "Items" and still use the existing sort. It's not only inheritance. A Generic engine may accept a custom sort algorithm using an appropriate "setter". It is the flexibility to enhance our code without modifying it. Think of a generic code as one that you can burn into a chip! You will never modify it. But you will extend, plug and enhance it. Configurable: Control runtime behavior by applying external parameters without modifying the code. The flexibility with configurable code is the ability to accept external values - not more code - to customize the runtime behavior. For example, our File System engine can be defined to accept a threshold of maximum milliseconds it should wait for the sort to complete. Beyond that max time - it can abort the sorting algo. A Generic engine can be extended with a new kind that waits longer or never stop. But a configurable engine can run each time with a different value for the "max-sort-timeout-millis" for example. Why Without Any if statements? It's my rule of thumb - Having my flow "split" implies I might need to support other conditions or edge cases. Known or not. Modifying code means maintenance and we don't like maintenance. Less if statements - Less opportunities my code go wild. Factory - The Simplest Way public static SortStrategy getSortingStrategy(String strategyName) { return switch (strategyName) { case "bubble" -> new BubbleSort(); case "selection" -> new SelectionSort(); case "merge" -> new MergeSort(); case "quick" -> new QuickSort(); default -> new ShellSort(); }; } Using switch doesn't mean we're not using if for that matter. To support a new sort strategy, our factory will have to be modified. HashMap Based Factory class SortFactory { static Map strategyMap = new HashMap(); // initialize the strategy instances. static { strategyMap.put("quicksort", new QuickSort()); strategyMap.put("mergesort", new MergeSort()); strategyMap.put("heapsort", new HeapSort()); } public static SortStrategy getSortStrategy(String sortType) { // Return strategy from map, default to BubbleSort. return strategyMap.getOrDefault(sortType, new BubbleSort()); } } It's a nice progress but for a new sort strategy we still need to modify the factory. Map or Not - we'll need additional put. True Configurable Factory - Reflection Based static { Properties props = new Properties(); try (InputStream input = SortFactory.class.getClassLoader().getResourceAsStream(CONFIG_FILE)) { props.load(input); for (String sortName : props.stringPropertyNames()) { String className = props.getProperty(sortName); Class clazz = Class.forName(className); // Load class dynamically SortStrategy strategy = (SortStrategy) clazz.getDeclaredConstructor().newInstance(); // Instantiate strategyMap.put(sortName.toLowerCase(), strategy); } } catch (Exception e) { // No error handling for simplicity } strategyMap.putIfAbsent("bubblesort", DEFAULT_STRATEGY); // Ensure default } Now, we can introduce as many sort strategies we like while our Factory code never changes! Beyond Generic and Configurable ? It's fully configurable but not the best. In many cases - this solution is even not convenient for real software life cycle operations! Our solution so far can handle a new definition of a sort strategy but it is initialized only on application startup. What if our application is a server? must we restart it just because we're defining a new strategy? Dynamic Configuration It would be the same, but we'll have a load method that we can invoke each time the configuration changes. This can be done explicitly like clicking a "Refresh Strategy Configuration" button which will call the load method. Much better! But... What if our configuration parameter is an algorithm variable like an AI "reward-factor" which is called hundreds of times inside loops while the AI algo runs? If we change that parameter, we'll have to hit "refresh configs" each time? By File System Trigger We might trigger loading the map each time the "FileModified" event occurred for the properties file. We'll have to register for the FS events, but for oth

Yeah... Dude, I've seen a lot of accidents in my rear view mirror!
(:
And I'm grateful my first job was in a huge software company where I kept hearing those two solid basics:
- Generic
- Configurable
You got these two - There's basically nothing left to do.
Generic: Code you can extend without modifying it.
For example, a file system engine copy and sort "Items".
We can extend and introduce a special engine that can copy video file "Items" and still use the existing sort.
It's not only inheritance. A Generic engine may accept a custom sort algorithm using an appropriate "setter".
It is the flexibility to enhance our code without modifying it.
Think of a generic code as one that you can burn into a chip!
You will never modify it. But you will extend, plug and enhance it.
Configurable: Control runtime behavior by applying external parameters without modifying the code.
The flexibility with configurable code is the ability to accept external values - not more code - to customize the runtime behavior.
For example, our File System engine can be defined to accept a threshold of maximum milliseconds it should wait for the sort to complete. Beyond that max time - it can abort the sorting algo.
A Generic engine can be extended with a new kind that waits longer or never stop. But a configurable engine can run each time with a different value for the "max-sort-timeout-millis" for example.
Why Without Any if
statements?
It's my rule of thumb - Having my flow "split" implies I might need to support other conditions or edge cases. Known or not.
Modifying code means maintenance and we don't like maintenance.
Less if
statements - Less opportunities my code go wild.
Factory - The Simplest Way
public static SortStrategy getSortingStrategy(String strategyName) {
return switch (strategyName) {
case "bubble" -> new BubbleSort();
case "selection" -> new SelectionSort();
case "merge" -> new MergeSort();
case "quick" -> new QuickSort();
default -> new ShellSort();
};
}
Using switch
doesn't mean we're not using if
for that matter.
To support a new sort strategy, our factory will have to be modified.
HashMap Based Factory
class SortFactory {
static Map<String, SortStrategy> strategyMap = new HashMap<>();
// initialize the strategy instances.
static {
strategyMap.put("quicksort", new QuickSort());
strategyMap.put("mergesort", new MergeSort());
strategyMap.put("heapsort", new HeapSort());
}
public static SortStrategy getSortStrategy(String sortType) {
// Return strategy from map, default to BubbleSort.
return strategyMap.getOrDefault(sortType, new BubbleSort());
}
}
It's a nice progress but for a new sort strategy we still need to modify the factory. Map or Not - we'll need additional put
.
True Configurable Factory - Reflection Based
static {
Properties props = new Properties();
try (InputStream input = SortFactory.class.getClassLoader().getResourceAsStream(CONFIG_FILE)) {
props.load(input);
for (String sortName : props.stringPropertyNames()) {
String className = props.getProperty(sortName);
Class> clazz = Class.forName(className); // Load class dynamically
SortStrategy strategy = (SortStrategy) clazz.getDeclaredConstructor().newInstance(); // Instantiate
strategyMap.put(sortName.toLowerCase(), strategy);
}
} catch (Exception e) {
// No error handling for simplicity
}
strategyMap.putIfAbsent("bubblesort", DEFAULT_STRATEGY); // Ensure default
}
Now, we can introduce as many sort strategies we like while our Factory code never changes!
Beyond Generic and Configurable ?
It's fully configurable but not the best.
In many cases - this solution is even not convenient for real software life cycle operations!
Our solution so far can handle a new definition of a sort strategy but it is initialized only on application startup.
What if our application is a server? must we restart it just because we're defining a new strategy?
Dynamic Configuration
It would be the same, but we'll have a load
method that we can invoke each time the configuration changes.
This can be done explicitly like clicking a "Refresh Strategy Configuration" button which will call the load method.
Much better! But...
What if our configuration parameter is an algorithm variable like an AI "reward-factor" which is called hundreds of times inside loops while the AI algo runs?
If we change that parameter, we'll have to hit "refresh configs" each time?
By File System Trigger
We might trigger loading the map each time the "FileModified" event occurred for the properties file. We'll have to register for the FS events, but for other config cases, we'll need to find a clever solution each time.
Is there a better way to listen to any config change without refreshing or reloading doing any heavy I/O to have the latest data?
And do we have to refresh all config data even if only one item changed?
How about adding permissions to the config file?
Where does the config file reside?
Can we make it faster having cached configs?
And, update local cache only when "delta changes"?
I'm obviously out of scope!
Factory Without any if
is sufficient in many cases. It's great.
For the rest of the Dynamic, Real-Time "Delta Changes" only, Role based access, centralized hub of configurations per environment, with configs hierarchies - we'll have to go on solutions like websockets, a smart Server and SDK but that would require me to offer our solution for real-time config management but I'm not into marketing here - just talking Java Factory without any if
.
Remember - Generic and Configurable - You got these two - You got the rest of the day off. Or get ready for your next task - time to clean-shine your desk...