#Expression Problemとは
http://maoe.hatenadiary.jp/entry/20101214/1292337923
静的型付き言語で、再コンパイルなしで、新しいデータ型を追加したり、新しい関数を追加できるようにするにはどうしたらよいか
という問題である。
何も考えずにクラスを設計すると、
- データ構造は追加しやすいが操作が追加しにくい
- 操作は追加しやすいがデータ構造は追加しにくい
のどちらかになりやすい。
データ構造も操作も追加すくするにはどうしたらいいか、というのがExpression Problemの問題意識である。
#Javaでの解決方法
HaskellやOCamlやScalaなどで高度な言語機能を用いた解法が既に有名(?)だが、Javaの言語機能でも同様にExpression Problemが解決できることを示す。
- サブタイピング+戻り値の共変性を使う方法
- サブタイピング+Genericsを使う方法
上記2種類の方法が存在する。この記事では2の方法を紹介する。
##例題
まずは単純なインタプリタを例にして考える。コード簡略化のためLombokを使用しているので注意。
import lombok.AllArgsConstructor;
import lombok.Data;
interface Exp {
int eval();
}
@Data
@AllArgsConstructor
class Add<E extends Exp> implements Exp {
private E e1;
private E e2;
@Override
public int eval() {
return e1.eval() + e2.eval();
}
}
@Data
@AllArgsConstructor
class Lit implements Exp {
private int x;
@Override
public int eval() {
return x;
}
}
AddクラスのフィールドがEという型パラメータになっているところがミソ。
##操作の追加
このクラスに対してprint操作の追加を行う。
interface ExpP extends Exp {
String print();
}
class LitP extends Lit implements ExpP {
LitP(int x) {
super(x);
}
@Override
public String print() {
return String.valueOf(getX());
}
}
class AddP<E extends ExpP> extends Add<E> implements ExpP {
AddP(E e1, E e2) {
super(e1, e2);
}
@Override
public String print() {
return getE1().print() + " + " + getE2().print();
}
}
既存クラスをいじらずに問題なく追加できた。
##クラスの追加
Sub(引き算)を表すクラスを追加する。
@Data
@AllArgsConstructor
class Sub<E extends Exp> implements Exp {
private E e1;
private E e2;
@Override
public int eval() {
return e1.eval() - e2.eval();
}
}
class SubP<E extends ExpP> extends Sub<E> implements ExpP {
SubP(E e1, E e2) {
super(e1, e2);
}
@Override
public String print() {
return getE1().print() + " - " + getE2().print();
}
}
どちらの場合でも、既存クラスを修正せずに操作の追加/クラスの追加が行えている。
これらを使う側のクラスは以下のようになる。
public class Main {
public static void main(String[] args) {
new Add<Exp>(new Lit(1), new Lit(3)).eval();
new Sub<Exp>(new Lit(1), new Lit(3)).eval();
new AddP<ExpP>(new LitP(1), new LitP(3)).print();
new SubP<ExpP>(new LitP(1), new LitP(3)).print();
}
}
高い拡張性が必要な状況(データ構造と操作の両方を拡張することが予想される)では、このデザインパターン(名称不明?)を覚えておいて損はないのではないか、と思う。
なお「サブタイピング+戻り値の共変性を使う」方法の方にも興味がある方は、下記の参考文献を参照されたし。(※この記事の内容は下記論文の丸パクリです)