web-dev-qa-db-ja.com

イベントハンドラーとインターフェイス

IDataIOと呼ばれるインターフェイスがあります。

public interface IDataIO
{
  event DataReceivedEvent DataReceived;
  //.....more events,methods and properties
}

このインターフェイスを実装する複数のクラス、つまりUdpIOTcpIOSerialIOもあります。

これで、異なる入力/出力ハードウェアを切り替えることができるIOクラスができました。このクラスの各インスタンスにはCurrentIODeviceプロパティがあり、これはSerialIOUdpIOまたはTcpIOのいずれかになります。このプロパティが割り当てられると、1つ以上のハンドラーをDataReceivedEventにアタッチして、受信データを受信したときにGUIに通知する必要があるほか、通知が必要な他のクラスを通知します。

public class IO
{
  IDataIO CurrentIODevice;

  public IO()
  {
    SerialIO serial = new SerialIO();
    TcpIO tcp = new TcpIO();
    UdpIO udp = new UdpIO();
    CurrentIODevice = serial;
  }
}

複数のIOManagerオブジェクトを保持するIOクラスもあります。

public class IOManager
{
  List<IO> Ports = new List<IO>();
  public IOManager()
  {
    Ports.Add(new IO());
    Ports.Add(new IO());
  }

  Ports[0].CurrentIODevice = serial;
  Ports[0].CurrentIODevice.DataReceivedHandler += MyGuiUpdate;
  Ports[0].CurrentIODevice.DataReceivedHandler += MyDataProcessing;
}

私の懸念(問題のatmではありません)は、実行時に異なるIDataIOインターフェイス間でどのように変更するかです。

実行時に次のステートメントを実行すると、どのような影響がありますか。

//i know this is illegal but just to demonstrate
IOManager.Ports[0].CurrentIODevice = tcp; 

イベントハンドラーは引き続き機能しますか(正しく機能しますか)?

CurrentIODeviceが割り当てられる前にイベントの割り当てを解除し、その後にハンドラーを再度割り当てる必要がありますか?これが事実である場合、私はこのアプローチがかなり厄介になるのを見ることができます、それで誰かがこの問題に対してより良いアプローチを持っているなら、私はすべて耳にしています:)

21
Simon

いいえ、ハンドラーは古いオブジェクトにアタッチされているため、機能しません。インターフェイスは、オブジェクトへのインターフェイスを提供します。それを一種のcontractと見なしますが、それ自体は別のオブジェクトではありません。

インターフェースの異なる実装を(実行時に)切り替える必要があり、すべてのハンドラーを機能させ続けるには、インターフェース自体に同じオブジェクト参照が必要です 戦略パターン (詳細またはもっと少なく)。

あなたの場合、例えば、IDataIOオブジェクトにDataIOインターフェースを実装できます。プロパティ(またはメソッド、その意図はより明確だと思います)を公開して、そのインターフェイスの異なる実装(シリアル、TCPなど)を切り替えます。それが唯一のものになります。イベントハンドラーをそのインターフェイスにアタッチする1つのオブジェクト(具体的な実装が変更されると、ハンドラーが削除されます)そのオブジェクトのユーザーは、使用している具体的な実装が何であれ、常にそれを見ることができます。

これは、この概念を説明する小さな例です。一般的なインターフェースはこれです:

interface IDataIO
{
    void Write(byte[] data);

    byte[] Read();

    event EventHandler DataReceived;
}

これはIDataIOの具体的な実装であり、他のクラスはこのクラスのみを直接使用します。

sealed class DataIO : IDataIO
{
    public void SetChannel(IDataIO concreteChannel)
    {
        if (_concreteChannel != null)
            _concreteChannel.DataReceived -= OnDataReceived;

        _concreteChannel = concreteChannel;
        _concreteChannel.DataReceived += OnDataReceived;
    }

    public void Write(byte[] data)
    {
        _concreteChannel.Write(data);
    }

    public byte[] Read()
    {
        return _concreteChannel.Read();
    }

    public event EventHandler DataReceived;

    private IDataIO _concreteChannel;

    private void OnDataReceived(object sender, EventArgs e)
    {
        EventHandler dataReceived = DataReceived;
        if (dataReceived != null)
            dataReceived(this, e);
    }
}

最後に、テスト用のコード:

class Test
{
    public Test()
    {
        _channel = new TcpIO();

        _channel.DataReceived += OnDataReceived;
    }

    public void SetChannel(IDataIO channel)
    {
        _channel.SetChannel(channel);

        // Nothing will change for this "user" of DataIO
        // but now the channel used for transport will be
        // the one defined here
    }

    private void OnDataReceived(object sender, EventArgs e)
    {
        // You can use this
        byte[] data = ((IDataIO)sender).Read();

        // Or this, the sender is always the concrete
        // implementation that abstracts the strategy in use
        data = _channel.Read();
    }

    private DataIO _channel;
}
16
Adriano Repetti

明らかに、あなたは 戦略パターン を考慮すべきです。最初にコードを投稿し、後で説明します。

public interface IDataIO
{
    event DataReceivedEvent DataReceived;

    //this the new added method that each IO type should implement.
    void SetStrategy();
}

public class SerialIO : IDataIO
{
    public void SetStrategy()
    {
        //put the code that related to the Serial IO.
        this.DataReceivedHandler += MyGuiUpdate;
        this.DataReceivedHandler += MyDataProcessing;
    }
}

public class TcpIO : IDataIO
{
    public void SetStrategy()
    {
        //put the code that related to the Tcp IO.
        //I will not implement it because it is a demo.
    }
}

public class UdpIO : IDataIO
{
    public void SetStrategy()
    {
        //put the code that related to the Udp IO.
        //I will not implement it because it is a demo.
    }
}

public class IO
{
    IDataIO port = new IDataIO();

    public void SetIOType(IDataIO ioType)
    {
        this.port = ioType;

        port.SetStrategy();
    }

}

public class IOManager
{
    List<IO> ports = new List<IO>();

    SerialIO serial = new SerialIO();
    TcpIO tcp = new TcpIO();

    ports[0].SetIOType(serial);
    ports[1].SetIOType(tcp);
}
  1. インターフェイスIDataIOは、すべてのIOタイプが実装する必要がある基本を定義します。

  2. IDataIOから派生したSerialIO、TcpIO、UdpIOクラスは、それぞれ独自のニーズを満たすためにメソッドSetStrategy()を実装します。

  3. IOクラスはフィールドを所有します(名前付きポート)はIDataIOタイプを参照します。このフィールドは、実行時に特定のIOタイプに設定することができます。 IOクラスで定義されているメソッドSetIOType()。このメソッドが呼び出されると、「ポート」フィールドがどのタイプを参照しているかがわかり、SetStrategy()メソッドを呼び出すと、実行されますIOクラスのいずれかでオーバーライドされたメソッド。

  4. IOManagerクラスはクライアントです。特定のIOタイプ、たとえばSerialIOが必要な場合、IOクラスを新規に作成し、SerialIOクラスインスタンスを渡すことによってSetIOType()メソッドを呼び出すだけです。 、およびSerialIOタイプに関連するすべてのロジックが自動的に設定されます。

私の説明があなたを助けることを願っています。

2
ugoa