【问题标题】:Abstract Factory for Template Class Dilemma模板类困境的抽象工厂
【发布时间】:2015-08-14 13:47:34
【问题描述】:

概述

技术问题是当前的设计似乎需要一个用于 C++ 模板工作者类的抽象工厂,据我所知,这是不可能的。因此,我需要一个替代解决方案来防止客户端依赖于工作人员的实现细节以及来自任何特定客户端环境的工作人员。

环境

我有一个Worker 类,它在内部需要一个容器类C 来记忆处理信息。处理信息组织在BufferType 结构中,因此Worker 在内部处理C< BufferType > 类型的成员。另一方面,我们不希望Worker 的客户知道Worker::BufferType,这反映了Worker 的API 无关的实现细节可能会随着时间而改变。

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&lt; Worker::BufferType &gt;实例传递给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

【问题讨论】:

  • 我认为有一些可能的解决方案。但我不清楚你的系统的公共接口是什么。 BufferType 的声明是否应该在 public .h 中声明 Worker 类?
  • 不,不应该,因为它代表了一种数据结构,仅供Worker 在内部使用,不应向公共 API 公开。在我的“丑陋解决方法”中,我不得不将它移到那里,以便客户端能够生成正确专业化的CImpl,但这正是我在那里使用“丑陋”一词的原因。

标签: c++ templates abstract-factory


【解决方案1】:

CImpl&lt;T&gt;是一个必须由客户端提供的模板类,它必须专门化为一个CImpl&lt;BufferType&gt;。在专业化时,CImplBufferType 都必须定义。一种可能性是要求客户端提供一个包含C&lt;T&gt; 实现定义的CImpl.h 文件:

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 文件分成两部分,一个 public 接口和一个 private 一个客户端不应直接使用的接口。

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();

【讨论】:

  • 好的,那么 Worker 和 C 以及在公共接口中,BufferType 是一个私有实现,CImpl 只有客户端知道。我还没有理解最后一部分,我应该考虑一下一段时间后提出一些建议。`
  • @ExternalCompilerError: 使用客户端提供的 CImpl 使用 2 个实现编辑帖子。
  • 感谢您的详细建议!对于提案一(客户端提供的cimpl.h),客户端代码看起来与它应该的完全一样。另外,客户端不依赖BufferType,我们可以更新worker而无需重新编译或重新部署客户端。但是,让worker.cpp 包含cimpl.h 需要构建不同版本的worker,每个环境一个,我会有多个不同版本的cimpl.h 和编译的worker,每个环境一个。
  • 第二个提议,虽然它的优点是它更明确地说明了Worker API 的哪一部分是真正公开的,但它显然也有我的“丑陋的解决方法”的类似缺点,即BufferType 已暴露,要求客户端在工厂实例化期间显式使用它。
  • 我想得越多,我就越怀疑我的最佳解决方案的想法,其中客户端和Worker 是两个可单独部署的组件,Worker 不依赖于CImpl 和不在BufferType 上的客户端可以通过Worker 使用C&lt;BufferType&gt;。但是,您的建议引发了以下想法:如果我没有将BufferType 直接用于容器项,而是将Worker 更改为使用C&lt;shared_ptr&lt;BufferType&gt;&gt;,那么客户端应该可以在不知道@ 的情况下创建类型化工厂987654351@的大小。
【解决方案2】:

有时过度使用模式是不好的。如果您不希望客户了解您BufferType,您应该在CImplBufferType 中使用模板专业化,并在没有工厂模式的情况下构建您的工人。

【讨论】:

  • 如果我正确理解了您的建议,这意味着我将在 Worker 类中对 CImpl 进行专业化。如果Worker 可以依赖CImpl,我会同意你的观点,抽象工厂确实是模式过度使用的一个例子。但是CImpl针对某个环境实现了C,从而引入了Worker不允许拥有的新依赖。 Worker 必须只知道抽象接口C
  • 模板 class C { private: T x; };类 CImpl :public C { };这就是我的回答工作者的意思,不会受到模板的影响
  • 对,但我需要将其设为class CImpl : public C&lt;BufferType&gt;,但不应该有一个同时知道CImplBufferType 的源文件(只有客户知道@987654335 @,并且只有 Worker 关于 BufferType)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多