2

私は、ユーザーが設定を変更してから情報をストリーミングすることにより、ハードウェアのビットと対話できるようにするツールを書いています。

これを行うために、いくつかのスレッドを実行EquipmentInterfaceしています。これらは。DataProcessorで接続されていQueueます。

EquipmentInterfaceスレッドには、機器の設定を変更するメソッドがあり(たとえばRotateRefocus、結果の情報(CurrentAngleおよびCurrentFocalDistance)がに追加されQueueます。設定が正しくなると、方法がStartStreamingありStopStreaming、ストリーミングが開始されると、機器からのデータがパケット化されてキューに追加されます。

BaseMessageキューに配置されるすべての情報は、メッセージタイプの表示を含む単一のクラスから派生します。次に、角度、焦点距離、ストリーミングの開始と終了、そしてもちろんデータ自体のメッセージタイプを導き出しました。

キューのDataProcessorもう一方の端をリッスンし、現在の角度/焦点距離に応じて、後続のデータを処理します。

これで、データプロセッサに、switchステートメントを使用して着信メッセージの型チェックを行う関数があります。これらのメッセージは、適切な型にダウンキャストされ、適切なハンドラーに渡されます。実際には、単一のキューをリッスンするDataProcessorだけでなく、実際には複数のキューに複数のリスナーがあります(ディスクに保存するものもあれば、GUIに情報を表示するものもあります)。情報を追加するたびに、新しいBaseMessage派生クラスを作成し、その基本クラスに新しい型を追加してから、新しいメッセージに対処するために各コンシューマーのswitchステートメントを更新する必要があります。

このアーキテクチャについての何かが私には間違っていると感じ、私は最近ダウンキャストについてたくさん読んでいます。私が見たものから、一般的なコンセンサスは、私がしていることは悪いコードの臭いであるということのようです。Boostを使用する提案を見たことがありますが、switchステートメントよりもきれいに見えません(何かが足りないのではないでしょうか?)。

だから私の質問は:私はswitch-statement / downcastingソリューションを避けようとしているべきですか?もしそうなら、どのように?

私の実装はC++/ CLIであるため、.netまたはC++ソリューションのいずれかが私が求めているものです。

編集-iammilindとstfaanvからのコメントに基づいて、これはあなたが提案している種類のものですか?

class QueuedItem
{
public:
    QueuedItem() { }
    virtual ~QueuedItem() { }

};

class Angle : public QueuedItem
{
public:
    Angle() {}
    virtual ~Angle() { }
};

class FocalLength : public QueuedItem
{
public:
    FocalLength() {}
    virtual ~FocalLength() { }
private:

};


class EquipmentHandler
{
protected:
    virtual void ProcessAngle(Angle* angle) {}; 
    virtual void ProcessFocalLength(FocalLength* focalLength) {};   

public:
    void ProcessMessages(QueuedItem* item)
    {
        Angle* pAngle = dynamic_cast<Angle*>(item);
        if( pAngle != NULL )
        {
            ProcessAngle(pAngle);
        }
        FocalLength* pFocalLength = dynamic_cast<FocalLength*>(item);
        if( pFocalLength != NULL )
        {
            ProcessFocalLength(pFocalLength);
        }

    }
};

class MyDataProcessor : public EquipmentHandler
{
protected:
    virtual void ProcessAngle(Angle* angle) override { printf("Processing Angle"); }
    virtual void ProcessFocalLength(FocalLength* focalLength) override { printf("Processing FocalLength"); };   
};


int _tmain(int argc, _TCHAR* argv[])
{

    // Equipment interface thread...
    FocalLength* f = new FocalLength();
    QueuedItem* item = f; // This gets stuck onto the queue

    // ...DataProcessor thread (after dequeuing)
    QueuedItem* dequeuedItem = item;

    // Example of a DataProcessor implementation.
    // In reality, this would 
    MyDataProcessor dataProc;
    dataProc.ProcessMessages(dequeuedItem);

    return 0;
}

...そしてそれは単純化できますか?少し不格好な感じがしますが、それProcessMessagesが、switchステートメントと基本クラスのある種の列挙されたメッセージタイプ識別子なしでそれを行うことができる唯一の方法です。

4

3 に答える 3

2

ビジターデザインパターンを試すことができます:http://en.wikipedia.org/wiki/Visitor_pattern

BaseVisitor各DataProcessorは、各タイプのメッセージを処理するための仮想メソッドを定義するクラスから継承します。基本的に、これらのメソッドは単なる初心者です。

新しいメッセージタイプを定義するときは、このメッセージタイプのnoop実装を使用して新しい仮想メソッドをに追加しますBaseVisitor。次に、子DataProcessorクラスがこのメッセージタイプを処理する場合は、これDataProcessorでのみ仮想メソッドをオーバーライドします。他のすべてDataProcessorは手つかずのままです。

    #include <iostream>


    class FocalLength;
    class Angle;
    class EquipmentVisitor;

    class QueuedItem
    {
    public:
            QueuedItem() { }
            virtual ~QueuedItem() { }

            virtual void AcceptVisitor(EquipmentVisitor& visitor) = 0;
    };

    class EquipmentVisitor
    {
    public:
            virtual ~EquipmentVisitor() {}

            virtual void Visit(FocalLength& item) {}
            virtual void Visit(Angle& item)       {}

            void ProcessMessages(QueuedItem* item)
            {
                    item->AcceptVisitor(*this);
            }
    };

    class Angle : public QueuedItem
    {
    public:
            Angle() {}
            virtual ~Angle() { }

            void AcceptVisitor(EquipmentVisitor& visitor) { visitor.Visit(*this); }
    };

    class FocalLength : public QueuedItem
    {
    public:
            FocalLength() {}
            virtual ~FocalLength() { }

            void AcceptVisitor(EquipmentVisitor& visitor) { visitor.Visit(*this); }
    private:

    };

    class MyDataProcessor : public EquipmentVisitor
    {
    public:
            virtual ~MyDataProcessor() {}

            void Visit(Angle& angle)             { std::cout << "Processing Angle" << std::endl; }
            void Visit(FocalLength& focalLength) { std::cout << "Processing FocalLength" << std::endl; }
    };


    int main(int argc, char const* argv[])
    {
            // Equipment interface thread...
            FocalLength* f    = new FocalLength();
            QueuedItem*  item = f; // This gets stuck onto the queue

            // ...DataProcessor thread (after dequeuing)
            QueuedItem* dequeuedItem = item;

            // Example of a DataProcessor implementation.
            // In reality, this would
            MyDataProcessor dataProc;
            dataProc.ProcessMessages(dequeuedItem);

            return 0;
    }
于 2012-04-11T09:14:09.673 に答える
0

次のいずれかを実行できます。

処理コード(ステートメントのそれぞれcaseのように)をオブジェクト(オブジェクトの階層または完全に無関係なタイプ)に委任します。switchHandlerHandlerBase

次に、メッセージにHandlerオブジェクトへの参照を保持させます(階層の場合は、BaseMessageレベルで実行できます。関連のないオブジェクトの場合は、個々の特殊なメッセージタイプの一部として実行できます)。これにより、メッセージを渡すことができます。BaseMessage::Handle()メソッドを介してそれらを処理します。編集:このメソッドは仮想ではありません。

もちろん、HandlerBase階層のパスをたどる場合でもstatic_cast、メッセージをどのタイプに戻す必要がありますが、それは問題ありません。メッセージは、独自のハンドラー(タイプを認識しているはずです)でのみ作成する必要があります。とりあえず。

例:

// BaseMessage.hpp
#include <iostream>

class BaseMessage
{
public:
  BaseMessage(HandlerBase* pHandler);
  : m_pHandler(pHandler)
  {}

  virtual ~BaseMessage()
  {}

  void  SetHandler(HandlerBase* pHandler)
  {
    m_pHandler = pHandler;
  }

  void  Handle()
  {
    assert(m_pHandler != 0);
    m_pHandler->Handle(this);
  }

protected:
  HandlerBase*  m_pHandler; // does not own it - can be shared between messages
};

// HandlerBase.hpp
class HandlerBase
{
public:
  HandlerBase()
  {}

  virtual ~HandlerBase()
  {}

  virtual void Handler(BaseMessage* pMessage) =0;
}

// message and handler implementations
class AMessage: public BaseMessage
{
public:
  AMessage(BaseHandler* pHandler)
    : BaseMessage(pHandler)
  {}

  ~AMessage() {}

  void  DoSomeAness()
  {
    std::cout << "Being an A..." << std::endl;
  }
};

class AHandler
{
public:
  AHandler()
  {}

  virtual ~AHandler()
  {}

  virtual void Handle(BaseMessage* pMessage)
  {
    AMessage *pMsgA(static_cast<AMessage*>(pMessage));
    pMsgA->DoSomeAness();
  }
};

class BMessage: public BaseMessage
{
public:
  BMessage(BaseHandler* pHandler)
    : BaseMessage(pHandler)
  {}

  ~BMessage() {}

  void  DoSomeBness()
  {
    std::cout << "Being a B..." << std::endl;
  }
};

class BHandler
{
public:
  BHandler()
  {}

  virtual ~BHandler()
  {}

  virtual void Handle(BaseMessage* pMessage)
  {
    BMessage *pMsgB(static_cast<BMessage*>(pMessage));
    pMsgB->DoSomeBness();
  }
};


// the thread
static std::list<BaseMessage*> msgQueue;

int HandlerThread(void *pData)
{
  while(true)   // find some more sophisticated way to break
  {
    while(!msgQueue.empty())
    {
      msgQueue.front()->Handle();
      msgQueue.pop_front();
    }
    // delay and stuff
  }
  return 0;
}

int main(int argc, char** argv)
{
  start_thread(HandlerThread, 0); // your favorite API here

  AHandler  aHandler;
  BHandler  bHandler;

  msqQueue.push_back(new AMessage(&aHandler));
  msqQueue.push_back(new BMessage(&bHandler));
  msqQueue.push_back(new BMessage(&bHandler));
  msqQueue.push_back(new AMessage(&aHandler));
  msqQueue.push_back(new AMessage(&aHandler));
  msqQueue.push_back(new BMessage(&bHandler));
  msqQueue.push_back(new AMessage(&aHandler));
  msqQueue.push_back(new BMessage(&bHandler));

  return 0;
}

編集:はい、本質的に、これはビジターパターンです。

于 2012-04-11T07:20:52.243 に答える
0

2つのハンドラーに4つのメッセージを送信するための私によると最も単純なメッセージ処理:

#include <iostream>
#include <queue>
#include <memory>

class HandlerA
{
public:
  void doA1() { std::cout << "A1\n"; }
  void doA2(const std::string& s) { std::cout << "A2: " << s << "\n"; }
};

class HandlerB
{
public:
  void doB1() { std::cout << "B1\n"; }
  void doB2(const std::string& s) { std::cout << "B2: " << s << "\n"; }
};


class BaseMsg
{
public:
  virtual ~BaseMsg() {}
  void send();
  virtual void handle() { execute(); }
  virtual void execute() = 0;
};

typedef std::shared_ptr<BaseMsg> Msg;
class Medium
{
  std::queue<Msg> queue;
public:
  void send(Msg msg) { queue.push(msg); }
  void process()
  {
    while (! queue.empty())
    {
      std::cout << "Processing\n";
      queue.front()->handle();
      queue.pop();
    }
  }
};

class BaseMsgHndlrA : public BaseMsg
{
protected:
  HandlerA& ha;
public:
  BaseMsgHndlrA(HandlerA& ha_) : ha(ha_) { }
};

class BaseMsgHndlrB : public BaseMsg
{
protected:
  HandlerB& hb;
public:
  BaseMsgHndlrB(HandlerB& hb_) : hb(hb_) { }
};

class MsgA1 : public BaseMsgHndlrA
{
public:
  MsgA1(HandlerA& ha_) : BaseMsgHndlrA(ha_) { }
  virtual void execute() { ha.doA1(); } 
};

class MsgA2 : public BaseMsgHndlrA
{
public:
  MsgA2(HandlerA& ha_) : BaseMsgHndlrA(ha_) { }
  virtual void execute() { ha.doA2("Msg A2"); } 
};

class MsgB1 : public BaseMsgHndlrB
{
public:
  MsgB1(HandlerB& hb_) : BaseMsgHndlrB(hb_) { }
  virtual void execute() { hb.doB1(); } 
};

class MsgB2 : public BaseMsgHndlrB
{
  std::string s;
public:
  MsgB2(HandlerB& hb_, const std::string s_) : BaseMsgHndlrB(hb_), s(s_) { }
  virtual void execute() { hb.doB2(s); } 
};

int main()
{
  Medium medium;
  HandlerA handlerA;
  HandlerB handlerB;

  medium.send(Msg(new MsgA1(handlerA)));
  medium.send(Msg(new MsgA2(handlerA)));
  medium.send(Msg(new MsgB1(handlerB)));
  medium.send(Msg(new MsgB2(handlerB, "From main")));

  medium.process();
}

これは、仮想関数を使用して、いくつかのパラメーターを使用して適切なハンドラーにディスパッチするだけです。
handle()関数は厳密には必要ありませんが、メッセージの階層を定義するときに役立ちます。
汎用メッセージは、bindで埋めることができるstd :: functionを保持できるため、キューに入れられたアクションごとにメッセージクラスを作成する代わりに、パラメーターを含む実際の関数を送信できます。
実際の送信を非表示にするために、ハンドラーは自分で送信を行うことができるため、送信スレッドからすぐにアクセスできます。

ただし、複数のメッセージをより多くのハンドラーに送信する必要がある場合は、ダブルディスパッチ(ビジター)を使用できます。

于 2012-04-12T07:05:44.193 に答える