Project Euler Problem 10を例にトランザクションスクリプトとドメインモデル

トランザクションスクリプトドメインモデルの理解のために。しつこいくらいが丁度いいw

アルゴリズムおよびコードの大部分はhttp://d.hatena.ne.jp/pgf2/20080527/1211868834より拝借しています。ありがとうございます!

コードと概略

  • Transaction Scriptブロックでは、3つのprivate staticメソッドを組み合わせて使っています。
    • primes()で素数の配列を取得し、sum()で配列の合計値を取得しています。
  • Domain Modelブロックでは、内部クラスであるPrimesクラスを使っています。
    • primesオブジェクトのsum()から、素数の合計値を取得しています。
  • Problem 10では200万以下の素数の合計となってるけど、計算に時間がかかるので、試用向けに10万以下としています。
import java.util.*;

/**
 * Project Euler Problem 10
 */
public class Problem10 {

    public static void main(String[] args) {
        int max = 100000; 
        
        { //Transaction Script
            long start = System.currentTimeMillis();
            System.out.println(Problem10.sum(Problem10.primes(max)));
            long end = System.currentTimeMillis();
            System.out.println("time:"+ (end - start));
        }
        
        { //Domain Model
            long start = System.currentTimeMillis();
            System.out.println(new Primes(max).sum());
            long end = System.currentTimeMillis();
            System.out.println("time:"+ (end - start));
        }
    }
    
    private static Long[] primes(long maxval){
        Deque<Long> primes = new ArrayDeque<Long>();
        primes.add(2L);
        for (long val=3L; val<=maxval; val+=2)
            if (isPrime(val, primes)) 
                primes.add(val);
        return primes.toArray(new Long[primes.size()]);
    }

    private static boolean isPrime(long val, Collection<Long> que) {
        boolean noPrime = false;
        for (long prime : que) {
            if (val / 2 < prime) {
                break ;
            } else if (val % prime == 0) {
                noPrime = true;
                break ;
            }
        }
        return !noPrime;
    }

    private static long sum(Long[] vals) {
        long sum = 0;
        for (Long val : vals) sum += val.longValue();
        return sum;
    }
    
    public static class Primes {
        public final int maxValue;
        public final Collection<Long> primes;
        
        private ArrayDeque<Long> que = new ArrayDeque<Long>();
        private Long sum;
        
        public Primes(int maxValue) {
            this.maxValue = maxValue;
            que.add(2L);
            for (long val = 3L; val <= maxValue; val+=2) 
                if (isPrime(val))
                    que.add(val);
            this.primes = Collections.unmodifiableCollection(que);
        }
        
        public long sum() {
            if (sum != null) return sum;
            sum = 0l;
            for (Long val : primes) sum += val;
            return sum;
        }
        
        private boolean isPrime(long val) {
            for (long prime : que) {
                if (val / 2 < prime) return true;
                if (val % prime == 0) return false;
            }
            return true;
        }
    }
}

考察

  • 拝借した元コードではメソッドは非staticで定義されていましたが、クラスのインスタンスとは無関係のため、staticにしてみました。
  • というか、なぜか微妙に早くなってます。Transaction Scriptが2800前後なのに対して、Domain Modelが2500前後。
  • トランザクションスクリプトドメインモデルでは、利用者の利用方法が違うのはわかる。
    • 「Problem10.sum(Problem10.primes(max))」を「Problem10.sumOfPrimes(max)」としても同様。Problem10っていうサーバに規定されたサービスから結果のみを取り出す方式。
  • Primes#sum()について。メソッドで取得するのではなく下記のようにして、「primes.sum」を参照してもらう方法でもアリだと思う。
//private long sum;
public final long sum;

//...

private long sum() {
    //...
}
    • ただし、その場合はコンストラクタでsumを計算しなきゃならないため、合計値計算が不要な場合は無駄な処理が走ることになる。今回はメソッドとして定義し、初回以外は前回計算のsum値を利用するようにしてみた。
    • 引数なしのメソッドとプロパティって違いってあまりないわけで、Rubyの「obj.close」みたいに括弧を省略できる仕様はアリだと思った。
    • public final long sum;にした場合、Primesクラスは完全に状態を持たないクラスとなる。外部から更新されないので、getterやsetterは不要。
      • StrutsのAction Formなんて、フレームワーク利用者の視点では状態を持たないデータなわけで、いちいちsetterとかgetterとかでアクセスしなきゃいかん理由ってないよねぇ。全てfinal扱いとしてコンストラクタでバリデーションとか設定させた方がすっきりしそうな気がする。バリデーションとか、各setterに分散しなきゃいけないのって見難いよね。