Introduction
This article describes a Dynamic Decorator pattern, which can be used to add extra functionality to an object dynamically. The Dynamic Decorator makes use of .NET remoting and reflection technologies. It gets rid of the design time work needed in the traditional Decorator pattern (one of GoF patterns). It is very flexible and has great advantages when new functionality needs to be added to object methods.
Background
While using the GoF Decorator pattern, we need to design decoration code at compile time. I found it difficulty to maintain the code while requirements evolve and new features are added. I thought that it would be nice to have an easier and maintenance free way to extend functionality of an object. It led me to have the idea of Dynamic Decorator.
Using the Code
At the core of Decorator pattern is that new functionality can be added to object methods without modifying the class from which the object is instantiated. Say, your software has a well tested class Employee
, which implements an IEmployee
interface as follows.
public interface IEmployee
{
System.Int32? EmployeeID { get; set; }
System.String FirstName { get; set; }
System.String LastName { get; set; }
System.DateTime DateOfBirth { get; set; }
System.String FullName();
System.Single Salary();
}
public class Employee : IEmployee
{
#region Properties
public System.Int32? EmployeeID { get; set; }
public System.String FirstName { get; set; }
public System.String LastName { get; set; }
public System.DateTime DateOfBirth { get; set; }
#endregion
public Employee(
System.Int32? employeeid
, System.String firstname
, System.String lastname
, System.DateTime bDay
)
{
this.EmployeeID = employeeid;
this.FirstName = firstname;
this.LastName = lastname;
this.DateOfBirth = bDay;
}
public Employee() { }
public System.String FullName()
{
System.String s = FirstName + " " + LastName;
Console.WriteLine("Full Name: " + s);
return s;
}
public System.Single Salary()
{
System.Single i = 10000.12f;
Console.WriteLine("Salary: " + i);
return i;
}
}
After release of your software, some customers want security right check before calling the Salary
method. And you think it would be useful to put some log information before and after method calls for debugging and support purposes.
There are several ways you can implement these new features and requirements. You can modify your Employee
class directly to add these new features. Or you can use the GoF's Decorator pattern to create some decoration classes for objects of Employee
. Or you can use the Dynamic Decorator. The source code of an implementation of Dynamic Decorator can be found in the zip file.
Here, I am going to demonstrate how the Dynamic Decorator can be used to achieve it at runtime without modifying Employee
class or creating decoration classes. The following code is all you need to add security check and log to methods of an object of Employee
.
static void Main(string[] args)
{
IEmployee em = new Employee(1, "John", "Smith", new DateTime(1990, 4, 1));
IEmployee tpCheckRight = (IEmployee)ObjectProxyFactory.CreateProxy(
em,
new String[] { "Salary" },
new Decoration(new DecorationDelegate(UserRightCheck), null),
null);
IEmployee tpLogCheckRight = (IEmployee)ObjectProxyFactory.CreateProxy(
tpCheckRight,
new String[] { "Salary", "FullName" },
null,
new Decoration(new DecorationDelegate(ExitLog), null));
try
{
tpLogCheckRight.FullName();
Console.WriteLine("");
tpLogCheckRight.Salary();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private static void UserRightCheck(object target, object[] parameters)
{
Console.WriteLine("Do security check here");
}
private static void ExitLog(object target, object[] parameters)
{
Console.WriteLine("Do exit log here");
}
In the above code, an instance of Employee
, em
, is created. Then, ObjectProxyFactory.CreateProxy
is called with em
object as the first parameter. The second parameter is a string
array which includes names of the object methods to which you want additional functionality to be added, { "Salary" }
, for this case. The third parameter is a Decoration
instance. The logic implemented in its delegate will be executed prior to the method calls specified in the second parameter. For this case, the UserRightCheck
method is passed to the delegate with no parameter, and it will be executed prior to Salary
method call. The fourth parameter is the other Decoration
instance. The logic implemented in its delegate will be executed after the method calls specified in the second parameter. For this case, there is no logic that needs to be executed after Salary
method call.
The call of ObjectProxyFactory.CreateProxy
returns a proxy of em
object, tpCheckRight
. From now on, you can use tpCheckRight
just like the em
object. For instance, if you call Salary
method using tpCheckRight
, the UserRightCheck
will be executed, and then em
's Salary
method.
In the above code, we pass tpCheckRight
in a second ObjectProxyFactory.CreateProxy
call in order to add ExitLog
logic after execution of Salary
and FullName
methods of the em
object.
When execution of the above code, you will see the following output:
Full Name: John Smith
Do exit log here
Do security check here
Salary: 10000.12
Do exit log here
As you can see, the ExitLog
logic is called after the actual method calls of the object while the UserRightCheck
is called before the actual method call of the object.
Points of Interest
New features are added to object methods on the fly without changing the original class.
No groundwork at design time is needed comparing to the GoF's Decorator pattern.
Better design can be achieved by not mingling various features into a mighty class (separation of concerns).
What's Next
In the article, Add Aspects to Object Using Dynamic Decorator, I dive into more details on Dynamic Decorator's important features and discuss how to add aspects to object at runtime and enhance them using Dynamic Decorator.