私はいくつかのアプリケーションで作業しており、アーキテクチャ上の決定を下す必要があります。私が持っている問題は、私が作業しなければならないINTERFACEの仕様の欠如です。 当分の間、私はINTERFACEが '仕事'と 'プリント'の2つの機能を持つことが分かっているとしましょう。当時、私はこれらの関数が必要とするパラメータを推測することができ、パラメータは完全に異なっている可能性があります(たとえば、これらの関数を格納する関数ポインタのコンテナを持つことはできません)。インタフェースの変更から保護するためのタグディスパッチ
このプロジェクトは多分時間がかかり、この間に私が使用しているインターフェイスがますます指定されるようになります(私はそれが頻繁に変更できるということです)。私が達成したいのは、何らかの形でこれらの変化から身を守ることです。つまり、インターフェースが変わったときに必要な作業量を最小限に抑えることです。また、インターフェースに現れるかもしれない新しい機能のために自分自身を準備してください。
私はコードの中で何らかの仕組みについて考え始めました。私は、このメカニズムがINTERFACEから関数を呼び出すようにしたい。私は、このメカニズムが、愚かな無限のif-elseステートメントから解放されることを望みます。そして私はインターフェイスから分離するためにテストで嘲笑することができるメカニズムを持っていたいと思います。
ディスパッチメカニズムの可能性については、メタプログラミング、boost :: hana、mplについては読んでいます。 boost :: variantやboost :: anyに関数を格納する方法、関数にタプルを渡したり、index_sequenceの助けを借りて展開する方法について説明しました。しかし、解決策のどれも私にとって素晴らしく優雅ではありませんでした。
私は以下のようなものを発明しましたが、完璧とはほど遠いものです。しかし、私はあなたにそのようなアプローチについて何を考えているかお尋ねしたいと思います。私の場合は大丈夫ですか?それは維持可能ですか?拡張可能?エレガント?
#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);
}
新機能は、新しいメンバーが新しいの保存、「コール」の新しいオーバーロードを追加し、私は私のクラスのコードを変更する必要がありますINTERFACEに表示されますときので、私は、これは完璧なソリューションではありません知っています新しいディスパッチタグを追加します。しかし、少なくとも私はINTERFACEの既存の関数のパラメータリストの変更から何とか保護されています(私はコード内の呼び出し場所を変更するだけです)。
私は、インターフェイスの各機能を格納するためのメンバ(マップに関数を格納し、マップを使用して呼び出しを送信する)を提供する必要性を解消したいと思っています。また、 INTERFACEの各メソッドに対して「コール」します。しかし、私は自分の状況でこれをどうやって行うのか分かりません。それぞれの機能には異なる議論があります。
これを行うためのより良い解決策はありますか?
。私は努力の中で、あなたのコードのユーザビリティを犠牲にしていることを将来の証拠にすることを恐れています。 'B :: request()'を使うためには、正しいタグを見つけなければならず、関数のシグネチャが何も伝えていないので、正しいパラメータを渡す必要があります。 –
クリスように感謝します。多分あなたは正しいと思う...私はこれについてもっと考えなければならない。 – YotKay