Реализация шаблона посетителя. Проблема столкновения астероида и космического корабля
Я пытаюсь узнать о двойной рассылке и шаблоне посетителей, но следующий код явно неверен. Я, должно быть, упускаю что-то очевидное, но я не знаю, как это исправить. Кто-нибудь может осветить меня? Я не знаю, как поместить конкретные экземпляры в посетителя, я должен создать конструктор в конкретных классах посетителей?
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 для таких сценариев.
С тем же успехом вы можете взглянуть на модель Посредника.
Согласно странице Википедии,
С помощью шаблона-посредника связь между объектами инкапсулируется с объектом-посредником. Объекты больше не общаются напрямую друг с другом, а вместо этого общаются через посредника. Это уменьшает зависимости между взаимодействующими объектами, тем самым снижая связь.
Конкретно, в вашем случае Посредник будет классом, к которому все Collidable
s будут зарегистрированы, и это будет контролировать их на предмет столкновений. Когда происходит столкновение, Посредник будет вызывать HandleCollision(Collidable other)
метод на обоих сталкивающихся объектах.
Еще одним преимуществом этого является то, что вы не связаны с какими-либо конкретными реализациями; Ваш механизм столкновения зависит от Collidable
абстракция. Завтра вы можете добавить еще один класс и сделать Collidable
Интерфейс будет готов вписаться в этот механизм, ничего не меняя.