Aspect-oriented programming on native C++
C++ CMake Other
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
.ci
config
examples
fas
tests
tutorial
.gitignore
.gitlab-ci.yml
.travis.yml
AUTHORS
CMakeLists.txt
COPYING
ChangeLog
GPL-LICENSE.txt
MIT-LICENSE.txt
NEWS
README
README.md
TODO
fasg++
remove~.sh

README.md

faslib

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

Статья на хабре про списки типов и мета-алгоритмы (без АОП).

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

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

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

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

cmake -DBUILD_TESTING=ON ..
make
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. Это позволит вам повторно использовать аспекты в качестве стратегий и/или комбинировать их.

далее...