Добавление функциональности класса через композицию - PullRequest
4 голосов
/ 13 августа 2010

Предположим, у нас есть абстрактный класс Element, из которого получены классы Triangle и Quadrilateral.

Предположим, что эти классы используются вместе с методами интерполяции, которые зависят от формы элемента.Итак, в основном мы создаем абстрактный класс InterpolationElement, из которого мы получаем InterpolationTriangle и InterpolationQuadrilateral.

Затем, чтобы включить функциональность интерполяции в классы Triangle и Quadrilateral, мы добавляем элемент данных const-reference в класс Element типа InterpolationElement, то есть:

class Element
{
public:
    Element(const InterpolationElement& interp);

    const InterpolationElement& getInterpolation() const;

private:
    const InterpolationElement& interpolation;
};

Затем мы создаем метод (как описано Скоттом Мейерсом, Effective C ++), который создает локальный статический объект класса InterpolationTriangle как

const InterpolationTriangle& getInterpolationTriangle()
{
    static InterpolationTriangle interpolationTriangle;

    return interpolationTriangle;
}

, чтобы можно было создать класс Triangleкак:

class Triangle : public Element
{
public:
    Triangle() : Element( getInterpolationTriangle() ) {}
};

Вот мой вопрос: правильный ли этот подход, чтобы включить методы интерполяции в мой класс Element?Используется ли это в профессиональных сценариях?

Я мог бы напрямую реализовать все методы интерполяции в классе Element (как чисто виртуальные) и переопределить их в производных классах Triangle и Quadrilateral.Однако этот подход кажется мне громоздким, поскольку каждый раз, когда мне нужно улучшить или реализовать новые функции интерполяции, мне придется делать это на этих классах.Более того, классы становятся все больше и больше (много методов), используя этот подход.

Я хотел бы услышать от вас несколько советов и комментариев

Заранее спасибо.


Дополнительные детали:

class InterpolationElement
{
public:
    InterpolationElement();

    virtual double interpolationMethod1(...) = 0;
                      :
    virtual double interpolationMethodN(...) = 0;
}

class InterpolationTriangle : public InterpolationElement
{
public:
    InterpolationTriangle () {}

    virtual double interpolationMethod1(...) { // interpolation for triangle }
                      :
    virtual double interpolationMethodN(...) { // interpolation for triangle }
}

class InterpolationQuadrilateral : public InterpolationElement
{
public:
    InterpolationTriangle () {}

    virtual double interpolationMethod1(...) { // interpolation for quadrilateral}
                      :
    virtual double interpolationMethod1(...) { // interpolation for quadrilateral}
}

Ответы [ 5 ]

1 голос
/ 23 августа 2010

Первое, что приходит мне в голову, это посетитель GoF Design Pattern

Исходя из того, что я понимаю в вашей проблеме, эта схема предназначена для точного решения этой проблемы.

Каждый объект Visitor определяет метод интерполяции или алгоритм, применяемый к вашему объекту.

Таким образом, класс Element вообще не растет с каждой новой функциональностью. Однажды созданный шаблон Visitor позволяет расширить функциональность, не затрагивая определение базового класса.

1 голос
/ 23 августа 2010

Вы всегда можете немного повозиться с шаблонами. Сначала у нас есть высший класс.

class Element {
    public:
        virtual void calculate() const = 0;
};

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

template <typename Interpolation>
class Element_Impl : public Element {
    protected:
        Interpolation m_interpolation;
};

И интерполяционные классы. Обратите внимание, они не братья и сестры, потому что им это не нужно.

class InterpolationTriangle {
    public:
        double interpolate(double a, double b) const {
            std::cout << "interpolation triangle" << std::endl;
        }
};
class InterpolationQuadrilateral {
    public:
        double interpolate(double a, double b) const {
            std::cout << "interpolation quadrilateral" << std::endl;
        }
};

И, наконец, реальные элементы и небольшая основная процедура.

class Triangle : public Element_Impl<InterpolationTriangle> {
    public:
        void calculate() const {
            m_interpolation.interpolate(1.0, 2.0);
        }
};
class Quadrilateral : public Element_Impl<InterpolationQuadrilateral> {
    public:
        void calculate() const {
            m_interpolation.interpolate(2.0, 3.0);
        }
};
int main() {
    const Element &a = Triangle();
    const Element &b = Quadrilateral();
    a.calculate();
    b.calculate();
}

Резюме:

  • при необходимости вы можете легко переключать класс интерполяции для каждого элемента.
  • нет двойного доступа vtable (сначала для вычисления Элемента, а затем для методов интерполяции InterpolationElement), как в примере Матье. Каждый элемент знает во время компиляции, какой класс интерполяции он использует.
  • Element_Impl - уродливый бит, но он спасает нас от коппасты. Вы можете расширить его, реализовав метод интерполяции оболочки
  • http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

Одним из способов является использование статических методов и определение оболочки в Element_Impl - все еще только в одном месте.

class Element {
    public:
        virtual void calculate() const = 0;
};

template <typename Interpolation>
class Element_Impl : public Element {
    protected:
        void interpolate(double, double) const {
            Interpolation::interpolate(1, 1);
        }
};

class InterpolationTriangle {
    public:
        static double interpolate(double a, double b) {
            std::cout << "interpolation triangle" << std::endl;
        }
};

class InterpolationQuadrilateral {
    public:
        static double interpolate(double a, double b) {
            std::cout << "interpolation quadrilateral" << std::endl;
        }
};

class Triangle : public Element_Impl<InterpolationTriangle> {
    public:
         void calculate() const {
            interpolate(1.0, 2.0);
        }
};

class Quadrilateral : public Element_Impl<InterpolationQuadrilateral> {
    public:
        void calculate() const {
            interpolate(2.0, 3.0);
        }
};

int main() {
    const Element &a = Triangle();
    const Element &b = Quadrilateral();

    a.calculate();
    b.calculate();
}
1 голос
/ 13 августа 2010

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

1 голос
/ 13 августа 2010

В вашем предложении есть одна маленькая проблема: вы добавили метод, связанный с интерполяцией, в свой базовый класс и изменили конструктор ...

Итак, прежде всего, если вы хотите это сделатьтаким образом, вот как вы должны это сделать:

class Element
{
public:

private:
  // similar signature to a `clone` method
  virtual InterpolationElement* interpolation() const = 0;
};

class Triangle
{
public:

private:
  virtual InterpolationTriangle* interpolation() const
  {
    return new InterpolationTriangle();
  }
};

Здесь есть 2 преимущества:

  • Больше нет необходимости изменять конструктор каждого из производных объектов
  • Объект стратегии больше не const, что позволяет ему поддерживать состояние во время вычислений ... как ссылка на текущий объект, который интерполируется.

Однако это все ещеТребуется изменить класс Element и каждый из его производных классов.Вас это не беспокоит?на двойной диспетчерской, чтобы работать должным образом.Однако он позволяет настроить иерархию Element ONCE (с помощью метода accept), а затем добавить столько операций, сколько вы пожелаете.И это здорово.

1 голос
/ 13 августа 2010

Классы используются вместе с интерполяцией методов .Почему эти методы должны быть в одноэлементном объекте?Синглтон здесь выглядит очень проблематично.

class Element
{
public:
    virtual double interpolationMethod1(...) = 0;
                  :
    virtual double interpolationMethodN(...) = 0;

};

class Triangle : public Element
{
public:
    virtual double interpolationMethod1(...) { // interpolation for triangle }
                  :
    virtual double interpolationMethodN(...) { // interpolation for triangle }
}

Кроме того, добро пожаловать на SO!

...