Абстрактная Фабрика для Дилеммы Класса Шаблонов

обзор

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

Настройки

у меня есть Worker класс, который внутренне требует класса контейнера C запомнить обработку информации. Обработка информации организована в виде BufferType структура, так Worker внутренне имеет дело с членом типа C< BufferType >, С другой стороны, мы не хотим Workerклиенты, чтобы знать о Worker::BufferType, которые отражают API-нерелевантные детали реализации Worker это может измениться со временем.

Ситуация осложняется тем, что C является абстрактным классом, который имеет различные реализации функциональных возможностей контейнера в зависимости от фактической среды (например, базы данных). Очевидно, мы не хотим Worker Чтобы знать об этом, он должен работать в любой среде, для которой существует реализация C,

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

Вот контейнеры, с которыми мне приходится иметь дело:

template <class T>
class C {
// container interface
};

template <class T>
class CImpl : public C<T> {
// implements C's interface
};

Вот как мне бы хотелось реализовать Worker учебный класс:

class Worker {
public:
Worker( AbstractCFactory& f ) : _f( &f ), _buffer( NULL ) {}
void doSomething() {
_buffer = _f->create<BufferType>( );
// ... do something with _buffer
}
private:
AbstractCFactory* _f;
typedef struct { int someInfo; } BufferType;
C< BufferType >* _buffer;
};

Однако для этого потребуется что-то вроде следующего с точки зрения заводов:

class AbstractCFactory {
public:
template <class T>
virtual C< T >* create() = 0;
};

class ConcreteCFactory : public AbstractCFactory {
public:
template <class T>
virtual C< T >* create() {
return new CImpl< T >();
}
};

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

Гадкий обходной путь

Во-первых, я реализовал фабрики как шаблоны, чтобы create() функция:

template <class T>
class AbstractCFactory {
public:
virtual C< T >* create() = 0;
};

template <class T>
class ConcreteCFactory : public AbstractCFactory {
public:
virtual C< T >* create() {
return new CImpl< T >();
}
};

Затем я переместил BufferType на общедоступный интерфейс Worker, позволяя клиенту создать ConcreteCFactory< Worker::BufferType > экземпляр для передачи Workerконструктор.

class Worker {
public:
typedef struct { int someInfo; } BufferType;
Worker( AbstractCFactory< BufferType >& f ) : _f( &f ), _buffer( NULL ) {}
void doSomething() {
_buffer = _f->create( );
// ... do something with _buffer
}
private:
AbstractCFactory< BufferType >* _f;
C< BufferType >* _buffer;
};

Очевидно, что мой обходной путь не является реальным решением, потому что он все еще вводит нежелательную зависимость клиента от частных деталей реализации Worker (BufferType) выставляя их на общедоступный интерфейс.

Вопрос

Есть ли правильное решение, которое не заставит меня нарушить инкапсуляцию, то есть сохранить Worker независимо от CImpl и клиент независим от Worker::BufferType?

4

Решение

CImpl<T> является шаблоном класса, который должен быть предоставлен клиентом, и он должен быть специализирован на CImpl<BufferType>, Во время специализации оба CImpl а также BufferType должны быть определены. Один из вариантов — попросить клиента предоставить CImpl.h файл, содержащий определение реализации C<T>:

cimpl.h (предоставляется клиентом)

template <class T>
class CImpl : public C<T> {
// implements C's interface
};

worker.h:

class IC {
};

template <class T>
class C: public IC {
// container interface
};

class AbstractCFactory {
public:
virtual IC* create() = 0;
};

class Worker {
public:
Worker( AbstractCFactory& f ) : _f( &f ), _buffer( 0 ) {}
void doSomething();
private:
AbstractCFactory* _f;
C< struct BufferType >* _buffer;
};

AbstractCFactory& getDefaultFactory();

worker.cpp

#include "worker.h"#include "cimpl.h"#include <stdlib.h>

template <class T>
class ConcreteCFactory : public AbstractCFactory {
public:
virtual IC* create() {
return new CImpl< T >();
}
};

struct BufferType {
int someInfo;
};

void Worker::doSomething() {
_buffer = static_cast<C<BufferType>*>(_f->create( ));
// ... do something with _buffer
}

AbstractCFactory& getDefaultFactory() {
static ConcreteCFactory<BufferType> f;

return f;
}

Пример использования:

#include "worker.h"...
AbstractCFactory& f = getDefaultFactory();
Worker w(f);
w.doSomething();

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

worker.h

class IC {
};

template <class T>
class C: public IC {
// container interface
};

class AbstractCFactory {
public:
virtual IC* create() = 0;
};

struct BufferType;

class Worker {
public:
Worker( AbstractCFactory& f ) : _f( &f ), _buffer( 0 ) {}
void doSomething();
private:
AbstractCFactory* _f;
C< struct BufferType >* _buffer;
};

AbstractCFactory& getFactory();

#include "private.h"

private.h:

struct BufferType {
int someInfo;
};

template<class I>
class CFactory: public AbstractCFactory {
public:
virtual IC* create() {
return new I();
}
};

template<class I>
AbstractCFactory& getFactory() {
static CFactory<I> fact;

return fact;
}

worker.cpp:

#include "worker.h"
void Worker::doSomething() {
_buffer = static_cast<C<BufferType>*>(_f->create( ));
// ... do something with _buffer
}

Пример использования:

#include "worker.h"...
template <class T>
class CImpl : public C<T> {
// implements C's interface
};
...
AbstractCFactory& f = getFactory<CImpl<BufferType>>();
Worker w(f);
w.doSomething();
1

Другие решения

Иногда чрезмерное использование шаблонов не хорошо. Если вы не хотите, чтобы клиент знал о вас BufferType Вы должны использовать специализацию шаблона в вашем CImpl с BufferType и построить свой рабочий без фабричного образца.

0