If you are interested in reading this article in Spanish ๐ช๐ธ, check out my blog:
The Developer's Dungeon
Introduction
Since the ending of last year, I have been getting into Functional Programming(FP), I am still very bad at it.
I find some concepts super hard to understand, things still feel very unnatural but throughout this process, I started noticing a few things we do regularly in Object-Oriented programming(OOP) that are a lot easier to implement in functional programming languages, they require less code and sometimes the implementation comes by default provided by the language itself.
In a previous article, I talked about how the SOLID Principles apply to FP, you can read it here, today I am gonna do a similar analysis but regarding Design Patterns, specifically some of the Design Patterns in the original GoF Book
Strategy Pattern
Let us start with the original definition:
The strategy pattern is a design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.
Strategy lets the algorithm vary independently from clients that use it.
What does this look like?
In OOP languages this generally involves separate classes for each strategy which they all implement a common interface, then another class makes use of the strategy interface without actually knowing which strategy implementation has been selected at runtime, with this we separate the concerns of selecting the algorithm and the differences in implementation between strategies while having the possibility of selecting one or the other during runtime.
In FP this pattern becomes extremely easy to implement and requires much less code, I am gonna use JavaScript but it can be equally easy to implement on full functional languages.
const strategy1 = () => {
console.log('run strategy 1');
};
const strategy2 = () => {
console.log('run strategy 2');
};
const consumer = (runStrategy) => {
/*
Do other stuff
*/
runStrategy();
}
const selectedStrategy = condition ? strategy1 : strategy2;
consumer(selectedStrategy);
As you can see, in FP, the Strategy Pattern becomes just as simple as passing a function as a parameter. There is much less code involve because we don't have to depend on classes or inheritance to achieve the same functionality.
Factory Pattern
The factory method pattern is a pattern that uses methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. This is done by creating objects by calling a factory methodโeither specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classesโrather than by calling a constructor.
In OOP languages this generally involves separate classes that encapsulate the business logic of creating objects and that can decide which object create during runtime. This often involves setting specific parameters on the created object depending on what we want to instantiate.
In FP this pattern becomes extremely easy to implement:
const behavior1 = () => {
console.log('do behavior 1');
};
const behavior2 = () => {
console.log('do behavior 2');
};
const factory = (condition) => {
/*
Do other stuff
*/
if (condition) behavior1;
return behavior2;
}
As you can see, higher-order functions give us a simple solution again, instead of having behavior being defined by classes an inheritance, we just return a function that encapsulates the desired behavior or object.
Decorator Pattern
The decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern.
In OOP languages you wrap an object into another and provide extra functionality that the original one didn't have, without actually modifying its implementation, furthermore you can choose at runtime which functionality you need to add to the existing object.
In FP this is achieved by Composition
, one of the core ideas of FP is to separate functionality into small functions that can be composed to form other behaviors, so instead of having a class you wrap around, you take small functions like LEGO
blocks and piece them all together to create new behavior. Let's see an example:
const compose = (...fns) => x => fns.reduceRight ((v, f) => f(v), x);
function isBiggerThanThree(value) {
return value > 3
}
function mapBoolToHumanOutput(value) {
return value ? "yes": "no"
}
const biggerThanThreeAndMapOutput = compose(
mapBoolToHumanOutput,
isBiggerThanThree
)
biggerThanThreeAndMapOutput(3)
In this example, we define two functions that do one little thing and then compose them to generate new behavior, as long as composition apply we can keep adding more functionality to the mix ๐
Observer Pattern
The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
This pattern relies on the idea of Push vs Pull. Instead of having an object constantly checking if the state of another has changed, the second object notifies all others when its own state has changed.
In FP this is a super used pattern in the form of Callbacks, Events, and Observers. They are also highly used in libraries like RX.js for Reactive Functional Programming
I am gonna refer to an answer to "What are Callbacks?" in Stack Overflow.
Singleton Pattern
The singleton pattern is a software design pattern that restricts the instantiation of a class to one "single" instance. This is useful when exactly one object is needed to coordinate actions across the system. The term comes from the mathematical concept of a singleton.
In OOP languages this involves creating a static instance of an object to make it available everywhere without instantiation and making sure that if someone tries to instantiate it again, the previous instance is returned.
In FP this pattern becomes completely unnecessary, there is no global state that needs to be exposed to the entire application since data is fully separated from functions. Also, all functions exist in a global namespace, are always accessible, they take input and produce a result without affecting the outside world.
Conclusion
As you can see, it seems that some of the patterns that have evolved within the OOP world seem to give those languages the benefits that FP gets for free, without losing the things that characterize OOP like control over state and data.
There are a few other patterns that I didn't mention because they fit into the solutions proposed by the previously mentioned examples.
As I continue on my journey into functional programming I will probably find more examples and cases that will fit this assumption of mine. If you liked this article please let me know in the comments so I will keep bringing them in.
Don't forget to share it ๐
Top comments (6)
It's so wonderful to see how simple some OOP patterns are in the FP world, and achieved with much fewer lines of code. ๐ช
Loved the article!
Great article, keep up the good work
FP also has its own patterns. In particular Monad is one of such patterns.
Totally true, but this was more of a comparison between the patterns of the gang of four book to functional programming, not the other way around. But yes, functional programming has its own patterns though I think they are much smaller and simpler
Well, from my point of view they are not simpler, but different. The "apply my function when data available" way of thinking behind monads is a true pleasure after countless "if's" of traditional imperative code.