First of all, usually you can't implement a design pattern using some mechanism of a language, as design patterns are not expressible directly in the language type system and syntax. Design patterns describe a particular methodology for solving recurring problems in software development. As soon, as a language provides a mechanism to express a design pattern directly, this is no longer considered a design pattern. Thus something that is a design pattern in one language, becomes a mechanism in another language. For example, a loop in assembly language is a design pattern, though in most modern languages it's a syntactic construct. A presence of design patterns usually indicates a lack of expressivity of a particular language or programming paradigm. Though, no matter how expressive your language, there always be abstractions, that can't be implemented directly using the language mechanisms.
You should also understand that GoF design patterns were written with the OOP paradigm in mind with peculiarities and limitations of OOP languages of that time. So they are not always applicable or even needed in OCaml/Reason or any other languages with parametric polymorphism and first-class functions.
In particular, the problem that the Builder pattern is trying to solve is an absence of first-class constructors and parametric polymorphism. Since we have both in Reason, we are usually not bothered with designing complex hierarchies of types. Another limitation of OOP is an absence of algebraic data types, that are ideal language mechanism for implementing complex compound data structures such as abstract syntax trees (expression parsing trees) and so on.
With all this said, you can still use the Builder pattern in Reason, but most likely you don't actually need it, as the language provides much better and more expressible mechanisms for solving your problem. Let's use the SportsCarBuilder code from Wikipedia, as our working example,
/// <summary>
/// Represents a product created by the builder
/// </summary>
public class Car
{
public string Make { get; }
public string Model { get; }
public int NumDoors { get; }
public string Colour { get; }
public Car(string make, string model, string colour, int numDoors)
{
Make = make;
Model = model;
Colour = colour;
NumDoors = numDoors;
}
}
/// <summary>
/// The builder abstraction
/// </summary>
public interface ICarBuilder
{
string Colour { get; set; }
int NumDoors { get; set; }
Car GetResult();
}
/// <summary>
/// Concrete builder implementation
/// </summary>
public class FerrariBuilder : ICarBuilder
{
public string Colour { get; set; }
public int NumDoors { get; set; }
public Car GetResult()
{
return NumDoors == 2 ? new Car("Ferrari", "488 Spider", Colour, NumDoors) : null;
}
}
/// <summary>
/// The director
/// </summary>
public class SportsCarBuildDirector
{
private ICarBuilder _builder;
public SportsCarBuildDirector(ICarBuilder builder)
{
_builder = builder;
}
public void Construct()
{
_builder.Colour = "Red";
_builder.NumDoors = 2;
}
}
public class Client
{
public void DoSomethingWithCars()
{
var builder = new FerrariBuilder();
var director = new SportsCarBuildDirector(builder);
director.Construct();
Car myRaceCar = builder.GetResult();
}
}
We will provide a one-to-one translation from C# to Reason, to show the direct counterparts of C# mechanisms in Reason. Note, we will not build an idiomatic Reason code, people will unlikely follow the Builder Pattern in Reason.
The Car
class defines an interface of a build product. We will represent it as a module type in Reason:
module type Car = {
type t;
let make : string;
let model : string;
let numDoors : int;
let colour: string;
let create : (~make:string, ~model:string, ~numDoors:int, ~colour:string) => t;
};
We decided to make the car type abstract (letting an implementor to choose a particular implementation, whether it would be a record, an object, or maybe an key to a SQL database of cars.
We will now define a corresponding interface for the car builder:
module type CarBuilder = {
type t;
type car;
let setColour : (t,string) => unit;
let getColour : t => string;
let setNumDoors : (t,int) => unit;
let getNumDoors : t => int;
let getResult : t => car;
}
Now let's implement a concrete builder. Since we decided to make the car type abstract, we need to parametrize our concrete builder with the car type. In OCaml/Reason, when you need something to parametrize with a type, you usually use functors.
module FerariBuilder = (Car: Car) => {
type t = {
mutable numDoors: int,
mutable colour: string
};
exception BadFerrari;
let setColour = (builder, colour) => builder.colour = colour;
let getColour = (builder) => builder.colour;
let setNumDoors = (builder, n) => builder.numDoors = n;
let getNumDoors = (builder) => builder.numDoors;
let getResult = (builder) =>
if (builder.numDoors == 2) {
Car.create(~make="Ferrari", ~model="488 Spider",
~numDoors=2, ~colour=builder.colour)
} else {
raise(BadFerrari)
};
};
And finally, let's implement a director.
module Director = (Car: Car, Builder: CarBuilder with type car = Car.t) => {
let construct = (builder) => {
Builder.setColour(builder, "red");
Builder.setNumDoors(builder, 2)
};
};
I will let you implementing the user code as an exercise. Hint, you need to start with a concrete implementation of the Car
interface. You may look and play with the code (including the OCaml and Javascript version) at Try Reason.