I'm trying to apply the Decorator Design Pattern to the following situation:
I've 3 different kind of forms: Green, Yellow, Red.
Now, each of those forms can have different set of attributes. They can have a minimize box disabled, a maximized box disabled and they can be always on top.
I tried to model this the following way:
Form <---------------------------------------FormDecorator
/\ /\
|---------|-----------| |----------------------|-----------------|
GreenForm YellowForm RedForm MinimizeButtonDisabled MaximizedButtonDisabled AlwaysOnTop
Here is my GreenForm code:
public class GreenForm : Form {
public GreenForm() {
this.BackColor = Color.GreenYellow;
}
public override sealed Color BackColor {
get { return base.BackColor; }
set { base.BackColor = value; }
}
}
FormDecorator:
public abstract class FormDecorator : Form {
private Form _decoratorForm;
protected FormDecorator(Form decoratorForm) {
this._decoratorForm = decoratorForm;
}
}
and finally NoMaximizeDecorator:
public class NoMaximizeDecorator : FormDecorator
{
public NoMaximizeDecorator(Form decoratorForm) : base(decoratorForm) {
this.MaximizeBox = false;
}
}
So here is the running code:
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(CreateForm());
}
static Form CreateForm() {
Form form = new GreenForm();
form = new NoMaximizeDecorator(form);
form = new NoMinimizeDecorator(form);
return form;
}
The problem is that I get a form that isn't green and that still allows me to maximize it. It is only taking in consideration the NoMinimizeDecorator form. I do comprehend why this happens but I'm having trouble understanding how to make this work with this Pattern.
I know probably there are better ways of achieving what I want. I made this example as an attempt to apply the Decorator Pattern to something. Maybe this wasn't the best pattern I could have used(if one, at all) to this kind of scenario. Is there any other pattern more suitable than the Decorator to accomplish this? Am I doing something wrong when trying to implement the Decorator Pattern?
The problem here is that you're not actually implementing the decorator pattern. For a proper implementation of the pattern, you need to subclass Form
to create your decorator, and then intercept all operations taken on your decorator and forward them to your private Form
instance. You sort of do that, except that aside from assigning a reference in the FormDecorator
constructor, you never again use that private Form
instance. The net result is that you create a GreenForm
, then wrap it in a NoMaximizeDecorator
, and then you wrap that in a NoMinimizeDecorator
. But because you never forward operations taken against the NoMinimizeDecorator
to the wrapped Form
instance, only the NoMinimizeDecorator
instance actually applies any behavior to the instance that's used. This fits with what you observe when you run your code: a standard window with a disabled Minimize button.
Form
is a really bad example for creating decorators in C#, because most of its properties and methods are non-virtual, meaning if you're accessing the decorated form via a Form
reference, you have no way to intercept the base class's properties - you can't effectively "wrap" Form
.
EDIT
It occurs to me that the statement "Form is a really bad example for creating decorators in C#" really begs the question of what is a good example. Typically, you'll use the decorator pattern to provide a custom interface implementation without implementing the entire implementation from scratch. A very common example is generic collections. Most everything that wants list functionality doesn't depend on, e.g., List<String>
, but rather on IList<String>
. So, if you for example want a custom collection that won't accept strings shorter than 5 characters, you would use something like the following:
public class MinLengthList : IList<String>
{
private IList<string> _list;
private int _minLength;
public MinLengthList(int min_length, IList<String> inner_list)
{
_list = inner_list;
_minLength = min_length;
}
protected virtual void ValidateLength(String item)
{
if (item.Length < _minLength)
throw new ArgumentException("Item is too short");
}
#region IList<string> Members
public int IndexOf(string item)
{
return _list.IndexOf(item);
}
public void Insert(int index, string item)
{
ValidateLength(item);
_list.Insert(index, item);
}
public void RemoveAt(int index)
{
_list.RemoveAt(index);
}
public string this[int index]
{
get
{
return _list[index];
}
set
{
ValidateLength(value);
_list[index] = value;
}
}
#endregion
#region ICollection<string> Members
public void Add(string item)
{
ValidateLength(item);
_list.Add(item);
}
public void Clear()
{
_list.Clear();
}
public bool Contains(string item)
{
return _list.Contains(item);
}
public void CopyTo(string[] array, int arrayIndex)
{
_list.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _list.Count; }
}
public bool IsReadOnly
{
get { return _list.IsReadOnly; }
}
public bool Remove(string item)
{
return _list.Remove(item);
}
#endregion
#region IEnumerable<string> Members
public IEnumerator<string> GetEnumerator()
{
return _list.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return ((IEnumerable)_list).GetEnumerator();
}
#endregion
}
public class Program
{
static void Main()
{
IList<String> custom_list = new MinLengthList(5, new List<String>());
custom_list.Add("hi");
}
}
This is a misapplication of the decorator pattern. The decorator pattern is concerned with the behavior of objects. You're constructing objects which falls under the creational umbrella. While you might be able to wrap your head around "not having a maximize button" being a behavior it sounds a little off kilter.
I don't think there's a real way to fix your design though. The decorator pattern just doesn't fit. Any attempt to fix this up is just going to be incredibly crufty when you could just use a Builder.
What I could see doing is decorating the Builder of a form to perform these actions while building. It would look something like this...
public interface IFormBuilder {
public Form BuildForm();
}
public class FormBuilder : IFormBuilder {
public Form BuildForm(){
return new Form();
}
}
public class NoMaximizeFormBuilder : IFormBuilder {
private IFormBuilder _builder;
public NoMaximizeFormBuilder (IFormBuilder builder){
_builder = builder;
}
public Form BuildForm(){
f = _builder.BuildForm();
f.MaximizeBox = false;
return f;
}
}
And you could use it like this...
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(CreateForm());
}
static Form CreateForm() {
var b = new FormBuilder();
var b = new NoMaximizeFormBuilder(b);
return b.Build();
}
But even that is a little ugly. You might be able to transform this into a fluent interface for building forms.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With