イベントブローカー、パート1



一般に、複雑で動的なシステムでは、変化するコンポーネントの構成に対応することは非常に困難であり、何らかの方法で(これは21世紀です!)特殊​​なコンテナーを使用してコンポーネントを作成する問題を解消した場合、まだ完全に相互作用しません対象となります。 たとえば、.Net(およびおそらく他の言語でも)のイベントに対する反応は、非常に軽微なレベルで行われました。 そして当然、このタスクにはあらゆる種類のインフラストラクチャソリューションが登場します。


挑戦する


ほとんどの開発者、特にアウトソーシングに参加する開発者にとって、イベント(私はほとんどイベントを意味します)への反応はまったく存在しないか、恐ろしい力で存在しますが、非常に予測可能な方法で存在します。 たとえば、誰かがボタンをクリックしてbutton_Clickイベントが発生button_Click

イベントと密接に連携する必要があるとすぐに、ゲームはすぐに停止します。 サッカー選手とコーチを連れて行きましょう。

public class FootballPlayer<br/>
{<br/>
public string Name { get;set; }<br/>
public void Score()<br/>
{<br/>
var e = Scored;<br/>
if (e != null ) e( this , new EventArgs());<br/>
}<br/>
public event EventHandler Scored; <br/>
}<br/>
public class FootballCoach<br/>
{<br/>
public void Watch(FootballPlayer p)<br/>
{<br/>
p.Scored += (_, __) =><br/>
{<br/>
Console.WriteLine( "I'm happy that {0} scored" , p.Name);<br/>
};<br/>
}<br/>
}<br/>

この場合、サブスクリプションと通知はうまく機能します。

var p = new FootballPlayer { Name = "Arshavin" };<br/>
var c = new FootballCoach();<br/>
c.Watch(p);<br/>
p.Score(); // coach saw it!

ここでの冗談は、オブジェクトをコピーすると( MemberwiseClone()またはBinaryFormatterを使用したディープコピー)、このオブジェクトのすべてのサブスクリプションが失われることです。

var p2 = c.Clone(); // deep copy :)
p.Score();<br/>

もちろん、サブスクリプションは手動で復元するか、イベントの代わりにデリゲートのセットだけを使用して開始するか、...で開始できます。 そこに、 Expression<T> 、そのようなものがあります。 しかし、これは問題の一部にすぎません。

問題の次の部分は、ある時点でオブジェクトにイベントを自動的にサブスクライブさせたいことです。 たとえば、選手がフィールドに入った-コーチが彼を追い始めます。 ちなみに、「サブスクライブ解除」でも同じことです。 すべてを誇張すると、次のようなものが得られます。

class FootballCoach<br/>
{<br/>
public FootballCoach(FootballPlayer[] players)<br/>
{<br/>
foreach ( var p in players) {<br/>
p.EntersField += new EventHandler(StartWatchingPlayer);<br/>
p.LeavesField += new EventHandler(StopWatchingPlayer);<br/>
}<br/>
}<br/>
}<br/>

そして、青にStartXxxまで続きます-各StartXxxでサブスクライブされ、各EndXxxサブスクライブ解除されます。 しかし、それだけではありません。

システムにそのようなオブジェクトが多数あると想像してください。 それらのすべてが他のすべてにメッセージを送信します。 +=で投稿すると、コードの残酷な一貫性と完全なテスト不能性が得られます(また、メッセージのテストは一般に困難です)。

そして最後に、設定の不備について一言述べる必要があります。 結局のところ、 送信者に関係なく 、特定の種類の通知を受け取りたい場合があります。 たとえば、ジャッジは、プレイヤーやコーチなど、誰に呪われているのかを気にしません。 (これは象徴的に私です。)そして、信じられないかもしれませんが、100個のバッチでイベントに反応するなど、あらゆる種類のトリッキーな変換を1時間に1回、満月の13日の金曜日のみに行いたい場合があります。 なぜ現在の状況はそれほど傾向がないのか。

パブサブ


平均的な開発者は、すぐに自分のイベントブローカーを作成したいと思っています。 そして、どうして、どうして。 シンプルでシンプルなクラスを取り、記述します。

public class EventBroker<br/>
{<br/>
private MultiDictionary< string , Delegate> subscriptions = <br/>
new MultiDictionary< string , Delegate>( true );<br/>
public void Publish<T>( string name, object sender, T args)<br/>
{<br/>
foreach ( var h in subscriptions[name])<br/>
h.DynamicInvoke(sender, args);<br/>
}<br/>
public void Subscribe<T>( string name, Delegate handler)<br/>
{<br/>
subscriptions.Add(name, handler);<br/>
}<br/>
}<br/>

スレッドセーフを再生できます( ReaderWriterLockSlimを本能的にReaderWriterLockSlim )など。 しかし、これの本質は変わらないでしょう。 イベントのサブスクリプションを置き換えることができるブローカーを獲得しました。 もちろん、ここではQoSを取得できず、イベント選択に関連するすべてのロジックをペンで記述する必要がありますが、既にいくつかの進歩があります。たとえば、 nameを分類子として含めることで、1つのクラスが複数のイベントのハンドラーに同時に署名できる状況を作成しました。

プレイヤーはもはやイベントを投稿しません。

public class FootballPlayer<br/>
{<br/>
private readonly EventBroker broker;<br/>
public string Name { get; set; }<br/>
public FootballPlayer(EventBroker broker)<br/>
{<br/>
this .broker = broker;<br/>
}<br/>
public void Injured()<br/>
{<br/>
broker.Publish( "LeavingField" , this , new PlayerInjuredEventArgs());<br/>
}<br/>
public void SentOff()<br/>
{<br/>
broker.Publish( "LeavingField" , this , new PlayerSentOffEventArgs());<br/>
}<br/>
}<br/>

トレーナーはブローカーを介してサインアップします:

public class FootballCoach<br/>
{<br/>
private readonly EventBroker broker;<br/>
public FootballCoach(EventBroker broker)<br/>
{<br/>
this .broker = broker;<br/>
}<br/>
public void Watch(FootballPlayer player)<br/>
{<br/>
broker.Subscribe<EventArgs>( "LeavingField" ,<br/>
new EventHandler(PlayerIsLeavingField));<br/>
}<br/>
public void PlayerIsLeavingField( object sender, EventArgs args)<br/>
{<br/>
Console.WriteLine( "Where are you going, {0}?" ,<br/>
(sender as FootballPlayer).Name);<br/>
}<br/>
}<br/>

ここでは、キヤノンによると、すべてがEventArgsから引数を継承するという事実に関連するポリモーフィズムを期待しています。 ここでは、厳密な型指定は重要ではありません。 いつでもキャストできます。 これがすべての外観です:

var uc = new UnityContainer();<br/>
uc.RegisterType<EventBroker>(<br/>
new ContainerControlledLifetimeManager());<br/>
var p = uc.Resolve<FootballPlayer>();<br/>
p.Name = "Arshavin" ;<br/>
var c = uc.Resolve<FootballCoach>();<br/>
<br/>
c.Watch(p);<br/>
<br/>
p.Injured();<br/>
p.SentOff();<br/>

驚くべきことは、実際にはイベントがメッセージングに置き換わったことです。 私のように、NServiceBusや他のシステムでたくさん遊んでいるなら、もちろんこれは完全に自然な変態のように思えます。

上記の例では、ブローカーはシングルトンとしてコンテナに登録されているため、プレーヤーとコーチの両方が同じコピーを受け取ります。 ここでは、 静的なシナリオでは、誰が何にサブスクライブしているのかがわかっているときに、サブスクリプションを宣言的に記述することができるという非常に微妙なヒントを実際に作成していません:

public class FootballCoach<br/>
{<br/>
[SubscribesTo( "PlayerIsLeaving" )]<br/>
public void PlayerIsLeavingField( object sender, EventArgs args)<br/>
{<br/>
Console.WriteLine( "Where are you going, {0}?" ,<br/>
(sender as FootballPlayer).Name);<br/>
}<br/>
}<br/>

この投稿の次の部分でこれがどのように行われるかを見ることができますが、待つことができない人のために、 ここをご覧になることをお勧めします。 情報はおそらく少し時代遅れですが、原理自体は明確だと思います。

中間結論


ここで与えられる解決策は、いくつかの理由で最終的なものではありません。 第一に、どういうわけか、コンポーネントとブローカーを明示的に接続することはコーシャーではありません。各クラスに転送し、これらのクラスで明示的に使用する必要があることがわかります。 次のパートで見るように、この問題は非常に簡単に解決できます。

2番目の問題は、既に説明したように見えますが、イベント処理の一部のロジックの記述が1つの基準(「分類子」のようなもので機能する文字列リテラル)によって制限されていることです。 特にLINQなどの強力なツールの存在に関連して、そのようなメソッドにロジックを制限するのは愚かです。 (ヒントは明確だと思います。)

Source: https://habr.com/ru/post/J97285/


All Articles