ビジネスロジックに影響を与えない進捗表示 - C#,Java

#進捗表示とビジネスロジックを分離する
重い処理に対して、どのくらいまで処理を行ったかを知りたい場合がある。ある人にバッチでコンソールに進捗表示をさせる処理の作成を頼んだ結果、サンプルコードのようなコードを作成していた。話を聞いてみると、オブザーバーパターンで実装したとのこと。実装はともかく考え方は参考になったので、サンプルコードで考察してみる。

##サンプルコード
Somethingクラス内に進捗表示をするため、progressCounterを渡している。
進捗表示:ProgressCounter(ConsoleTestプロジェクト）
ビジネスロジック:Something.Do(Qiitaプロジェクト)
※ConsoleTestからQiitaプロジェクトを参照している。

```csharp
        void Main()
        {
            var something = new Something();
            var progressCounter = new ProgressCounter();
            something.Do(progressCounter);
        }

    public class Something
    {
        public void Do(IProgressCounter progressCounter)
        {
            int maxCount = 10;
            for(int i = 0; i < maxCount; i++) 
            {
                // なんらかの処理

                // 進捗表示
                if (progressCounter != null)
                {
                    progressCounter.Inc();
                    progressCounter.Display();
                }
            }
        }
    }

    class ProgressCounter : IProgressCounter
    {
        private int _count = 0;

        public void Display()
        {
            Console.WriteLine(_count);
        }

        public void Inc()
        {
            _count++;
        }
    }

    public interface IProgressCounter
    {
        void Display();

        void Inc();
    }

```

##考察
このコードはビジネスロジック側に暗黙的に影響を与えている。
ProgressCounerが処理件数を持っており、Something.Doと処理件数が一致していなければいけない。一ループ、一件の前提が必要でありcontiuneを入れてスキップをいれただけで件数が一致しなくなる。
処理件数はDoから渡すだけでよく、２重持ちをする必要はない。

一方、インターフェースにしたことで、進捗表示はコンソール以外でも対応可能である。Windows FormのProgressBarを使用しても、ビジネスロジックには何の影響もない。この点は評価に値する。

IProgressCounterを関数の引数にしているが、ビジネスロジックで使用する引数のみにするべきである。依存性の注入のほうが良い。

##リファクタリング後のコード
明示的な開始と終了処理が必要だと思うので、Start,End関数を追加している。また、NullObjectパターンを前提にしているので、Nullチェックはしない。
コーディングの都合上、改良後のコードは別フォルダに配置しているので、Modfy.ProgressCounterやQiita.CounterOK.Somethingという名前空間にしている。
尚、マルチスレッド化は考えず、元のコードを活かすようにした。

```csharp

        void ModifiedMain()
        {
            // 改良後
            var progressCounter = new Modify.ProgressCounter();
            var something = new Qiita.CounterOK.Something(progressCounter);
            something.Do();
        }

    public class Something
    {
        private readonly IProgressCounter _progressCounter = null;

        public Something(IProgressCounter progressCounter)
        {
            _progressCounter = progressCounter;
        }

        public void Do()
        {
            int maxCount = 10;
            // nullの代わりにNullObjectを渡してくる前提のため、nullチェックはしない。
            _progressCounter.MaxCount = maxCount;
            _progressCounter.Start();

            for (int i = 0; i < maxCount; i++) 
            {
                // なんらかの処理

                // 進捗表示
                _progressCounter.Update(i + 1);
            }

            _progressCounter.End();
        }
    }

    public interface IProgressCounter
    {
        int MaxCount { get; set; }
        void Start();
        void Update(int count);
        void End();
    }

    public class ProgressCounter : IProgressCounter
    {
        public int MaxCount { get; set; }
        public void Start()
        {
            System.Console.WriteLine("処理開始");
        }

        public void Update(int count)
        {
            Console.WriteLine(count + "/" + MaxCount);
        }

        public void End()
        {
            System.Console.WriteLine("処理完了");
        }
    }
```

##まとめ
リファクタリングすることで、改良前よりはビジネスロジックへの影響は少なくなった。ビジネスロジック内に進捗表示処理が残ってしまうのは仕方ない。
本来は自由につけはずしができるようなクラス設計が望ましい。
つけはずしであれば、デコレーターでDecorator.DoをSomething.Doに被せることは可能だが、シグネチャを合わせないといけないので現実的ではない。

##サンプルコード Java 2017/3/24 追記
```Java
    void main() {
        Something something = new Something();
        ProgressCounter progressCounter = new ProgressCounter();
        something.Do(progressCounter);
    }

public class Something {

    public void Do(IProgressCounter progressCounter) {
        int maxCount = 10;
        for (int i = 0; i < maxCount; i++) {
            // なんらかの処理

            // 進捗表示
            if (progressCounter != null) {
                progressCounter.inc();
                progressCounter.display();
            }
        }
    }
}

public interface IProgressCounter {
    void display();

    void inc();
}

public class ProgressCounter implements IProgressCounter {

    private int count = 0;

    @Override
    public void display() {
        System.out.println(count);
    }

    @Override
    public void inc() {
        count++;
    }

}

```

##リファクタリング後 Java
```java
    void main() {
        ProgressCounter progressCounter = new ProgressCounter();
        Something something = new Something(new ProgressCounter());
        something.Do();
    }

public class Something {

    private final IProgressCounter progressCounter;

    public Something(IProgressCounter progressCounter) {
        this.progressCounter = progressCounter;
    }

    public void Do() {
        int maxCount = 10;
        // nullの代わりにNullObjectを渡してくる前提のため、nullチェックはしない。
        progressCounter.setMaxCount(maxCount);
        progressCounter.start();

        for (int i = 0; i < maxCount; i++) {
            // なんらかの処理

            // 進捗表示
            progressCounter.update(i + 1);
        }

        progressCounter.end();
    }
}

public interface IProgressCounter {
    int getMaxCount();

    void setMaxCount(int maxCount);

    void start();

    void update(int count);

    void end();
}

public class ProgressCounter implements IProgressCounter {
    private int maxCount = 0;

    @Override
    public int getMaxCount() {
        return maxCount;
    }

    @Override
    public void setMaxCount(int maxCount) {
        this.maxCount = maxCount;
    }

    @Override
    public void start() {
        System.out.println("処理開始");
    }

    @Override
    public void update(int count) {
        System.out.println(count + "/" + maxCount);
    }

    @Override
    public void end() {
        System.out.println("処理完了");
    }

}

```

[目次](http://qiita.com/csharpisthebest/items/57f27a33085f01211166)