Embracing functional style within object-oriented paradigm
Use polymorphic dispatch and perplexed objects to embrace functional style within object oriented programming.
In this article, we will discuss a design pattern which will use coarse-grained models to approach functional style in object oriented paradigm. This pattern is about adding a layer of indirection to define the operations/methods/behaviour for a class in one place, an interface, without even touching the class definitions. This pattern is often called visitor pattern.
Roughly, visitor pattern can be described as,
Defining an operation or behaviour of a class or a type in one interface and then use that interface by polymorphism to provide the behaviour to the required class or type.
What was that ? In simple words,
Define behaviour in one place for a set of classes or types without having to touch the class or that type itself.
If you still feel tempted by these definitions then don’t worry. I have added a list of points at the end of the article for this pattern that summarises it in brief and it may help you to use this pattern in your application or library.
This pattern lets us use the functional programming model within the object oriented programming. Let’s consider an example. Say we have two types of cars - Sedan and Hatchback.
We want to be able to define methods on these classes for driving, accelerating, brake and etc without having to add new methods to the class every time. So how do we do it ? First, we will define the methods in a separate interface.
Now to execute these methods, we will need to define a new class that implements this interface. Oh! This is JavaScript, so we will just use a reference to the object CarVisitor. In Java, you would use the below syntax —
Let’s come back to JavaScript and define a class to use this interface. We need to consider one thing, i.e we require some sort of way to operate a particular type of car. We need to define a concrete method to operate different types of cars. Let’s see how -
In the above example, we have declared an abstract method called operate which takes a reference to our interface CarVisitor.
This is an abstract method so we will implement it in the classes that will be derived from this class Car using polymorphism (we will use method signature).
Now let’s define two other classes, Sedan and Hatchback which will extend the class Car and will override the method operate using method signature.
Simple!
You can see that we haven’t even touched the class definitions for Sedan and Hatchback, and have defined the methods separately for both the classes in an interface. So every time we need to add some functionality to these classes, we jump into the interface CarVisitor directly and not touching the classes itself. Neat!
Now to execute a method on a car we just need to call operate and pass in the visitor for the method that we want to execute. The operate method will then call the appropriate visitor method on the visitor by passing the current context of execution i.e your favourite this.
Given below is the complete example.
Awesome!
Following is a list of points (a brief summary) for this pattern which may help you in getting a better understanding and usage of this pattern.
- No need to select each class every time to inject the behaviour.
- Disoriented objects (classes don’t contain the methods).
- Classes/Types and function/methods/behaviour are totally distinct.
- To implement a behaviour, you just need to declare one function only in the interface and not in every class.
- Similar to pattern matching (functional paradigm), execute the function based on a particular type.
- Makes it trivial to add new methods.
- Functional style within an OOP language.
- Easy to route types to the correct method based on the visitor.
- Uses polymorphic dispatch to select appropriate method on the visitor.
Obviously you can add more refinements to this pattern. It was just a glimpse!
Considering an another scenario
Let’s try to understand this pattern with another example without using any code. The example below is inspired from Bob Nystrom’s work.
In the above example, rows are classes and columns are operations.
In object-oriented environment, you’d use classes to encapsulate the state and behaviour, and imagine that all the code in one row lives together. This also makes it easy to add new classes by adding new rows to the table and extending it further without touching the code for other classes.
BUT, imagine if you wanted to add a new operation or a new column inside that table. You’ll need to touch all the three classes to add the method individually to each class. This is heavy and messy stuff.
Pattern matching to the rescue!
Hmm… so you decided to use functional programming instead to tackle this problem as it does not have classes with methods, in fact both are recognisably different. So you would use something called pattern matching. You will define a single function and then based on different types (classes in OOP) execute that function. This makes it more easy because now you just need to add a particular function and then execute it using type-switch approach.
BUT, you need to add a new type which is hard. You have to add a new case to all of the pattern matches in all of the existing functions that you have created.
SO WHAT THE HELL I SHOULD USE ?
Calm down son, every programming paradigm has its own features and fragments associated to it. In object oriented programming, you were managing your code along with the rows and in functional programming, you were creating column’s worth of code together into the functions. It does makes sense to use a particular paradigm which suites our architecture perfectly but in situations like these you may run into a problem in which you may not have any solution to tackle this problem.
So, why not to use a pattern that let’s us taste both the sweets, or programming paradigms (if you’ve diabetes) without running into some hacky solutions ? Woah! Exactly, you can use visitor pattern to embrace functional programming within the object-oriented paradigm. Case solved!
You define all the operations inside an interface (or an object in JavaScript) and then use polymorphism to override the method using method signatures to execute those operations.
That’s it! Let me know if you use this pattern in your application or library.
If you liked reading this article, then don’t forget to share it on Twitter. I am @NTulswani on Twitter.
Thank you so much for reading! Have a nice day 😃