Java.use(better);

前の記事次の記事
Java.use(better);


Episode#06

NullObject パターンの効能 -- if と別れる50の方法


《関連記事》

■ NullObject パターンの応用

二分木を利用して式を評価する事例で話を進めます。ここでは、定数と二項式だけを対象とします。たとえば、式 (3+4)-2 を表わす二分木は、次のようになります。

    Expr expression = new Expr("-",
      new Expr("+", new Expr("3"), new Expr("4")),
      new Expr("2"));
    System.out.println(expression.eval());

これを実行すると、次の結果が得られます。

5

この結果から、式の値が 5 になるのが分ります。

これは、次のように実現します。

class Expr {
  private String value;
  private Expr left;
  private Expr right;
  Expr(String value, Expr left, Expr right) {
    this.value = value;
    this.left  = left;
    this.right = right;
  }
  Expr(String value) {
    this(value, null, null);
  }
  int eval() {
    int op1 = (left  == null) ? 0 : left.eval();
    int op2 = (right == null) ? 0 : right.eval();
    if (value.equals("+")) return op1 + op2;
    if (value.equals("-")) return op1 - op2;
    return Integer.valueOf(value);
  }
}

ここでも、NullPointerException が発生しないように、条件演算子 ?: に伴う条件式が必要です。そのため、コードが複雑で見通しも悪くなるだけでなく、バグの温床になりがちです。

この問題を解決するために「NullObject パターン」のアプローチを発展させます。今度は、

    Expr expression = new Expr("-",
      new Expr("+", new Const("3"), new Const("4")),
      new Const("2"));

Expr に代えて、定数に特化した Const を利用します。すると、

  int eval() {
    int op1 = left.eval();
    int op2 = right.eval();
    if (value.equals("+")) return op1 + op2;
    if (value.equals("-")) return op1 - op2;
    return 0;
  }

(if 文は残るものの)条件演算子に伴う条件式が不要になるので、コードが簡潔で見通しも良くなります。そのためには、

class Expr {
  Expr(String value) {
    this(value, null, null);
  }
}

引数を1つだけ持つコンストラクター Expr(String) に代えて、

class Const extends Expr {
  Const(String value) { super(value, null, null); }
  int eval() { return Integer.valueOf(value); }
}

定数に特化したクラス Const を用意します。

たとえば、NullObject パターンを適用して、特化したクラス NullNode を「0」と見なすなら、定数を表わすために特化したクラス Const は「1」と見なせます。すると「2」に当たるクラスがあってもいいはずです。今度は、

    Expr expression = new Sub(
      new Add(new Const(3), new Const(4)),
      new Const(2));

Expr に代えて、加算/減算に特化した Add/Sub を利用します。そこでは、

abstract class Expr {
  protected int value;
  protected Expr left;
  protected Expr right;
  Expr(int value, Expr left, Expr right) {
    this.value = value;
    this.left  = left;
    this.right = right;
  }
  abstract int eval();
}

Expr を具象クラスから抽象クラスに変更して、eval も具象メソッドから抽象メソッドに変更します。すると、

class Const extends Expr {
  Const(int value) { super(value, null, null); }
  int eval() { return value; }
}
class Add extends Expr {
  Add(Expr left, Expr right) { super(0, left, right); }
  int eval() { return left.eval() + right.eval(); }
}
class Sub extends Expr {
  Sub(Expr left, Expr right) { super(0, left, right); }
  int eval() { return left.eval() - right.eval(); }
}

すべての if 文や条件演算子に伴う条件式が不要になるので、コードが簡潔で見通しも良くなります。


 ↑ TOP

》作業中です《

update*13/02/11 20:15:34