Saturday, January 16, 2010

C# dynamic types and the design patterns.

NOTES
This post is resurrected. I accidentally deleted this earlier.

Preface
The question that gives me no rest lately is how I can use C# 4.0 dynamic features in the real-world .NET applications. For a long time I had no ideas except of using dynamic objects in LINQ queries. It is briefly discussed in the comments to C# 4.0 - the first look at the DYNAMICITY post. But today I know another answer and it seems to be breathtaking. The answer is we don't need to use the OOP design patterns if we have the support of the "Dynamicity Forces".

Design patterns
Design patterns are part of the fundamental of Object Oriented Programming. Tremendous amount of resources have appeared on design patterns after the book Design Patterns: Elements of Reusable Object-Oriented Software was published in 1994. Each person involved in OOP programming was learning the design patterns. I used to use the design patterns in my every-day-work. Design patterns typically show relationships and interactions between classes or objects, without specifying the final application classes or objects that are involved. Technically that means that the type of the object which is passed to be handled by the application isn't known at compile time. Design patterns use the objects inheritance, composition, etc. to effectively solve the problem then the final object type isn't known at compile time. But wait...now we have the C# dynamic types which is intended to be used when the type of an object isn't known at compile time! Yes, I assert that any well-known design pattern can be avoided by using the dynamic types. Let me show the example.

Bridge design pattern(classic approach).
Bridge design pattern is very powerful and applies to many situations. Its purpose - "decouple an abstraction from its implementation so that the two can vary independently" - is not possible to understand without the real-world example. The classic example is shown below. I copied the code from the wikipedia article and updated it a bit(C# 3.0 is used).

internal interface IDrawingAPI
{
   void DrawCircle(double x, double y, double radius);
   void DrawLine(double x1, double y1, double x2, double y2);
}

internal class DrawingAPI1 : IDrawingAPI
{
   public void DrawCircle(double x, double y, double radius)
   {
      Console.WriteLine("API1.circle at {0}:{1} radius {2}", x, y, radius);
   }
   public void DrawLine(double x1, double y1, double x2, double y2)
   {
      Console.WriteLine("API1.line from {0},{1} to {2},{3}", x1, y1, x2, y2);
   }
}

internal class DrawingAPI2 : IDrawingAPI
{
   public void DrawCircle(double x, double y, double radius)
   {
      Console.WriteLine("API2.circle at {0}:{1} radius {2}", x, y, radius);
   }
   public void DrawLine(double x1, double y1, double x2, double y2)
   {
      Console.WriteLine("API2.line from {0},{1} to {2},{3}", x1, y1, x2, y2);
   }
}

internal interface IShape
{
   void Draw();
}

internal sealed class LineShape : IShape
{
   public double X1 { get; set; }
   public double X2 { get; set; }
   public double Y1 { get; set; }
   public double Y2 { get; set; }
   public IDrawingAPI DrawingAPI { get; set; }
   public void Draw()
   {
      DrawingAPI.DrawLine(X1, Y1, X2, Y2);
   }
}

internal class CircleShape : IShape
{
   public IDrawingAPI DrawingAPI{ get; set;}
   public double X{ get; set;}
   public double Y{ get; set;}
   public double Radius{ get; set;}
   public void Draw()
   {
      DrawingAPI.DrawCircle(X, Y, Radius);
   }
}

internal class BridgePatternClient
{
   public static void Main(string[] args)
   {
      var shapes = new IShape[2];
      shapes[0] = new CircleShape {X = 1, Y = 2, Radius = 3, DrawingAPI = new DrawingAPI1()};
      shapes[1] = new LineShape {X1 = 1, Y1 = 2, X2 = 3, Y2 = 4, DrawingAPI = new DrawingAPI2()};
      foreach (IShape shape in shapes)
      {
         shape.Draw();
      }
   }
}

In this example bridge design pattern is used to decouple the shape abstraction from the drawing implementation. Indeed there are alternate ways to design such a system, but these ways have the important design flaws. This is greatly described in Design Patterns Explained: A New Perspective on Object-Oriented Design book(must read!). Now let me show how I can accomplish the same task as shown above by using dynamic types.


Dynamicity in action.
The code that shown below is written in Visual Studio 2010 and it works fine. It's so simple that doesn't need to be explained. Just look at this.

internal sealed class DrawingAPI1
{
   public void DrawCircle(double x, double y, double radius)
   {
      Console.WriteLine("API1.circle at {0}:{1} radius {2}", x, y, radius);
   }
   public void DrawLine(double x1, double y1, double x2, double y2 )
   {
      Console.WriteLine("API1.line from {0},{1} to {2},{3}", x1, y1, x2, y2);
   }
}

internal sealed class DrawingAPI2
{
   public void DrawCircle(double x, double y, double radius)
   {
      Console.WriteLine("API2.circle at {0}:{1} radius {2}", x, y, radius);
   }
   public void DrawLine(double x1, double y1, double x2, double y2)
   {
      Console.WriteLine("API2.line from {0},{1} to {2},{3}", x1, y1, x2, y2);
   }
}

internal sealed class Line
{
   public double X1 { get; set; }
   public double X2 { get; set; }
   public double Y1 { get; set; }
   public double Y2 { get; set; }
   public dynamic DrawingAPI { get; set; }
   public void Draw()
   {
      DrawingAPI.DrawLine(X1, Y1, X2, Y2);
   }
   }

internal sealed class Circle
{
   public double X { get; set; }
   public double Y { get; set; }
   public double Radius { get; set; }
   public dynamic DrawingAPI { get; set; }
   public void Draw()
   {
      DrawingAPI.DrawCircle(X, Y, Radius);
   }
}

public class Program
{
   [STAThread]
   static void Main()
   {
      DrawingAPI1 api1 = new DrawingAPI1();
      DrawingAPI2 api2 = new DrawingAPI2();
      dynamic[] shapes = { new Line { X1 = 1, Y1 = 1, X2 = 2, Y2 = 3, DrawingAPI = api1 },
               new Circle { X = 10, Y = 20, Radius = 40.4f, DrawingAPI = api2 }
      };
      foreach (dynamic shape in shapes)
      {
         shape.Draw();
      }
   }
}

So, when we don't know the final type of the Shape and Drawing API we use dynamic type. Do you like it?


In conclusion.
This post is about dynamic features of C# of course. But it brings up more general question. In our days the programming technologies changes are incredible. It's good practice to keep up with Changes. But sometimes the changes can make us forgetting the good, old-fashioned technologies. I think that we should be careful with this effect. The new technologies can be awesome, greatly suitable for my needs. The old technologies can also be suitable for the problems I solve. A good problem-solver tries several possible solutions and selects the best one. A good problem-solver doesn't use the technology just because it is cutting-edge.

No comments:

Post a Comment