2012-01-26 4 views
8

EDIT: Я работаю с C++.Самый элегантный способ получить этот полиморфизм Проблема

Итак, я создаю методы/функции для проверки пересечения между фигурами. Я по существу есть это:

class Shape {}; 

class Rectangle : public Shape {}; 

class Circle : public Shape {}; 

class Line : public Shape {}; 

Теперь, мне нужно решить, как наилучшим образом, чтобы написать фактические методы/функции для проверки пересечения. Но все мои формы будут сохранены в списке Shape указателей, так что я буду называть метод/функцию базовой формы:

bool intersects (Shape* a, Shape* b); 

В тот момент, мне нужно, чтобы определить, какие типы «а» форм и 'b', поэтому я могу правильно обнаруживать столкновения. Я могу легко сделать один из них, просто используя некоторые виртуальные методы:

Это было бы определить одну из форм («а» теперь «это»). Тем не менее, мне все равно нужно будет получить тип «b». Очевидное решение состоит в том, чтобы дать Shape переменной 'id', чтобы классифицировать, какую форму она есть, а затем «переключать» через эти, а затем использовать dynamic_cast. Тем не менее, это не очень элегантно, и кажется, что для этого нужно больше возможностей OO.

Любые предложения?

+7

Это классическая проблема, которая решается двойной отправкой. – Mankarse

+0

Хотя мои первоначальные поиски никогда не появлялись с двойной отправкой, я вижу, как он решает мою проблему. Благодарю. EDIT: Я бы проголосовал за это, но я не уверен, что у меня недостаточно репутации, или я просто не могу найти кнопку для голосования. –

ответ

7

Как @Mandarse отметил, что это типичная проблема двойной отправки. В объектно-ориентированных языках или, например, языках C++, которые могут реализовать объектно-ориентированные концепции, это обычно решается с использованием шаблона Visitor.

Интерфейс Visitor сам определяет один обратный вызов для конкретного типа.

class Circle; 
class Rectangle; 
class Square; 

class Visitor { 
public: 
    virtual void visit(Circle const& c) = 0; 
    virtual void visit(Rectangle const& r) = 0; 
    virtual void visit(Square const& s) = 0; 
}; 

Для этого приспособлена иерархия Shape. Нам нужны два метода: один для приема любого типа посетителя, другой - для создания «подходящего» посетителя пересечения.

class Visitor; 
class Intersecter; 

class Shape { 
public: 
    virtual void accept(Visitor&) const = 0; // generic 
    virtual Intersecter* intersecter() const = 0; 
}; 

intersecter прост:

#include "project/Visitor.hpp" 

class Intersecter: public Visitor { 
public: 
    Intersecter(): result(false) {} 
    bool result; 
}; 

Например, для круга это даст:

#include "project/Intersecter.hpp" 
#include "project/Shape.hpp" 

class Circle; 

class CircleIntersecter: public Intersecter { 
public: 
    explicit CircleIntersecter(Circle const& c): _left(c) {} 

    virtual void visit(Circle const& c); // left is Circle, right is Circle 
    virtual void visit(Rectangle const& r); // left is Circle, right is Rectangle 
    virtual void visit(Square const& s); // left is Circle, right is Square 

private: 
    Circle const& _left; 
}; // class CircleIntersecter 


class Circle: public Shape { 
public: 
    virtual void accept(Visitor& v) const { v.visit(*this); } 

    virtual CircleIntersecter* intersecter() const { 
    return new CircleIntersecter(*this); 
    } 
}; 

И использование:

#include "project/Intersecter.hpp" 
#include "project/Shape.hpp" 

bool intersects(Shape const& left, Shape const& right) { 
    boost::scope_ptr<Intersecter> intersecter(left.intersecter()); 
    right.accept(*intersecter); 
    return intersecter->result; 
}; 

Если другие методы должны механизм двойной отправки, затем вам нужно создать еще один класс, подобный «Intersecter-like», который обертывает результат и наследует от Visitor и новый метод «Factory», внедренный в Shape, который переопределяется производными классами для обеспечения соответствующей операции. Это немного затянуто, но работает.

Примечание: разумно исключить intersect(circle, rectangle) и intersect(rectangle, circle), чтобы получить тот же результат. Вы можете кодировать код некоторыми методами и иметь CircleIntersecter::visit делегатов для конкретной реализации. Это позволяет избежать дублирования кода.

+0

Контрактор, который вы определили для пересечения, не имеет аргументов, а позже вы вызываете его с помощью аргументов. Зачем? – user1754322

+0

@ user1754322: Я не мог определить звонок, действительно, мне кажется, что я вообще его не называю. Вы были смущены командой 'boost :: scoped_ptr intersecter (left.intersecter());' где я создаю переменную * с именем 'intersecter'? –

+0

Я думал 'boost :: scoped_ptr intersecter (left.intersecter());' было что-то вроде 'Intersecter intersecter (слева.intersecter()), 'не так ли? – user1754322

4

Andrei Alexandrescu подробно описал эту проблему в своем классическом Modern C++ Design. Сопутствующая библиотека Loki содержит the implementation for Multi-Methods.

Update

Локи обеспечивает три реализацию Multi-методы, в зависимости от потребностей пользователя. Некоторые из них предназначены для простоты, некоторые для скорости, некоторые из них хороши для низкого сцепления, а некоторые обеспечивают большую безопасность, чем другие. Глава книги охватывает около 40 страниц, и предполагается, что читатель знаком со многими концепциями книги - если вам удобно использовать boost, то Loki может быть по вашей аллее. Я действительно не могу отдать это ответ, приемлемый для SO, но я указал вам на лучшее объяснение предмета для C++, о котором я знаю.

+1

@anon_downvoter, если вы не оправдываете свое действие, что мало помогает. – justin

+0

отсутствует код возможно? Во всяком случае, много методов будут полезны. –

+0

@ MatthieuM. Может быть. Я расширил свой ответ, чтобы объяснить, почему я не привел пример. Благодарю. – justin

1

Вы можете добавить поле shapeType к каждому Shape

Например:

class Shape { 
    virtual shapetype_t getShapeType() const; 
    // ... 
} 
2

Полиморфизм во время выполнения C++ имеет одну отправку (базовый класс vtable).

Существуют различные решения вашей проблемы, но ни один из них не является «изящным», поскольку все они пытаются заставить язык делать больше, чем он может поддерживать (Alexandrescu Loki multimethods - очень хорошо скрытый набор хаков: это инкапсулирует «плохие вещи», но не делает их хорошими)

Концепция, здесь вам нужно написать все функции возможных комбинаций и найти способ их вызова на основе фактический тип TWO во время выполнения. «Шаблон посетителя» (вызов виртуальной связи из другой виртуальной функции), технику «mutimethod» (используйте общую таблицу dspatch), «динамическое преобразование» в виртуальную функцию или «dual dynamic_cast» из всех функции все делают одно и то же: вызовите функцию после двух косвенных действий. Ни один из них не может быть технически определен «лучше, чем другой», поскольку результирующая производительность в основном одинакова.

Но некоторые из них стоят дороже, чем другие при написании кода, а другие - в обслуживании кода. Вы, скорее всего, попытаетесь оценить в своем случае, что такое компромисс. Сколько других классов, вы думаете, что вам может понадобиться добавить в будущем?

Смежные вопросы