Уже не в первый раз сталкиваюсь с необходимостью написать параллельную иерархию классов.
Проще объяснить на конкретном примере. Пусть есть:
class Shape {};
class Circle : public Shape{};
class Ellipse : public Shape{};
Допустим, мы хотим сохранять каждый класс в базе данных, то есть добавить методы
save() и load().
В голову не приходит ничего кроме:
class Shape {};
class Cicle : virtual public Shape{};
class Ellipse : virtual public Shape{};
class PersistentShape : virtual public Shape {
virtual void load() = 0;
virtual void save() = 0;
};
class PersistentCicle : public Cicle, public PersistentShape
{};
class PersistentEllipse : public Ellipse, public PersistentShape
{};
Можно ли это сделать лучше? Как вы поступаете в таких случаях?
Нужно не декорировать существующие методы, а добавить новую функциональность. Кроме того, как быть в таком случае —
пусть Shape из предыдущего примера является базовым классом компоновщика,
то есть у нас есть:
class Shape {};
class ShapeContainer {};
В этом случае мы должны получить:
class PersistentShape : public ?
{
virtual void save()
{//do smtng}
}
class PersistentShapeContainer : public ?
{
virtual void save()
{
for_each( shapes_.begin(), shapes_.end(), mem_fun(&Shape::save) );
}
deque<Shape*> shapes_;
}
Как добиться такого поведения в рамках декоратора?
Здравствуйте, dormouse, Вы писали:
D>Нужно не декорировать существующие методы, а добавить новую функциональность. Кроме того, как быть в таком случае — D>пусть Shape из предыдущего примера является базовым классом компоновщика, D>то есть у нас есть:
D>
Здравствуйте, dormouse, Вы писали:
D>Нужно не декорировать существующие методы, а добавить новую функциональность. Кроме того, как быть в таком случае — D>пусть Shape из предыдущего примера является базовым классом компоновщика, D>то есть у нас есть:
Если не стоит цели максимально запутать простую вещь, то я бы посоветовал просто добавить к Shape методы save и load. Работать будет совершенно также . Если надо, что далеко не факт, то пронаследуй Shape от интерфейса ISerializable. И все. Не надо мудрить.
Вообще то применение / не применение любого паттерна зависит от целей / ограничений архитектуры. Короче — если не знаешь, зачем нужен паттерн — не применяй вообще.
Здравствуйте, dormouse, Вы писали:
D>Уже не в первый раз сталкиваюсь с необходимостью написать параллельную иерархию классов. D> ... D>Можно ли это сделать лучше? Как вы поступаете в таких случаях?
> Проще объяснить на конкретном примере. Пусть есть: >
> class Shape {};
> class Circle : public Shape{};
> class Ellipse : public Shape{};
>
> > Допустим, мы хотим сохранять каждый класс в базе данных, то есть добавить методы > save() и load(). > Можно ли это сделать лучше? Как вы поступаете в таких случаях?
А почему бы не отделить мух от котлет и не заморачивать класс Shape подробностями персистенции? Пусть живут сами по себе, а некие другие классы, типа mapper'ы какие-нить, будут заниматься их персистентностью?
Либо сделать нечто аналогичное уже предложенному, то есть придумать интерфейс IPersistent с парой методов save/load и его реализовать.
Просто если идти путем Shape-Circle-PersistentCircle, то отнаследовавшись от Circle придется дублировать код из PersistentCircle по сохранению свойств базового класса, а это не есть гуд, либо извращаться как-то использованием PersistentCircle — лишняя по сути зависимость.
Но.. может я не совсем удачный пример привел.. Попробую переформулировать:
Допустим, мы пишем библиотеку, в которой есть абстрактный класс
class Painter
{
virtual void drawCircle( Circle * ) = 0;
virtual void drawEllipse( Ellipse * ) = 0;
};
Мы реализовали этот интерфейс для окон
class WidgetPainter : public Painter {};
принтера
class PrinterPainter : public Painter {};
и так далее.
Пусть Вася Пупкин, который пользуется нашей либой, хочет расширить наш интерфейс:
class ExtendedPainter : public Painter
{
virtual void drawCat( Cat * ) throw() = 0;
};
Затем он должен реализовать его для окон, принтера и так далее, но у него нет никакого желания писать каждый раз:
class ExtendedWidgetPainter : public Painter
{
virtual void drawCircle( Circle * c )
{
painter_ ->drawCicle(c);
}
WidgetPainter * painter_;
};
Возникает жаление сделать так:
class ExtendedWidgetPainter : public Painter, public WidgetPainter
{};
чтобы унаследовать и интерфейс, и подходящую ему реализацию, но в этом случае необходимо виртуальное наследование. Можно, конечно, не наследовать ExtendedWidgetPainter от Painter'а — но в этом случае придется постоянно использовать cast'ы, либо же я чего-то не понимаю
На месте этого Painter'а, кстати, может быть Ваш Serializator.
Но.. может я не совсем удачный пример привел.. Попробую переформулировать:
Допустим, мы пишем библиотеку, в которой есть абстрактный класс
class Painter
{
virtual void drawCircle( Circle * ) = 0;
virtual void drawEllipse( Ellipse * ) = 0;
};
Мы реализовали этот интерфейс для окон
class WidgetPainter : public Painter {};
принтера
class PrinterPainter : public Painter {};
и так далее.
Пусть Вася Пупкин, который пользуется нашей либой, хочет расширить наш интерфейс:
class ExtendedPainter : public Painter
{
virtual void drawCat( Cat * ) throw() = 0;
};
Затем он должен реализовать его для окон, принтера и так далее, но у него нет никакого желания писать каждый раз:
class ExtendedWidgetPainter : public ExtendedPainter
{
virtual void drawCircle( Circle * c )
{
painter_ ->drawCicle(c);
}
WidgetPainter * painter_;
};
Возникает жаление сделать так:
class ExtendedWidgetPainter : public ExtendedPainter, public WidgetPainter
{};
чтобы унаследовать и интерфейс, и подходящую ему реализацию, но в этом случае необходимо виртуальное наследование. Можно, конечно, не наследовать ExtendedPainter от Painter'а — но в этом случае придется постоянно использовать cast'ы, либо же я чего-то не понимаю
На месте этого Painter'а, кстати, может быть Ваш Serializator.
Здравствуйте, dormouse, Вы писали:
D>Возникает жаление сделать так:
D>class ExtendedWidgetPainter : public ExtendedPainter, public WidgetPainter
D>{};
Нормальное желание. Это у нас как Painter(по старому) так и ExtendedPainter D>чтобы унаследовать и интерфейс, и подходящую ему реализацию, но в этом случае необходимо виртуальное наследование. Можно, конечно, не наследовать ExtendedPainter от Painter'а — но в этом случае придется постоянно использовать cast'ы, либо же я чего-то не понимаю D>На месте этого Painter'а, кстати, может быть Ваш Serializator.
В данном случае, как раз описан недостаток паттерна Visitor. В частности то, что он хреново расширяется. Приходится писать практически во все классы. Но я не вижу особо в этом смысл. Легче написать:
Это более полезно, так как вам не придется модифицировать код который уже пользуется Painter. И вы можете реализовать GetExtendedPainter только для тех объектов которые вам нужны. Что именно будет браться Painter или ExtendedPainter решает вызывающий код. Смотря какая функциональность ему нужна. Старые вызовы, могут так и не узнать о расширенной функциональности.
Правда тут нужно иметь ввиду, как логически связаны функциональности Painter и ExtendedPainter. Если это разные вещи, то и смешивать их не стоит, потому как могут произойти изменения в которых придется вымарать Painter из ExtendedPainter. А это будет проблематично.
Здравствуйте, dormouse, Вы писали:
D>Уже не в первый раз сталкиваюсь с необходимостью написать параллельную иерархию классов. D>Проще объяснить на конкретном примере. Пусть есть: ..........................
ИМХО — Visitor.
Добавь у каждого класса по методу Accept который будет принимать некий абстрактный Visitor,
у этого визитора должны быть на каждый класс по методу.
и твой Accept будет вызывать подходящий.
Для, например, сохранения ты сможешь в цикле бежать по всем фигурам,
и вызывать Accept передавая Visistor предназначеный для сохраненния.
Наверно я путанно объяснил, почитай лучше о нем гдето (например у Andrei Alexandrescu)
Здравствуйте, dormouse, Вы писали:
D>Уже не в первый раз сталкиваюсь с необходимостью написать параллельную иерархию классов. D>Проще объяснить на конкретном примере. Пусть есть: D>
D>class Shape {};
D>class Circle : public Shape{};
D>class Ellipse : public Shape{};
D>
D>Допустим, мы хотим сохранять каждый класс в базе данных, то есть добавить методы D>save() и load(). D>В голову не приходит ничего кроме: D>
D>class Shape {};
D>class Cicle : virtual public Shape{};
D>class Ellipse : virtual public Shape{};
D>class PersistentShape : virtual public Shape {
D> virtual void load() = 0;
D> virtual void save() = 0;
D>};
D>class PersistentCicle : public Cicle, public PersistentShape
D>{};
D>class PersistentEllipse : public Ellipse, public PersistentShape
D>{};
D>
D>Можно ли это сделать лучше? Как вы поступаете в таких случаях?
Используем сериализацию. Оператор сериализации генерит макрос из описания полей класса.