Реализация шаблона посетителя. Проблема столкновения астероида и космического корабля

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

interface Collidable
{
    void Accept(IVisitor other);
}

class Asteroid : Collidable
{
    public void Accept(IVisitor other)
    {
        Console.Write("[Asteroid] ");
        other.visitAsteroid(this);
    }
}

class Spaceship : Collidable
{
    public void Accept(IVisitor other)
    {
        Console.Write("[Spaceship] ");
        other.visitSpaceship(this);
    }
}

interface IVisitor
{
    void visitAsteroid(Asteroid a);
    void visitSpaceship(Spaceship s);
}

class CollisionWithAsteroidVisitor : IVisitor
{
    public void visitAsteroid(Asteroid a)
    {
        Console.WriteLine("Collided with asteroid");
    }

    public void visitSpaceship(Spaceship s)
    {
        Console.WriteLine("Collided with asteroid");
    }
}

class CollisionWithSpaceShipVisitor : IVisitor
{
    public void visitAsteroid(Asteroid a)
    {
        Console.WriteLine("Collided with spaceship");
    }

    public void visitSpaceship(Spaceship s)
    {
        Console.WriteLine("Collided with spaceship");
    }
}

    static void Main(string[] args)
    {
        Asteroid a1 = new Asteroid();
        Asteroid a2 = new Asteroid();
        Spaceship s1 = new Spaceship();
        Spaceship s2 = new Spaceship();

        s1.Accept(new CollisionWithAsteroidVisitor()); // this must be wrong
        s1.Accept(new CollisionWithSpaceShipVisitor()); // this must be wrong
    }

2 ответа

Решение

Как я понимаю, вы хотите добиться того, чтобы разные объекты могли сталкиваться друг с другом, и когда такое столкновение происходит, участники будут знать, с каким другим видом объектов они столкнулись.

Чтобы достичь этого без использования рефлексии (или, как вы говорите, RTTI, хотя это термин C++), рекомендуется использовать шаблон Visitor. Что вы сделали неправильно, так это то, что в этом сценарии оба объекта будут действовать как акцептор и как посетитель, в зависимости от того, с кем столкнулся, с каким. Объект, с которым мы сталкиваемся, будет акцептором ("принимает сталкивающийся объект"), а объект, который сталкивается с другим, становится посетителем ("посещает / сталкивается с объектом-акцептором).

Роли акцептора и посетителя могут быть изменены на противоположные, когда сталкивающийся объект является другим (движущийся астероид сталкивается с космическим кораблем или движущийся космический корабль сталкивается с неподвижным астероидом). Из этого примера вы можете видеть, что один объект может выступать в роли акцептора или посетителя в зависимости от случая. Это должно быть отражено в иерархии классов, поэтому оба объекта должны реализовывать интерфейс ICollidable и IVisitor.

Я переписал код, который вы выложили, поэтому классы Asteroid и Spaceship реализуют два интерфейса. Дополнительные классы посетителей больше не нужны, потому что наши объекты сами стали посетителями:

interface ICollidable
{
    void Accept(IVisitor other);
}

interface IVisitor
{
    void VisitAsteroid(Asteroid a);
    void VisitSpaceship(Spaceship s);
}

class Asteroid : ICollidable, IVisitor
{
    public void Accept(IVisitor other)
    {
        Console.Write("[Asteroid] ");
        other.VisitAsteroid(this);
    }

    public void VisitAsteroid(Asteroid a)
    {
        Console.WriteLine("Collided with asteroid");
    }

    public void VisitSpaceship(Spaceship s)
    {
        Console.WriteLine("Collided with asteroid");
    }
}

class Spaceship : ICollidable, IVisitor
{
    public void Accept(IVisitor other)
    {
        Console.Write("[Spaceship] ");
        other.VisitSpaceship(this);
    }

    public void VisitAsteroid(Asteroid a)
    {
        Console.WriteLine("Collided with spaceship");
    }

    public void VisitSpaceship(Spaceship s)
    {
        Console.WriteLine("Collided with spaceship");
    }
}


class Main
{
    public static void Main(string[] args)
    {
        Asteroid a1 = new Asteroid();
        Asteroid a2 = new Asteroid();
        Spaceship s1 = new Spaceship();
        Spaceship s2 = new Spaceship();

        s1.Accept(a1);
        s1.Accept(as);
        a1.Accept(s1);
        a2.Accept(a2);
    }
}

И если вы запустите программу, вы получите следующий вывод в консоли:

[Spaceship] Collided with asteroid
[Spaceship] Collided with spaceship
[Asteroid] Collided with spaceship
[Asteroid] Collided with asteroid

Надеюсь, вам стало ясно, как использовать шаблон Visitor для таких сценариев.

С тем же успехом вы можете взглянуть на модель Посредника.

Согласно странице Википедии,

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

Конкретно, в вашем случае Посредник будет классом, к которому все Collidables будут зарегистрированы, и это будет контролировать их на предмет столкновений. Когда происходит столкновение, Посредник будет вызывать HandleCollision(Collidable other) метод на обоих сталкивающихся объектах.

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

Другие вопросы по тегам