Применение шаблона декоратора полиморфно и проблемы связывания в C ++ - PullRequest
4 голосов
/ 21 августа 2009

Я пытаюсь сделать реализацию настольной игры на C ++ Carcassonne . Я пытаюсь сделать объект из плиток, который имеет четыре стороны и одну из трех основных ландшафтов (поле, дорога, город).

Лучший интерфейс для создания плитки, который я мог придумать, имел вид:

City city;
city_city_city_city = new Tile(city, city, city, city);

Где класс Tile определяется следующим образом ...

class Tile
{
 public:
  Tile(Terrain& top_terrain,
       Terrain& right_terrain, 
       Terrain& bottom_terrain,
       Terrain& left_terrain)
    {   
      top_side_.reset(top_terrain.Decorate(new TopSide()));
      right_side_.reset(right_terrain.Decorate(new RightSide());
      bottom_side_.reset(bottom_terrain.Decorate(new BottomSide()));
      left_side_.reset(left_terrain.Decorate(new LeftSide()));
    }

 private:
 boost::scoped_ptr<TopSide> top_side_;
 boost::scoped_ptr<RightSide> right_side_;
 boost::scoped_ptr<BottomSide> bottom_side_;
 boost::scoped_ptr<LeftSide> left_side_;
};

Я хотел, чтобы конструктор инициализировал каждую конкретную сторону (TopSide, RightSide, BottomSide, LeftSide), которая наследуется от базового класса Side. Идея заключалась в том, чтобы определить виртуальный метод Decorate в классе Terrain, который бы возвращал экземпляр SideDecorator для определенного типа Terrain.

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

Я мог бы создавать конкретные классы, такие как TopFieldSide, BottomRoadSide и т. Д., Но я подумал, что шаблон декоратора будет чище. Однако я не уверен в том, является ли полиморфное использование метода Decorate () неправомерным.

Конечно, я мог бы создать плитки вида:

Tile(CityDecorator(new TopSide), 
     CityDecorator(new RightSide),
     FieldDecorator(new BottomSide),
     RoadDecorator(new LeftSide));

Но предыдущая версия кажется намного чище.

Мой вопрос в том, что ... Это приемлемый подход или есть более простой / чистый способ, которого мне не хватает?

Мое использование этого подхода заставляет меня сталкиваться с проблемами связывания, потому что я должен включить путь к SideDecorator в Terrain, а Terrain используется в SideDecorator и в производных классах. Простая директива #include "side_decorator.h" в terrain.h вызывает много ошибок компиляции, из-за чего трудно определить, является ли это проблемой предварительного объявления или что-то еще не скомпрометировано в моем коде ...

Ответы [ 6 ]

1 голос
/ 21 августа 2009

Как насчет того, чтобы Сторона производила декорированный результат, основанный на аргументе Terrain, а не наоборот? Тогда Terrain понадобится только метод, чтобы указать сегменты, в которых он нуждается, и их ориентация может быть установлена ​​Стороной. Может быть, это облегчит сцепление?

Не имеет отношения к связыванию, но рассмотрите возможность более широкого использования универсального программирования в своем дизайне - не очевидно, что стороны, ландшафты и / или сегменты не должны использовать универсальность вместо наследования или его наследования. В некотором смысле это скорее реализация, чем проблема дизайна, но она влияет на дизайн. К сожалению, я не думаю, что понимаю область приложения достаточно глубоко, чтобы предлагать более конкретные предложения (я играл в «Каркассон» однажды, довольно давно, но помимо того, что это забавно, я не особо помню; -).

0 голосов
/ 22 октября 2012

Я мог бы создавать конкретные классы, такие как TopFieldSide, BottomRoadSide и т. Д., Но я подумал, что шаблон декоратора будет чище.

Шаблон Decorator добавляет сложность, которая действительно окупается, только если вы намереваетесь добавить новые декораторы, но не хотите изменять свое основное приложение. Если правила вашей игры фиксированы (не может быть ничего, кроме конкретных классов, которые вы упомянули), то я бы сказал, что сложность не окупится.

Если вы хотите применить Decorator, чтобы выучить или освоить шаблон, то сделайте это. Но имейте в виду, что решаемая проблема может не присутствовать в ваших требованиях к дизайну.

0 голосов
/ 21 августа 2009

Я думаю, что ваше решение в порядке. Фактически вам требуется виртуальный конструктор , чтобы создать правильный тип декоратора на основе типа, который он декорирует. Для этого требуется фабричный метод , которым является Decorate. Он скрывает детали того, какой декоратор создать для Tile, но ваши Terrain классы должны знать, что они могут быть украшены.

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

0 голосов
/ 21 августа 2009

Другой возможностью было бы использование шаблонов

template <class Top, class Right, class Bottom, class Left>
class Tile
{
  TopSide top;
  RightSide right;
  BottomSide bottom;
  LeftSide left;
  Tile()
  {
     // imaginary syntax to "decorate" a side
     top.type = new Top();
     right.type = new Right();
     bottom.type = new Bottom();
     left.type = new Left();
  }
}

Tile<City, Road, City, Field> tile();

Синтаксис будет очень чистым, особенно с помощью typedef, которые вы можете автоматически генерировать, например:

typedef Tile<Field, Road, City, Road> Tile_F_R_C_R;

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

0 голосов
/ 21 августа 2009

a Плитка имеет 4 стороны, каждая из которых является одной из поля, дороги, города

class Side {
  // things common to all sides
};

class Field : public Side {
  // things only found in a field
};

class Road : public Side {
  // things only found in a road
};

class City : public Side {
  // things only in a city
};

class Tile {
 collection of 4 Sides;
 Tile(Side *top, Side *left, Side *right, Side *bottom);
};

...

// ex. usage:
Tile myTile( new Road, new City, new City, new Field );

Почему это не делает все, что вы хотели? Примечание: ссылки, указатели, что угодно, детали реализации не важны для вопроса дизайна. Я думаю, что шаблон дизайна является излишним

0 голосов
/ 21 августа 2009

Примечание о проблеме циклических зависимостей: вы можете использовать идиому PImpl, как описано в этой статье Гуру недели (еще один простой пример здесь ).

...