Как спроектировать простую фабрику объектов C++?

В моем приложении есть 10-20 классов, которые создаются один раз[*]. Вот пример:

class SomeOtherManager;

class SomeManagerClass {
public:
    SomeManagerClass(SomeOtherManager*);
    virtual void someMethod1();
    virtual void someMethod2();
};

Экземпляры классов содержатся в одном объекте:

class TheManager {
public:
    virtual SomeManagerClass* someManagerClass() const;
    virtual SomeOtherManager* someOtherManager() const;
    /** More objects... up to 10-20 */
};

В настоящее время TheManager использует оператор new для создания объектов.

Мое намерение состоит в том, чтобы иметь возможность заменить с помощью плагинов реализацию SomeManagerClass (или любого другого класса) на другую. Для замены реализации необходимо 2 шага:

  1. Определите класс DerivedSomeManagerClass, который наследует SomeManagerClass [плагин]
  2. Создайте новый класс (DerivedSomeManagerClass) вместо стандартного (SomeManagerClass) [приложение]

Я предполагаю, что мне нужна какая-то фабрика объектов, но она должна быть довольно простой, поскольку всегда нужно создать только один тип (реализация по умолчанию или реализация пользователя).

Любая идея о том, как спроектировать простую фабрику, как я только что описал? Учтите тот факт, что в будущем может быть больше классов, поэтому их будет легко расширить.

[*] Меня не волнует, если это произойдет более одного раза.

Изменить: обратите внимание, что в TheManager содержится более двух объектов.


person kshahar    schedule 02.12.2008    source источник
comment
Вы хотите заменить экземпляры SomeManagerClass во время выполнения или во время разработки?   -  person Joris Timmermans    schedule 02.12.2008
comment
SomeManagerClass создается только один раз — при запуске приложения, поэтому я хочу изменить это создание. Однако это должно быть во время выполнения, поскольку код, который заменяет реализацию по умолчанию, находится в динамически подключаемом плагине.   -  person kshahar    schedule 02.12.2008
comment
FWIW, я думаю, что каждый раз, когда вы называете менеджера классов, это потому, что вы не знаете, что он на самом деле делает. Вы также можете назвать это коробкой или вещью. Название - запах, имхо.   -  person metao    schedule 13.02.2009


Ответы (14)


Я думаю, что здесь есть две отдельные проблемы.

Одна проблема: как TheManager называет класс, который он должен создать? Он должен хранить какой-то указатель на «способ создания класса». Возможные решения:

  • хранение отдельного указателя для каждого типа класса с возможностью его установки, но вы уже сказали, что вам это не нравится, так как это нарушает принцип DRY
  • ведение какой-то таблицы, где ключом является перечисление или строка; в этом случае сеттер представляет собой одну функцию с параметрами (конечно, если ключ является перечислением, вы можете использовать вектор вместо карты)

Другая проблема: что это за «способ создания класса»? К сожалению, мы не можем напрямую хранить указатели на конструкторы, но можем:

  • создать, как указывали другие, фабрику для каждого класса
  • просто добавьте статическую функцию «создать» для каждого класса; если они сохраняют непротиворечивую подпись, вы можете просто использовать их указатели на функции

Шаблоны могут помочь избежать ненужного дублирования кода в обоих случаях.

person UncleZeiv    schedule 13.02.2009
comment
Этот ответ заставил меня переосмыслить различные возможности дизайна и реализации. Это также хорошо в качестве руководства к другим ответам, поэтому оно получает награду. Я хотел бы поблагодарить всех за участие, у меня не хватает символов, чтобы поблагодарить всех вас :) - person kshahar; 15.02.2009
comment
@UncleZeiv Спустя столько лет я все еще ненавижу тебя за то, что ты украл мой значок :P - person Emiliano; 06.12.2017
comment
@Emiliano, ты имеешь в виду награду :), и я даже проголосовал за тебя! ‹3 - person UncleZeiv; 06.12.2017

Предполагая класс (plugin1), который наследуется от SomeManagerClass, вам нужна иерархия классов для построения ваших типов:

class factory
{
public:
    virtual SomeManagerClass* create() = 0;
};

class plugin1_factory : public factory
{
public:
    SomeManagerClass* create() { return new plugin1(); }
};

Затем вы можете назначить эти фабрики std::map, где они привязаны к строкам.

std::map<string, factory*>  factory_map;
...
factory_map["plugin1"] = new plugin1_factory();

Наконец, вашему TheManager просто нужно знать имя плагина (в виде строки) и он может вернуть объект типа SomeManagerClass всего одной строкой кода:

SomeManagerClass* obj = factory_map[plugin_name]->create();

EDIT: если вам не нравится иметь один класс фабрики плагинов для каждого плагина, вы можете изменить предыдущий шаблон следующим образом:

template <class plugin_type>
class plugin_factory : public factory
{
public:
   SomeManagerClass* create() { return new plugin_type(); }
};

factory_map["plugin1"] = new plugin_factory<plugin1>();

Я думаю, что это гораздо лучшее решение. Кроме того, класс «plugin_factory» может добавить себя в «factory_map», если вы передадите строку конструктору.

person Emiliano    schedule 11.02.2009

Я ответил в другом вопросе SO о фабриках С++. См. здесь, если гибкая фабрика представляет интерес. Я пытаюсь описать старый способ использования макросов из ET++, который отлично сработал для меня.

ET++ был проектом по переносу старого MacApp на C++ и X11. Для этого Эрик Гамма и другие начали думать о шаблонах проектирования.

person epatel    schedule 10.02.2009

Я бы создал «базовую» фабрику, в которой есть виртуальные методы для создания всех основных менеджеров, и пусть «мета-менеджер» (TheManager в вашем вопросе) использует указатель на базовую фабрику в качестве параметра конструктора.

Я предполагаю, что «фабрика» может настраивать экземпляры CXYZWManager, наследуя их, но в качестве альтернативы конструктор CXYZWManager может принимать другие аргументы в «пользовательской» фабрике.

Длинный пример кода, который выводит «CSomeManager» и «CDerivedFromSomeManager»:

#include <iostream>
//--------------------------------------------------------------------------------
class CSomeManager
  {
  public:
    virtual const char * ShoutOut() { return "CSomeManager";}
  };

//--------------------------------------------------------------------------------
class COtherManager
  {
  };

//--------------------------------------------------------------------------------
class TheManagerFactory
  {
  public:
    // Non-static, non-const to allow polymorphism-abuse
    virtual CSomeManager   *CreateSomeManager() { return new CSomeManager(); }
    virtual COtherManager  *CreateOtherManager() { return new COtherManager(); }
  };

//--------------------------------------------------------------------------------
class CDerivedFromSomeManager : public CSomeManager
  {
  public:
    virtual const char * ShoutOut() { return "CDerivedFromSomeManager";}
  };

//--------------------------------------------------------------------------------
class TheCustomManagerFactory : public TheManagerFactory
  {
  public:
    virtual CDerivedFromSomeManager        *CreateSomeManager() { return new CDerivedFromSomeManager(); }

  };

//--------------------------------------------------------------------------------
class CMetaManager
  {
  public:
    CMetaManager(TheManagerFactory *ip_factory)
      : mp_some_manager(ip_factory->CreateSomeManager()),
        mp_other_manager(ip_factory->CreateOtherManager())
      {}

    CSomeManager  *GetSomeManager()  { return mp_some_manager; }
    COtherManager *GetOtherManager() { return mp_other_manager; }

  private:
    CSomeManager  *mp_some_manager;
    COtherManager *mp_other_manager;
  };

//--------------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[])
  {
  TheManagerFactory standard_factory;
  TheCustomManagerFactory custom_factory;

  CMetaManager meta_manager_1(&standard_factory);
  CMetaManager meta_manager_2(&custom_factory);

  std::cout << meta_manager_1.GetSomeManager()->ShoutOut() << "\n";
  std::cout << meta_manager_2.GetSomeManager()->ShoutOut() << "\n";
  return 0;
  }
person Joris Timmermans    schedule 02.12.2008
comment
Мне нравится это решение, но рассмотрим случай, когда TheManagerFactory необходимо вернуть 20 разных классов. Это означает, что он должен знать (используя оператор #include) все 20 классов. - person kshahar; 02.12.2008
comment
Базовый класс фабрики может предварительно объявить все типы менеджеров в заголовке (без зависимостей), и хотя cpp будет включать все менеджеры, его нужно будет построить только один раз (или несколько раз). Пользовательским фабрикам нужно только #include тех менеджеров, которые они переопределяют. - person Joris Timmermans; 02.12.2008

Вот решение, о котором я подумал, оно не самое лучшее, но, возможно, оно поможет придумать лучшие решения:

Для каждого класса будет класс-создатель:

class SomeManagerClassCreator {
public:
    virtual SomeManagerClass* create(SomeOtherManager* someOtherManager) { 
        return new SomeManagerClass(someOtherManager); 
    }
};

Затем создатели соберутся в один класс:

class SomeManagerClassCreator;
class SomeOtherManagerCreator;

class TheCreator {
public:
    void setSomeManagerClassCreator(SomeManagerClassCreator*);
    SomeManagerClassCreator* someManagerClassCreator() const;

    void setSomeOtherManagerCreator(SomeOtherManagerCreator*);
    SomeOtherManagerCreator* someOtherManagerCreator() const;
private:
    SomeManagerClassCreator* m_someManagerClassCreator;
    SomeOtherManagerCreator* m_someOtherManagerCreator;
};

И TheManager будет создан с помощью TheCreator для внутреннего создания:

class TheManager {
public:
    TheManager(TheCreator*);
    /* Rest of code from above */
};

Проблема этого решения в том, что оно нарушает DRY — для каждого создателя класса мне пришлось бы писать сеттер/геттер в TheCreator.

person kshahar    schedule 02.12.2008

Кажется, что с шаблонами функций было бы намного проще, чем с шаблоном абстрактной фабрики.

class ManagerFactory
{
public:
    template <typename T> static BaseManager * getManager() { return new T();}
};

BaseManager * manager1 = ManagerFactory::template getManager<DerivedManager1>();

Если вы хотите получить их через строку, вы можете создать стандартную карту из строк в указатели на функции. Вот работающая реализация:

#include <map>
#include <string>

class BaseManager
{
public:
    virtual void doSomething() = 0;
};

class DerivedManager1 : public BaseManager
{
public:
    virtual void doSomething() {};
};

class DerivedManager2 : public BaseManager
{
public:
    virtual void doSomething() {};
};

class ManagerFactory
{
public:
    typedef BaseManager * (*GetFunction)();
    typedef std::map<std::wstring, GetFunction> ManagerFunctionMap;
private:
    static ManagerFunctionMap _managers;

public:
    template <typename T> static BaseManager * getManager() { return new T();}
    template <typename T> static void registerManager(const std::wstring& name)
    {
        _managers[name] = ManagerFactory::template getManager<T>;
    }
    static BaseManager * getManagerByName(const std::wstring& name)
    {
        if(_managers.count(name))
        {
            return _managers[name]();
        }
        return NULL;
    }
};
// the static map needs to be initialized outside the class
ManagerFactory::ManagerFunctionMap ManagerFactory::_managers;


int _tmain(int argc, _TCHAR* argv[])
{
    // you can get with the templated function
    BaseManager * manager1 = ManagerFactory::template getManager<DerivedManager1>();
    manager1->doSomething();
    // or by registering with a string
    ManagerFactory::template registerManager<DerivedManager1>(L"Derived1");
    ManagerFactory::template registerManager<DerivedManager2>(L"Derived2");
    // and getting them
    BaseManager * manager2 = ManagerFactory::getManagerByName(L"Derived2");
    manager2->doSomething();
    BaseManager * manager3 = ManagerFactory::getManagerByName(L"Derived1");
    manager3->doSomething();
    return 0;
}

EDIT: прочитав другие ответы, я понял, что это очень похоже на решение FactorySystem Дейва Ван ден Эйнде, но я использую указатель шаблона функции вместо создания экземпляров шаблонных фабричных классов. Я думаю, что мое решение немного легче. Из-за статических функций единственный объект, который создается, — это сама карта. Если вам нужна фабрика для выполнения других функций (DestroyManager и т. д.), я думаю, что его решение более расширяемо.

person vishvananda    schedule 14.02.2009

Вы можете реализовать фабрику объектов со статическими методами, которые возвращают экземпляр класса менеджера. На фабрике вы можете создать метод для типа менеджера по умолчанию и метод для любого типа менеджера, которому вы даете аргумент, представляющий тип класса менеджера (скажем, с перечислением). Этот последний метод должен возвращать интерфейс, а не класс.

Редактировать: я постараюсь дать некоторый код, но учтите, что мои времена на С++ прошли довольно давно, и пока я занимаюсь только Java и некоторыми сценариями.

class Manager { // aka Interface
    public: virtual void someMethod() = 0;
};

class Manager1 : public Manager {
    void someMethod() { return null; }
};

class Manager2 : public Manager {
    void someMethod() { return null; }
};

enum ManagerTypes {
    Manager1, Manager2
};

class ManagerFactory {
    public static Manager* createManager(ManagerTypes type) {
        Manager* result = null;
        switch (type) {
        case Manager1:
             result = new Manager1();
             break;
        case Manager2:
             result = new Manager2();
             break;
        default:
             // Do whatever error logging you want
             break;
        }
        return result;
     }
 };

Теперь вы сможете вызывать Factory через (если вам удалось заставить работать пример кода):

Manager* manager = ManagerFactory.createManager(ManagerTypes.Manager1);
person boutta    schedule 02.12.2008
comment
Спасибо за код, я имел в виду в своем вопросе, что хочу заменить новый Manager2(); операторы с созданием производных классов. - person kshahar; 02.12.2008
comment
Я не понимаю сути, извините. Вы можете расширить перечисление и фабрику, чтобы получить другие классы. - person boutta; 02.12.2008

Я бы использовал такие шаблоны, так как не вижу смысла в классах фабрик:

class SomeOtherManager;

class SomeManagerClass {
public:
    SomeManagerClass(SomeOtherManager*);
    virtual void someMethod1();
    virtual void someMethod2();
};


class TheBaseManager {
public:
      // 
};

template <class ManagerClassOne, class ManagerClassOther> 
class SpecialManager : public TheBaseManager {
    public:
        virtual ManagerClassOne* someManagerClass() const;
        virtual ManagerClassOther* someOtherManager() const;
};

TheBaseManager* ourManager = new SpecialManager<SomeManagerClass,SomeOtherManager>;
person David Allan Finch    schedule 02.12.2008

Вам следует ознакомиться с руководством по адресу http://downloads.sourceforge.net/papafactory/PapaFactory20080622.pdf?use_mirror=fastbull

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

Крис

person Community    schedule 12.03.2009

Мх я не разбираюсь на сто процентов, да и заводские штучки из книг и статей мне не особо нравятся.


Если все ваши менеджеры имеют одинаковый интерфейс, вы можете получить его от базового класса и использовать этот базовый класс в своей программе. В зависимости от того, где будет принято решение о том, какой класс будет создан, вы должны использовать идентификатор для создания (как указано выше) или обработать решение, какой менеджер создавать экземпляр внутри.


Другой способ - реализовать «политику», например, с помощью шаблонов. Так что You ManagerClass::create() возвращает конкретный экземпляр SomeOtherManagerWhatever. Это заложило бы решение, какой менеджер сделать в коде, который использует ваш менеджер - возможно, это не предназначено.

Или таким образом:


template<class MemoryManagment>
class MyAwesomeClass
{
    MemoryManagment m_memoryManager;
};

(или что-то в этом роде) С помощью этой конструкции вы можете легко использовать другие менеджеры, просто изменив экземпляр MyAwesomeClass.


Также класс A для этой цели может быть немного чрезмерным. В вашем случае, я думаю, подойдет фабричная функция. Ну это больше вопрос личных предпочтений.

person Ronny Brendel    schedule 02.12.2008

Если вы планируете поддерживать плагины, которые подключаются динамически, ваша программа должна будет обеспечивать стабильный ABI (бинарный интерфейс приложения), что означает, что вы не можете использовать C++ в качестве основного интерфейса, поскольку C++ не имеет стандартного ABI.

Если вы хотите, чтобы плагины реализовывали интерфейс, который вы определяете сами, вам нужно будет предоставить заголовочный файл интерфейса программисту плагинов и стандартизировать очень простой интерфейс C, чтобы создавать и удалять объект.

Вы не можете предоставить динамическую библиотеку, которая позволит вам «обновить» класс плагина как есть. Вот почему вам нужно стандартизировать интерфейс C, чтобы создать объект. В таком случае использование объекта C++ возможно, если ни один из ваших аргументов не использует возможно несовместимые типы, такие как контейнеры STL. Вы не сможете использовать вектор, возвращенный другой библиотекой, потому что вы не можете гарантировать, что их реализация STL такая же, как ваша.

Менеджер.ч

class Manager
{
public:
  virtual void doSomething() = 0;
  virtual int doSomethingElse() = 0;
}

extern "C" {
Manager* newManager();
void deleteManager(Manager*);
}

PluginManager.h

#include "Manager.h"

class PluginManager : public Manager
{
public:
  PluginManager();
  virtual ~PluginManager();

public:
  virtual void doSomething();
  virtual int doSomethingElse();
}

PluginManager.cpp

#include "PluginManager.h"

Manager* newManager()
{
  return new PluginManager();
}
void deleteManager(Manager* pManager)
{
  delete pManager;
}

PluginManager::PluginManager()
{
  // ...
}

PluginManager::~PluginManager()
{
  // ...
}

void PluginManager::doSomething()
{
  // ...
}

int PluginManager::doSomethingElse()
{
  // ...
}
person Vincent Robert    schedule 09.02.2009

Вы не говорили о TheManager. Похоже, вы хотите, чтобы это контролировало, какой класс используется? или, может быть, вы пытаетесь связать их вместе?

Похоже, вам нужен абстрактный базовый класс и указатель на используемый в настоящее время класс. Если вы хотите связать, вы можете сделать это как в абстрактном классе, так и в классе менеджера. Если абстрактный класс, добавьте член в следующий класс в цепочке, если менеджер, то отсортируйте его в том порядке, в котором вы будете использовать в списке. Вам понадобится способ добавления классов, поэтому вам понадобится addMe() в менеджере. Похоже, вы знаете, что делаете, поэтому ваш выбор должен быть правильным. Я рекомендую список с функцией addMe, и если вам нужен только 1 активный класс, тогда функция в TheManager решает, что это будет хорошо.

person Community    schedule 11.02.2009

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

Я бы разбил его на 3 части.

1) Плагины будут принадлежать классу FrameWork. Этот класс отвечает за публикацию интерфейсов, предоставляемых плагинами.

2) Класс PlugIn будет владеть компонентами, которые выполняют работу. Этот класс отвечает за регистрацию экспортированных интерфейсов и привязку импортированных интерфейсов к компонентам.

3) Третий раздел, компоненты являются поставщиками и потребителями интерфейсов.

Чтобы сделать вещи расширяемыми, их запуск и запуск можно разбить на этапы.

  1. Создать все.
  2. Подсоедините все.
  3. Начать все.

Чтобы сломать вещи.

  1. Остановить все.
  2. Уничтожить все.
class IFrameWork {
public:
    virtual ~IFrameWork() {}
    virtual void RegisterInterface( const char*, void* ) = 0;
    virtual void* GetInterface( const char* name ) = 0;
};

class IPlugIn {
public:
    virtual ~IPlugIn() {}
    virtual void BindInterfaces( IFrameWork* frameWork ) {};
    virtual void Start() {};
    virtual void Stop() {};
};

struct SamplePlugin :public IPlugIn {
    ILogger* logger;

    Component1 component1;
    WebServer  webServer;

public:
    SamplePlugin( IFrameWork* frameWork ) 
        :logger( (ILogger*)frameWork->GetInterface( "ILogger" ) ),  //assumes the 'System' plugin exposes this
        component1(),
        webServer( component1 )
    {
        logger->Log( "MyPlugin Ctor()" );

        frameWork->RegisterInterface( "ICustomerManager", dynamic_cast( &component1 ) ); 
        frameWork->RegisterInterface( "IVendorManager", dynamic_cast( &component1 ) ); 
        frameWork->RegisterInterface( "IAccountingManager", dynamic_cast( &webServer ) ); 
    }

    virtual void BindInterfaces( IFrameWork* frameWork ) {
        logger->Log( "MyPlugin BindInterfaces()" );

        IProductManager* productManager( static_cast( frameWork->GetInterface( "IProductManager" ) ) );
        IShippingManager* shippingManager( static_cast( frameWork->GetInterface( "IShippingManager" ) ) );

        component1.BindInterfaces( logger, productManager );
        webServer.BindInterfaces( logger, productManager, shippingManager );
    }

    virtual void Start() {
        logger->Log( "MyPlugin Start()" );

        webServer.Start();
    }

    virtual void Stop() {
        logger->Log( "MyPlugin Stop()" );

        webServer.Stop();
    }
};

class FrameWork :public IFrameWork {
    vector plugIns;
    map interfaces;
public:
    virtual void RegisterInterface( const char* name, void* itfc ) {
        interfaces[ name ] = itfc;
    }
    virtual void* GetInterface( const char* name )  {
        return interfaces[ name ];
    }

    FrameWork() {
        //Only interfaces in 'SystemPlugin' can be used by all methods of the other plugins
        plugIns.push_back( new SystemPlugin( this ) );

        plugIns.push_back( new SamplePlugin( this ) ); 
        //add other plugIns here

        for_each( plugIns.begin(), plugIns.end(), bind2nd( mem_fun( &IPlugIn::BindInterfaces ), this ) );
        for_each( plugIns.begin(), plugIns.end(), mem_fun( &IPlugIn::Start ) );
    }

    ~FrameWork() {
        for_each( plugIns.rbegin(), plugIns.rend(), mem_fun( &IPlugIn::Stop ) );
        for_each( plugIns.rbegin(), plugIns.rend(), Delete() );
    }
};

person jyoung    schedule 13.02.2009

Вот минимальная реализация фабричного шаблона, которую я придумал примерно за 15 минут. Мы используем аналогичный, который использует более продвинутые базовые классы.

#include "stdafx.h"
#include <map>
#include <string>

class BaseClass
{
public:
    virtual ~BaseClass() { }
    virtual void Test() = 0;
};

class DerivedClass1 : public BaseClass 
{ 
public:
    virtual void Test() { } // You can put a breakpoint here to test.
};

class DerivedClass2 : public BaseClass 
{ 
public:
    virtual void Test() { } // You can put a breakpoint here to test.
};

class IFactory
{
public:
    virtual BaseClass* CreateNew() const = 0;
};

template <typename T>
class Factory : public IFactory
{
public:
    T* CreateNew() const { return new T(); }
};

class FactorySystem
{
private:
    typedef std::map<std::wstring, IFactory*> FactoryMap;
    FactoryMap m_factories;

public:
    ~FactorySystem()
    {
        FactoryMap::const_iterator map_item = m_factories.begin();
        for (; map_item != m_factories.end(); ++map_item) delete map_item->second;
        m_factories.clear();
    }

    template <typename T>
    void AddFactory(const std::wstring& name)
    {
        delete m_factories[name]; // Delete previous one, if it exists.
        m_factories[name] = new Factory<T>();
    }

    BaseClass* CreateNew(const std::wstring& name) const
    {
        FactoryMap::const_iterator found = m_factories.find(name);
        if (found != m_factories.end())
            return found->second->CreateNew();
        else
            return NULL; // or throw an exception, depending on how you want to handle it.
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    FactorySystem system;
    system.AddFactory<DerivedClass1>(L"derived1");
    system.AddFactory<DerivedClass2>(L"derived2");

    BaseClass* b1 = system.CreateNew(L"derived1");
    b1->Test();
    delete b1;
    BaseClass* b2 = system.CreateNew(L"derived2");
    b2->Test();
    delete b2;

    return 0;
}

Просто скопируйте и вставьте первоначальное консольное приложение Win32 в VS2005/2008. Я люблю указывать на что-то:

  • Вам не нужно создавать конкретную фабрику для каждого класса. Шаблон сделает это за вас.
  • Мне нравится помещать весь фабричный шаблон в отдельный класс, чтобы вам не приходилось беспокоиться о создании фабричных объектов и их удалении. Вы просто регистрируете свои классы, компилятор создает фабричный класс, а фабричный объект создается по шаблону. По истечении срока службы все фабрики полностью уничтожаются. Мне нравится эта форма инкапсуляции, так как нет путаницы в том, кто управляет сроком службы фабрик.
person Dave Van den Eynde    schedule 13.02.2009