インターフェースを考える(10)ISpecificationのテスト
この記事では以下の記事で実装したクラスをテストしてみて、クライアントコードの具合を考えてみます。
記事の順序やらテストのタイミングがぐちゃぐちゃですが、チラシの裏ブログなのでご容赦を・・・
nomoredeathmarch.hatenablog.com
まず、最初のテストコードは以下の通り
using Nmdm.Specifications; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Nmdm.SpecificationsTests { [TestClass] public class SpecificationsTest { [TestMethod] public void TestMethod() { var satisfiedSpec = new SpecificationIsSatisfied<object>(); Assert.IsTrue(satisfiedSpec.IsSatisfiedBy(new object())); var notSatisfiedSpec = new SpecificationIsNotSatisfied<object>(); Assert.IsFalse(notSatisfiedSpec.IsSatisfiedBy(new object())); } } }
固定でtrueやfalseを返すSpecificationクラスの動作を確認。
型パラメータはなんでも良いのでobjectクラスを指定する。
で、続いてAndSpecificationの動作確認、まずは拡張メソッドを使わないで書くと↓のような具合
// AndSpecificationをNewして書くと。 ISpecification<object> spec1 = new AndSpecification<object>(satisfiedSpec, satisfiedSpec); Assert.IsTrue(spec1.IsSatisfiedBy(new object())); ISpecification<object> spec2 = new AndSpecification<object>(satisfiedSpec, notSatisfiedSpec); Assert.IsTrue(spec1.IsSatisfiedBy(new object()));
これだとAndやOrが続いた時にnew AndSpecification(new AndSpecification(…))とネストしてってわかりにくい。
拡張メソッドを使って書くと。
// AndSpecificationを拡張メソッドで書く Assert.IsTrue(satisfiedSpec.And(satisfiedSpec).IsSatisfiedBy(new object())); Assert.IsFalse(satisfiedSpec.And(notSatisfiedSpec).IsSatisfiedBy(new object()));
という具合に数珠つなぎに書けると…
うーん、
これってITaskやIAction等、汎用的なインターフェースと拡張メソッドを駆使すれば、
個々のドメインモデルの実装を省コード化出来るような気がする・・・
ドメインモデルはデータ構造を提供し、個々の処理やチェック処理は小さいIActionやISpecificationを組み合わせて実現すると・・・
ただそれをやってしまうと、ドメインモデルがただの入れ物クラスになってしまうんだよなぁ・・・
良し悪しはともかく、引き続きインターフェースをこねくりまわすと良い考えが浮かぶかもしれない・・・
インターフェースを考える(9)インターフェースの組み合わせ
前回ISpecificationでAndやOrといった論理演算を拡張メソッドで簡単に記述出来るようにしましたが、
思うにこれって他の汎用的なインターフェースにすべて適用出来ますね。
以下前回の記事
nomoredeathmarch.hatenablog.com
ただ適用する際の注意点としては
・拡張メソッド内で具体的な処理(AndやOrといった汎用性の高いものは別として)に依存させない。
・拡張メソッドのシグネチャのいずれかに、自身が提供する型が含まれる。
今回はこれを引数、戻り値の無い、ITaskに適用してみます。
まずITaskの復習
namespace Nmdm.Tasks { public interface ITask { void Do(); } }
うん、シンプル、なにかを受け取らず、なにも返さず、ただDoメソッドの呼び出しをトリガーになんらかの処理を実行する。
このITaskを連続で実行するためのクラスとして用意したのがCompositeTask、
前回のAndSpecificationと同様に手直ししたものが以下の通り。
namespace Nmdm.Tasks { public sealed class CompositeTask:ITask { public CompositeTask(ITask one,ITask other) { this.One = one ?? new NullTask(); this.Other = other ?? new NullTask(); } private ITask One { get; } private ITask Other { get; } public void Do() { this.One.Do(); this.Other.Do(); } } }
これを毎回New CompositeTaskを呼ばなくて良いように拡張メソッドを実装する。
namespace Nmdm.Tasks { public static class CompositeTaskExtension { public static ITask Add(this ITask one,ITask other) { return new CompositeTask(one, other); } } }
これでクライアント側はITaskを実装したすべてのクラスについて(ITask).Add(ITask).Do()という具合に記述出来るようになると。
インターフェースを考える(8)判定処理と拡張メソッド
ISpecificationの組み合わせ方について
これまで以下のような実装をしていました。
using System.Linq; using System.Collections.Generic; namespace Nmdm.Specifications { public sealed class AndSpecification<T> : ISpecification<T> { public AndSpecification(IEnumerable<ISpecification<T>> specs) { this.Specifications = specs; } private IEnumerable<ISpecification<T>> Specifications { get; } public bool IsSatisfiedBy(T obj) { if (obj == null) return false; if (this.Specifications == null) return false; if (this.Specifications.Count() <= 0) return false; foreach (var specification in this.Specifications) { if (!specification.IsSatisfiedBy(obj)) return false; } return true; } } }
が、この実装だと気になる点がいくつかある。
・処理が妙にくどい、複雑
・クライアントコードにList
・DDDのSpecificationパターンのように流れるような記述(A And B みたいな)が出来ず可読性が損なわれる。
かといってDDDのようにクラスを継承させる形はとりたくない。
すこし手直ししてみる。
まずはこのクラス自体、コンストラクタでIEnumerableを受け取らず、シンプルに二つのISpecificationの実装クラスを受け取るようにする。
namespace Nmdm.Specifications { public sealed class AndSpecification<T> : ISpecification<T> { public AndSpecification(ISpecification<T> one,ISpecification<T> other) { this.One = one ?? new SpecificationIsNotSatisfied<T>(); this.Other = other ?? new SpecificationIsNotSatisfied<T>(); } private ISpecification<T> One { get; } private ISpecification<T> Other { get; } public bool IsSatisfiedBy(T obj) { return this.One.IsSatisfiedBy(obj) && this.Other.IsSatisfiedBy(obj); } } }
コンストラクタ内ではIsSatisfiedByメソッド内でのnullチェックを排除するためnullが渡されたらSpecificationIsNotSatisfied
実装は以下の通り。
namespace Nmdm.Specifications { public sealed class SpecificationIsNotSatisfied<T> : ISpecification<T> { public bool IsSatisfiedBy(T obj) { return false; } } }
OrSpecificationクラスも同様に手直し
namespace Nmdm.Specifications { public sealed class OrSpecification<T> : ISpecification<T> { public OrSpecification(ISpecification<T> one, ISpecification<T> other) { this.One = one ?? new SpecificationIsNotSatisfied<T>(); this.Other = other ?? new SpecificationIsNotSatisfied<T>(); } private ISpecification<T> One { get; } private ISpecification<T> Other { get; } public bool IsSatisfiedBy(T obj) { return this.One.IsSatisfiedBy(obj) || this.Other.IsSatisfiedBy(obj); } } }
以上でAnd、Orの論理演算を実現するクラスは出来たけども、
じゃあこれを使う時に毎回New AndSpecification
ただのAnd演算をするだけなのに毎回20文字以上ソースコードが増えてしまう。
そこでISpecificationインターフェースの拡張メソッドを用意する。
namespace Nmdm.Specifications { public static class AndSpecificationExtension { public static ISpecification<T> And<T>(this ISpecification<T> one, ISpecification<T> other) { return new AndSpecification<T>(one, other); } } }
namespace Nmdm.Specifications { public static class OrSpecificationExtension { public static ISpecification<T> Or<T>(this ISpecification<T> one, ISpecification<T> other) { return new OrSpecification<T>(one, other); } } }
これで毎回New AndSpecificationとかNew OrSpecificationという記述をしなくても、
ISpecificationを実装したクラスであればAndまたはOrという記述でインスタンス同士をつなげることが出来、
冒頭で触れたList
ただ、拡張メソッドを用いるという解決策で考えなければいけないことがある。
・拡張メソッドはあくまでStaticメソッドのシンタックスシュガーであり、特定の実装クラスに依存することで処理の組み換えが出来なくなる。
・同じシグネチャで解決されるインスタンスメソッドがあればそちらが優先される。そのため、第三者に提供される型に対して拡張メソッドを追加するとライブラリの更新により意図しない動作に代わる可能性がある。
・安易に多用するとインテリセンスに毎回出てきてうっとおしい。
この実装に限って言うと、
・AndやOrといった論理演算が非常に汎用性が高い。
・ISpecification自体は自分で実装したインターフェースである。
・これらを除けば依存しているのはISpecificationインターフェースであり、実装に対する依存が無い。
ということになるので、これはこれでありなんじゃないかなぁ、と思う。
インターフェースを考える(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で痒いところに手が届いた感