14

I need to design a class hierarchy for my C# project. Basically, class' functionalities are similar to WinForms classes so let's take WinForms toolkit as an example. (However, I can't use WinForms or WPF.)

There are some core properties and functionalities that every class needs to provide. Dimensions, position, color, visibility (true/false), Draw method, etc.

I need design advice I have used a design with an abstract base class and interfaces that are not really types but more like behaviors. Is this a good design? If not, what would be a better design.

The code looks like this:

abstract class Control
{
    public int Width { get; set; }

    public int Height { get; set; }

    public int BackColor { get; set; }

    public int X { get; set; }

    public int Y { get; set; }

    public int BorderWidth { get; set; }

    public int BorderColor { get; set; }

    public bool Visible { get; set; }

    public Rectangle ClipRectange { get; protected set; }

    abstract public void Draw();
}

Some Controls can contain other Controls, some can be only contained (as children) so I'm thinking of making two interfaces for these functionalities:

interface IChild
{
    IContainer Parent { get; set; }
}

internal interface IContainer
{
    void AddChild<T>(T child) where T : IChild;
    void RemoveChild<T>(T child) where T : IChild;
    IChild GetChild(int index);
}

WinForms controls display text so this also goes into interface:

interface ITextHolder
{
    string Text { get; set; }
    int TextPositionX { get; set; }
    int TextPositionY { get; set; }
    int TextWidth { get; }
    int TextHeight { get; }

    void DrawText();
}

Some Controls can be docked inside their parent Control so:

enum Docking
{ 
    None, Left, Right, Top, Bottom, Fill
}

interface IDockable
{
    Docking Dock { get; set; }
}

...and now let's create some concrete classes:

class Panel : Control, IDockable, IContainer, IChild {}
class Label : Control, IDockable, IChild, ITextHolder {}
class Button : Control, IChild, ITextHolder, IDockable {}
class Window : Control, IContainer, IDockable {}

The first "problem" I can think of here is that the interfaces are basically set in stone once they get published. But let's assume I will be able to make my interfaces good enough to avoid need of making changes to them in the future.

Another issue I see in this design is that every of these classes would need to implement it's interfaces and duplication of code will quickly occur. For example in Label and Button the DrawText() method derives from the ITextHolder interface or in every class derived from IContainer management of children.

My solution for this issue is to implement this "duplicated" functionalities in dedicated adapters and forward calls to them. So both Label and Button would have a TextHolderAdapter member that would be called inside methods inherited from the ITextHolder interface.

I think this design should shield me from having to many common functionalities in the base class which could quickly get bloated with virtual methods and unnecessary "noise code". Changes of behavior would be accomplished by extending adapters and not Control-derived classes.

I think this is called the "Strategy" pattern and although there are millions of questions and answers on that topic, I would like to ask you for your opinions as to what I take into consideration for this design and what flaws you can think of in my approach.

I should add that there is an almost 100% chance that future requirements will call for new classes and new functionalities.

13
  • Why not simply inherit from System.ComponentModel.Component or System.Windows.Forms.Control or any of the other existing base classes? Why do you need to create your own control hierarchy and define all these functions again from scratch? Jul 1, 2011 at 13:41
  • 3
    IChild seems like a horrible name.
    – Raynos
    Jul 1, 2011 at 13:49
  • @Cody: first, I can't use WinForms or WPF assemblies, second - come on, it is just an example of a design problem I am asking about. If it easier for you think shapes or animals. my classes need to behave similar to WinForms controls but not in every way and not exactly like them
    – grapkulec
    Jul 1, 2011 at 14:06
  • 2
    It doesn't make a lot of sense to say that you can't use the WinForms or WPF assemblies, yet you would be able to use whatever custom control framework that you were to create. This is a serious case of re-inventing the wheel, and I can't imagine for what possible purpose. But if you absolutely must, why not just follow the example of the WinForms controls? That makes this question pretty much obsolete. Jul 1, 2011 at 14:11
  • 1
    @graphkulec that's why it's a comment :) If I had actual useful feedback to give I would have answered your question. It's still a horrible name ;)
    – Raynos
    Jul 1, 2011 at 14:17

3 Answers 3

5

[1] Add virtual "getters" & "setters" to your properties, I had to hack another control library, because I need this feature:

abstract class Control
{
    // internal fields for properties
    protected int _Width;
    protected int _Height;
    protected int _BackColor;
    protected int _X;
    protected int _Y;
    protected bool Visible;

    protected int BorderWidth;
    protected int BorderColor;


    // getters & setters virtual !!!

    public virtual int getWidth(...) { ... }
    public virtual void setWidth(...) { ... }

    public virtual int getHeight(...) { ... }
    public virtual void setHeight(...) { ... }

    public virtual int getBackColor(...) { ... }
    public virtual void setBackColor(...) { ... }

    public virtual int getX(...) { ... }
    public virtual void setX(...) { ... }

    public virtual int getY(...) { ... }
    public virtual void setY(...) { ... }

    public virtual int getBorderWidth(...) { ... }
    public virtual void setBorderWidth(...) { ... }

    public virtual int getBorderColor(...) { ... }
    public virtual void setBorderColor(...) { ... }

    public virtual bool getVisible(...) { ... }
    public virtual void setVisible(...) { ... }

    // properties WITH virtual getters & setters

    public int Width { get getWidth(); set setWidth(value); }

    public int Height { get getHeight(); set setHeight(value); }

    public int BackColor { get getBackColor(); set setBackColor(value); }

    public int X { get getX(); set setX(value); }

    public int Y { get getY(); set setY(value); }

    public int BorderWidth { get getBorderWidth(); set setBorderWidth(value); }

    public int BorderColor { get getBorderColor(); set setBorderColor(value); }

    public bool Visible { get getVisible(); set setVisible(value); }

    // other methods

    public Rectangle ClipRectange { get; protected set; }   
    abstract public void Draw();
} // class Control

/* concrete */ class MyControl: Control
{
    public override bool getVisible(...) { ... }
    public override void setVisible(...) { ... }
} // class MyControl: Control

I know this suggestion is more "verbose" or complex, but, its very useful in real world.

[2] Add an "IsEnabled" property, do not confuse with "IsReadOnly":

abstract class Control
{
    // internal fields for properties
    protected bool _IsEnabled;

    public virtual bool getIsEnabled(...) { ... }
    public virtual void setIsEnabled(...) { ... }

    public bool IsEnabled{ get getIsEnabled(); set setIsEnabled(value); }
} // class Control

Means you may have to show your control, but don't show any information.

[3] Add a "IsReadOnly" property, do not confuse with "IsEnabled":

abstract class Control
{
    // internal fields for properties
    protected bool _IsReadOnly;

    public virtual bool getIsReadOnly(...) { ... }
    public virtual void setIsReadOnly(...) { ... }

    public bool IsReadOnly{ get getIsReadOnly(); set setIsReadOnly(value); }
} // class Control

That means the control can display information, but cannot be changed by the user.

4
  • although I don't like all these virtual methods in base class I will give them second chance in my thinking about design. and you reminded me that I really need IsReadOnly property :)
    – grapkulec
    Jul 1, 2011 at 18:16
  • @grapkulec Since it its the base class, you must specify that the derived classes will have those properties, but, that the methods that control behavior can be change upon each class purpouse ;-)
    – umlcat
    Jul 1, 2011 at 18:19
  • mmkey, I see your point and thank you for taking time to answer
    – grapkulec
    Jul 1, 2011 at 18:24
  • 1
    I accepted this as an answer because once I started implementing my "cool" design with interfaces I quickly found myself in need of virtual setters and sometimes getters as well. That does not mean that I don't use interfaces at all I just limited their use to places where they are really needed.
    – grapkulec
    Jul 4, 2011 at 12:21
3

Nice question! The first thing I notice is that the draw method doesn't have any parameters, where does it draw? I think it should receive some Surface or Graphics object as parameter (as interface of course).

One more thing I find odd is the IContainer interface. It has GetChild method that returns a single child and has no parameters - maybe it should return a collection or if you move the Draw method to an interface it could then implement this interface and have a draw method that can draw its internal children collection without exposing it.

Maybe for ideas you could look at WPF as well - it is a very well designed framework in my opinion. Also you may take a look at the Composition and Decorator design patterns. The first one may be useful for container elements and the second for adding functionality to elements. I can't say how well that will work at first look, but here's what I think:

To avoid the duplication you could have something like primitive elements - like the text element. Then you can build the more complicated elements using these primitives. For example, a Border is an element that has a single child. When you call its Draw method it draws a border and a background and then draws its child inside the border. A TextElement only draws some text. Now if you want a button, you can build one by composing those two primitives, i.e. putting text inside the Border. Here the Border is something like a Decorator.

The post is getting rather long so let me know if you find that idea interesting and I can give some examples or more explanation.

2
  • 1
    missing params are not a problem, it is just a sketch of real methods after all. Regarding WPF I have designed layout system based on their idea so I think I have composition part already in place, as for decorators I will have to think about it. your idea of primitive elements sounds reasonable and I definitely give it a try. thanks!
    – grapkulec
    Jul 1, 2011 at 11:58
  • @grapkulec You're welcome! About params - yeah, it's ok, I just wanted to be sure I understand your intensions correctly. I'm glad you like the idea. Good luck!
    – Georgi Stoyanov
    Jul 1, 2011 at 12:16
2

Cutting out the code and going to the core of your question "Is my design with abstract base class and interfaces not as types but more as behaviors is good design or not.", I'd say there's nothing wrong with that approach.

I have in fact used such an approach (in my rendering engine) where each interface defined the behavior the consuming subsystem expected. For example, IUpdateable, ICollidable, IRenderable, ILoadable, ILoader and so on and so forth. For clarity, I also separated each of these "behaviors" into separate partial classes like "Entity.IUpdateable.cs", "Entity.IRenderable.cs" and tried to keep the fields and methods as independent as possible.

The approach of using interfaces to define behavioral patterns also agrees with me because generics, constraints and co(ntra)variant type parameters work very well together.

One of things I observed about this method of design was: If ever you find yourself defining an interface with more than a handful of methods, you're probably not implementing a behavior.

1
  • did you run into any problems or in any moment you was regretting such design and had to fight with your own code to actually get intended things done? like for example suddenly it turned out that you need to create so many behavior implementations that it just made no sense and you wished you just sticked to old theme "make base class with million virtuals and just inherit and override the hell out from it"?
    – grapkulec
    Jul 1, 2011 at 23:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.