Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Applying the Decorator Pattern to Forms

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?

like image 611
devoured elysium Avatar asked Mar 31 '10 15:03

devoured elysium


2 Answers

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");
    }
}
like image 95
Dathan Avatar answered Oct 04 '22 10:10

Dathan


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.

like image 37
Jason Punyon Avatar answered Oct 04 '22 08:10

Jason Punyon