朋友,抽象类和工厂模式

Friends, Abstract classes and Factory pattern

本文关键字:工厂 模式 抽象类 朋友      更新时间:2023-10-16

大家好。。。

我在公司做一个复杂的项目,我在项目中使用了一些扭曲的工厂设计模式。省略细节;我有一些类(我称之为"设备"(,只能由"阅读器"创建:

class DeviceBase // this is a virtual base class
{
  public:
   //some stuff
   friend class ReaderBase; // this is OK and necessary I guess?
  private:
   DeviceBase(); // cannot create a device directly
   //some more stuff
}
class Device1: public DeviceBase // some extended device
{
  public:
   //some stuff 
  private:
   //some more stuff
}
class Device2: public DeviceBase  // some other extended device
{
  public:
   //some stuff
  private:
   //some more stuff
}

现在的"阅读器",恰好是设备的工厂:

class ReaderBase
{
  private:
    DeviceBase[] _devices; // to keep track of devices currently "latched"
  public:
    // some other methods, getters-setters etc ...
    // this method will create the "Devices" :
    virtual bool PollforDevice ( DeviceType, timeout) = 0; 
}

现在,这是我的工厂课;但它(正如你所看到的(是纯虚拟的。我有一些特殊的读者继承自这本书:

 class InternalReader: public ReaderBase
 {
   public:
     // define other inherited methods by specifics of this reader
     bool PollforDevice( DeviceType dt, timeout ms)
     {
         switch(dt)
         {
           case Device1: { /* create new device1 and attach to this reader */ } break;
           case Device2: { /* create new device2 and attach to this reader */ } break;
         }
         // show goes on and on...
     }
 }
 class ExternalReader: public Reader
 {
   public:
     // define other inherited methods by specifics of this reader
     bool PollforDevice( DeviceType dt, timeout ms)
     {
         switch(dt)
         {
           case Device1: { /* create new device1 and attach to this reader */ } break;
           case Device2: { /* create new device2 and attach to this reader */ } break;
         }
         // show goes on and on...
     }
 }

我使用这种模式的原因是:我为一个可以同时连接多个"阅读器"的系统写作,我必须同时使用它们。

还有这些"设备":我也可以公开他们的构造函数,每个人都会很高兴;但我想确保它们不是由代码编写者自己创建的(以确保其他代码编写者(

现在的问题是:

  1. 我应该在每个"设备"中明确声明ReaderBase是朋友吗?或者仅仅在基础上声明"DeviceBase"就足够了
  2. 我应该明确地把从"ReaderBase"继承的"Readers"也是这些设备的朋友的每一个"设备"都放进去,还是只放ReaderBase就足够了
  3. 与其让整个"ReaderBase"类成为朋友,我可以(也应该(让成员方法"PollforDevice"成为朋友吗?知道这是一种纯粹的虚拟方法,这会让继承的副本也成为朋友吗

很抱歉这个问题很长,但我只想说清楚。

提前感谢。。。

为什么要担心像DeviceBase这样的纯抽象基类的可构造性?如果它是一个设计得当的契约或抽象基类,那么无论如何都无法构造它。除非你必须适应某种你没有提到的框架,否则只需做与隐藏相反的事情,例如:

struct DeviceBase {
    virtual void Foo() = 0;
    virtual void Bar() = 0;
    virtual ~DeviceBase() = default;
};

顺便说一句,声明构造函数或析构函数private将非常有效地使类"密封"。如果由于某种原因DeviceBase不抽象(在我看来这是一个严重的设计缺陷(,则使构造函数protected而不是private。您需要麻烦的是具体Device类的构造函数可访问性。假设您要"发布"这些实现类(即,库的用户可以访问它们的定义(,并且您希望强调禁止直接构建,请使用"访问习语"(我为此发明的名称(:

namespace impl_detail {
    class DeviceAccess;
}
class ConcreteDevice1 : public DeviceBase {
    friend class impl_detail::DeviceAccess;
    // implementation of DeviceBase and all other stuff go 
    // into the "private" section
};
namespace impl_detail {
    class DeviceAccess {
        template< class TDevice >
        static DeviceBase* Create()
        {
            return new TDevice;
        }
    };
};

Reader类中,使用impl_detail::DeviceAccess::Create构造Device实例,例如:

// Your ExternalReader::PollForDevice...
switch (dt) {
    case Device1:
        return impl_detail::DeviceAccess::Create<ConcreteDevice1>();
    case Device2: 
        // etc...
}

长话短说,最好的解决方案是根本不发布具体的实现类,其次是某种限制构建的"心理障碍",例如上述类型的。。。

  1. 我应该在每个"设备"中明确声明ReaderBase是朋友吗?或者仅仅在基础上声明"DeviceBase"就足够了
  2. 我应该明确地把从"ReaderBase"继承的"Readers"也是这些设备的朋友的每一个"设备"都放进去,还是只放ReaderBase就足够了

由于友谊不是继承的(在友谊关系的任何一方(,您的计划唯一可行的方式是在每个派生设备中声明每个派生阅读器的友谊。这在Reader类和Device类之间创建了一个紧密耦合,这不是一个好的设计。

3( 与其让整个"ReaderBase"类成为朋友,我可以(也应该(让成员方法"PollforDevice"成为朋友吗?知道这是一种纯粹的虚拟方法,这会让继承的副本也成为朋友吗?

您可以将ReaderX::PollforDevice作为朋友,而不是整个ReaderX类,但这对您没有多大帮助,只会为难以解决的循环依赖关系打开大门。


实际上,很难创建这样一种设计,即层次X的类只能由层次Y的类创建,而不能由其他人创建,而不在两个层次中的类之间创建紧密耦合。我的方法是

  1. 首先,教育你的同事,如果他们想要DeviceX,那么他们可以从ReaderY那里获得,而不是通过其他方式。请确保在代码审查中强制执行此操作。所有其他步骤都只是损害控制
  2. 确保只有BaseDevice类暴露在Reader实现之外的代码中
  3. 使所有设备类的析构函数受到保护。这确保了设备类只能由派生类或好友清理(并自动排除非好友的堆栈分配(。如果有人不小心直接尝试使用Device类,应该让他们三思而后行
  4. 使ReaderBase成为DeviceBase的朋友,并赋予ReaderBase一个功能来进行设备的实际清理。这是确保设备可以被清理所必需的