Как мне разработать набор связанных классов, где только некоторые из них поддерживают определенную операцию? - PullRequest
10 голосов
/ 07 сентября 2011

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

Только некоторые из этих предметов поддерживают fill , тогда как другие нет.

Как лучше всего реализовать заливку для элементов слайда в этом случае? Вот два способа, о которых я подумал:

  1. Создание интерфейса Fillable и реализация этого интерфейса для элементов слайдов которые поддерживают заполнение, сохраняя все свойства, связанные с заполнением в интерфейсе. При переборе списка элементов слайдов динамически в Fillable, и в случае успеха выполните операцию, связанную с заполнением.

  2. Сделать класс fill. Сделайте указатель fill частью класса элемента слайда, назначьте fill Объект на указатель fill для тех объектов, которые поддерживают заполнение, а для остальных из них оставить нулевым. Дайте функцию GetFill, которая будет возвращать fill для элементов, если она существует, в противном случае возвращает NULL.

Какой лучший подход для этого? Я заинтересован в производительности и ремонтопригодности.

Ответы [ 6 ]

10 голосов
/ 07 сентября 2011

Я бы сделал комбинацию из двух. Создайте свой Fillable интерфейс и сделайте так, чтобы он был типом возврата для вашего GetFill метода. Это лучше, чем динамический подход. Использование динамического приведения к запросу для интерфейса требует, чтобы фактический объект элемента слайда реализовал интерфейс, если он должен его поддерживать. Однако с помощью метода доступа, подобного GetFill, у вас есть возможность предоставить ссылку / указатель на некоторый другой объект, который реализует интерфейс. Вы также можете просто вернуть this, если интерфейс фактически реализован этим объектом. Такая гибкость помогает избежать раздувания классов и способствует созданию повторно используемых компонентов, которые могут совместно использоваться несколькими классами.

Edit: Этот подход также хорошо работает с шаблоном нулевого объекта. Вместо возврата нулевого указателя для объектов, которые не поддерживают Fillable, вы можете вернуть простой неоперативный объект, который реализует интерфейс. Тогда вам не нужно постоянно проверять наличие нулевых указателей в клиентском коде.

2 голосов
/ 07 сентября 2011

Ответ , это зависит .

Я не вижу смысла в необходимости загромождать ваш базовый интерфейс с fill/get_fillable_instance/..., если не каждый объект должен обрабатывать заполнение.Однако вы можете обойтись просто с помощью

struct slide_object
{
    virtual void fill() {} // default is to do nothing
};

, но это зависит от того, считаете ли вы, что fill должен появиться в абстрактном классе объекта слайда.Однако это редко следует делать, если исключение не заполняется, что является исключительным.

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

В некоторых случаях я успешно использовал этот подход, и он прост и удобен в обслуживании, если логика диспетчеризации не рассеяна.(т.е. есть только одно или два места, где вы динамически применяете).

Если ожидается, что у вас будет более похожее на заливку поведение, тогда dynamic_cast - неправильный выбор, поскольку он приведет к

if (auto* p = dynamic_cast<fillable*>(x))
   ...
else if (auto* p = dynamic_cast<quxable*>(x))
   ...

что плохо.Если вам это понадобится, реализуйте шаблон Visitor.

0 голосов
/ 08 сентября 2011

Я предлагаю использовать интерфейс для фигур с методом, который возвращает заполнитель. Например:

class IFiller {
public:
    virtual void Fill() = 0;

protected:
    IFiller() {}
    virtual ~IFiller() {}
};

class IShape {
public:
    virtual IFiller* GetFiller() = 0;

protected:
    IShape() {}
    virtual ~IShape() {}
};

class NullFiller : public IFiller {
public:
    void Fill() { /* Do nothing */ }
};

class Text : public IShape {
public:
    IFiller* GetFiller() { return new NullFiller(); }
};

class Rectangle;
class RectangleFiller : public IFiller {
public:
    RectangleFiller(Rectangle* rectangle) { _rectangle = rectangle; }
    ~RectangleFiller() {}

    void Fill() { /* Fill rectangle space */ }

private:
    Rectangle* _rectangle;
};

class Rectangle : IShape {
public:
    IFiller* GetFiller() { return new RectangleFiller(this); }
};

Я считаю, что этот метод проще поддерживать и расширять, хотя он не вызывает серьезных проблем с производительностью.

0 голосов
/ 07 сентября 2011

Можно хранить Вариант элементов, таких как boost::variant.

Вы можете определить boost::variant<Fillable*,Item*> (вы должны использовать умные указатели, если у вас есть право собственности), а затем иметь список тех вариантов, по которым нужно итерировать.

0 голосов
/ 07 сентября 2011

Кажется, что то, что вы ищете, близко к Capability Pattern .Ваш # 2 близок к этому шаблону.Вот что я бы сделал:

Создайте класс заполнения.Сделайте указатель заполнения частью класса элемента слайда, назначьте объекту заполнения указатель заполнения только для тех объектов, которые поддерживают заполнение, а для остальных из них он будет нулевым.Создайте функцию GetCapability (Capability.Fill), которая будет возвращать заливку для элементов, если она существует, в противном случае возвращает NULL.Если некоторые из ваших объектов уже реализуют интерфейс Fillable, вы можете вместо этого вернуть объект, приведенный к указателю Fillable.

0 голосов
/ 07 сентября 2011

Создание базового класса SlideItem:

class SlideItem {
    public:
        virtual ~SlideItem();
        virtual void fill() = 0;
};

Затем создайте пустую реализацию для тех, кого вы не можете заполнить:

class Button : public SlideItem {
    public:
        void fill() { }
};

И правильная реализация заполнения для остальных:

class Rectangle : public SlideItem {
    public:
        void fill() { /* ... fill stuff ... */ }
};

и поместите их все в контейнер ... если вы хотите заполнить их, просто позвоните всем ... просты в обслуживании ... и кого волнует производительность


Если вам действительно нужен быстрый код, ваше первое решение, безусловно, хорошо. Но если вы делаете это так, убедитесь, что вам не нужно разыгрывать его каждый раз, когда вы хотите заполнить. Разыграйте их один раз и поместите указатели в заполняемый контейнер. Затем переберите этот заполняемый контейнер, если вам нужно заполнить.

Опять же, ИМХО, вы приложили слишком много усилий, без разумного прироста производительности. (конечно, я не знаю вашу заявку, это может быть оправдано, но обычно нет)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...