寻找设计模式以减少虚方法重载 [英] Looking for design pattern to reduce virtual method overloads

查看:123
本文介绍了寻找设计模式以减少虚方法重载的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个大的(〜100)数量的类从一个共同的基类( Device )派生。每个设备都可以接受类似大量命令的某个子集。不同的命令可以有不同的数字和类型的参数,所以每个命令都是由自己的类型封装的(如果需要可以改变)。



允许我向一个设备传递一个命令,只给出 Device 基类的指针/引用,以便设备可以访问命令的类型和参数? / p>

选项我想出:


  1. 最简单的方法是添加一个单独的虚拟方法接受每个命令类型到基类 Device 。然而,这将导致基类中的大量虚拟方法只在非常少的派生类中被覆盖。

  2. 我考虑了这个的访问者模式,但是由于

  3. 使用RTTI(或每个命令唯一的枚举/标识符)来确定命令类的数量,命令类型,然后切换/ if分支到相应的代码。这感觉很脏,因为它绕过正常的C ++多态。此外,dynamic_cast在这里非常皱眉,所以这个选项是非常多的。

任何模式的建议,在 Device 基类中没有大量虚拟方法?

解决方案

p>这是经典的双调度问题



我已经碰到这种模式几次,并使用以下策略来处理它。



让我们说基类 有一个函数,返回一个id,它可以是一个整数类型,字符串类型或其他可以用作地图中的键。

  struct命令
{
typedef< SomeType> IDType;
virtual IDType getID()const = 0;
};

Device 的界面可以简化为:

  struct命令; 
struct Device
{
virtual execute(Command const& command)= 0;
};

让我们说 DeviceABCD 类型,并且我们通过基类指针/引用操作的实际设备是 DeviceABCD 。在第一次调度中,执行命令的调用被分派到 DeviceABCD :: execute()



DeviceABCD :: execute()的实现被分派到另一个执行真正工作的函数。



一个适当的框架来正确执行第二次调度。在框架中:


  1. 需要有command ID - >command executor的映射。

  2. 需要有一种方法来注册一个命令执行者给定一个命令ID。

你可以得到命令执行器给定一个命令ID。如果存在命令执行器,则可以简单地将命令执行分派给命令执行器。



这个框架可以被的所有子类型使用, Device 。因此,该框架可以在 Device 本身中实现,或者在作为 Device 的对等体的助手类中实现。我喜欢第二种方法,并建议创建几个类: CommandExecutor CommandDispatcher



CommandExecutor.h:

  struct CommandExecutor 
{
virtual execute命令const& command)= 0;
};

CommandDispatcher.h:

  class CommandDispatcher 
{
public:
void registerCommandExecutor(Command :: IDType commandID,
CommandExecutor * executor);

void executeCommand(command const& command);

std :: map< Command :: IDType,CommandExecutor *>& getCommandExecutorMap();

public:
std :: map< Command :: IDType,CommandExecutor *>地图;
};

CommandDispatcher.cpp:

  void CommandDispatcher :: registerCommandExecutor(Command :: IDType commandID,
CommandExecutor * executor)
{
getCommandExecutorMap()[commandID] = executor;
}

void CommandDispatcher :: executeCommand(command const& command)
{
CommandExecutor * executor = getCommandExecutorMap()[commandID];
if(executor!= nullptr)
{
executor-> execute(command);
}
else
{
throw< AnAppropriateExecption> ;;
}
}

std :: map< Command :: IDType,CommandExecutor *> CommandDispatcher :: getCommandExecutorMap()
{
return theMap;
}

如果 DeviceABCD 执行 Command12 Command34 ,其实现方式如下:



DeviceABCD.cpp:

  struct Command12Executor:public CommandExecutor 
{
virtual void execute const& command){...}
};

struct Command34Executor:public CommandExecutor
{
virtual void execute(command const& command){...}
};

DeviceABCD :: DeviceABCD():commandDispatcher_(CommandExecutor)
{
static Command12Executor executor12;
static Command34Executor executionor34;

//这假设你可以获得Command12
//的所有实例的ID,而没有类的实例,即它是类的静态数据。

commandDispatcher_.registerExecutor(Command12Type,& executor12);
commandDispatcher_.registerExecutor(Command34Type,& executor34);
}

使用该框架,实现 DeviceABCD :: execute()很简单。

  void DeviceABCD :: execute(Command const& command )
{
commandDispatcher_.executeCommand(command);
}

这是精简到一个点,甚至可以在基类中实现。你需要在派生类中实现它,只有在命令被分派到正确的CommandExecutor之前需要按下命令或更新一些其他状态。


I have a large (~100) number of classes derived from a common base class (Device). Every device can accept a certain subset of a similarly large number of commands. Different commands can have different numbers and types of parameters, so every command is encapsulated by its own type (this could be changed if necessary).

What patterns are there that would allow me to pass a command to a device, given only a pointer/reference to the Device base class, such that the device could access the type and parameters of the command?

Options I've come up with:

  1. The most straightforward method would be to add a separate virtual method accepting each command type to the base class Device. However, this would end up with a large number of virtual methods in the base class that are only overridden in very few of the derived classes.
  2. I considered the visitor pattern for this, but since the number of command classes is approximately equal to the number of device classes, this doesn't really gain anything.
  3. Using RTTI (or an enum/identifier unique to each command) to determine the command type, and then a switch/if to branch to the appropriate code. This feels pretty dirty since it bypasses the normal C++ polymorphism. Also, dynamic_cast is very much frowned upon here, so this option is pretty much out.

Any suggestions for a pattern that would allow this cleanly without a huge number of virtual methods in the Device base class?

解决方案

This is the classic double dispatch problem.

I have ran into this pattern a few times and used the following strategy for dealing with it.

Let's say the base class Command has a function that returns an "id", which could be an integral type, string type, or something else that can be used as a key in a map.

struct Command
{
   typedef  <SomeType> IDType;
   virtual IDType getID() const = 0;
};

The interface of Device could be simplified to:

struct Command;
struct Device
{
   virtual execute(Command const& command) = 0;
};

Let's say DeviceABCD is one of the derived types and that the actual device on which we are operating through a base class pointer/reference is a DeviceABCD. In the first dispatch, the call to execute a command is dispatched to DeviceABCD::execute().

The implementation of DeviceABCD::execute() it is dispatched to another function that does the real work.

You need a framework in place to correctly perform the second dispatch. In the framework:

  1. There needs to be map of "command ID" -> "command executor".
  2. There needs to be way to register a "command executor" given a "command ID".

Based on those, you can get the "command executor" given a "command ID". If there is a "command executor", the command execution can simply be dispatched to the "command executor". If not, you need to deal with the error, most likely by raising an exception.

This framework can be use by all sub-types of Device. Hence, the framework can be implemented in Device itself or in a helper class that is a peer to Device. I prefer the second approach and would recommend creating couple of classes: CommandExecutor and CommandDispatcher.

CommandExecutor.h:

struct CommandExecutor
{
   virtual execute(Command const& command) = 0;
};

CommandDispatcher.h:

class CommandDispatcher
{
   public:
      void registerCommandExecutor(Command::IDType commandID,
                                   CommandExecutor* executor);

      void executeCommand(Command const& command);

      std::map<Command::IDType, CommandExecutor*>& getCommandExecutorMap();

   public:
      std::map<Command::IDType, CommandExecutor*> theMap;
};

CommandDispatcher.cpp:

void CommandDispatcher::registerCommandExecutor(Command::IDType commandID,
                                                CommandExecutor* executor)
{
   getCommandExecutorMap()[commandID] = executor;
}

void CommandDispatcher::executeCommand(Command const& command)
{
   CommandExecutor* executor = getCommandExecutorMap()[commandID];
   if ( executor != nullptr )
   {
      executor->execute(command);
   }
   else
   {
      throw <AnAppropriateExecption>;
   }
}

std::map<Command::IDType, CommandExecutor*>& CommandDispatcher::getCommandExecutorMap()
{
   return theMap;
}

If DeviceABCD can execute Command12 and Command34, its implementation would look something like:

DeviceABCD.cpp:

struct Command12Executor : public CommandExecutor
{
    virtual void execute(Command const& command) { ... }
};

struct Command34Executor : public CommandExecutor
{
    virtual void execute(Command const& command) { ... }
};

DeviceABCD::DeviceABCD() : commandDispatcher_(CommandExecutor)
{
   static Command12Executor executor12;
   static Command34Executor executor34;

   // This assumes that you can get an ID for all instances of Command12
   // without an instance of the class, i.e. it is static data of the class.

   commandDispatcher_.registerExecutor(Command12Type, &executor12);
   commandDispatcher_.registerExecutor(Command34Type, &executor34);
}

With that framework in place, the implementation of DeviceABCD::execute() is very simple.

void DeviceABCD::execute(Command const& command)
{
   commandDispatcher_.executeCommand(command);
}

This is streamlined to a point where it can even be implemented in the base class. You need to implement it in the derived class only if there is a need to massage the command or update some other state before the command gets dispatched to the right CommandExecutor.

这篇关于寻找设计模式以减少虚方法重载的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆