Здравствуйте, IAZ, Вы писали:
IAZ>Здравствуйте, Denwer, Вы писали:
D>>Здравствуйте, Аноним, Вы писали:
D>>
А>>>Т.е., хотелось бы иметь средства, которые позволят легко расширять (возможно, чужие) библиотеки (для своих нужд) причем, так, чтобы эти расширения и сами библиотеки можно было бы легко сопровождать, и, желательно, независимо друг от друга.
А>>>Denwer, я правильно понял?
А>>>--
А>>>Дмитрий
D>>ДА ДА ДА
IAZ>Хороший способ расширения библиотеки классов — это использование оберток.
Хороший! Тем не менее, он не всегда срабатывает. Например, если нужно добавить не просто методы, а и члены-данные.
Думаю, что не мешает ввести немного конкретики. Допустим у нас есть класс Figure с методом move(int, int) и классы Square и Circle, которые наследуются от Figure и реализуют move(int, int) по-своему.
class Figure {
private:
// . . .
public:
virtual void move(int dx, int dy) =0;
// . . .
};
class Square: public Figure {
private:
int left, top, right, bottom;
// . . .
public:
virtual void move(int dx, int dy);
// . . .
};
class Circle: public Figure {
private:
int cx, cy, radius;
// . . .
public:
virtual void move(int dx, int dy);
// . . .
};
А теперь мы хотим отслеживать перемещения фигур. Скажем, когда какая-нибудь фигура перемещается она посылает сообщения своему подписчику (или подписчикам). Попробуем организовать подписку таким образом:
class MovingSubscriber {
public:
virtual void beforeMoving(int dx, int dy) =0;
virtual void afterMoving(int dx, int dy) =0;
};
class FigureWithSubscription: public Figure {
private:
MovingSubscriber *subscriber;
public:
virtual void realMove(int dx, int dy) =0;
virtual void move(int dx, int dy) {
if(subscriber) subscriber->beforeMoving(dx, dy);
realMove(dx, dy);
if(subscriber) subscriber->afterMoving(dx, dy);
}
virtual void setSubscriber(MovingSubscriber *s) {
subscriber = s;
}
};
Однако, Square и Circle ничего не знают о FigureWithSubscription и поэтому никаких сообщений при своем перемещении никому не посылают. Попробуем сделать так:
template<class T>
class GenericFigureWithSubscription: public T {
private:
MovingSubscriber *subscriber;
public:
virtual void move(int dx, int dy) {
if(subscriber) subscriber->beforeMoving(dx, dy);
T::move(dx, dy);
if(subscriber) subscriber->afterMoving(dx, dy);
}
virtual void setSubscriber(MovingSubscriber *s) {
subscriber = s;
}
};
class SquareWithSubscription: public GenericFigureWithSubscription<Square> {
};
class CircleWithSubscription: public GenericFigureWithSubscription<Circle> {
};
Вроде все нормально. Но следует заметить, что количество дополнительного кода прямо пропорционально количеству прямых наследников от Figure, что не есть хорошо. Но и это еще не все! Данные изменения касаются только объектов, созданных разработчиком, который знает о подписке и знает, что теперь нужно создавать объекты класса SquareWithSubscription а не просто Square. Т.е. подписаться на премещения объектов, которые были созданы где-то в недрах библиотеки невозможно. Для того, чтобы сделать это реальностью, придется делать класс-оболочку, к которому можно будет подписаться и который будет делегировать вызовы реальному объекту.
class WrapperWithSubscription: public Figure {
private:
Figure *figure;
MovingSubscriber *subscriber;
public:
WrapperWithSubscription(Figure *f): figure(f) {}
virtual void move(int dx, int dy) {
if(subscriber) subscriber->beforeMoving(dx, dy);
figure->move(dx, dy);
if(subscriber) subscriber->afterMoving(dx, dy);
}
virtual void setSubscriber(MovingSubscriber *s) {
subscriber = s;
}
}
А после этого следить, чтобы все двигали фигуры только через врапперы! Как, оказывается, легко тривиальную задачу превратить в большую неприятность!
Получается, что
расширить библиотеку таким образом, каким явно не предусмотрел разработчик этой библиотеки, зачастую крайне трудно, если и вообще возможно.
Впрочем, чтобы немного упростить себе жизнь, придумали аспектно-ориентированное программирование. В частности, задача с перемещениями фигур могла бы решаться при помощи такого аспекта (здесь приведен псевдокод):
aspect FigureSubscrition {
// добавление в класс Figure член-данное subscriber
MovingSubscriber *Figure::subscriber;
// добавление в класс Figure метод setSubscriber
virtual void Figure::setSubscriber(MovingSubscriber *s) {
subscriber = s;
}
// определение кода, который будет вызываться до и после вызова метода.
before(Figure *p, int dx, int dy): target(p) && call(void Figure::move(int dx, int dy)) {
if(p->subscriber) p->subscriber->beforeMoving(dx, dy);
}
after(Figure *p, int dx, int dy): target(p) && call(void Figure::move(int dx, int dy)) {
if(p->subscriber) p->subscriber->afterMoving(dx, dy);
}
}