ОО дизайн и зеркальные / дублированные методы - PullRequest
5 голосов
/ 06 декабря 2009

У меня проблема в ОО-дизайне, когда я получаю дубликат кода в 2 разных классах. Вот что происходит:

В этом примере я хочу обнаружить столкновение между игровыми объектами.

У меня есть базовый CollisionObject, который содержит общие методы (такие как checkForCollisionWith) и CollisionObjectBox, CollisionObjectCircle, CollisionObjectPolygon, которые расширяют базовый класс.

Эта часть дизайна кажется нормальной, но вот что меня беспокоит: звоню

aCircle checkForCollisionWith: aBox

выполнит проверку столкновения окружности и ящика внутри подкласса Circle. В обратном порядке

aBox checkForCollisionWith: aCircle

выполнит проверку столкновения бокса и круга внутри подкласса бокса.

Проблема здесь в том, что код коллизии Circle vs Box повторяется, так как он есть в классах Box и Circle. Есть ли способ избежать этого, или я подхожу к этой проблеме неправильно? Сейчас я склоняюсь к тому, чтобы иметь вспомогательный класс со всем дублирующим кодом и вызывать его из объектов aCircle и aBox, чтобы избежать дублирования. Мне любопытно, есть ли более элегантное ОО-решение для этого?

Ответы [ 7 ]

3 голосов
/ 06 декабря 2009

Это типичная ловушка в развитии ОО. Однажды я также попытался решить столкновения таким способом - только с треском провалился.

Это вопрос владения. Действительно ли класс Box владеет логикой столкновения с кружком? Почему не наоборот? Результатом является дублирование кода или делегирование кода столкновения из круга в блок. Оба не чистые. Двойная отправка не решает эту проблему - та же проблема с владением ...

Итак, вы правы - вам нужны сторонние функции / методы, которые решают конкретные коллизии и механизм, который выбирает правильную функцию для двух сталкивающихся объектов (здесь может использоваться двойная диспетчеризация, но если количество примитивов коллизий ограничено, вероятно, 2D массив функторов - более быстрое решение с меньшим количеством кода).

3 голосов
/ 06 декабря 2009

То, что вы хотите, называется мультидиспетчер .

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

Это можно эмулировать на основных языках ООП или использовать напрямую, если вы используете Common Lisp.

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

Вот фальшивка на наших "современных" языках:

abstract class CollisionObject {
    public abstract Collision CheckForCollisionWith(CollisionObject other);
}

class Box : CollisionObject {
    public override Collision CheckForCollisionWith(CollisionObject other) {
        if (other is Sphere) { 
            return Collision.BetweenBoxSphere(this, (Sphere)other);
        }
    }
}

class Sphere : CollisionObject {
    public override Collision CheckForCollisionWith(CollisionObject other) {
        if (other is Box) { 
            return Collision.BetweenBoxSphere((Box)other, this);
        }
    }
}

class Collision {
    public static Collision BetweenBoxSphere(Box b, Sphere s) { ... }
}

Вот в Common Lisp:

(defmethod check-for-collision-with ((x box) (y sphere))
   (box-sphere-collision x y))

(defmethod check-for-collision-with ((x sphere) (y box))
   (box-sphere-collision y x))

(defun box-sphere-collision (box sphere)
    ...)
1 голос
/ 21 января 2011

У меня была та же проблема (работа в Задаче C), и я нашел для этого обходной путь - определить внешнюю функцию для решения столкновения, когда я уже знаю типы для обоих объектов.

Например, если у меня есть Rectangle и Circle, оба реализуют протокол (вид интерфейса для этого языка) Shape ..

@protocol Shape

-(BOOL) intersects:(id<Shape>) anotherShape;
-(BOOL) intersectsWithCircle:(Circle*) aCircle;
-(BOOL) intersectsWithRectangle:(Rectangle*) aRectangle;

@end

определяет intersectsWithCircle для Rectangle и intersectsWithRectangle для Circle следующим образом

-(BOOL) intersectsWithCircle:(Circle*) aCircle
{
    return CircleAndRectangleCollision(aCircle, self);
}

и ...

-(BOOL) intersectsWithRectangle:(Rectangle*) aRectangle
{
    return CircleAndRectangleCollision(self, aRectangle);
}

Конечно, это не решает проблему связывания Double Dispatch, но, по крайней мере, позволяет избежать дублирования кода

1 голос
/ 06 декабря 2009

Вы не говорите, какой язык вы используете, поэтому я предполагаю, что это что-то вроде Java или C #.

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

В качестве альтернативы есть отдельный класс CollisionDetection, который проверяет наличие коллизий между парами объектов, и если два объекта сталкиваются, то он вызывает соответствующие методы для объектов, например, bomb.explode () и player.die (). Класс может иметь большую справочную таблицу с каждым типом объекта вдоль строк и столбцов, а также записи, дающие методы для вызова обоих объектов.

0 голосов
/ 07 декабря 2009

Первый вариант: Сделать столкновение направленным. Например, если ящик неподвижен, он не проверяет свои собственные столкновения с чем-либо еще; но движущийся круг проверяет столкновение с коробкой (и другими стационарными объектами). Это не интуитивно понятно, потому что всю жизнь нас учат «равным и противоположным реакциям». Ловушка: движущиеся объекты будут дублировать столкновения с другими движущимися объектами.


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

Скажите, что у коробки есть id = 2, а у круга есть id = 5. Затем будет выполнено «окно сталкивается с кругом», так как box.id

0 голосов
/ 06 декабря 2009

Вы должны использовать checkForCollisionWith: aCollisionObject, и, поскольку все ваши объекты расширяют CollisionObject, вы можете поместить туда всю общую логику.

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

0 голосов
/ 06 декабря 2009

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

...