Friday, July 31, 2009

Typesafe abstractions with .net generics

The common example for generics in .net (and java) is usually for typesafe collections and typesafe operations. Safety in the workplace is very important so it's a good idea to use generic collections whenever possible. However generics, especially in .net (java generics are mostly fake) can be very powerful things to incorporate as part of our design (Made up fact - did you know that catching a ClassCastException is the no.2 cause of head injury among developers? The no.1 cause is NullPointerExeption!).

So the first example i want to talk about is of using generics to help us define a clear relationship between an abstract base class and it's children.

Generally speaking, we use an abstract base class when we have some common responsibilities for all the children, but the base class itself lacks some functionality and cannot be instantiated.

We can use a generic type definition for the abstract base to further refine this relationship and provide the inheriting classes with a type specific method signature they need to implement.
Without generics we'd be stuck with the lowest common denominator (just like on the tonight show).

So here is an example of some abstract base class that does some work, and delegates the type specific work to the children:


public abstract class AbstractProductCreator <SourceType,TargetType>
where SourceType : Material
where TargetType : Product
{
public TargetType CreateProduct(SourceType material)
{ //let's call this "buisness logic" :)
preprocessMaterial(material);
//get the product from the inheriting child
TargetType product = ProcessMaterial(material);
//do some more "buisness logic"
finalizeProduct(product);
//return the completed, type specific product!
return product;

}
private void PreprocessMaterial(Material material)
{
//umm..lets say we melt it...
}
private void FinalizeProduct(Product product)
{
//put it in a shiny box?
}

//the only method our children will have to implement
public abstract TargetType ProcessMaterial(SourceType source);
//without generics this method signature would be:
//public abstract Product ProcessMaterial(Material material);

}


So our AbstractProductCreator does some type specific work with the generic level he knows and hands it to the child for the specifics, and then finalizes it before returning.

our child only needs to do what it is supposed to, and to define its own rule (what gets converted into what).
Here's an example of a famous inheritance of this base class:


class IPhoneCreator : AbstractProductCreator <RainbowsAndBunnies,IPhone>
{
public IPhone ProcessMaterial(RainbowsAndBunnies rnb)
{
//this is the actual implementation of the IPhone creation process.
return new IPhone(rnb.GetBunnies(),rnb.GetBunnies());
//its, um..copyrighted i think..so..um..dont tell anyone you saw it here..
};

}

Once we define our child, we completely define the scope of our responsibility.
Visual studio's Intellisense will also implement the skeleton of the method for us if we perform the right ceremony. (right click on the abstract class name+ implement methods if i recall correctly).

The downside of this pattern, and its an unfortunate common pitfall of generics in general - is that once you start defining something in generic terms, it tends to infect your application.
Anyone who uses an instance of our children doesn't have to worry about the generic type definition, but usually some other layer of our application that orchestrates a more fundamental mechanism doesn't really care about the specific type and wants to treat everything in more abstract terms.

This layer may want to have access to the AbstractProductCreator, but cant because this layer will have to define a variable that looks something like:


AbstractProductCreator <SomeMaterialtype,SomeOtherMaterialType> creator;

This sort of thing is really very far from what we originally intended.
So what we want is some way of abstracting things for the lower layers of the application into the most general terms they need to know about while maintaining type specific implementations in the higher layers.

So, how do we solve this problem?

There's a nice method i came up with after thinking about this problem for a few days.(challenge: without reading on, try to think of a solution. code it (the first few attempts are likely to reach some dead end). if it can compile and run, let me know how long it took you to solve it).

The solution:
Our abstract base class can already invoke the typesafe methods of it's children. now we only need some way of invoking the abstract base without all the generic additions.
To do that we add a new interface!


public interface ProductMaker
{
public Product MakeProduct(Material material);
}

//Our base class can implement this interface and

public abstract class AbstractProductCreator <SourceType, TargetType>
where SourceType : Material
where TargetType : Product
{

public Product MakeProduct(Material material)
{
//make sure someone is not trying to hand us the wrong kind of material.
if (!material.GetType().Equals(typeof(SourceType)))
throw new Exception("Type " + material.GetType() + " not supported by" + this.GetType());
//the only cast ugliness we introduce.
return this.CreateProduct((SourceType)material);
}


public TargetType CreateProduct(SourceType material)
{
//same as before..
}

}

Now the lower layers can refer to the abstract base through the interface, and not worry about the Generic type definitions at all!

i.e.

maker.MakeProduct(product);


Without generics we'd be lost. No generics means we can either do the type specific implementation and lose the abstraction, or use the abstraction but then no one can use the type specific and all the inheriting classes have to do the casts themselves.

I hope you enjoyed this article,any comments/questions/criticism/praise/award nominations are welcome as usual.

On the next episode i'll give an example of how to extend this concept into a constructing a binding layer that allows both the lower and upper layers to be separate and type specific.
I will also be exposing the manufacturing process of the mac book air.

No comments: