Decorator pattern for classes with many properties
Update: @JeremyDWill rightly pointed out that you can't derive a generic type from one of its parameters...
If you think of the decorator as a "subclass that adds new properties", you could do something like:
public class MyDecorator<T> : T
{
public int MyDecoratorProperty1 { get; set; }
public int MyDecoratorProperty2 { get; set; }
}
Then you can create instances of MyDecorator<DataBag>
, and MyDecorator<OtherClass>
, etc. The existing properties are accessible because the MyDecorator<>
is specific to the type of the generic argument, and derives from that class.
You can create a wrapper which contains the decorated object:
public class MyDecorator<T>
{
public MyDecorator(T decoratedObject)
{
this.DecoratedObject = decoratedObject;
}
public T DecoratedObject { get; private set; }
public int MyDecoratorProperty1 { get; set; }
public int MyDecoratorProperty2 { get; set; }
}
The advantage is that getting to the decorated properties is easy: myObj.MyDecoratorProperty1
. The downside is that you now have to go through the DecoratedObject
member to get to the base object:
DataBag bag = new DataBag("", null, null);
MyDecorator<DataBag> deco = new MyDecorator<DataBag>(bag);
deco.DecoratedObject.Height = 2;
If you can't subclass from the decorate (you need to support multiple decorators at a time, say), you'll have to do something like an "attached property"... your decorator class would have to keep a dictionary of original objects and the decorated properties. With a few extension methods, you could make these properties "look" like native members of the decorated class as long as you know the types getting decorated in advance (or are willing to decorate any object):
public static class AttachedDecorator
{
private class Properties
{
public int MyDecoratorProperty1 { get; set; }
public int MyDecoratorProperty2 { get; set; }
}
private static Dictionary<object, Properties> map = new Dictionary<object, Properties>();
public static int GetMyDecoratorProperty1(object obj)
{
Properties props;
if (map.TryGetValue(obj, out props))
{
return props.MyDecoratorProperty1;
}
return -1; // or some value that makes sense if the object has no decorated property set
}
public static int GetMyDecoratorProperty2(object obj) { /* ... */ }
public static void SetMyDecoratorProperty1(object obj, int value)
{
Properties props;
if (!map.TryGetValue(obj, out props))
{
props = new Properties();
map.Add(obj, props);
}
props.MyDecoratorProperty1 = value;
}
public static void SetMyDecoratorProperty2(object obj, int value) { /* ... */ }
}
public static class DecoratorExtensions
{
private static int GetMyDecoratorProperty1(this object obj)
{
return AttachedDecorator.GetMyDecoratorProperty1(obj);
}
private static void SetMyDecoratorProperty1(this object obj, int value)
{
return AttachedDecorator.GetMyDecoratorProperty1(obj, value);
}
// ...
}
Your code then might look like:
DataBag myData = new DataBag();
myData.SetMyDecoratorProperty1(7);
Console.WriteLine("prop1: {0}", myData.GetMyDecoratorProperty1());
This is the simplest way to decorate:
public class PrettyBag : DataBag
{
public int Decoration1 { get; set; }
public int Decoration2 { get; set; }
}
If you want to create a facade and hide some of the DataBag properties instead of just adding properties then you can make the members of DataBag protected.
With interfaces you could do:
public interface IDataBag
{
...
}
public class DataBag : IDataBag
{
...
}
public interface IPrettyBag : IDataBag
{
int Decoration1 { get; set; }
int Decoration2 { get; set; }
}
public class BigBag : IPrettyBag
{
public int Decoration1 { get; set; }
public int Decoration2 { get; set; }
}
public interface SmallBag : IPrettyBag
{
public int Decoration1 { get; set; }
public int Decoration2 { get; set; }
}
When implementing decorator I usually do following. First - extract interface of decorated object and make decorated object implement that interface:
public interface IDataBag
{
string UserControl { get; set; }
string LoadMethod { get; set; }
dynamic Params { get; set; }
int Height { get; set; }
}
Next - create a decorator, which delegates all calls to decorated object (all decorators will inherit from this decorator):
public class DataBagDecorator : IDataBag
{
private IDataBag _dataBag;
public DataBagDecorator(IDataBag dataBag)
{
_dataBag = dataBag;
}
public virtual string UserControl
{
get { return _dataBag.UserControl; }
set { _dataBag.UserControl = value; }
}
// other members
}
Last - creating decorators:
public class FooDataBag : DataBagDecorator
{
public FooDataBag(IDataBag dataBag)
: base(dataBag) { }
public override string UserControl
{
// added behavior
get { return "Foo" + base.UserControl; }
set { base.UserControl = value; }
}
// you don't need to override other members
}
Usage:
IDataBag dataBag = new FooDataBag(new DataBag());