Geeks With Blogs
Buhay Programmer Dont byte more than you can chew

One of the more common design patterns used in object oriented languages like C# is the Composite Pattern.   The main benefit of using this pattern is that it allows us to treat individual objects and composite of objects in the same way.  In the UML below, the type Component is abstract and defines the operations (method1 and method2) which are implemented by the concrete types Leaf and Composite.  The type Composite contains a collection of Component objects.  Due to the fact that it inherits from Component we can write code that invokes method1 and method2 without knowing the specific type.

Composite Pattern UML

For example, suppose we work at a fast food chain and we'd like to write code for computing the price of  fries, burger, coke or a combo meal.  Following the composite pattern, we could create an abstract class called Product which has the GetPrice() method and then one class each for Burger, Fries, Coke and ComboMeal.

Component  ---  maps to   -->  Product
Leaf                --- maps to    -->  Burger, Fries, Coke
Composite   --- maps to    ---> ComboMeal

Our C# code would look like

 public abstract class Product
    {
        
public abstract double GetPrice();
    }
    
public class Fries : Product
    {
        
public override double GetPrice()
        {
            
return 1.0;
        }
    }
    
public class Burger : Product
    {
        
public override double GetPrice()
        {
            
return 5.0;
        }
    }
    
public class Coke : Product
    {
        
public override double GetPrice()
        {
            
return 2.0;
        }
    }
    
public class ComboMeal : Product
    {
        
List<Product> _products = new List<Product>();
        
public void Add(Product p)
        {
            
this._products.Add(p);
        }

        
public override double GetPrice()
        {
            
var sum = 0.0;
            
foreach (var p in _products)
                sum += p.GetPrice();

            
return sum;
        }
    }

we can then write a helper method that Computes a price. This method does not need to know whether its computing the price of a single item (Fries, Burger ) or that of a combo meal.

 public static double ComputePrice(Product p)
 {

       return p.GetPrice();
 }


Suppose, someone ordered 1 fries and 1 Burger separately, our code will be,

 var totalFriesAndBurger = ComputePrice(new Fries()) + ComputePrice(new Burger());
          

 Now if another one ordered a Combo meal (Fries + Coke + Burger), our code will be

var combo = new ComboMeal();
combo.Add(
new Fries());
combo.Add(
new Burger());
combo.Add(
new Coke());
var totalCombo = ComputePrice(combo);

 That's it. 

For our C# implementation (not counting the helper method ComputePrice() ), we have 5 classes and approximately 40 lines of code.

 

Now lets see how we can implement the same pattern in F#.

Knowing that  the types Fries, Burger, Coke and ComboMeal are just different representations of the Product type, we can model this relationship cleanly in F# using discriminated unions.   To represent the the combo meal, we make it into a tupleof 2 products.  An item of this tuple can be a single product (Coke, Fries or Burger) or any combination (i.e a combo)

type Product =
| Fries 
of double
| Coke 
of double 
| Burger 
of double
| Combo 
of Product * Product

 And how about the GetPrice method, you'd ask?  That's easy enough; F# allows us to add methods to union types. Implementing that, our code now looks like this. Notice the "with member w.GetPrice()" part?

type Product =
| Fries 
of double
| Coke 
of double 
| Burger 
of double
| Combo 
of Product * Product
with
 member w.GetPrice() = 
    
match w with 
    | Fries(p) | Coke(p) | Burger(p)
-> p
    | Combo(p1,p2) 
-> p1.GetPrice() + p2.GetPrice()

Again, if someone ordered a combo meal our code will look like

let combomeal = Combo(fries, Combo(burger, coke))
Console.WriteLine(
"Combo meal Total = " + combomeal.GetPrice().ToString())

 That's just cool isn't it? With F# we ony 1 Union type and about 10 lines of code

Posted on Friday, December 4, 2009 4:27 AM | Back to top


Comments on this post: Composite Design Pattern in F#

# re: Composite Design Pattern in F#
Requesting Gravatar...
Discriminated Unions are not default serializable so should be used with great care in a business environment.
Left by DannyAsher on Dec 05, 2009 7:01 AM

# re: Composite Design Pattern in F#
Requesting Gravatar...
And of course in your c# version, you can make composite objects at runtime, but in the f# version you can't - so why not:

| Combo of Product list

Also in the c# version, you can use partial classes to define other meal types in different files, etc.
Left by MikeRo on Dec 05, 2009 10:18 AM

# re: Composite Design Pattern in F#
Requesting Gravatar...
Hey,

Cool post but I think I'm missing something - where do you define the individual prices for the products in the F# version of the code?

For example in the C# version Fries cost 1.0 and a Burger costs 5.0.

Mark
Left by Mark Needham on Dec 19, 2009 1:02 PM

# re: Composite Design Pattern in F#
Requesting Gravatar...
@Mark

The price can be assigned like so...

let fries = Fries(1.0)
let coke = Coke(2.0)
let burger = Burger(5.0)
Left by Erik on Dec 21, 2009 12:54 AM

# re: Composite Design Pattern in F#
Requesting Gravatar...
I think this post is incomplete, if you wanted to show composite, you would need to be able to add any number of items, you are sort of showing it here with the fries+combo, but what if you end up with more than 2 items? anyways, the signature should be something like:
type Product =
| Fries of double
| Coke of double
| Burger of double
| Combo of seq<Product>
with
member w.GetPrice() =
match w with
| Fries(p) | Coke(p) | Burger(p)-> p
| Combo l -> l |> Seq.fold(fun acc l -> acc + l.GetPrice())0.0

then, you would do:

> let a = Coke 2.0;;

val a : Product = Coke 2.0

> let b = Burger 3.0;;

val b : Product = Burger 3.0

> let c = Fries 4.0;;

val c : Product = Fries 4.0

> let combo = Combo [a;b;c];;

val combo : Product = Combo [Coke 2.0; Burger 3.0; Fries 4.0]

> combo.GetPrice();;
val it : double = 9.0
>

hth,
alex
Left by alex on Oct 19, 2011 1:19 PM

Your comment:
 (will show your gravatar)


Copyright © Erik Araojo | Powered by: GeeksWithBlogs.net