河上です。
Java 8 でやっとラムダ式が使えるようになって喜んでいる今日このごろですが、業務アプリケーション・プログラミングであるドメイン駆動設計という文脈でどういった変化がおこるのかを考えてみます。
まず適用できそうなところとして思いつくのは以下です。
- Specificationパターンの実装
- ファーストクラスコレクション内部のコレクション操作
それぞれの実装例として幾分恣意的ではありますが、シンプルな要件を、ラムダ式を利用しないバージョンと、利用するバージョンでそれぞれ実装してみたいと思います。
要件:「ある会員制サイトで、年収800万円以上、且つ、正社員、且つ、課長以上、というステータスを持つ会員を幹部候補として区別し、絞込などをおこないたい」
まずは、年収800万円以上、且つ、正社員、且つ、課長以上という、複合条件をSpecificationパターンの実装方法の1つである、「Compositeパターンによる条件ツリー方式」で記述してみます。
ラムダ式を利用しない
public class ExecutiveMemberSpecification {
MemberSpecification 年収800万以上 = new MemberSpecification() {
@Override
public boolean isSatisfied(Member 会員) {
return 会員.年収().greaterEqual(800);
}
};
MemberSpecification 正社員 = new MemberSpecification() {
@Override
public boolean isSatisfied(Member 会員) {
return 会員.雇用形態().equals(EmploymentType.正社員);
}
};
MemberSpecification 課長以上 = new MemberSpecification() {
@Override
public boolean isSatisfied(Member 会員) {
return 会員.役職().greaterEqual(Position.課長);
}
};
public boolean isSatisfied(Member 会員) {
return 年収800万以上
.and(正社員)
.and(課長以上)
.isSatisfied(会員);
}
}
abstract class MemberSpecification {
abstract boolean isSatisfied(Member member);
MemberSpecification and(MemberSpecification specification) {
return new MemberSpecification() {
@Override
boolean isSatisfied(Member member) {
return MemberSpecification
.this.isSatisfied(member)
&& specification.isSatisfied(member);
}
};
}
MemberSpecification or(MemberSpecification specification) {
return new MemberSpecification() {
@Override
boolean isSatisfied(Member member) {
return MemberSpecification
.this.isSatisfied(member)
|| specification.isSatisfied(member);
}
};
}
}
ラムダ式を利用する
public class ExecutiveMemberSpecification {
final Predicate<Member> 年収800万以上
= it -> it.年収().greaterEqual(800);
final Predicate<Member> 正社員
= it -> it.雇用形態().equals(EmploymentType.正社員);
final Predicate<Member> 課長以上
= it -> it.役職().greaterEqual(Position.課長);
public boolean isSatisfied(Member 会員) {
return 年収800万以上
.and(正社員)
.and(課長以上)
.test(会員);
}
}
ラムダ式を利用しない場合に比べてコードにあるノイズがほぼ消えていることがわかります。
次は、このSpecificationを使って、実際に会員を絞り込むコレクション操作部分です。
ラムダ式を利用しない
public class Members {
final List<Member> values;
public Members(List<Member> values) {
this.values = values;
}
ExecutiveMemberSpecification 幹部候補者条件 = new ExecutiveMemberSpecification();
public List<Member> 幹部候補者リスト() {
List<Member> members = new ArrayList<>();
for (Member 会員 : values)
if (幹部候補者条件.isSatisfied(会員)) members.add(会員);
return members;
}
}
ラムダ式を利用する
public class Members {
final List<Member> values;
public Members(List<Member> values) {
this.values = values;
}
final ExecutiveMemberSpecification 幹部候補者条件 = new ExecutiveMemberSpecification();
public List<Member> 幹部候補者リスト() {
return values.stream()
.filter(幹部候補者条件::isSatisfied).collect(Collectors.toList());
}
}
こちらではラムダ式を利用しない場合に比べて絞り込むという部分がfilterというメソッドとしてより直接的に表現出来ている部分に好感がもてます。
コードの例としては以上となります。コードの細かい解説などはなく不親切とは思いますが、雰囲気だけでも感じていただければと思います。
上記例以外にもソートなどの記述がシンプルになったりと、メリットが多いと感じるラムダ式ですが、実を言うと、上記の例も含めて、私がドメインモデルのコードでラムダ式を使う部分は局所的です。
それは、例えば「ドメインモデルの外部からラムダ式を注入するようなモデルの汎用化をおこなう事」が、「用途や文脈に合わせた特殊化を行う」というドメイン駆動設計の思想と相反するのではないかという考えがあるからで、今のところそれを変えるような経験に出会っていません。
もちろん、ドメインモデルの外のインフラストラクチャやフレームワーク内部ではラムダ式をつかった汎用化がかなりの武器になることに疑問はありません。
他につっこみや「ドメインモデル内でのラムダ式活用例」としてなにか事例等あればご意見ください。