インターフェースを考える(7)IPredicate
IPredicateの拡張について書きます。
前回実装した記事は↓から
nomoredeathmarch.hatenablog.com
まずは前回のTestedTaskを少し手直し。
using System; using Nmdm.Tasks; namespace Nmdm.Predicates { public sealed class TestedTask : ITask { public TestedTask(IPredicate predicate,ITask trueTask,ITask falseTask) { this.Predicate = predicate ?? new FalsePredicate(); this.TrueTask = trueTask ?? new NullTask(); this.FalseTask = falseTask ?? new NullTask(); } private IPredicate Predicate { get; } private ITask TrueTask { get; } private ITask FalseTask { get; } public void Do() { if(this.Predicate.Test()) { this.TrueTask.Do(); return; } this.FalseTask.Do(); } } }
コンストラクタで引数が指定されなかったnullオブジェクトを設定する。
しかしnullオブジェクトパターンでこういう使い方はどうなのだろう?
例えばPredicateがなんらかの事情でnullが入ってきたとして、
その時点で例外が投げられた方が不具合に気づきやすい作りと言えるんじゃないだろうか・・・・?
もしかしたら例外は起きないけど論理的な不具合を誘発してしまう可能性も考えられる。
ただ、そんなことを一つ一つ考慮してたら切りがないのかなぁ、、、
「使い方を間違えたやつが悪い、クラスは正常だ!!!」みたいな話をしたいわけじゃないけど、
クラスのスタンスというか責務が明確になっていれば良いとも思える。
nullだったら既定の動きはこうです。と。
良くないとはわかっていますが、こういうこまかーいところで悩んで進みが悪くのは良くないですね。
さて、先ほどのTestedTaskクラスと似ているようで似ていないTaskAttachedPredicateクラスを作りました。
using Nmdm.Tasks; namespace Nmdm.Predicates { public sealed class TaskAttachedPredicate : IPredicate { public TaskAttachedPredicate(IPredicate predicate, ITask trueTask, ITask falseTask) { this.Predicate = predicate ?? new FalsePredicate(); this.TrueTask = trueTask ?? new NullTask(); this.FalseTask = falseTask ?? new NullTask(); } private IPredicate Predicate { get; } private ITask TrueTask { get; } private ITask FalseTask { get; } public bool Test() { if (this.Predicate.Test()) { this.TrueTask.Do(); return true; } this.FalseTask.Do(); return false; } } }
何が違うのかというと、TestedTaskはITaskの実装でTaskAttachedPredicateはIPredicateの実装ということです。
どちらもIPredicateの検証結果がTrueだったらTrueTaskを、FalseだったらFalseTaskを実行する、というところまでは同じですが、
TaskAttachedPredicateではそのあとにbool値を返します。
前者は「判定結果に応じて指定されたタスクに分岐する」という仕事と行い。
後者は「判定結果を返し、その結果に応じて添付されたTaskを実行する」という仕事をします。
前者の方は割と使い道が想像しやすいと思います。
なんらかのチェック処理をして、Trueならこっちの処理、Falseだったらこっちの処理に分岐する。というありがちなものです。
じゃあ後者は何かっていうと、
例えば、複数のチェック処理を連続して行い、途中失敗したらログに出力する。とか、
チェック処理を連続して行って、失敗した検証内容をまとめて表示する。といった使い道を想像しています。
TestedTaskの方でも同じことは出来るのですが、
連続したチェック処理やチェック処理の後の処理を分岐の両方に渡してやる必要があり、
シンプルに考えて「だめだったら追加でこれをやっとく」っていう考え方で処理を組み立てれないのに違和感を覚えました。
WPF パフォーマンス分析ツールを入れてみる。
WPFのチューニングが全然捗らないので分析ツールを導入してみる。
バインディングはほんと便利だと思うけど、こういうツールなしにチューニングするのはきついですね・・・
とりあえずこの記事で目についたWPF Performance Suiteというのを入れてみよう。
Windows SDKというやつの中に入っているのか・・・
そもそもWindows SDKってなに???
というわけでWikiで確認
ええと、Microsoftが公開しているWindowsのアプリ開発者向けの補助ツール集って位置づけでいいのかな?
Visual Studioインストール時に選択すれば一緒に入るみたいだけど自分のPCは入れてない様子だったので
Microsoftのホームページからダウンロードしてインストール。
既定のままインストールすると「C:\Program Files (x86)\Windows Kits」というフォルダが出来るので、
WPF Performance Suiteの実行ファイル「WpfPerf.exe」を探す。
、、、
、、、
うん、見つからない。
とりあえずMSDNを読んでみる
記事自体が古い・・・
ぐぐってぐぐっていきついた先が↓
あれ・・・WpfPerfってもうWindows Sdkに入っていない・・??
というか・・・
Visual Studio 2015は診断ツールがその変わりってことかな・・・
なんかすごい時間を無駄にしてしまった・・・
なんか2015の診断ツールはCPUとかメモリとか勝手にリアルタイムで表示されて便利だなーくらいの認識だったのですが、
一応WPFアプリケーションのパフォーマンス診断にも使えるみたいですね・・・・
ええと・・
簡単にまとめると
・メニューの「デバッグ」で診断ツール表示させて「CPU使用率」タブをクリック
・「CPUプロファイリング」をクリックすると情報収集のオンオフを切り替えれる
・アプリケーションをデバック起動する。
・デバッグ実行を一時停止すると「CPU使用率」タブ内にメソッド毎のCPU使用時間やCPU使用率が出てくるよ。
最初からこれ見れば良かった・・・????
WPF 実行環境によっては画面がぼやける・・・
WPFではまったのでメモします。
開発用マシンとは異なるOS、ハードで実行すると画面がぼやけてしまった。
アンチエイリアス処理の結果かなぁとは思ったけどぼやけかたが余りに酷い。
なにかないかと試してみたのがこちら↓
FrameworkElement.UseLayoutRounding プロパティ (System.Windows)
UIのルート要素でtrue(既定はfalse)を指定すると実用に耐えるくらい改善されました。
二つの環境を見比べるとアスペクト比が若干崩れていますがとりあえず我慢・・・
WPFってはまりどころがたくさんあって心が折れそう・・・
C# Linq覚えなきゃなぁ
今更ですがIEnumerable.Countで痒いところに手が届いた感
インターフェースを考える(6)IEvent
以前書いたドメインイベントの実装について、
インターフェースの側から考える。
nomoredeathmarch.hatenablog.com
nomoredeathmarch.hatenablog.com
まず作成したインターフェースについて復習
まず、イベントを表すクラスのインターフェースIEvent
namespace Nmdm.Events { public interface IEvent { } }
あくまで印をつけるためのマーカー
続いてそのインターフェースを受け取るIHandler
namespace Nmdm.Events { public interface IHandler { void Handle(IEvent value); bool CanHandle(IEvent value); } }
こちらはパブリッシャーからの受信機構となるメソッド。
でここからが今回の本題、
Publisherクラスをstaticで実装したけども、これもインターフェースで抽象化してみる。
以下がインターフェースを抜き出したIPublisher
namespace Nmdm.Events { public interface IPublisher { void Publish(IEvent e); void Subscribe(IHandler handler); void Unsubscribe(IHandler handler); } }
staticじゃなくなったことで単体テストがしやすくなったけど、
半面、どこからでもイベント通知、購読することが出来なくなったわけだ。
ただ、staticクラスのように振舞わせたければシングルトンなりこのクラスをキャッシュするためだけのスタティッククラスを作れば良いわけだし、
それであれば通常のクラスとして設計すれば良いとも思える。
これを以前作ったPublisherクラスに実装させる。
using System; using System.Collections.Generic; using System.Threading; namespace Nmdm.Events { public class Publisher:IPublisher { private static Mutex Mutex { get; } = new Mutex(); private static List<WeakReference<IHandler>> Subscribers { get; } = new List<WeakReference<IHandler>>(); public void Publish(IEvent e) { try { Mutex.WaitOne(); if (e == null) throw new ArgumentNullException("e"); var list = new List<IHandler>(); list.AddRange(Subscribers.ConvertAll<IHandler>(x => ToStrong(x))); list.RemoveAll(x => x == null || !x.CanHandle(e)); list.ForEach(x => x.Handle(e)); Subscribers.RemoveAll(x => ToStrong(x) == null); } finally { Mutex.ReleaseMutex(); } } private static IHandler ToStrong(WeakReference<IHandler> obj) { IHandler ret; obj.TryGetTarget(out ret); return ret; } public void Subscribe(IHandler subscriber) { try { Mutex.WaitOne(); if (subscriber == null) throw new ArgumentNullException("subscriber"); if (Subscribers.Contains(new WeakReference<IHandler>(subscriber))) return; Subscribers.Add(new WeakReference<IHandler>(subscriber)); } finally { Mutex.ReleaseMutex(); } } public void Unsubscribe(IHandler subscriber) { try { Mutex.WaitOne(); if (subscriber == null) throw new ArgumentNullException("subscriber"); Subscribers.RemoveAll(x => subscriber.Equals(ToStrong(x))); } finally { Mutex.ReleaseMutex(); } } } }
インターフェースを考える(5)IAction その2
前回に続きIActionについて考える。
IPredicateやITaskと同じように組み合わせ用にCompositeActionクラスを作る。
using System; using System.Collections.Generic; namespace Nmdm.Actions { public sealed class CompositeAction<TContext> : IAction<TContext> { public CompositeAction(IEnumerable<IAction<TContext>> actions) { if (actions == null) throw new ArgumentNullException("actions"); this.Actions = actions; } private IEnumerable<IAction<TContext>> Actions { get; } public void Do(TContext context) { foreach(var obj in this.Actions) { obj.Do(context); } } } }
IPredicateの時も気になったけど、このクラスを使う時にList型の変数を使ってIActionをコレクションに入れてからコンストラクタを呼び出すのかって考えるといまいちな感じがする。
かといって、クラスは基本不変にしたい・・・・
ということで↓へ改造
using System; using System.Collections.Generic; namespace Nmdm.Actions { public sealed class CompositeAction<TContext> : IAction<TContext> { public CompositeAction() { this.Actions = new IAction<TContext>[] { }; } private CompositeAction(IEnumerable<IAction<TContext>> actions) { if (actions == null) throw new ArgumentNullException("actions"); this.Actions = actions; } private IEnumerable<IAction<TContext>> Actions { get; } public CompositeAction<TContext> Add(IAction<TContext> action) { if (action == null) throw new ArgumentNullException("actions"); var actions = new List<IAction<TContext>>(this.Actions); actions.Add(action); return new CompositeAction<TContext>(actions); } public void Do(TContext context) { foreach(var obj in this.Actions) { obj.Do(context); } } } }
コンストラクタをプライベートにしてAddメソッドを追加、メソッドの戻り値でprivateなコンストラクタで生成した結果を返して不変かつ流暢なインターフェースに。
このクラス自体がIActionを実装しているからAddRangeメソッドはいらないと思う。
ついでにIActionの受け口をAddメソッドだけにしたから個々の要素に対してもnullチェックが出来ている。
元のソースと比べると複雑さが増しているけどIAction以外への依存はなく変更の可能性が低いから良いかと思います。
==2017.03.14追記===
やっぱり改造やめ、読みにくいから、というならまだしも、
書きにくいから、という理由でメソッドを増やすのに少し抵抗を感じる。
インターフェースを考える(4)IAction
C#実践開発手法よりIActionインターフェースです。
namespace Nmdm.Actions { public interface IAction<TContext> { void Do(TContext context); } }
単一の引数を受け取る戻り値の無いインターフェースで、
引数は型パラメータで指定出来る・・・と
使いどころはどこなんだろう。
こないだ書いたITaskに引数が一つ付いただけなわけですが・・・
nomoredeathmarch.hatenablog.com
手段と目的がごっちゃになってるけど何かスマートな使い方は出来ないものか・・・
たとえば、ドメインイベントのハンドラにイベントの具体的な処理を着脱できるようにするとか?
まずIHandlerインターフェス(前の記事ではISubscriberだったけどC#実践開発手法の表記に合わせた。)
namespace Nmdm.Events { public interface IHandler { void Handle(IEvent value); bool CanHandle(IEvent value); } }
こいつの実装クラスとして、着脱可能なアクションをコンストラクタで受け取るHandlerクラス
using System; using Nmdm.Actions; namespace Nmdm.Events { public sealed class Handler<T> : IHandler where T:IEvent { public Handler(IAction<T> action) { if (action == null) throw new ArgumentNullException("action"); this.Action = action; } private IAction<T> Action { get; } public bool CanHandle(IEvent value) { if (value == null) return false; return value is T; } public void Handle(IEvent value) { if (!this.CanHandle(value)) return; this.Action.Do((T)value); } } }
コンストラクタでIAction
あとはHandlerクラスがドメインイベントの通知を受け取る度にIAction
ドメインイベントとは違うけど、MVVMのメッセージ通知とかに使えるかな?
イベントでメッセージの内容を受け取って、アクション側でメッセージ表示の具体的な方法を実装する。
最初は標準のメッセージボックスを表示してたけど、そのうちカスタマイズしたメッセージボックスにすげ変えると・・・
パブリッシャに予約してなければ単体テストもそのままで良さそう。
イベント通知される場面なら↓みたいなIActionのモックと入れ替えても良いのか。
namespace Nmdm.Actions { public sealed class ActionMock<T> : IAction<T> { public void Do(T context) { } } }
「イベントの通知を受け取る」っていう仕事と「イベントの情報を読み込んで処理を実行する。」っていう仕事を分離して実装出来ますね。