обзор
Техническая проблема заключается в том, что текущий дизайн требует абстрактной фабрики для рабочего класса шаблона 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
?
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();
Иногда чрезмерное использование шаблонов не хорошо. Если вы не хотите, чтобы клиент знал о вас BufferType
Вы должны использовать специализацию шаблона в вашем CImpl
с BufferType
и построить свой рабочий без фабричного образца.