C ++ - Идентификация семейства полиморфных классов без введения тесной связи - PullRequest
2 голосов
/ 07 июня 2011

Предположим, у меня есть абстрактный базовый класс под названием Component, который является корнем иерархии компонентов GUI.В таком случае у нас может быть два подкласса, Button и Label, оба из которых также являются абстрактными классами и существуют как корень своих собственных соответствующих иерархий конкретных классов.

Конкретные классы, наследуемые от Button, могут включать RoundButtonи SquareButton.

Конкретные классы, наследуемые от Label, могут включать TextLabel и PictureLabel.

Наконец, предположим, что есть агрегатный класс Container, который содержит коллекцию объектов Component.

Проблема в том, чтоУ меня есть указатели на Компонентные объекты, но мне нужно идентифицировать их как кнопки или метки.Например, если я хочу указать, что все кнопки должны иметь больший шрифт для внутреннего текста, я мог бы перебрать все объекты Component в контейнере и каким-то образом определить, какие из них являются кнопками, и вызвать некоторый метод, специфичный для кнопок.

Один из способов, с помощью которого эти "семьи" Компонента могут идентифицировать себя, - это строки.

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!

Это слишком сложно, так как Компонент оставляет задачу определения этих семейств своим детям;это не требует знания того, какие семьи существуют.Клиенту необходимо знать о различных семействах компонентов, но если он пытается нацелить конкретный компонент на какую-то операцию, этого нельзя избежать.

Однако предположим, что у нас действительно высокие требования к производительности, и мыхочу избежать сравнения строк.Также было бы неплохо избежать виртуализации этой функции, чтобы мы могли встроить ее.Кроме того, если каждому подклассу Component потребуется объявить глобальную константу, было бы неплохо как-то изменить класс Component, чтобы либо сделать это требованием, либо сделать его ненужным.

Одним из решений этой проблемы являетсячтобы определить перечислитель в Component.h

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

В этом случае getFamilyID () может просто возвращать перечисление COMPONENT_FAMILY, и мы можем просто сравнивать целые числа.К сожалению, это означает, что любые новые семейства компонентов должны быть «зарегистрированы» в этом перечислении, что легко, но не совсем интуитивно понятно для других программистов.Кроме того, метод все еще должен быть виртуальным, если мы не сделаем нестатический элемент COMPONENT_FAMILY, который, как мы знаем, будет иметь чрезвычайно низкое количество элементов (не идеально).

Что может быть хорошим способом решения этой проблемы?В моем случае производительность является ключевым фактором, и хотя что-то похожее на решение enum кажется достаточно простым, мне остается только задуматься, не пропускаю ли я лучший способ.

--- EDIT ---
Я понимаю, что, вероятно, следует указать, что в реальной системе эквивалент Контейнера может хранить только 1 компонент из каждого семейства.Следовательно, компоненты на самом деле хранятся на карте, например:

std:map<COMPONENT_FAMILY, Component*>

Применительно к моему простому примеру здесь это будет означать, что контейнер может содержать только 1 кнопку, 1 метку и т. Д.

Это позволяет очень легко проверять наличие определенного типа Компонента (время регистрации).Поэтому этот вопрос больше о том, как представлять COMPONENT_FAMILY и как определить тип компонента, когда я добавляю его на карту.

Другими словами, единственная цель компонента - идентифицировать его как определенныйчасть функциональности, добавленная в Контейнер, и вместе все компоненты определяют конкретное поведение для контейнера.

Итак, мне не нужно знать тип Компонента, это уже подразумевается.Я специально спрашиваю Контейнер для конкретного типа Компонента.Мне нужен способ, чтобы Компонент мог сообщить свой тип, чтобы он мог отображаться в первую очередь.

Ответы [ 6 ]

5 голосов
/ 07 июня 2011

Мне нужно идентифицировать их как кнопки или метки.

Это ваша проблема.Такое предположение, как бы часто оно ни было, обычно ошибочно.

  • Почему вы считаете, что вам необходимо их идентифицировать?
  • На каком этапе вашего кода важны эти знания?

Вероятно, вы можете обойти «требование» знать их конкретный тип, назначив стратегию пользовательского интерфейса для элемента управления во время строительства.Получить свойство шрифта этой стратегии пользовательского интерфейса во время рисования (или в любое время) в базовом классе:

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_; }
    ...
};

Получить из базы:

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_;
    }
}

Более гибкий дизайн - сохранить общий доступуказатели на IVisualStrategy в конкретных управляющих классах и конструктор-инжектор, вместо того, чтобы жестко устанавливать их в Heavy или Light.

Кроме того, для совместного использования шрифта между объектами в этом проекте, Flyweight Может применяться.

4 голосов
/ 07 июня 2011

Динамическое приведение будет делать это без введения каких-либо магических констант:

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

ОБНОВЛЕНИЕ: теперь вы обновили вопрос с требованием для ключей карты на основе типов, динамическое приведение не будет работать.Один из способов избежать связывания центрального перечисления может состоять в том, чтобы иметь генератор статического ключа, что-то вроде:

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();
1 голос
/ 07 июня 2011

Просто используйте dynamic_cast, вот для чего оно.О, и потребность в этом обычно считается плохой.

1 голос
/ 07 июня 2011

Использовать шаблон Double Dispatcher

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

0 голосов
/ 07 июня 2011

Я думаю, шаблон посетителя может быть применен к вашему делу.Используя этот шаблон, вы можете избежать добавления метода getFamilyID () в вашу иерархию и позволить компилятору выполнять диспетчеризацию за вас.Таким образом, вам не нужно помещать в код много условных логик if (dynamic_cast<> ).

0 голосов
/ 07 июня 2011

Вы можете использовать dynamic_cast, чтобы проверить, является ли данный Component экземпляром Button или одним из его подклассов:

Button* btn = dynamic_cast<Button*>(component);
if (btn) {
    // it's a button!
    btn->setFontSize(150);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...