Chapter 3. F# and Design Patterns

I constantly hear people say that F# is a cool cutting-edge technology, but they do not know how to use it. In previous chapters, I showed how C# developers can pretty much map their existing imperative, LINQ, and data-structure knowledge to F#. However, this is not enough know-how to design or implement a component or a system. In this chapter, I use well-known design patterns to introduce performing system design by using F#. The samples in this chapter use unique F# language features to implement well-known design patterns. These samples will help you start to think of F# as something other than a niche language.

I do not see a huge difference between computer language and human language. Both languages are used to convey human thinking, only the audiences are different. One is the computer, and the other is a human. If you want to master a language and use it to write a beautiful article, having knowledge of only the basic words of that language would definitely not be enough. Likewise, if people really want to use F# fluently in their daily programming work, they need to know more than how to write a float type and a FOR loop.

In this chapter, a number of design patterns are implemented in F#. These implementations should help you gain more insight about how our team designed the language and, consequently, how to use these features to solve system-design problems. Ultimately, my goal is to help you start to really think in F# terms.

There are some design patterns that are easily implemented with more advanced F# language features, such as F# object expressions. I am not going to discuss every aspect of these features. More detailed information about these special language features will be presented in Chapter 5. If any aspects of this chapter are not clear, I encourage you to refer to Chapter 5, where F# unique features are introduced in detail.

Using Object-Oriented Programming and Design Patterns

Like many well-studied concepts, design pattern has many definitions. In this book, I borrow the definition from the Wikipedia page on the topic (http://en.wikipedia.org/wiki/Software_design_pattern). My quick definitions of the design patterns in this chapter are also largely based on Wikipedia.

The design pattern is the reusable solution template for a problem. It can speed up the development process by providing tested, proven development paradigms. The effective software design requires considering problems that may not become obvious until later in the implementation. Reusing design patterns helps to prevent subtle issues that can cause major problems, and it also improves code readability for coders and architects who are familiar with the patterns.

From the preceding statements, you can see that design patterns are not necessarily tied to specific languages or programming paradigms. Given that the object-oriented programming (OOP) paradigm is the most used, most design-pattern implementations and discussions are based on languages that target OOP—for example, C#. Some people from the functional programming community have suggested that design patterns are merely a means to address flaws in OOP languages. I will not go into the details of this topic; instead, I will cover how to use F# to implement design patterns.

First, I’ll cover three basic concepts in programming languages that primarily target OOP:

  • Encapsulation is a construct that facilitates the bundling of data with methods (or other functions) that operate on that data.

  • Inheritance is a way to compartmentalize and reuse code. It creates a subtype based on an existing type.

  • Polymorphism: subtype polymorphism, which is almost universally called just polymorphism in the context of object-oriented programming, is the ability to create a variable, a function, or an object that has more than one form.

The typical C# implementations of design patterns often use all three of these concepts. In the rest of the chapter, you will see how F# can use both OOP and functional features to implement most common design patterns.

Before demonstrating these design patterns, I’d like to remind you that a design pattern can have more than one implementation. Each of the implementations in the following examples show different F# language features in practice. Additionally, they provide a better way to apply F# in component or system design than what would be achieved by simply porting over a C# implementation.

Working with F# and Design Patterns

Let’s start by looking at some of the design patterns that will be discussed in this chapter along with the definitions of each. Note that the following definitions are from an OOP perspective, so the definitions occasionally still use object-oriented terminology:

  • The chain of responsibility pattern avoids coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. It chains the receiving objects and passes the request along the chain until an object handles it.

  • The decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

  • The observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

  • The proxy pattern provides a surrogate or placeholder for another object to control access to it.

  • The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern lets the algorithm vary independently from clients that use it.

  • The state pattern allows an object to alter its behavior when its internal state changes.

  • The factory pattern lets a class defer instantiation to subclasses.

  • The adapter pattern and bridge pattern are both used to convert the interface of a class into another interface. The adapter pattern lets classes work together that couldn’t otherwise because of incompatible interfaces. If we don’t focus on interfaces or classes, we can rephrase the definition to a shorter one: These are patterns that provide a way to allow incompatible types to interact.

  • The singleton pattern ensures a class has only one instance and provides a global point of access to it.

  • The command pattern is used to allow an object to store the information needed to execute some other functionality at a later time. For example it can help implement a redo-undo scenario.

  • The composite pattern describes a group of objects that are to be treated in the same way as a single instance of an object. The intent of a composite is to compose objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly. The visitor pattern separates the algorithm implementation from the data structure. These two patterns can work together. The composite pattern forms a tree structure, and the visitor pattern applies a function to the tree structure and brings the result back.

  • The template pattern is, as its name suggests, a program or algorithm skeleton.

  • The private data class pattern is used to encapsulate fields and methods that can be used to manipulate the class instance.

  • The builder pattern provides abstract steps of building objects. Using this pattern allows a developer to pass different implementations of abstract steps.

  • The façade pattern allows you to create a higher level interface that can be used to make it easier to invoke underlying class libraries.

  • The memento pattern saves an object’s internal state for later use.

Working with the Chain of Responsibility Pattern

The chain of responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains a set of logic that describes the types of command objects it can handle and how to pass off those it cannot handle to the next processing object in the chain. The sample in Example 3-2 shows a physical check process that needs to make sure that a person’s age is between 18 and 65, that their weight is no more than 200 kilograms, and that they are taller than 120 centimeters.

The type in Example 3-2 is called a Record. Example 3-2 uses a Record to store a patient’s medical data. It has several named fields that are used to hold the patient’s data. It is very much like a database record. Example 3-1 shows how to define a Record type and create a record object. The sample code creates a point record that has its X and Y fields set to (1, 1).

Example 3-1. Defining a record type and creating a Record object
// define a point record
type Point2D = {
    X : float
    Y : float
}

// create original point record
let originalPoint = { X = 0.0; Y = 0.0 }

// create (1,1) point record
let onePoint = { X = 1.0; Y = 1.0 }

The record object implicitly forces data initialization; therefore, initial values are not optional when creating a Record type. The invoker must define the patient with some data, and this eliminates any possible initialization problems.

Example 3-2. Chain of responsibility pattern
// define a record to hold a person's age and weight
type Record = {
    Name : string;
    Age : int
    Weight: float
    Height: float
}

// Chain of responsibility pattern
let chainOfResponsibility() =

    // function to check that the age is between 18 and 65
    let validAge record =
        record.Age < 65 && record.Age > 18

    // function to check that the weight is less than 200
    let validWeight record =
        record.Weight < 200.

    // function to check that the height is greater than 120
    let validHeight record =
        record.Height > 120.

    // function to perform the check according to parameter f
    let check f (record, result) =
        if not result then record, false
        else record, f(record)

    // create chain function
    let chainOfResponsibility = check validAge >> check validWeight >> check validHeight

    // define two patients' records
    let john = { Name = "John"; Age = 80; Weight = 180.; Height = 180. }
    let dan = { Name = "Dan"; Age = 20; Weight = 160.; Height = 190. }

    printfn "John's result = %b" (chainOfResponsibility (john, true) |> snd)
    printfn "Dan's result = %b" (chainOfResponsibility (dan, true) |> snd)

Execution result from the chain of responsibility sample

John's result = false
Dan's result = true

Note

You have to execute the chainOfResponsibility function to get the result shown.

In the implementation in Example 3-2, three functions (responsibilities) are composed into a chain and the data is passed along the chain when it is being processed. The parameter passed in contains a Boolean variable that decides whether the data can be processed. In Example 3-2, all the functions are in effect AND-ed together. The parameter passed into the first function contains a Boolean value. The successive function can be invoked only if the Boolean value is true.

The other implementation is used for pipelining, as shown in Example 3-3, rather than function composition. The chainTemplate higher-order function takes a process and canContinue function. The canContinue function always returns true, and the process function is a simple “increase one” function. The execution result is 2.

Example 3-3. Chain of responsibility sample using pipelining
// chain template function
let chainTemplate processFunction canContinue s =
    if canContinue s then
        processFunction s
    else s

let canContinueF _ = true
let processF x = x + 1

//combine two functions to get a chainFunction
let chainFunction = chainTemplate processF canContinueF

// use pipeline to form a chain
let s = 1 |> chainFunction |> chainFunction

printfn "%A" s

The other chain of responsibility implementation uses the partial pattern feature in F#. I introduced the unit of measure to make the code readable. The process goes from the first case and stops when the condition is met. The sample code is listed in Example 3-2. The sample code checks the height and weight value for some predefined criteria. The person’s data is checked against NotPassHeight and then NotPassWeight if his height passes the validation criteria. The code also demonstrates how to use the F# unit-of-measure feature, which avoids possible confusion because of the unit of measure used. The parameter for makeCheck is #Person, which means that any object of type Person or derived from a Person type can be passed in.

Example 3-4 uses units-of-measure language constructs within a calculation. Only the number with the same unit of measure can be involved in the same calculation. Example 3-4 shows how to define a kilogram (kg) unit and decorate it with a number.

Example 3-4. Defining and using a kg unit of measure
// define unit-of-measure kg
[<Measure>] type kg

// define 1kg and 2kg variables
let oneKilo = 1<kg>
let twoKilo = 1<kg> + 1<kg>

The None and Some(person) syntax in the sample code in Example 3-6 represents a Nullable-type-like data structure called an option. You can think of None as NULL. The special function let (| NotPassHeight | _ |) is called an active pattern. It takes a person parameter and decides whether the person meets certain criteria. If the person meets the criteria, the function returns Some(person) and triggers the match statement. Example 3-5 shows how to use the Some()/None syntax to check for an odd number. This sample introduced several new concepts. I will come back to these concepts in detail in Chapter 5.

Example 3-5. Using active pattern, option, and match to check for an odd number
// define an active pattern function to check for an odd number
let (| Odd | _ |) x = if x % 2 = 0 then None else Some(x)

// define a function to check for an odd number
let findOdd x =
    match x with
    | Odd x -> printfn "x is odd number"
    | _ -> printfn "x is not odd number"

// check odd number
findOdd 3
findOdd 4

Execution result

x is odd number
x is not odd number
Example 3-6. Chain of responsibility pattern using partial pattern matching
// define two units of measure: cm and kg
[<Measure>] type cm
[<Measure>] type kg

// define a person class with its height and weight set to 0cm and 0kg
type Person() =
    member val Height = 0.<cm> with get, set
    member val Weight = 0.<kg> with get, set

// define a higher order function that takes a person record as a parameter
let makeCheck passingCriterion (person: #Person) =
    if passingCriterion person then None  //if passing, say nothing, just let it pass
    else Some(person)   //if not passing, return Some(person)

// define NotPassHeight when the height does not meet 170cm
let (| NotPassHeight | _ |) person = makeCheck (fun p -> p.Height > 170.<cm>) person

// define the NotPassWeight when weight does not fall into 100kg and 50kg range
let (| NotPassWeight | _ |) person =
    makeCheck (fun p -> p.Weight < 100.<kg> && p.Weight > 50.<kg>) person

// check incoming variable x
let check x =
    match x with
    | NotPassHeight x -> printfn "this person is not tall enough"
    | NotPassWeight x -> printfn "this person is out of weight range"
    | _ -> printfn "good, this person passes"

// create a person with 180cm and 75kg
let p = Person(Height = 180.<cm>, Weight = 75.<kg>)

// perform the chain check
check p

Execution result

good, this person passes

Working with the Adapter Pattern

The adapter pattern is a design pattern that translates one interface for a type into an interface that is compatible with some other type. An adapter allows classes to work together that normally could not because of incompatible types. In Example 3-8, we use the Generic Invoke(GI) function as an adapter or bridge to invoke two methods of incompatible types. By using the GI function, a common interface is no longer needed and the function can still be invoked. The GI function is a static type constraint function, it requires that type T define a certain member function. For example, in Example 3-7, it requires that the type T has a canConnect function that takes void (unit) and returns a Boolean. (Note that F# requires you to declare a function as “inline” when arguments of the function are statically resolved type parameters such as those in the following code listing.)

Example 3-7. GI function
// define a GI function
let inline canConnect (x : ^T) = (^T : (member CanConnect : unit->bool) x)

The interesting thing about the design pattern implementation in Example 3-8 is that Cat and Dog do not have any common base class or interface. However, they can still be processed in a unified function. This implementation can be used to invoke the legacy code, which does not share any common interface or base class. (You should note, by the way, that this is a sloppy way of solving the problem and should be considered only when no other option is available.)

Imagine that you have two legacy systems that need to be integrated and that you do not have access to the source code. It would be difficult to integrate the systems in other languages, but it’s possible and even easy in F# using the generic invoke technique.

Example 3-8. The adapter pattern (bridge pattern)
//define a cat class
type Cat() =
    member this.Walk() = printfn "cat walks"

// define a dog class
type Dog() =
    member this.Walk() = printfn "dog walks"

// adapter pattern
let adapterExample() =
    let cat = Cat()
    let dog = Dog()

    // define the GI function to invoke the Walk function
    let inline walk (x : ^T) = (^T : (member Walk : unit->unit) x)

    // invoke GI and both Cat and Dog
    walk(cat)
    walk(dog)

Execution result from adapter pattern sample

cat walks
dog walks

Note

The implementation in Example 3-8 can also be viewed as a bridge pattern.

Working with the Command Pattern

The command pattern is a design pattern in which an object is used to represent and encapsulate all the information needed to call a method at a later time. Example 3-10 shows how to use the command pattern to implement a redo-undo framework. This is an example of typical usage of the command pattern in the OOP world.

Example 3-9 defines a result using the ref keyword. The ref keyword defines a reference type that points to the value 7. The result is a reference cell. You can think of the ref keyword as a way to define a mutable variable.

Example 3-9. Reference cell
// define a reference cell to value 0
let a = ref 0

// define a function to increase a's value by 1
let increaseA() =
    a := !a + 1

// increase a's value and print out result
increaseA()
printfn "a = %A" !a

Execution result

a = 1

Note

F# provides incr and decr to increase or decrease reference cell values by 1. When using the incr function, the increaseA function becomes let increaseA() = incr a.

Note

The := operator is used to assign a new value to the content of the reference cell. The ! (pronounced bang) operator is used to retrieve the reference cell content.

Example 3-10. Command pattern
// define a command record
type Command = { Redo: unit->unit; Undo: unit->unit }

let commandPatternSample() =

    // define a mutable storage
    let result = ref 7

    // define the add command
    let add n = {
        Redo = (fun _ -> result := !result + n)
        Undo = (fun _ -> result := !result - n) }

    // define the minus command
    let minus n = {
        Redo = (fun _ -> result := !result - n)
        Undo = (fun _ -> result := !result + n) }

    // define an add 3 command
    let cmd = add 3
    printfn "current state = %d" !result

    // perform add 3 redo operation
    cmd.Redo()
    printfn "after redo: %d" !result

    // perform an undo operation
    cmd.Undo()
    printfn "after undo: %d" !result

Execution result from the command pattern sample obtained by invoking the commandPatternSample function

current state = 7
after redo: 10
after undo: 7

Note

There is no storage structure for command history; however, adding such a storage structure is trivial.

Note

According to the MSDN documentation (http://msdn.microsoft.com/en-us/library/dd233186.aspx), a mutable variable should be used instead of a reference cell whenever possible. The preceding code uses a reference cell just for demo purposes. You can convert this code to use a mutable variable.

There is another implementation that emphasizes that the command can be treated like data. The code defines two types of commands: deposit and withdraw. The Do and Undo functions are used to perform the do and undo actions. See Example 3-12.

To implement this Do and Undo functionality, it is helpful to understand the F# discriminated union (DU) feature. Example 3-11 demonstrates how to use a DU to check whether or not the given time is a working hour. Note how the first DU, DayOfAWeek, looks a lot like an enum, but without the default numeric value. In the second example, TWorkingHour, the DU case Hour has a tuple value, where the first element of the tuple is a DayOfAWeek and the second element is an integer.

Example 3-11. Using DU to check whether the given time is a working hour
// define day of the week
type DayOfAWeek =
    | Sunday
    | Monday
    | Tuesday
    | Wednesday
    | Thursday
    | Friday
    | Saturday

// define working hour
type TWorkingHour =
    | Hour of DayOfAWeek * int

// check that the working hour is Monday to Friday 9:00 to 17:00
let isWorkingHour day =
    match day with
    | Hour(Sunday, _) -> false
    | Hour(Saturday, _) -> false
    | Hour(_, time) -> time >= 9 && time <= 17

// check if Sunday is working hour
let sunday = Hour(Sunday, 9)
printfn "%A is working hour? %A" sunday (isWorkingHour sunday)

// check if Monday 10:00 is working hour
let monday = Hour(Monday, 10)
printfn "%A is working hour? %A" monday (isWorkingHour monday)

Execution result

Hour (Sunday,9) is working hour? false
Hour (Monday,10) is working hour? true

Now that you understand discriminated unions, you can apply them to the command pattern.

Example 3-12. Command pattern implementation II
// define two command types
type CommandType =
    | Deposit
    | Withdraw

// define the command format, which has a command type and an integer
type TCommand =
    | Command of CommandType * int

// mutable variable result
let result = ref 7

// define a deposit function
let deposit x = result := !result + x

// define a withdraw function
let withdraw x = result := !result - x

// do function to perform a do action based on command type
let Do = fun cmd ->
    match cmd with
    | Command(CommandType.Deposit, n) -> deposit n
    | Command(CommandType.Withdraw,n) -> withdraw n

// undo function to perform an undo action based on command type
let Undo = fun cmd ->
    match cmd with
    | Command(CommandType.Deposit, n) -> withdraw n
    | Command(CommandType.Withdraw,n) -> deposit n

// print the current balance
printfn "current balance %d" !result

// deposit 3 into the account and print the balance
let depositCmd = Command(Deposit, 3)
Do depositCmd
printfn "after deposit: %d" !result

// undo the deposit command and print the balance
Undo depositCmd
printfn "after undo: %d" !result

Execution result

current balance 7
after deposit: 10
after undo: 7

Working with the Observer Pattern

The observer pattern is a pattern in which a subject object maintains a list of its observer dependents. The subject automatically notifies its dependents of any changes by calling one of the dependent’s methods. The implementation in Example 3-13 passes the function into the subject, and the subject notifies its changes by calling this function along with some parameters.

Example 3-13. Observer pattern
// define a subject
type Subject() =
    // define a default notify function
    let mutable notify = fun _ -> ()

    // subscribe to a notification function
    member this.Subscribe notifyFunction =
        let wrap f i = f i; i
        notify <- wrap notifyFunction >> notify

    // reset notification function
    member this.Reset() = notify <- fun _ -> ()

    // notify when something happens
    member this.SomethingHappen k =
        notify k

// define observer A
type ObserverA() =
    member this.NotifyMe i = printfn "notified A %A" i

// define observer B
type ObserverB() =
    member this.NotifyMeB i = printfn "notified B %A" i

// observer pattern
let observer() =
    // create two observers
    let a = ObserverA()
    let b = ObserverB()

    // create a subject
    let subject = Subject()

    // let observer subscribe to subject
    subject.Subscribe a.NotifyMe
    subject.Subscribe b.NotifyMeB

    // something happens to the subject
    subject.SomethingHappen "good"

Execution result from the observer pattern sample obtained by invoking the observer function

notified B "good"
notified A "good"

F#’s Observable module can be used to implement this pattern as well. In Example 3-14, an event is defined along with three observers of the event. Compared to the version in Example 3-13, this version is much more lightweight. The myEvent value is bound to an instance of the F# event type. For the Observable module to subscribe to the event, you have to publish the event. After the event is published, the Observable.add function is used to add the event-handler function to this event. When the event is fired by using Trigger, all the event-handler functions will be notified.

Example 3-14. Using the Observable module to implement the observer pattern
// define an event
let myEvent = Event<_>()

// define three observers
let observerA = fun i -> printfn "observer A noticed something, its value is %A" i
let observerB = fun i -> printfn "observer B noticed something, its value is %A" i
let observerC = fun i -> printfn "observer C noticed something, its value is %A" i

// publish the event and add observerA
myEvent.Publish
|> Observable.add observerA

// publish the event and add observerA
myEvent.Publish
|> Observable.add observerB

// publish the event and add observerA
myEvent.Publish
|> Observable.add observerC

//fire event with value 1
myEvent.Trigger 1

Execution result

observer A noticed something, its value is 1
observer B noticed something, its value is 1
observer C noticed something, its value is 1

Working with the Decorator Pattern

The decorator pattern can be used to extend (a.k.a. decorate) the functionality of an object at run-time. In Example 3-15, the decorator pattern is used along with the composite operator to add new logic to the existing function. As the function is passed dynamically into a structure, the run-time behavior can be easily changed. The sample code defines a property that exposes a function. This function can then be changed at runtime.

Example 3-15. Decorator pattern
// define the Divide class
type Divide() =
    // define basic divide function
    let mutable divide = fun (a,b) -> a / b

    // define a property to expose the function
    member this.Function
        with get() = divide
        and set(v) = divide <- v

    // method to invoke the function
    member this.Invoke(a,b) = divide (a,b)

// decorator pattern
let decorate() =

    // create a divide instance
    let d = Divide()

    // set the check zero function
    let checkZero (a,b) = if b = 0 then failwith "a/b and b is 0" else (a,b)

    // invoke the function without check zero
    try
        d.Invoke(1, 0) |> ignore
    with e -> printfn "without check, the error is = %s" e.Message

    // add the check zero function and then invoke the divide instance
    d.Function <- checkZero >> d.Function
    try
        d.Invoke(1, 0) |> ignore
    with e -> printfn "after add check, error is = %s" e.Message

Execution result from the decorator pattern sample obtained by invoking the decorate function

without check, the error is = Attempted to divide by zero.
after add check, error is = a/b and b is 0

Working with the Proxy Pattern

The proxy pattern uses a class that acts as a placeholder or interface for another object or function. It’s often used for caching, to control access, or to delay the execution or creation of an object that is costly in the form of time or resources. See Example 3-16. The CoreComputation class hosts two calculation functions, named Add and Sub. The class also exposes a proxy class from which a user can get access to the computation.

Example 3-16. Proxy pattern
// define core computation
type CoreComputation() =
    member this.Add(x) = x + 1
    member this.Sub(x) = x - 1
    member this.GetProxy name =
        match name with
        | "Add" -> this.Add, "add"
        | "Sub" -> this.Sub, "sub"
        | _ -> failwith "not supported"

// proxy implementation
let proxy() =
    let core = CoreComputation()

    // get the proxy for the add function
    let proxy = core.GetProxy "Add"

    // get the compute from proxy
    let coreFunction = fst proxy

    // get the core function name
    let coreFunctionName = snd proxy

    // perform the core function calculation
    printfn "performed calculation %s and get result = %A" coreFunctionName (coreFunction 1)

Execution result from the proxy pattern sample obtained by invoking the proxy function

performed calculation add and get result = 2

Working with the Strategy Pattern

The strategy pattern is a software design pattern whereby algorithms can be selected and used at runtime. Example 3-17 uses a function to hold different strategies. During runtime, the strategy can be modified.

Example 3-17. Strategy pattern
// quick sort algorithm
let quicksort l =
    printfn "quick sort"

// shell short algorithm
let shellsort l =
    printfn "shell short"

// bubble short algorithm
let bubblesort l =
    printfn "bubble sort"

// define the strategy class
type Strategy() =
    let mutable sortFunction = fun _ -> ()
    member this.SetStrategy f = sortFunction <- f
    member this.Execute n = sortFunction n

let strategy() =
    let s = Strategy()

    // set strategy to be quick sort
    s.SetStrategy quicksort
    s.Execute [1..6]

    // set strategy to be bubble sort
    s.SetStrategy bubblesort
    s.Execute [1..6]

Execution result from the strategy pattern sample obtained by invoking the strategy function

quick sort
bubble sort

Note

The sample code does not really implement three sorting algorithms. Instead, the code simply outputs the name of the algorithm that would be used.

Example 3-17 shows how to implement this pattern using the OOP paradigm. However, the strategy pattern can be implemented more succinctly with a functional approach. Example 3-18 shows how to use the higher-order function named executeStrategy to implement this pattern using a functional paradigm.

Example 3-18. Strategy pattern using a higher-order function
// quick sort algorithm
let quicksort l =
    printfn "quick sort"

// shell short algorithm
let shellsort l =
    printfn "shell short"

// bubble short algorithm
let bubblesort l =
    printfn "bubble sort"

let executeStrategy f n = f n

let strategy() =
    // set strategy to be quick sort
    let s = executeStrategy quicksort
    // execute the strategy against a list of integers
    [1..6] |> s

    // set strategy to be bubble sort
    let s2 = executeStrategy bubblesort
    // execute the strategy against a list of integers
    [1..6] |> s2

Working with the State Pattern

The state pattern is used to represent the ability to vary the behavior of a routine depending on the state of an object. This is a clean way for an object to partially change its type at runtime. Example 3-19 shows that the interest rate is decided by the internal state: account balance. The higher the balance is, the higher the interest is that a customer will receive. In the sample, I also demonstrate how to use the unit-of-measure feature.

Example 3-19. State pattern
// define account state
type AccountState =
    | Overdrawn
    | Silver
    | Gold

// define unit of measure as US dollar
[<Measure>] type USD

// define an account that takes the unit of measure
type Account<[<Measure>] 'u>() =
    // field to hold the account balance
    let mutable balance = 0.0<_>

    // property for account state
    member this.State
        with get() =
            match balance with
            | _ when balance <= 0.0<_> -> Overdrawn
            | _ when balance > 0.0<_> && balance < 10000.0<_> -> Silver
            | _ -> Gold

    // method to pay the interest
    member this.PayInterest() =
        let interest =
            match this.State with
                | Overdrawn -> 0.
                | Silver -> 0.01
                | Gold -> 0.02
        interest * balance

    // deposit into the account
    member this.Deposit x =
        let a = x
        balance <- balance + a

    // withdraw from account
    member this.Withdraw x =
        balance <- balance - x

// implement the state pattern
let state() =
    let account = Account()

    // deposit 10000 USD
    account.Deposit 10000.<USD>

    // pay interest according to current balance
    printfn "account state = %A, interest = %A" account.State (account.PayInterest())

    // deposit another 2000 USD
    account.Withdraw 2000.<USD>

    // pay interest according to current balance
    printfn "account state = %A, interest = %A" account.State (account.PayInterest())

Execution result from the state pattern sample obtained by invoking the state function

account state = Gold, interest = 200.0
account state = Silver, interest = 80.0

In F#, one way to implement a state machine is with a MailboxProcessor. The F# MailboxProcessor can be viewed as a message queue. It takes an asynchronous workflow as the processing logic. The asynchronous workflow will be introduced in the next chapter, and it can be thought of as a simple function being executed on a background thread. The Post method is used to insert a message into the queue, and the Receive method is used to get the message out of the queue. In Example 3-20, the variable inbox represents the message queue. When the state machine starts, it goes to state0, which is represented by the state0() function, and waits for user input. The state machine will transition to another state according to the user’s input.

Example 3-20. State pattern with F# MailBoxProcessor
open Microsoft.FSharp.Control

type States =
    | State1
    | State2
    | State3

type StateMachine() =
    let stateMachine = new MailboxProcessor<States>(fun inbox ->
                let rec state1 () = async {
                    printfn "current state is State1"
                    // <your operations>

                    //get another message and perform state transition
                    let! msg = inbox.Receive()
                    match msg with
                        | State1 -> return! (state1())
                        | State2 -> return! (state2())
                        | State3 -> return! (state3())
                    }
                and state2() = async {
                    printfn "current state is state2"
                    // <your operations>

                    //get another message and perform state transition
                    let! msg = inbox.Receive()
                    match msg with
                        | State1 -> return! (state1())
                        | State2 -> return! (state2())
                        | State3 -> return! (state3())
                    }
                and state3() = async {
                    printfn "current state is state3"
                    // <your operations>

                    //get another message and perform state transition
                    let! msg = inbox.Receive()
                    match msg with
                        | State1 -> return! (state1())
                        | State2 -> return! (state2())
                        | State3 -> return! (state3())
                    }
                and state0 () =
                    async {

                        //get initial message and perform state transition
                        let! msg = inbox.Receive()
                        match msg with
                            | State1 -> return! (state1())
                            | State2 -> return! (state2())
                            | State3 -> return! (state3())
                    }
                state0 ())

    //start the state machine and set it to state0
    do
        stateMachine.Start()

    member this.ChangeState(state) = stateMachine.Post(state)

let stateMachine = StateMachine()
stateMachine.ChangeState(States.State2)
stateMachine.ChangeState(States.State1)

Execution result in FSI

current state is state2
current state is State1

Note

If the preceding code is executed in Microsoft Visual Studio debug mode, Thread.Sleep is needed because the main process (thread) needs to give CPU cycles to the background execution.

Working with the Factory Pattern

The factory pattern in Example 3-21 is an object-oriented design pattern used to implement the concept of factories. It uses the function keyword as shortcut to the match statement. It can create an object without specifying the exact class of object that will be created. Example 3-22 shows an example that uses the object expression to implement the factory pattern.

Example 3-21. Using the function keyword
// define two types
type Type =
  | TypeA
  | TypeB

// check with function keyword
let checkWithFunction = function
    | TypeA -> printfn "type A"
    | TypeB -> printfn "type B"

// check with match keyword
let checkWithMatch x =
    match x with
    | TypeA -> printfn "type A"
    | TypeB -> printfn "type B"

In Example 3-22, the factory inside factoryPattern is actually a function. It is a shortcut for a match statement. The checkWithFunction and checkWithMatch functions in Example 3-21 are equivalent.

Example 3-22. Example of the factory pattern
// define the interface
type IA =
  abstract Action : unit -> unit

// define two types
type Type =
  | TypeA
  | TypeB

let factoryPattern() =
    // factory pattern to create the object according to the input object type
    let factory = function
      | TypeA -> { new IA with
                       member this.Action() = printfn "I am type A" }
      | TypeB -> { new IA with
                       member this.Action() = printfn "I am type B" }

    // create type A object
    let obj1 = factory TypeA
    obj1.Action()

    // create type B object
    let obj2 = factory TypeB
    obj2.Action()

Execution result from the factory pattern sample obtained by invoking the factoryPattern function

I am type A
I am type B

The factory function returns an object that is not familiar. Actually, the return type is something called an object expression, and this lightweight syntax can simplify your code significantly. If the object is not involved in inheritance, you can pretty much use an object expression to replace a class definition completely. Example 3-23 shows how to create an instance of interface IA using object expression syntax.

Example 3-23. Using object expression
// define the interface
type IA =
  abstract Action : unit -> unit

let a = { new IA with
            member this.Action() =
                printfn "this is from object expression" }

Working with the Singleton Pattern

The singleton pattern is a design pattern used to implement the mathematical concept of a singleton. It restricts the instantiation of a class to a single instance. This is useful when exactly one object is needed to coordinate actions across the system. One example of a singleton in F# is a value. An F# value is immutable by default, and this guarantees there is only one instance. Example 3-24 shows how to make sure that an F# class instance is a singleton. The sample declares a private constructor and ensures that the class has only one instance in memory.

Example 3-24. An example of the singleton pattern
// define a singleton pattern class
type A private () =
    static let instance = A()
    static member Instance = instance
    member this.Action() = printfn "action from type A"

// singleton pattern
let singletonPattern() =
    let a = A.Instance
    a.Action()

Working with the Composite Pattern

The composite pattern is a partitioning design pattern. The composite pattern describes a group of objects that are to be treated in the same way as a single instance of that object. The typical application is a tree structure representation. Example 3-25 demonstrates a tree structure. The sample focuses more on how to access this tree structure and bring back the result.

The dynamically generated wrapper object can be treated like a visitor to the tree. The visitor accesses the node and brings the result back to the invoker. In the sample code, the CompositeNode structure not only defines the tree but also defines three common ways to traverse the tree. It does the heavy lifting by encapsulating the tree traversal algorithm. The visitor defines how to process the single node and is responsible for bringing the result back to the invoker. In this sample, the visitor adds the value in the tree nodes and brings back the sum.

Example 3-25. An example of the composite pattern
// define visitor interface
type IVisitor<'T> =
    abstract member Do : 'T -> unit

// define a composite node
type CompositeNode<'T> =
    | Node of 'T
    | Tree of 'T * CompositeNode<'T> * CompositeNode<'T>
    with
        // define in-order traverse
        member this.InOrder f =
            match this with
            | Tree(n, left, right) ->
                left.InOrder f
                f n
                right.InOrder(f)
            | Node(n) -> f n

        // define pre-order traverse
        member this.PreOrder f =
            match this with
            | Tree(n, left, right) ->
                f n
                left.PreOrder f
                right.PreOrder f
            | Node(n) -> f n

        // define post order traverse
        member this.PostOrder f =
            match this with
            | Tree(n, left, right) ->
                left.PostOrder f
                right.PostOrder f
                f n
            | Node(n) -> f n

let invoke() =
    // define a tree structure
    let tree = Tree(1, Tree(11, Node(12), Node(13)), Node(2))

    // define a visitor, it gets the summary of the node values
    let wrapper =
        let result = ref 0
        ({ new IVisitor<int> with
                member this.Do n =
                    result := !result + n
        }, result)

    // pre-order iterates the tree and prints out the result
    tree.PreOrder (fst wrapper).Do
    printfn "result = %d" !(snd wrapper)

Execution result from the composite pattern sample obtained by calling the invoke function

result = 39

Working with the Template Pattern

The template pattern is, as its name suggests, a program or algorithm skeleton. It is a behavior-based pattern. In F#, we have higher-order functions that can serve as a template to generate other functions. It is natural to use higher-order functions to implement this pattern. Example 3-26 defines a three-stage database operation function named TemplateF. The actual implementation is provided outside of this skeleton function. I do not assume the database connection and query are all the same, so three functions are left outside of the class definition, and the user can define and pass in their own version of each.

Example 3-26. An example of the template pattern
// the template pattern takes three functions and forms a skeleton function named TemplateF
type Template(connF, queryF, disconnF) =
    member this.Execute(conStr, queryStr) =
        this.TemplateF conStr queryStr
    member this.TemplateF =
            let f conStr queryStr =
                connF conStr
                queryF queryStr
                disconnF ()
            f

// connect to the database
let connect conStr =
    printfn "connect to database: %s" conStr

// query the database with the SQL query string
let query queryStr =
    printfn "query database %s" queryStr

// disconnect from the database
let disconnect ()  =
    printfn "disconnect"

let template() =
    let s = Template(connect, query, disconnect)
    s.Execute("<connection string>", "select * from tableA")

template()

Execution result from the template pattern sample obtained by invoking the template function

connect to database: <connection string>
query database select * from tableA
disconnect

Note

The connect, query, and disconnect functions can be implemented as private functions in a class.

The class definition is convenient for C# projects that need to reference the implementation of this design pattern in an F# project. However, the class is not necessary in an F#-only solution. Example 3-27 shows how to use higher-order functions to implement the template pattern.

Example 3-27. Template pattern with a higher-order function
// connection, query, and disconnect functions
let connect(conStr ) = printfn "connect using %s" conStr
let query(queryStr) = printfn "query with %s" queryStr
let disconnect() = printfn "disconnect"

// template pattern
let template(connect, query, disconnect) (conStr:string) (queryStr:string)=
    connect(conStr)
    query(queryStr)
    disconnect()

// concrete query
let queryFunction = template(connect, query, disconnect)

// execute the query
do queryFunction "<connection string>" "select * from tableA"

Working with the Private Data Class Pattern

The private data class pattern is a design pattern that encapsulates class properties and associated data manipulation. The purpose of the private accessibility is to prevent the modification of these values. C# uses the readonly property, which does not have a setter function, to solve this problem. F# values are immutable by default, so implementing this readonly type of behavior is supported inherently. In the following example, I use an F# record type to implement the pattern by extending the record type. The with keyword in the code shown in Example 3-28 is a way to tell the compiler that some property, method, or both will be added to the record type. In the sample code, the circle data remains the same once it is created. Some object-oriented implementations even implement another class so that there is little chance to modify the values. The immutability of record types eliminates the needs of a second class, as well as the need for explicitly defining getter-only properties with a keyword.

Example 3-28. An example of the private data class pattern
type Circle = {
    Radius : float;
    X : float;
    Y : float }



with
    member this.Area = this.Radius**2. * System.Math.PI
    member this.Diameter = this.Radius * 2.
let myCircle = {Radius = 10.0; X = 5.0; Y = 4.5}
printfn "Area: %f Diameter: %f" myCircle.Area myCircle.Diameter

Working with the Builder Pattern

The builder pattern provides abstract steps of building objects. This allows you to pass different implementations of specific abstract steps. Example 3-29 demonstrates the abstract steps of making a pizza. The invoker can pass in different implementation steps to the cook function to generate different pizzas.

Example 3-29. An example of the builder pattern sample
// pizza interface
type IPizza =
    abstract Name : string with get
    abstract MakeDough : unit->unit
    abstract MakeSauce : unit->unit
    abstract MakeTopping: unit->unit

// pizza module that defines all recipes
[<AutoOpen>]
module PizzaModule =
    let makeNormalDough() = printfn "make normal dough"
    let makePanBakedDough() = printfn "make pan baked dough"
    let makeCrossDough() = printfn "make cross dough"

    let makeHotSauce() = printfn "make hot sauce"
    let makeMildSauce() = printfn "make mild sauce"
    let makeLightSauce() = printfn "make light sauce"

    let makePepperoniTopping() = printfn "make pepperoni topping"
    let makeFiveCheeseTopping() = printfn "make five cheese topping"
    let makeBaconHamTopping() = printfn "make bacon ham topping"

// define a pepperoni pizza recipe
let pepperoniPizza =
        {   new IPizza with
                member this.Name = "Pepperoni Pizza"
                member this.MakeDough() = makeNormalDough()
                member this.MakeSauce() = makeHotSauce()
                member this.MakeTopping() = makePepperoniTopping() }

// cook takes pizza recipe and makes the pizza
let cook(pizza:IPizza) =
    printfn "making pizza %s" pizza.Name
    pizza.MakeDough()
    pizza.MakeSauce()
    pizza.MakeTopping()

// cook pepperoni pizza
cook pepperoniPizza

Execution result from the builder pattern sample

making pizza Pepperoni Pizza
make normal dough
make hot sauce
make pepperoni topping

The pizza interface and object expression give the program a good structure, but it makes things unnecessarily complicated. The builder pattern requires the actual processing function or functions be passed in, which is a perfect use of higher-order functions. Example 3-30 uses a higher-order function to eliminate the interface and object expression.

Example 3-30. Builder pattern implementation using a higher-order function
// pizza module that defines all recipes
[<AutoOpen>]
module PizzaModule =
    let makeNormalDough () = printfn "make normal dough"
    let makePanBakedDough () = printfn "make pan baked dough"
    let makeCrossDough() = printfn "make cross dough"

    let makeHotSauce() = printfn "make hot sauce"
    let makeMildSauce() = printfn "make mild sauce"
    let makeLightSauce() = printfn "make light sauce"

    let makePepperoniTopping() = printfn "make pepperoni topping"
    let makeFiveCheeseTopping() = printfn "make five cheese topping"
    let makeBaconHamTopping() = printfn "make bacon ham topping"

// cook takes the recipe and ingredients and makes the pizza

let cook pizza recipeSteps =
    printfn "making pizza %s" pizza
    recipeSteps
    |> List.iter(fun f -> f())

[ makeNormalDough; makeMildSauce
  makePepperoniTopping ]
|> cook "pepperoni pizza"

Working with the Façade Pattern

The façade pattern provides a higher-level interface that makes invoking an underlying class library easier, more readable, or both. Example 3-31 shows how to perform an employment background check.

Example 3-31. An example of the façade pattern
// define Applicant record
type Applicant = { Name : string }

// library to perform various checks
[<AutoOpen>]
module SubOperationModule =
    let checkCriminalRecord (applicant) =
        printfn "checking %s criminal record..." applicant.Name
        true

    let checkPastEmployment (applicant) =
        printfn "checking %s past employment..." applicant.Name
        true

    let securityClearance (applicant, securityLevel) =
        printfn "security clearance for %s ..." applicant.Name
        true

// façade function to perform the background check
let isBackgroundCheckPassed(applicant, securityLevel) =
    checkCriminalRecord applicant
    && checkPastEmployment applicant
    && securityClearance(applicant, securityLevel)

// create an applicant
let jenny = { Name = "Jenny" }

// print out background check result
if isBackgroundCheckPassed(jenny, 2) then printfn "%s passed background check" jenny.Name
else printfn "%s failed background check" jenny.Name

Execution result from the façade pattern sample

checking Jenny criminal record...
checking Jenny past employment...
security clearance for Jenny ...
Jenny passed background check

Working with the Memento Pattern

The memento pattern saves an object’s internal state so that it can be used later. In Example 3-32, the particle class saves its location information and later restores that information back to the saved location. If the state data is relatively small, a list storage can easily turn the memento pattern into a redo-undo framework.

Example 3-32. An example of the memento pattern
// define location record
type Location = { X : float; Y : float }

// define a particle class with a location property
type Particle() =
    let mutable loc = {X = 0.; Y = 0.}
    member this.Loc
        with get() = loc
        and private set v = loc <- v
    member this.GetMemento() = this.Loc
    member this.Restore v = this.Loc <- v
    member this.MoveXY(newX, newY) = loc <- { X = newX; Y = newY }


// create a particle
let particle = Particle()

// save current state
let currentState = particle.GetMemento()
printfn "current location is %A" particle.Loc

// move particle to new location
particle.MoveXY(2., 3.)
printfn "current location is %A" particle.Loc

// restore particle to previous saved location
particle.Restore currentState
printfn "current location is %A" particle.Loc

Writing Design Patterns: Additional Notes

As I mentioned in the beginning of this chapter, design patterns have been criticized since their birth. Many functional programmers believe that design patterns are not needed when programming in a functional style. Peter Norvig, in his paper “Design Patterns in Dynamic Languages,” claims that design patterns are just missing language features and demonstrates that design patterns can be simplified or eliminated completely when using a different language. I am not planning to be part of these discussions. Design patterns are a way to represent a system or idea. It is really a de facto and concise way for many computer professionals to describe system design. If the program is simple and small, design patterns are often unnecessary. For these scenarios, the use of basic data and flow-control structure is enough. However, when a program becomes large and complicated, a tested approach is needed to organize thinking and avoid possible design flaws or bugs. If the basic data structure is analogous to a word in a sentence, design patterns can be viewed as the idea to organize an article.

As a functional-first programming language, F# is adept at creating code with a functional style. For example, the pipeline and function composition operators make function operation much easier. Instead of being confined to a class, the function can be freely passed and processed like data in F#. If the design pattern is mainly about how to pass an action/operation or coordinate the flow of an operation, the pipeline and function composition operators can definitely simplify the implementation. The chain of responsibility pattern is an example. The biggest change from C# is that a function in F# is no longer auxiliary to the data; instead, it can be encapsulated, stored, and manipulated in a class. The data (field and property) in a class can actually be provided as a function or as method parameters and remain auxiliary to the function. Additionally, the presence of a class is optional if the class only serves as an operation container. The builder pattern demonstrates a way to eliminate the class while still implementing the same functionality.

Functional programming can still have a structure to encapsulate logic into a unit. Functions, which can be treated like data, can be encapsulated in a class or inside a closure and, more importantly, the application of object expressions provides an even simpler way to organize the code. Example 3-33 shows different ways to encapsulate the data.

Example 3-33. Data encapsulation

F# closure

let myFunction () =
    let constValue = 100
    let f () = [1..constValue] |> Seq.iter (printfn "%d")
    f()

Object expression

let obj =
    let ctor = printfn "constructor code"
    let field = ref 8
    { new IA with
        member this.F() = printfn "%A" (!field)
      interface System.IDisposable with
        member this.Dispose() = ()}

Object expressions are great, because the type is created on the fly by the compiler. Instead of inventing a permanent boilerplate class to hold the function and data, you can use object expressions to quickly organize functions and data into a unit and get the job done. Imagine an investment bank with a bunch of mathematicians who lack a computer background: object expressions can let them quickly transform their knowledge into code without worrying about programmers complaining about their inability to implement complex inheritance hierarchies. The flattened structure from the object expression is a straightforward and suitable approach for quick prototyping and agile development. The command pattern is a good sample for demonstrating how to use object expressions to simplify the design.

Both functional programming and object-oriented programming have their own way of reusing the code. Object-oriented programming uses inheritance, while functional programming uses higher-order functions. Both approaches have loyal followers, and you might already be convinced that one is superior to the other. I say that both approaches have their own advantages under certain circumstances. Unfortunately, neither is a silver bullet that can be used to solve all problems. Using the right tool for the right job is the key. F#, which supports both OO and functional programming, provides both approaches, and this gives the developer the liberty to use the best way to perform the system design.

F# provides the alternative to encapsulation (object expressions) and inheritance (higher order functions): polymorphism. It can also be implemented by higher-order functions when given different parameters. This is yet another example of how F# provides a wide set of tools for developers to implement their components and systems.

In addition, the adapter pattern introduces the GI function, which breaks class encapsulation and makes possible communication between objects that do not share a common base class. It is not a recommended way to use the original object-oriented design; however, it is a feasible approach to wrap legacy code because of inaccessibility to the source code. It is not fair to blame a gun for causing crime and not blame the criminal. Likewise, F# provides this approach, but I’ll leave the decision to you regarding when and how to use it.

It is totally fine to copy a standard object-oriented approach when doing system design, especially when someone is new to a language. If you are motivated to use F# to write design patterns, here are some principles that I used to implement the design patterns in this chapter. If the design pattern is a behavior design pattern, its main focus is on how to organize the function, so consider using the function composition and pipeline operators. If the function needs to be organized into a unit, put the function into a module and use object expressions to organize the function. If the design pattern is a structural design pattern, I always question why extra structure is needed. If the extra structure is a placeholder for functionality, higher-order functions most likely will do the same job. If the extra structure is needed to make two unrelated objects work together, the GI function could be a good candidate to simplify the design.

F# is a young language and how to properly apply its language feature into the system design is still a new topic. Keep in mind that F# provides the OOP way of implementing class encapsulation, inheritance, and polymorphism. This chapter is only a small step to explore how to use F# in system design.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.142.230