メソッドチェーンを調べてたらFluent Interfaceについて知ったので考えてみる
はじめに
Fluent Interfaceってご存知ですか?私は今日知りました。
きっかけとして、メソッドチェーンを使ったクラスを実装しようとしていたのですが、名称がわからずググっていたら出てきました。
例えば、以下のような実装が該当します。
class Calculator {
protected value: number;
public constructor(value: number) {
this.value = value;
}
public sum(value: number): this {
this.value += value;
return this;
}
public subtract(value: number): this {
this.value -= value;
return this;
}
public print(): void {
console.log(this.value);
}
}
const calc: Calculator = new Calculator(0);
calc.sum(5).sum(5).subtract(3).print(); // 7
では、そもFluent Interfaceとはなんなのか?他にどんなものがあるのでしょうか?
Fluent Interface とは?
そもそもFluent Interfaceとはなんでしょうか?
偉大なる出典要確認のWikipediaさんによると、以下のように記載されています。
In software engineering, a fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a domain-specific language (DSL). The term was coined in 2005 by Eric Evans and Martin Fowler.
A fluent interface is normally implemented by using method chaining to implement method cascading (in languages that do not natively support cascading), concretely by having each method return the object to which it is attached, often referred to as this or self. Stated more abstractly, a fluent interface relays the instruction context of a subsequent call in method chaining, where generally the context is
Defined through the return value of a called method
Self-referential, where the new context is equivalent to the last context
Terminated through the return of a void context
Note that a "fluent interface" means more than just method cascading via chaining; it entails designing an interface that reads like a DSL, using other techniques like "nested functions and object scoping".
(https://en.wikipedia.org/wiki/Fluent_interface)
DDDで有名なEric EvansとMartin Fowlerによる造語とのことで、
ざっくりとは、各メソッドがそれにつながるオブジェクト(多くの場合thisやselfと呼ばれる)を返すことにより、メソッドチェーンを構築できるもの、voidの戻りによって終了するものということらしいです。
TypeScriptでの実装
実装として、次のような簡単なものを書いてみました。
class Calculator {
protected value: number;
public constructor(value: number) {
this.value = value;
}
public sum(value: number): this {
this.value += value;
return this;
}
public subtract(value: number): this {
this.value -= value;
return this;
}
public print(): void {
console.log(this.value);
}
}
const calc: Calculator = new Calculator(0);
calc.sum(5).sum(5).subtract(3).print(); // 7
関数の戻り値としてthisを返すことにより、メソッドチェーンが実現できています。
ここで、1つ疑問が生じます。Fluent Interfaceでは this
や self
を返すことが多いとのことですが、
Calculator自身を返すとどうなるでしょうか?
class Calculator {
protected value: number;
public constructor(value: number) {
this.value = value;
}
public sum(value: number): Calculator {
this.value += value;
return this;
}
public subtract(value: number): Calculator {
this.value -= value;
return this;
}
public print(): void {
console.log(this.value);
}
}
const calc: Calculator = new Calculator(0);
calc.sum(5).sum(5).subtract(3).print(); // 7
上記のようにしても、普通に実行できます。ですが、拡張性を考えたときには少し微妙です。
extendsして、Calcuartor2というクラスを実装し、メソッドを足してみましょう。
class Calculator2 extends Calculator {
public constructor(value: number) {
super(value);
}
public power(value: number): Calculator2 {
this.value **= value;
return this;
}
}
const calc2: Calculator2 = new Calculator2(0);
calc2.sum(5).sum(5).subtract(3).power(2).print(); // 7
残念!エラーとなってしまいます。Calculatorのメソッドの戻り値をCalculatorとしているためです。
thisにすれば問題なくextendsでの拡張や実行ができそうです。
他の実装:命令的に書いてみる
他の選択肢として、thisを返さずに、メソッドを命令的に記述することも考えられます。
これとFluent Interfaceを比較して、メリットデメリットを考えてみます。
class Calculator {
protected value: number;
public constructor(value: number) {
this.value = value;
}
public sum(value: number) {
this.value += value;
}
public subtract(value: number) {
this.value -= value;
}
public print(){
console.log(this.value);
}
}
const calc: Calculator = new Calculator(0);
calc.sum(5);
calc.sum(5);
calc.subtract(3);
calc.print(); // 7
Fluent Interfaceと比較すると、メソッドチェーンが使えなくなりますね。冗長な記述にも見えます。
反面、メソッドチェーンで繋がなくなったことで以下のような利点も考えられます。
- デバッグがしやすいこと
- メソッドの変更で他に副作用がでにくい
例えば、sumの戻り値をnumberにしたくなってしまった例を考えましょう。
public sum(value: number) : number {
this.value += value;
return this.value;
}
この場合、メソッドチェーンでつないでいた場合には中断され、後続でつないでいるすべての部分に影響が出る可能性があります。
そもそもFluent Interfaceの思想を壊す変更なわけですが、ルールとして周知できていなければ発生する可能性はありますね。
きちんとメソッドチェーンの設計をする必要が出てくるというわけです。
また、他にもメソッドチェーンが世界一周できるレベルで長くなってくると、パフォーマンスも気になってきます。
まとめ
Fluent Interfaceという単語を知ったので、簡単に調べて考えてみました。
メソッドチェーンでの実装は普通に行うこともあると思いますが、概念としての長短を考えてうまく使い分けたいですね。
Discussion