F# and Design Patterns for C# Developers
- 6/15/2013
- Using Object-Oriented Programming and Design Patterns
- Working with F# and Design Patterns
- Writing Design Patterns: Additional Notes
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 Listing 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 Listing 3-2 is called a Record. Listing 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. Listing 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).
Listing 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.
Listing 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
In the implementation in Listing 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 Listing 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 Listing 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.
Listing 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 Listing 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.
Listing 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. Listing 3-4 shows how to define a kilogram (kg) unit and decorate it with a number.
Listing 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 Listing 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. Listing 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.
Listing 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
Listing 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 Listing 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 Listing 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.)
Listing 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 Listing 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.
Listing 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
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. Listing 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.
Listing 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.
Listing 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
Listing 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
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 Listing 3-12.
To implement this Do and Undo functionality, it is helpful to understand the F# discriminated union (DU) feature. Listing 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.
Listing 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.
Listing 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 Listing 3-13 passes the function into the subject, and the subject notifies its changes by calling this function along with some parameters.
Listing 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 Listing 3-14, an event is defined along with three observers of the event. Compared to the version in Listing 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.
Listing 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 Listing 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.
Listing 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 Listing 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.
Listing 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. Listing 3-17 uses a function to hold different strategies. During runtime, the strategy can be modified.
Listing 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
Listing 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. Listing 3-18 shows how to use the higher-order function named executeStrategy to implement this pattern using a functional paradigm.
Listing 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. Listing 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.
Listing 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 Listing 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.
Listing 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
Working with the Factory Pattern
The factory pattern in Listing 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. Listing 3-22 shows an example that uses the object expression to implement the factory pattern.
Listing 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 Listing 3-22, the factory inside factoryPattern is actually a function. It is a shortcut for a match statement. The checkWithFunction and checkWithMatch functions in Listing 3-21 are equivalent.
Listing 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. Listing 3-23 shows how to create an instance of interface IA using object expression syntax.
Listing 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. Listing 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.
Listing 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. Listing 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.
Listing 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. Listing 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.
Listing 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
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. Listing 3-27 shows how to use higher-order functions to implement the template pattern.
Listing 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 Listing 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.
Listing 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. Listing 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.
Listing 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. Listing 3-30 uses a higher-order function to eliminate the interface and object expression.
Listing 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. Listing 3-31 shows how to perform an employment background check.
Listing 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 Listing 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.
Listing 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