No more Death March

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

インターフェースを考える(3)IPredicate

今回もC#実践開発手法から、IPredicateを題材にします。

まずはインターフェース

namespace Nmdm.Predicates
{
    public interface IPredicate
    {
        bool Test();
    }
}

ITaskインターフェース同様引数はありませんが、戻り値がbool型です。
戻り値をジェネリックにしてIFuncとしても良さそうですが、
条件判定という目的に特化している。と考えたら別のインターフェースにした方がシンプルかな。

ITaskの時と同じようにモックを作るのですが、bool型を返すのでtrueを返すモックとfalseを返すモックの2パターンを作ります。

まずは常にtrueを返すTruePredicateクラス

namespace Nmdm.Predicates
{
    public sealed class TruePredicate : IPredicate
    {
        public bool Test()
        {
            return true;
        }
    }
}

反対に常にfalseを返すFalsePredicateクラス

namespace Nmdm.Predicates
{
    public sealed class FalsePredicate : IPredicate
    {
        public bool Test()
        {
            return false;
        }
    }
}

IPredicateを組み立てるためのクラスですが、ここでは一旦置いておき、
ITaskとの連携を考えてみます。
まずはTrueかFalseか判定して処理を分岐させるクラスとしてTestedTaskを作ってみました。

C#実践開発手法の中で紹介されている分岐デコレータと同じですね。

using Nmdm.Tasks;

namespace Nmdm.Predicates
{
    public sealed class TestedTask : ITask
    {
        public TestedTask(IPredicate predicate,ITask trueTask,ITask falseTask)
        {
            this.Predicate = predicate;
            this.TrueTask = trueTask;
            this.FalseTask = falseTask;
        }

        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();
        }
    }
}

条件判定と処理を組み合わせて処理の分岐を表現しているわけですね。なるほど・・・

記事が長くなりそうなのでこのへんで一端切ります。

インターフェースを考える(2)ITaskインターフェース

C#実践開発手法で紹介されているもっとも単純なインターフェースITaskを作ってみます。

namespace Nmdm.Tasks
{
    public interface ITask
    {
        void Do();
    }
}

引数も無く、戻り値もなく、最もシンプルなインターフェースですね。

こういうインターフェースを作るとテストの時のモックが必要になるだろう。
というわけでITaskを実装したNullオブジェクト、NullTaskを作りました。

namespace Nmdm.Tasks
{
    public sealed class NullTask : ITask
    {
        public void Do()
        {
        }
    }
}


じゃあ今度はITaskを組み合わせて連続でDoメソッドを実行出来るクラスCompositeTaskクラスを作ってみます。

using System;
using System.Collections.Generic;

namespace Nmdm.Tasks
{
    public sealed class CompositeTask:ITask
    {
        public CompositeTask(IEnumerable<ITask> tasks)
        {
            if (tasks == null) throw new ArgumentNullException("tasks");
            this.TaskList.AddRange(tasks);
            this.TaskList.RemoveAll(x => x == null);
        }

        private List<ITask> TaskList { get; } = new List<ITask>();

        public void Do()
        {
            this.TaskList.ForEach(x => x.Do());
        }
    }
}

nullチェックのタイミングとかnullを無視する処理の書き方はともかく、
コンストラクタでIEnumerableを受け取ってDoメソッドですべてのタスクを実行するわけですね。

条件判定も何もないから、Compositeタスクさえあれば組み合わせるのには十分なような気がするけど、
書きながら思いついたのはTry-CatchやTry-Fillalyを仕込みたい時はどうするんだろう?という話

例えばDBへの書き込み処理なんかだとアプリケーション側で

            try
            {
                Hoge.BeginTransaction();
                // 永続化ロジックはここ
                Hoge.Commit();
            }
            finally
            {
                Hoge.RollBack();
            }

みたいな具合の処理を書きたくなる。
Begin⇒永続化⇒Commitの流れはCompositeTaskに分解したタスクを渡せば連続で実行出来るけどRollBackはそうはいかんですね。
トランザクション処理に特化したクラスを作るのであればITaskを実装したTransactionクラス的なのにITaskを実装した永続化ロジックを渡してやれば実現出来るでしょうけど、
あえてITaskだけに依存した方法で書くことを考えてみましょう。
まずはcatchを無視して、tryブロックとfinallyブロックに分けてITaskを実行するTryFinallyTaskを組んでみます。

using System;

namespace Nmdm.Tasks
{
    public sealed class TryFinallyTask : ITask
    {
        public TryFinallyTask(ITask tryTask,ITask finallyTask)
        {
            if (tryTask == null) throw new ArgumentNullException("tryTask");
            if (tryTask == null) throw new ArgumentNullException("finallyTask");
            this.TryTask = tryTask;
            this.FinallyTask = finallyTask;
        }
        private ITask TryTask { get; }
        private ITask FinallyTask { get; }
        public void Do()
        {
            try { this.TryTask.Do(); }
            finally { this.FinallyTask.Do(); }
        }
    }
}

というわけで実装してみました。
コンストラクタでtryブロックで実行する処理とfinallyブロックで実行する処理を受け取りプロパティに格納する。
Doメソッドでtry-finallyを使ってそれぞれのITaskを実行すると・・・

もう一つのtry-catchの方はどうなるだろう。
通常try-catchでやることと言えば例外をキャッチしてログを出力したり、
ファイルIOなんかの外部リソース周りの例外をキャッチして自作のクラスで利用者に通知を出したりかなぁ、
単にExceptionをキャッチするだけじゃなく具体的な例外をキャッチすることを考えるとこのインターフェースで表現出来る???

とりあえずITaskについて考えるのはこのくらいにしときます。
うーん、プログラムは小さくてシンプルな方が良いとは思うけど、やりすぎ感がプンプンしますねー。

インターフェースを考える(1)はじめに

オブジェクト指向プログラミングを勉強していると
実装ではなく抽象に依存する
というフレーズを目にすることがあります。

このカテゴリの記事では私自身の勉強のために、
とことんインターフェースを使ってみたらどうなるか、
実際にプログラミングしながら考察していこうかと思います。

c# リファクタリング 小さい方の値が欲しい。

二つのint型から小さい方の値がほしい時。

        public void Hoge(int one,int other)
        {
            int min;
            if (one < other)
            {
                min = one;
            }
            else
            {
                min = other;
            }
        }

じゃなくて。MathクラスのMinメソッドを使おう。

        public void Hoge(int one,int other)
        {
            int min;
            min = Math.Min(one, other);
        }

MSDNで見ればわかることですが、longやbyte等、大体の型のオーバーロードが用意されています。

Math.Min メソッド (System)

大きい方の値であればMath.Max

https://msdn.microsoft.com/ja-jp/library/system.math.max(v=vs.110).aspx

DateTime構造体にもこういうのないものかなぁと思ったらStackOverFlowでも似たような話がありました。

stackoverflow.com

残念な英語力なので雰囲気で提案されている解決方法を真似してみます。

まずは三項演算子

            DateTime min;
            min = one < other ? one : other;

Ticksプロパティ(long型)をMathクラスに渡す方法

            DateTime min;
            min = new DateTime(Math.Min(one.Ticks, other.Ticks));

Utilityクラス見たいなので再利用する。

    public static class DateTimeUtil
    {
        public static DateTime Min(DateTime one,DateTime other)
        {
            return new DateTime(Math.Min(one.Ticks, other.Ticks));
        }
        public static DateTime Max(DateTime one, DateTime other)
        {
            return new DateTime(Math.Max(one.Ticks, other.Ticks));
        }
    }

最後に挙げた安易にユーティリティクラスを作ることの是非は別として、
TicksをMathクラスに渡すのはへぇ~ってなりました。

C# 自作クラスの戻り値からnullを排除する。

自作クラスのプロパティやメソッドでなんらかの値を返すならnullは返さないようにしたい。
そのクラス自体のnullチェックを他のクラスがするのはわかるとして、
クラスが公開する「何か」についてはそのクラスが責任を持つべき。

string型のプロパティがあるならnullの代わりにstring.Emptyを返せばいいし。
他のクラスを返すならnullObjectパターンを使えばいい。
コレクションなら空のコレクションを返す。

C# リファクタリング コレクションを変換

コレクションの内容を一定のルールに従って違う型に変換したい場合

        public void Hoge()
        {
            var list = new List<object>();
            list.Add(new object());
            list.Add(new object());
            list.Add(new object());
            list.Add(new object());
            list.Add(new object());

            var list2 = new List<int>();
            foreach(var obj in list)
            {
                list2.Add(obj.GetHashCode());
            }
        }

foreachを取り除くには

        public void Hoge()
        {
            var list = new List<object>();
            list.Add(new object());
            list.Add(new object());
            list.Add(new object());
            list.Add(new object());
            list.Add(new object());

            var list2 = new List<int>();
            list2.AddRange(list.ConvertAll<int>(x => x.GetHashCode()));
        }

ConvertAllメソッドを使ってやる。

C# リファクタリング foreachで抽出

Listをぐるぐる回して条件に合致するものを別のインスタンスに入れている場合

        public void Hoge()
        {

            var list = new List<String>();
            list.Add("123");
            list.Add("1234");
            list.Add("12345");
            list.Add("123456");

            // 4文字以下のものをリストから抽出したい。
            var list2 = new List<string>();
            foreach(var str in list)
            {
                if (str.Length <= 4) list2.Add(str);
            }
        }

foreachをなるべく書かないようにする。

        public void Hoge()
        {

            var list = new List<String>();
            list.Add("123");
            list.Add("1234");
            list.Add("12345");
            list.Add("123456");

            // 4文字以下のものをリストから抽出したい。
            var list2 = new List<string>();
            list2 = list.FindAll(x => x.Length <= 4);
        }

さらに行数を減らすなら

        public void Hoge()
        {

            var list = new List<String>();
            list.Add("123");
            list.Add("1234");
            list.Add("12345");
            list.Add("123456");

            // 4文字以下のものをリストから抽出したい。
            var list2 = new List<string>(list.FindAll(x => x.Length <= 4));
        }

コンストラクタでそのまま渡してしまう。
だた括弧がネストしすぎると読みにくいし、デバッグ時にステップ実行しにくいという問題もある。
個人的にはステップ実行で動作確認するくらいならテストコード書けば良いと思いますが。

FindAllメソッドをRemoveメソッドに変えた場合

        public void Hoge()
        {

            var list = new List<String>();
            list.Add("123");
            list.Add("1234");
            list.Add("12345");
            list.Add("123456");

            // 4文字以下のものをリストから抽出したい。
            list.RemoveAll(x => x.Length >= 5);
        }

FindAllメソッドとの違いとしては
・FindAllメソッドは戻り値がList
・RemoveAllメソッドは実行したコレクション自体を編集し、戻り値がint