Strategy pattern in functional programming
Strategy pattern in functional programming

Strategy pattern in functional programming

Written on

During the past couple of weeks I was involved in architecting and implementing a solution for complex model transformations. It was part of a wider MDD (Model Driven Development) approach I was trying to comprehend and apply to the use case of the project I am currently working on. During this endeavor I was confronted with a number of challenges. Eventually it all came down to one common denominator: the Strategy Pattern.

For demonstration purposes, let’s reduce my use case to one simple problem. I have a USER model that comes from two different sources: a database and a web service. Each source returns it in a different form, though the forms basically contain the same data. I want to parse these external data forms into a transitional data form that my application understands. This transitional data form is called the Correspondence model (in MDD theory). Once I have both external models in the Correspondence model, I want to merge them and map the result to the API model. The API model is exposed from the application through the JSON REST API. The merging and mapping is out of scope for this article, so let’s stick with the parsing part.

I will now demonstrate how to approach this problem from two perspectives: OOP (Object-Oriented Programming) and FP (Functional Programming). I have a long history of involvement with OOP languages and have extensively studied the work of Martin Fowler and the Gang of Four. Since I have longer experience in OOP than in FP, I designed my first prototype in OOP. Then I moved forward and ported the OOP code into FP, using the fact that in JavaScript all functions are first-class objects.

OOP version

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Parser {
  constructor(parseStrategy) {
    this.parseStrategy = parseStrategy;
  }

  parse(externalModel) {
    return this.parseStrategy.parse(externalModel);
  }
}

class UserDbParser {
  parse(dbUser) {
    return ...correspondenceUser; // This would contain the parsing algorithm.
  }
}

class UserWebServiceParser {
  parse(webServiceUser) {
    return ...correspondenceUser; // This would contain the parsing algorithm.
  }
}

const userDbParser = new Parser(new UserDbParser());
const userWebServiceParser = new Parser(new UserWebServiceParser());

DB.user.findById(1).then(userDbParser.parse.bind(userDbParser));
WebService.user.fetchOne(1).then(userWebServiceParser.parse.bind(userWebServiceParser));

This is how USER model parsing looks in OOP using the Strategy Pattern. I would probably use some kind of IoC container to manage the instances of my classes.

FP version

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { curry } = require('ramda');

const parseDbUser = (dbUser) => { return ...correspondenceUser };
const parseWebServiceUser = (webServiceUser) => { return ...correspondenceUser };

const toCorrespondence = curry(
  (parseStrategy, dbOrWebServiceUser) => parseStrategy(dbOrWebServiceUser)
);

const fromDb = toCorrespondence(parseDbUser);
const fromWebService = toCorrespondence(parseWebServiceUser);

DB.user.findById(1).then(fromDb);
WebService.user.fetchOne(1).then(fromWebService);

This is how USER model parsing looks in FP using the Strategy Pattern, or at least my version of implementing the Strategy Pattern in FP. I could not find any coherent articles on the issue to validate my approach. Anyway, the FP version is less verbose, has less overhead, and doesn’t need any IoC. To tell you the truth, I immediately liked it better.

So there it is: the Strategy Pattern in FP. I hope this article saves somebody some time when researching a similar issue. And finally, one wonders how other OOP design patterns look in FP, right? ;]

Update (12.08.2017)

Alex Hart was kind enough to provide us with an approach to implementing the Strategy Pattern in Haskell.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
data Parser = WebParser | DbParser

class ParseStrategy p where
  parse :: p -> String -> String

instance ParseStrategy Parser where
  parse WebParser s = "code for parsing web stuff goes here"
  parse DbParser s = "code for parsing db stuff goes here"

fromDB = parse DbParser
fromWeb = parse WebParser

main :: IO ()
main = do
  -- decide which parser to utilize
  let s = fromDB "asdf"
  putStrLn s

Fork me on GitHub