faslib

Библиотека faslib предлагает новый способ модульной разработки программного обеспечения, называемый аспекто-ориентированным, используя исключительно конструкции языка C++.

faslib тестировалась на следующих компиляторах:

Установка и настройка

Компиляция не требуется, но необходимо конфигурирование с помощью cmake:

git clone git://github.com/migashko/faslib.github
cd faslib
mkdir build
cd build
cmake ..

Вы также можете загрузить сконфигурированный faslib c sourceforge.

Для компиляции примеров и тестов:

make

или по отдельности:

make tests
make examples
make tutorial

для запуска тестов:

ctest

Введение

Основная идея - дать разработчику возможность разрабатывать такие классы, в которых потенциальный пользователь, при необходимости, мог бы заменить тот или иной функционал с минимумом издержек. faslib поможет вам когда:

В качестве примера рассмотрим простой класс:

class foo1
{
public:
  void method1() { std::cout << "method-1.1" << std::endl; }
  void method2() { std::cout << "method-1.2" << std::endl; }
  void method3() { std::cout << "method-1.3" << std::endl; }
};

Предположим требуется предоставить пользователю возможность заменить функционал любого методов этого класса. Использовать для этого полиморфизм - не лучшая идея, классические стратегии слишком накладно, шаблонные стратегии неудобно. Более развернутые примеры (в том числе и с этими вариантами реализации) вы можете найти в tutorial/aop.

Для наглядности изобразим этот класс на картинке в виде монолитного блока:

foo

Для демонстрации возможностей разработаем класс foo2 с аналогичным функционалом, но используя концепции АОП. Сначала разобьем его на составляющие, выделив каждый метод в отдельные сущности которые в faslib называются адвайс-классами:

advices

struct ad_method1
{
  template<typename T>
  void operator()(T&) { std::cout << "method-2.1" << std::endl; }
};

struct ad_method2
{
  template<typename T>
  void operator()(T&) { std::cout << "method-2.2" << std::endl; }
};

struct ad_method3
{
  template<typename T>
  void operator()(T&) { std::cout << "method-2.3" << std::endl; }
};

Каких либо особых требований к адвайс-классам не предъявляется, но использование определенных правил при разработке адвайс-классов существенно облегчит жизнь и вам и потенциальным пользователям. Использование перегруженного оператора () с первым шаблонным параметром, в который передается контекст вызова (в данном случае это ссылка на foo2) является хорошей практикой, даже если в конкретном адвайс-классе он не нужен. Дело в том, что ссылка на контекст может очень понадобиться пользователю, который решит заменить ваш адвайс своим. Префикс “ad_” подскажет пользователю, что он имеет дело с адвайс-классом.

Далее для каждого адвайс-класса необходимо создать тег, по которому мы сможем обращаться к нему. Методов у нас три поэтому и тегов нам понадобиться тоже как минимум три:

struct _method1_;
struct _method2_;
struct _method3_;

Использование знака “_” для обрамления имени тега, чертовски удобная штука. Следующим этапом необходимо связать теги и адвайс-классы, создав таким образом собственно адвайсы:

ad_method

typedef fas::advice<_method1_, ad_method1> method1_advice;
typedef fas::advice<_method2_, ad_method2> method2_advice;
typedef fas::advice<_method3_, ad_method3> method3_advice;

Далее, необходимо объединить разрозненные адвайсы в список типов:

advice_list

typedef fas::type_list_n<
  method1_advice,
  method2_advice,
  method3_advice
>::type advice_list;

Cформируем аспект foo2_aspect:

foo2_aspect

struct foo2_aspect: fas::aspect< advice_list >{};

Ну и наконец разработаем сам аспектный класс foo2, в который внедрим аспект foo2_aspect:

template<typename A = fas::aspect<> >
class foo2
  : public fas::aspect_class<A, foo2_aspect>
{
public:
  void method1() { this->get_aspect().template get<_method1_>()( *this); }
  void method2() { this->get_aspect().template get<_method2_>()( *this); }
  void method3() { this->get_aspect().template get<_method3_>()( *this); }
};

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

Использовать аспектный класс foo2<> не сложнее обычного foo1:

  foo2<> f2;
  f2.method1();
  f2.method2();
  f2.method3();

Класс foo2<> изобразим следующим образом:

foo2_aspect

Полный пример здесь

Теперь попробуем заменить функционал method2 и method3, чтобы он выводил вместо строк "method-2.2" и "method-2.3", строки "method-3.2" и "method-3.3" соответственно.

Для этого аналогично сформируем аспект foo3_aspect:

foo3_aspect

struct ad_method3_2
{
  template<typename T>
  void operator()(T&) { std::cout << "method-3.2" << std::endl; }
};

struct ad_method3_3
{
  template<typename T>
  void operator()(T&) { std::cout << "method-3.3" << std::endl; }
};

struct foo3_aspect: fas::aspect< fas::type_list_n<
  fas::advice<_method2_, ad_method3_2>,
  fas::advice<_method3_, ad_method3_3>
>::type >{};

И внедрим его в класс foo2

  foo2<foo3_aspect> f3;
  f3.method1();
  f3.method2();
  f3.method3();

После внедрения произошло неявное объединение аспектов, в результате старый функционал, реализованный в адвайс-классах стал недоступен, но физически он остался аспекте.

foo3

В большинстве случаев это не проблема, но при необходимости его можно удалить с помощью конструкции remover или же наоборот обеспечить к нему доступ по другим тегам с помощью forward. Также имеется возможность связывать с одним адвайсом несколько тегов, с помощью псевдонимов (alias). Объединив несколько адвайсов в логическую группу ( group) у вас появляется возможность группового вызова (аналог событий, но на этапе компиляции). Специальные адвайсы-значения ( value_advice ) в отличии от обычных адвайсов не наследуют тип, а агрегируют его, что позволяет включить в аспект простые типы, такие как int. А с помощью type_advice, вы можете включить в аспект определение типа, например контейнера, и соответственно, пользователь может его заменить.

Для более сложных классов имеет смысл разработать несколько аспектов из адвайсов реализующих определенный функционал. Аспекты можно объединять явно, используя конструкцию aspect_merge. Это позволит вам повторно использовать аспекты в качестве стратегий и/или комбинировать их.

далее...