2

Suppose I have an abstract base class called Component, which is the root of a hierarchy of GUI components. In such a case, we might have two subclasses, Button and Label, both of which are also abstract classes and exist as the root of their own respective hierarchies of concrete classes.

Concrete classes inheriting from Button might include RoundButton and SquareButton.

Concrete classes inheriting from Label might include TextLabel and PictureLabel.

Finally, let's say there's an aggregate Container class that holds a collection of Component objects.

The problem is that I have pointers to Component objects, but I need to identify them as being either Buttons or Labels. For example, if I want to specify that all Buttons should have a larger font for their interior text, I could iterate over all of the Component objects in the Container and somehow determine which ones are buttons, and call some method specific to buttons.

One way for these Component "families" to identify themselves is with strings.

class Component {
public:
    virtual char const * const getFamilyID() const = 0;
};

// In Button.h
char const * const COMPONENT_BUTTON = "button";

class Button : public Component {
public:
    virtual char const * const getFamilyID() const { return COMPONENT_BUTTON; };
};

// Code sample
if (strcmp(component->getFamilyID(),COMPONENT_BUTTON) == 0)
    // It's a button!

This is loosly coupled in that Component leaves the task of defining these families to its children; it does not require knowledge of what families exist. The client needs to be aware of the different Component families, but if it's trying to target a specific one for some operation, then that can't be avoided.

However, suppose we have really high performance requirements and we want to avoid comparing strings. It would also be nice to avoid making this function virtual so we can inline it. Also, if every subclass of Component is going to need to declare a global constant, it might be nice to somehow modify the Component class to either make this a requirement or make it unnecessary.

One solution to this problem is to define an enumerator in Component.h

enum COMPONENT_FAMILY {
    COMPONENT_BUTTON = 0,
    COMPONENT_LABEL,
    // etc...
};

In this case getFamilyID() can just return a COMPONENT_FAMILY enum and we can basically just compare ints. Unfortunately, this means that any new component families will have to be "registered" in this enum, which is easy but isn't entirely intuitive for other programmers. Also, the method still has to be virtual unless we make a nonstatic COMPONENT_FAMILY member that we know will have extremely low cardinality (not ideal).

What would be a good way to solve this problem? In my case, performance is key, and while something similar to the enum solution seems easy enough, I'm left wondering if I'm overlooking a better way.

--- EDIT ---
I realize that I should probably point out that in the actual system, the Container equivalent can only store 1 Component from each family. Therefore, the Components are actually stored in a map such as:

std:map<COMPONENT_FAMILY, Component*>

Applied to my simple example here, this would mean that a Container could only contain 1 Button, 1 Label, etc.

This makes it really easy to check for the existence of a particular type of Component (log time). Therefore, this question is more about how to represent COMPONENT_FAMILY, and how to determine the Component's type when I'm adding it to the map.

In other words, a component's sole purpose is to be identified as a specific piece of functionality added to the Container, and together all of the components define a specific behavior for the container.

So, I don't need to know the type of a Component, that's already implied. I am specifically asking the Container for a specific type of Component. What I need is a way for the Component to communicate its type so it can be mapped in the first place.

Sisyphus
  • 4,181
  • 1
  • 22
  • 15
Shaun
  • 2,490
  • 6
  • 30
  • 39
  • You say "Unfortunately, this means that any new component families will have to be "registered" in this enum, which is easy but isn't entirely intuitive for other programmers" ... why? You can publish the enum in an API and also have a delimiter enum member `COMPONENT_END` which allows the clients to decide if they are out of the enum bounds. As long as you maintain backwards compatibility by only adding new members directly before the delimiter value, changes should be transparent to the client. Enums also have symbolic names for debugging, and can be defined in a header file. – Dennis Jun 07 '11 at 13:27
  • You're worried about speed for iterating over a bunch of GUI components in order to manipulate their fonts? That seems wrong. Could you possibly be slower than the font engine that has then to change all those fonts? or, FTM, the users, which have only their human eyes for following these update? – sbi Jun 07 '11 at 13:27
  • @sbi: This is, as my examples usually are, a simple example that captures the essence of the problem without providing too many unnecessary details. This actual system iterate over the components in containers, calling different functions for all of them, tens to hundreds of thousands of times per second. – Shaun Jun 07 '11 at 13:42
  • @Dennis: I mean it's not exactly intuitive for programmers implementing new families of classes. There's no problem with it from the client's perspective, or at least that's how I see it. When you create a new class, you'd have to somehow know that you need to edit some .h file somewhere. – Shaun Jun 07 '11 at 13:44
  • Ah yes... valid point :) – Dennis Jun 07 '11 at 13:57

6 Answers6

5

I need to identify them as being either Buttons or Labels.

That's your problem. That assumption, as common as it might be, is usually wrong.

  • Why do you believe that you need to identify them?
  • At what stage in your code is that knowledge important?

You can probably get around the "requirement" to know their concrete type by assigning a UI strategy for the control at construction time. Retrieve the font property of that UI strategy at paint time (or whenever) in the base class:

class IVisualStrategy
{
    ...
    virtual const Font& GetFont() const = 0;
    ...
};

class HeavyVisuals : public IVisualStrategy
{
    Font font_;
    ...
    HeavyVisuals() : font_(15, Bold) {}

    virtual const Font& GetFont() const { return font_; }
    ...
};

class LightVisuals : public IVisualStrategy
{
    Font font_;
    ...
    LightVisuals() : font_(12, Regular) {}

    virtual const Font& GetFont() const { return font_; }
    ...
};

Retrieve from base:

class Control
{
    ...
private:
    void OnPaintOrSomething()
    {
        DrawTextWithFont(GetVisualStrategy().GetFont());
    }

    virtual const IVisualStrategy& GetVisualStrategy() const = 0;
};

class Button : public Control
{
    HeavyVisualStrategy visualStrategy_;
    ...
private:
    virtual const IVisualStrategy& GetVisualStrategy() const
    {
        return visualStrategy_;
    }
}

class Label : public Control
{
    LightVisualStrategy visualStrategy_;
    ...
private:
    virtual const IVisualStrategy& GetVisualStrategy() const
    {
        return visualStrategy_;
    }
}

A more flexible design is to keep shared pointers to IVisualStrategy in the concrete control classes and constructor-inject it, instead of hard-setting them to Heavy or Light.

Also, to share font between objects in this design, the Flyweight pattern can be applied.

Johann Gerell
  • 24,991
  • 10
  • 72
  • 122
  • Some components represent systems that need to be "serviced" with methods specific to that family of components, but I would like to maintain a single generic list. This knowledge is important in the loop/thread that updates and services these systems. – Shaun Jun 07 '11 at 13:46
  • @Shaun: What's preventing the specific service method implementation from being a member of a strategy? Being that allows the component to be self-contained so that the base need not know what it is. – Johann Gerell Jun 07 '11 at 13:52
  • Wrote that first comment before you added the example code. Anyway, even if I just defined a pure virtual method called update() or wrote the equivalent into various strategy classes that I could hook up with specific component families (which is what I believe you are implying), it wouldn't be so simple. In my actual system, a component from one family might update a component from another family if it exists in the same container, components of a certain family might need to be updated in several passes, etc. The relationships are complex, and just being able to determine the type is easy. – Shaun Jun 07 '11 at 14:20
4

Dynamic casting will do this without introducing any magic constants:

if (Button * button = dynamic_cast<Button *>(component)) {
    // It's a button.
}

UPDATE: now you've updated the question with the requirement for type-based map keys, dynamic casting won't work. One approach to avoid the coupling of a central enumeration might be to have a static key generator, something like:

class Component
{
public:
    virtual int getFamilyID() const = 0;
    static int generateFamilyID() 
    {
        static int generator = 0;
        return generator++;
    }
};

class Label
{
public:
    virtual int getFamilyID() const {return familyID;}
private:
    static const int familyID;
};

class Button
{
public:
    virtual int getFamilyID() const {return familyID;}
private:
    static const int familyID;
};

const int Label::familyID  = Component::generateFamilyID();
const int Button::familyID = Component::generateFamilyID();
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • It may also help if Button would be an interface (or ABC in c++ terms). Else there is still tight coupling to the implementation. – Markus Kull Jun 07 '11 at 13:45
  • This is a great idea, given what I originally wrote. However, I updated my question to clarify that I'm actually searching the container for a specific type of object that I know beforehand, and I'm looking for a way to communicate that type. – Shaun Jun 07 '11 at 14:40
  • This forces the client to initialize and keep track of a collection of variables representing the various family IDs at runtime, which isn't as convenient as a global enum. However, it would decouple the Component class from its children as I asked, so +1 – Shaun Jun 07 '11 at 15:54
1

Use Double Dispatcher pattern

http://en.wikipedia.org/wiki/Double_dispatch

jcalvosa
  • 46
  • 1
1

Just use dynamic_cast, that's what it's for. Oh, and the need for this is generally considered to be badness.

Puppy
  • 144,682
  • 38
  • 256
  • 465
0

You can use dynamic_cast to test if a given Component is an instance of Button or one of its subclasses:

Button* btn = dynamic_cast<Button*>(component);
if (btn) {
    // it's a button!
    btn->setFontSize(150);
}
tdammers
  • 20,353
  • 1
  • 39
  • 56
0

I think visitor pattern can be applied to your case. By using this pattern you may avoid adding a getFamilyID() kind of method to your hierarchy and let the compiler do dispatching for you. This way you don't have to put a lot of if (dynamic_cast<> ) kind of conditional logic into your code.

O.C.
  • 6,711
  • 1
  • 25
  • 26