C++ 标记调度以防止接口更改

C++ 标记调度以防止接口更改,c++,boost,C++,Boost,我正在开发一些应用程序,需要做出一些架构决策。我遇到的问题是,我将不得不处理的接口缺乏规范。 假设目前我只知道这个界面有两个功能,“工作”和“打印”。目前,我只能猜测这些函数需要哪些参数,而且这些参数很可能完全不同(例如,我不能有一个函数指针容器来存储这些函数) 项目可能会持续一段时间,在这段时间内,我使用的界面将越来越具体(我的意思是,它可以经常更改)。我想实现的是以某种方式保护自己不受这些更改的影响,即在接口更改时将需要完成的工作量降至最低。并为可能出现在界面中的新功能做好准备 我开始考虑在

我正在开发一些应用程序,需要做出一些架构决策。我遇到的问题是,我将不得不处理的接口缺乏规范。 假设目前我只知道这个界面有两个功能,“工作”和“打印”。目前,我只能猜测这些函数需要哪些参数,而且这些参数很可能完全不同(例如,我不能有一个函数指针容器来存储这些函数)

项目可能会持续一段时间,在这段时间内,我使用的界面将越来越具体(我的意思是,它可以经常更改)。我想实现的是以某种方式保护自己不受这些更改的影响,即在接口更改时将需要完成的工作量降至最低。并为可能出现在界面中的新功能做好准备

我开始考虑在我的代码中使用某种机制来帮助我做到这一点。我希望这个机制从接口调用函数。我希望这个机制没有愚蠢的、无休止的if-else语句。我希望有一个机制,我可以在测试中模拟它,以与接口分离

我读了关于可能的调度机制,我读了关于元编程,boost::hana,mpl。我阅读了有关在boost::variant或boost::any中存储函数的内容,以及有关将元组传递给函数并借助索引_序列将其解包的内容。但对我来说,没有一个解决方案是好的和优雅的

我在下面发明了类似的东西,尽管它还远远不够完美。但我想问你对这种方法有何看法。我的情况可以吗?它可以维护吗?可扩展的?优雅

#include <iostream>
#include <functional>
using namespace std;

struct work_tag {};
struct print_tag {};

// --- real functions of the INTERFACE ---
void doWork(int a, int b, string s)
{
   cout << a << ", " << b << ", " << s << endl;
}

void doPrint(int a, double d, double e, int f, string s)
{
   cout << a << ", " << d << ", " << e << ", " << f << ", " << s << endl;
}

// --- fake function for tests ---
void fakeDoWork(int a, int b, string s)
{
   cout << "this is a fake: " << a << ", " << b << ", " << s << endl;
}

void fakeDoPrint(int a, double d, double e, int f, string s)
{
   cout << "this is a fake: " << a << ", " << d << ", " << e << ", " << f << ", " << s << endl;
}

// --- my class, it will do some operations and call the INTERFACE ---
struct B
{    
public:
   B(auto funForWorking, auto furForPrinting)
       : m_funForWorking(funForWorking), m_funForPrinting(furForPrinting)
   {}
   template <class... Args>
   void request(Args... args)
   {
       dispatch(std::forward<Args>(args)...);
   }

private:
   template <class... Args>
   void call(work_tag, Args... args)
   {
      m_funForWorking(std::forward<Args>(args)...);
   }

   template <class... Args>
   void call(print_tag, Args... args)
   {
      m_funForPrinting(std::forward<Args>(args)...);
   }

   template <class TYPE_TAG, class... Args>
   void dispatch(TYPE_TAG type_tag, Args... args)
   {
      call(type_tag, std::forward<Args>(args)...);
   }

private:
   std::function<decltype(doWork)>  m_funForWorking;
   std::function<decltype(doPrint)> m_funForPrinting;
};

int main()
{
    // production code
    B b(doWork, doPrint);
    b.request(work_tag{}, 1, 2, "a");
    b.request(print_tag{}, 1, 2.0, 3.0, 4, "bbb"s);

    // somewhere in the tests
    B btest(fakeDoWork, fakeDoPrint);
    btest.request(work_tag{}, 1, 2, "a");
    btest.request(print_tag{}, 1, 2.0, 3.0, 4, "bbb"s);
}
#包括
#包括
使用名称空间std;
结构工作标签{};
结构打印_标记{};
//---接口的实际功能---
空道工(整数a、整数b、字符串s)
{

这可能不是一个很有帮助的答案,但我认为如果不真正理解结构B的用途,就很难给出这个问题的好答案。它在生活中的用途是什么?是什么赋予了它凝聚力?它试图封装什么?也许在你的真实案例中,
B
除了为战争做其他事情你要去参加这个活动吗

在您当前的设计中,为了使用
B
,您需要对其包含的功能有深入的了解,因此我不认为它隐藏了这些功能的任何细节。它可以被以下内容取代:

struct B
{    
   std::function<decltype(doWork)>  work;
   std::function<decltype(doPrint)> print;
};

这对我来说很难闻。我担心,为了获得一些能够证明未来的东西,您正在牺牲代码的可用性。为了使用
B::request()
您必须找到正确的标记,并计算出要传递的正确参数,因为函数的签名不会告诉您任何信息。谢谢您,克里斯。也许您是对的……我必须仔细考虑一下。谢谢您,克里斯。但在您的方法中,我必须明确地称为“工作”和“打印”。这正是我试图做到的避免。可能我对问题的描述不太准确。让我澄清一下。我想要实现的是,首先有一些通用机制,将一些外部的高级请求转换为一些通用形式的可用请求类型(即标记)然后将它们传递给在给定平台上执行请求的类。换句话说,我希望有一个高层、中层和底层组件以某种方式彼此分离。顶层组件将是a,中层组件将是B,底层组件将是C。我希望中层组件,在我的例子中,B是一个转换器,仅只提供“sendRequest”方法。然后对B的sendRequest方法的调用(从A)可以直接转换为对低级C组件方法的调用。顶部组件(A)必须在对sendRequest的调用中指定可识别(自身)的标记,当然还要提供参数(取决于请求的类型)。然后“B”将通过标记发送请求(通过重载机制,而不是通过if-else)并调用最低级别组件C的适当方法,该方法将处理与给定平台上实际处理请求相关的所有内部事务。我希望对组件进行明确的分离:即接收外部应用程序请求的组件(a),以及负责在给定平台上处理请求的组件(C)。我需要的是一些翻译组件,中间层(B)。我不希望A了解C。我希望A组件只知道标记和参数列表。但我认为我没有完全做到这一点,因为如果C方法的参数列表更改,那么A组件也会受到影响。@YotKay我建议您扩展mcve,以给出
A
行为的示例。如何它得到标签了吗?它如何选择传递给
B
的参数?因为就目前情况而言,我仍然不明白
A
知道
B
成员的名字与
A
知道标签以及它必须传递给
B
的参数的选择有什么不同。在我的回答
A中
不需要知道
C
,这是隐藏的,只需要知道
B
持有的函数类型。
// production code
B b{doWork, doPrint};
b.work(1, 2, "a");
b.print(1, 2.0, 3.0, 4, "bbb"s);

// somewhere in the tests
B btest{fakeDoWork, fakeDoPrint};
btest.work(1, 2, "a");
btest.print(1, 2.0, 3.0, 4, "bbb"s);