読者です 読者をやめる 読者になる 読者になる

No more Death March

あるSEのチラシの裏 C# WPF

インターフェースを考える(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の方でも同じことは出来るのですが、
連続したチェック処理やチェック処理の後の処理を分岐の両方に渡してやる必要があり、
シンプルに考えて「だめだったら追加でこれをやっとく」っていう考え方で処理を組み立てれないのに違和感を覚えました。