2017-05-06 16 views
2

私はいくつかのアプリケーションで作業しており、アーキテクチャ上の決定を下す必要があります。私が持っている問題は、私が作業しなければならない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の各メソッドに対して「コール」します。しかし、私は自分の状況でこれをどうやって行うのか分かりません。それぞれの機能には異なる議論があります。

これを行うためのより良い解決策はありますか?

+2

。私は努力の中で、あなたのコードのユーザビリティを犠牲にしていることを将来の証拠にすることを恐れています。 'B :: request()'を使うためには、正しいタグを見つけなければならず、関数のシグネチャが何も伝えていないので、正しいパラメータを渡す必要があります。 –

+0

クリスように感謝します。多分あなたは正しいと思う...私はこれについてもっと考えなければならない。 – YotKay

答えて

0

これはおそらく有用な答えではありませんが、struct Bが何であるかを実際に理解することなくこの質問に良い答えを提供することは難しいと思います。人生の目的は何ですか?結束を与えるのは何ですか?何をカプセル化しようとしていますか?おそらくあなたの実際のケースでは、Bは、あなたのコールを関数に転送する以外の他のことをしていますか?

現在のデザインでは、Bを使用するには、その機能の詳細を隠しているとは思わないので、その機能を深く理解する必要があります。それは置き換えることができ:

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

、呼び出し元のコードは次のようになります。これは、私には、[早すぎる一般化](http://wiki.c2.com/?PrematureGeneralization)の匂い

// 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); 
+0

クリスように感謝します。しかしあなたのアプローチでは、私は明示的に '仕事'と '印刷'を呼び出さなければなりません。これはまさに私が避けようとしていたものです。多分私は問題の記述ではあまり正確ではありませんでした。これを少しはっきりさせてください。私が達成したいのは、いくつかの一般的なメカニズムを利用可能なリクエストタイプ(つまりタグ)の一般的な形式に変換し、その後、それらを与えられたプラットフォーム上で要求を実行するクラスに渡すことです。換言すれば、何らかの方法で互いに分離された上位レベル、中間レベル、および下位レベルのコンポーネントが必要でした。 – YotKay

+0

先頭のコンポーネントはA、中間はB、低レベルはCです。中間レベルのコンポーネントBを私の場合は「sendRequest」メソッドのみを提供するトランスレータにしたいと思います。次に、BのsendRequestメソッド(Aからの)の呼び出しを、低レベルのCコンポーネントのメソッドを呼び出すように直接変換することができます。先頭のコンポーネント(A)は、sendRequestの呼び出しで認識された(それ自身で)タグを指定しなければならず、もちろん(要求のタイプに応じて)パラメータを提供する必要があります。 – YotKay

+0

次に、 'B'は(if-elseでなく、オーバーロード機構によって)タグによってリクエストをディスパッチし、リクエストの実際の処理に関連するすべての内部処理を処理する、最低レベルのコンポーネントCの適切なメソッドを呼び出します特定のプラットフォームで私は、コンポーネント(つまり、外部アプリケーション(A)からのリクエストを受け取るコンポーネント)と、特定のプラットフォーム(C)でのリクエストの処理に注意を払うコンポーネントを明確に分離したいと考えました。私が必要としていたのは、いくつかの翻訳コンポーネント、中間層(B)です。 – YotKay

関連する問題