2022年10月26日水曜日

複数の型のIObservableを効率よく提供するには

仕事でWebSocketクライアントの実装を検討していたときのことです。
WebSocketは文字列とバイナリの2種類のデータを受信できるのですが、これをオブザーバーパターンに適用しようとして困ったことになりました。

public class OldWebSockerClient : IObservable<string>, IObservable<byte[]>
{
private event Action completed = () => { };
private event Action<Exception> error = _ => { };
private event Action<string> nextString = _ => { };
private event Action<byte[]> nextBytes = _ => { };
public IDisposable Subscribe(IObserver<string> observer)
{
completed += observer.OnCompleted;
error += observer.OnError;
nextString += observer.OnNext;
return new Diposable(() =>
{
completed -= observer.OnCompleted;
error -= observer.OnError;
nextString -= observer.OnNext;
});
}
public IDisposable Subscribe(IObserver<byte[]> observer)
{
completed += observer.OnCompleted;
error += observer.OnError;
nextBytes += observer.OnNext;
return new Diposable(() =>
{
completed -= observer.OnCompleted;
error -= observer.OnError;
nextBytes -= observer.OnNext;
});
}
}
public class Diposable : IDisposable
{
private readonly Action _disposeCallback;
public Diposable(Action disposeCallback)
{
_disposeCallback = disposeCallback;
}
public void Dispose()
{
_disposeCallback();
}
}

どちらか片方だけを受信するならこれで良いのですが、両方まとめて受信するクラスを作ると、エラーを完了の通知が2重に通知されて使いにくいものになってしまいます。
しかし文字列とバイナリを同梱するようなクラスを作って通知を1種類にするのも、受信側でわざわざif文を追加させるようなもので非効率ではないでしょうか。
ということでちょっと考えてみました。

public interface IWebSocketListener : IObserver<string>, IObserver<byte[]>
{
new void OnCompleted();
new void OnError(Exception error);
}
public class NewWebSockerClient : IObservable<string>, IObservable<byte[]>
{
// 省略
public IDisposable Subscribe(IWebSocketListener webSocketListener)
{
completed += webSocketListener.OnCompleted;
error += webSocketListener.OnError;
nextString += webSocketListener.OnNext;
nextBytes += webSocketListener.OnNext;
return new Diposable(() =>
{
completed -= webSocketListener.OnCompleted;
error -= webSocketListener.OnError;
nextString -= webSocketListener.OnNext;
nextBytes -= webSocketListener.OnNext;
});
}
}

2種類のIObserverを合体させたインターフェースを用意し、それをオーバーロードしたSubscribeメソッドで受信させるのです。
これなら片方だけ使う場合でも、両方使う場合でも対応させることができます。
両方使う場合のSubscribeメソッドがIObservableに合致したものではありませんが、IWebSocketListenerと対応する感じでインターフェースを作ればそれっぽくなるのではないでしょうか。

…また自分しか得しない記事を書いてしまいました。
次こそは他の方にも役立つ可能性のある技術記事を書きたいものです。

0 件のコメント:

コメントを投稿