Aspect C++支持“虛切面”,類似C++中的虛函數。方面可以繼承,“派生方面”可以重寫“基方面”的“虛切面”,類似C++中的類繼承。
有了這個特性,就可以實現一些模式了,這里列出
AOP中經典的觀察者(Observer)模式[注]。
[注]這個說法不太妥當,觀察者模式是經典的設計模式,這里的意思是說AOP經典的觀察者模式實現。它的代碼如下:
aspect ObserverPattern {
// 管理subjects和observers的數據結構
// TODO 
public:
// 角色接口
struct ISubject {};
struct IObserver {
virtual void update(ISubject *) = 0;
};
// 在派生方面中被重寫
pointcut virtual observers () = 0;
pointcut virtual subjects () = 0;
// subjectChange()匹配所有非const方法,但限定了subjects類
pointcut virtual subjectChange () =
execution(" %
::%(
) " && !" %
::%(
) const")
&& within(subjects ());
// 為每個subject/observer類增加基類,并插入通知代碼
advice observers () : baseclass(IObserver );
advice subjects () : baseclass(ISubject );
advice subjectChange () : after() {
ISubject * subject = tjp->that ();
updateObservers (subject );
}
// 具體操作
void updateObservers (ISubject * sub ) {
}
void addObserver (ISubject * sub , IObserver * ob ) {
}
void remObserver (ISubject * sub , IObserver * ob ) {
}
}; 其中“...” 部分是需要完成的C++實現代碼,可以簡單實現一個:
#ifndef __OBSERVER_PATTERN_AH__
#define __OBSERVER_PATTERN_AH__
#include <map>
#include <set>
using namespace std;
aspect ObserverPattern {
// 管理subjects和observers的數據結構
struct ISubject;
struct IObserver;
map < ISubject*, set<IObserver*> > listeners;
public:
// 角色接口
struct ISubject {};
struct IObserver {
virtual void update(ISubject *) = 0;
};
// 在派生方面中被重寫
pointcut virtual observers () = 0;
pointcut virtual subjects () = 0;
// subjectChange()匹配所有非const方法
pointcut virtual subjectChange () =
execution(" %
::%(
) " && !" %
::%(
) const")
&& within(subjects ());
// 為每個subject/observer類增加基類,并插入通知代碼
advice observers () : baseclass(IObserver );
advice subjects () : baseclass(ISubject );
advice subjectChange () : after() {
ISubject * subject = tjp->that ();
updateObservers (subject );
}
// 具體操作
void updateObservers (ISubject * sub ) {
const set<IObserver*>& observers = listeners[sub];
set<IObserver*>::const_iterator iter = observers.begin();
for (; iter != observers.end(); iter ++)
{
(*iter)->update(sub);
}
}
void addObserver (ISubject * sub , IObserver * ob ) { listeners[sub].insert(ob); }
void removeObserver (ISubject * sub , IObserver * ob ) { listeners[sub].erase(ob); }
};
#endif // __OBSERVER_PATTERN_AH__ 保存為ObserverPattern.ah供下面使用。
下面編寫一個應用實例:
1、car.h
#ifndef __CAR_H__
#define __CAR_H__
#include <string>
using namespace std;
class Car
{
string name;
int x;
int y;
enum Direction{South, East, North, West};
Direction direction;
Car (const Car&);
Car& operator = (const Car&);
public:
Car (const string& name);
void turnLeft ();
void turnRight ();
void forward (size_t step);
const string& getName() const;
int getX () const;
int getY () const;
Direction getDirection () const;
};
#endif // __CAR_H__
2、car.cc
#include "car.h"
#include <cassert>
Car::Car (const string& name_)
: name(name_), x(0), y(0), direction(South)
{
}
void Car::turnLeft ()
{
if (direction == West)
direction = South;
else
direction = (Direction)(int(direction) + 1);
}
void Car::turnRight ()
{
if (direction == South)
direction = West;
else
direction = (Direction)(int(direction) - 1);
}
void Car::forward (size_t step)
{
switch (direction)
{
case South:
y += step; break;
case East:
x += step; break;
case North:
y -= step; break;
case West:
x -= step; break;
default:
assert (!"Invalid direction");
}
}
const string& Car::getName() const
{
return name;
}
int Car::getX() const
{
return x;
}
int Car::getY() const
{
return y;
}
Car::Direction Car::getDirection() const
{
return direction;
}
3、dummy.h(這個用來測試Aspect C++的匹配模式會不會混亂)
#ifndef __DUMMY_H__
#define __DUMMY_H__
class Dummy
{
public:
void test_non_const (){}
void test_const () const {}
};
#endif // __DUMMY_H__
4、main.cc
#include "car.h"
#include "dummy.h"
int main()
{
Car car("No.1");
car.turnLeft();
car.forward(3);
car.turnLeft();
car.forward(9);
car.turnRight();
car.forward(12);
Car car1("No.2");
car1.forward(7);
car1.turnLeft();
car1.forward(3);
car.forward(5);
Dummy dummy;
dummy.test_non_const();
dummy.test_const();
return 0;
}
這個程序編譯運行,沒有任何輸出。有時候為了監視對象的狀態,可以在執行一個操作后加上一些打印狀態的代碼,當然這樣比較繁瑣;也可以在各個操作函數中加入這些代碼,但修改已經寫好的代碼總是不太舒服。
下面先實現一個Car狀態打印類:
5、car_logging.h
#ifndef __CAR_LOGGING__
#define __CAR_LOGGING__
#include "car.h"
#include <iostream>
using namespace std;
class CarLogging
{
public:
void printCarInfo (const Car& car)
{
static const char* direction_str[] = {"South", "East", "North", "West"};
cout << "Car name: " << car.getName()
<< ", direction: " << direction_str[int(car.getDirection())]
<< ", x: " << car.getX()
<< ", y: " << car.getY()
<< endl;
}
};
extern CarLogging g_carLogging;
#endif // __CAR_LOGGING__
6、car_logging.cc
#include "car_logging.h"
CarLogging g_carLogging;
7、CarLoggingObserver.ah
#ifndef __CAR_LOGGING_OBSERVER_AH__
#define __CAR_LOGGING_OBSERVER_AH__
#include "ObserverPattern.ah"
#include "car.h"
#include "car_logging.h"
aspect CarLoggingObserver : public ObserverPattern {
// 定義方面(ointcuts)
pointcut subjects() = "Car";
pointcut observers() = "CarLogging";
public:
advice observers() :
void update( ObserverPattern::ISubject* sub ) {
printCarInfo (*(Car*)sub);
}
advice construction (classes(subjects())) : after()
{
addObserver(tjp->that(), &g_carLogging);
}
advice destruction (classes(subjects())) : before()
{
removeObserver(tjp->that(), &g_carLogging);
}
};
#endif // __CAR_LOGGING_OBSERVER_AH__
這個方面重寫了subjects以及observers切面,并定義了observers在update被調用時執行的操作,另外還在Car的構造函數和析構函數中添加了注冊和注銷代碼。
運行ac++生成代碼,編譯并運行,結果如下:
這里演示的例子依舊選擇了不影響原始程序的做法,網上很多資料都把這個模式和實現代碼結合起來,由于Aspect C++編譯速度還是很慢,所以選擇“外掛”的方式,這樣不需要這些方面時,直接編譯C++代碼即可。
關于Aspect C++以及AOP,還有許多話題,不過不打算再繼續了,AOP是個廣泛的議題,局限在某一實現上只會使我們眼界變窄。
AOP被稱為設計模式最佳實踐者,它當之無愧。網上還有很多AOP實踐設計模式的資料。