Re[5]: Расширение С++
От: Аноним  
Дата: 25.03.03 13:26
Оценка: 11 (3)
Здравствуйте, 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);
    }
}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.