No more Death March

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

インターフェースを考える(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について考えるのはこのくらいにしときます。
うーん、プログラムは小さくてシンプルな方が良いとは思うけど、やりすぎ感がプンプンしますねー。