This post is Part 2 of a series on utilizing Design Patterns in the context of Functional Programming. If you are interested in learning more, stay tuned for more on my DEV page.
Introduction
As mentioned in the first part of the series, design patterns in Software Engineering (SE) have been widely popularized by the book Design Patterns: Elements of Reusable Object-Oriented Software written by the Gang of Four (GoF).
The book further categorizes design patterns into what kind of problems these patterns are designed to solve, namely the following categories:
- Creational - Patterns that provide ways to instantiate single or groups of objects.
- Structural - Patterns that provide ways to define relationships between classes and objects.
- Behavioural - Patterns that define manners of communication between classes and objects.
Previously, we looked at the Strategy Pattern, which is a Behavioural pattern.
You can learn more about the categorization of the different patterns using this excellent page by Vince Huston.
Vince's Periodic Table inspired diagram on Design Patterns.
[It's interactive too!](http://www.vincehuston.org/dp/)
For this article, we'll take a look at a Creational pattern: the Factory Method pattern.
The Factory Method Pattern
The Factory Method design pattern describes exposing a factory method in a Creator class without specifying the exact Product class that will be created. The factory method lets the Creator class defer instantiation of Products to its subclasses.
Whew! Once again, what a mouthful.
Let's break this definition down like we did last time. We have a few key components here:
- Creator - An interface or abstract class that defines a factory method which returns a Product.
- Product - An interface or abstract class that provides an abstract layer of reference to a concrete product.
- Factory Method - A method that is implemented by concrete creators, which specific a concrete product to return.
If this is confusing, do not fret! This is easier to visualize with some examples!
Retail Store Example
In the spirit of the previous article, let us use the same Retail Store example but in a different problem context.
Suppose that for the retail store, we want to introduce a simulator to better estimate our business. In our simulator, we have two different "customer modes" we can select, where we can simulate a really bad and picky customers, or simulate a really good and gracious customers.
We want to differentiate the way we deal with creating the different Customers but without affecting the rest of the simulator code. We can implement the Factory Method pattern as follows:
interface CustomerCreator {
public Customer createCustomer();
}
interface Customer {
public boolean isGood();
}
class GoodCustomerCreator implements CustomerCreator {
public Customer createCustomer() {
return new GoodCustomer();
}
}
class BadCustomerCreator implements CustomerCreator {
public Customer createCustomer() {
return new BadCustomer();
}
}
class GoodCustomer implements Customer {
public boolean isGood() {
return true;
}
}
class BadCustomer implements Customer {
public boolean isGood() {
return false;
}
}
As you can see, we can easily decouple the logic of having different ways to create and represent products. This pattern really shines when:
-
There are an increasing number of different ways of create products. Let say for example, in our store simulator, we now want a way that will randomly return us a Good or Bad customer. We can simply create a
RandomCustomerCreator
and encapsulate the randomizing logic there. Our parent process will still only have to maintain a reference to aCreator
class. -
There are an increasing number of products. Similarly, we only need to implement the
Customer
interface for any new customers and implement any additional logic there.
The Functional Approach
Let's see Strategy Pattern implemented in a Functional Programming-friendly way.
Currying
For this pattern, we can take advantage of a concept known as Currying. Currying is the concept mostly seen in functional programming and mathematics as the process where a function taking multiple arguments can be transformed into a function that takes in only a single argument while returning another function which accepts further arguments.
Another wordy definition! Let us illustrate this with a simple example:
Let us implement a function to add three numbers together:
const addMyTriple = (a, b, c) => {
return a + b + c;
}
addMyTriple(1, 2, 3) // Returns 6.
Currying this function would give us the following:
const addMyTriple = (a) => {
return (b) => {
return (c) => {
return a + b + c;
}
}
}
addMyTriple(1)(2)(3) // Returns 6.
This looks like a we're just increasing the verbosity of our function doesn't it? But the key is in realizing that we're returning the inner function every time. Suddenly, this becomes invaluable as we can fix variables and form other functions by using currying.
Here's an example. Let's say we want to add three numbers like before, but now we want to fix the first number to always be 10
.
We can simply compose a new function by doing this:
const addMyDoublePlusTen = addMyTriple(10);
addMyDoublePlusTen(2,3) // Returns 15.
Now let us apply this in implementing the Factory Method Pattern.
const customerCreator = (isGood) => {
return isGood ? goodCustomer : badCustomer;
}
const customer = (isGood) => {
return {
isGood: isGood
};
}
const goodCustomerCreator = () => {
return customerCreator(true);
}
const badCustomerCreator = () => {
return customerCreator(false);
}
const goodCustomer = () => {
return customer(true);
}
const badCustomer = () => {
return customer(false);
}
As you can see, we utilize currying here to differentiate the different creator functions by their type. We do that similarly for the customers themselves.
While this example seems intuitive, the key to understanding when to use the pattern is in two key nuances:
- How do you want to your instantiate objects? (Order, Composition, Configurations, etc.)
- How to represent the different types of objects? (Data Structure, etc.)
You might realize that this pattern maintains encapsulation of logic really nicely, even as the complexity of the example grows (which ties in with the whole point of using design patterns)!
Extending the example
To catalyze your learning process, think about some of the following questions and how you would implement them:
- Suppose in our simulator we maintain a list of 10 Customers. How would you implement the Creators for a good day and a bad day?
- What if there were instead 3 types of Customer? (Good, Bad, Neutral)
What's next
This post is Part 2 of a series on utilizing Design Patterns in the context of Functional Programming. If you are interested in learning more, stay tuned for more on my DEV page.
Top comments (1)
Hi, great post!
addMyDoublePlusTen(2,3) // Returns 15.
or
addMyDoublePlusTen(2)(3) // Returns 15.