メソッド戻り値のthis型とメソッドチェーン
fluent interface
fluent interfaceとは「流れるようなインターフェース」という意味で、method chaining(メソッドの連鎖)という小技を使って、可読性の高いコードを実現するメソッドの作り方のことです。よくドメイン固有言語(DSL)を提供するようなクラスを作るときに使われます。
四則演算ができる変哲もないクラスOperator
を考えます
ts
classOperator {protectedvalue : number;public constructor(value : number) {this.value =value ;}publicsum (value : number): void {this.value +=value ;}publicsubtract (value : number): void {this.value -=value ;}publicmultiply (value : number): void {this.value *=value ;}publicdivide (value : number): void {this.value /=value ;}}constop :Operator = newOperator (0);op .sum (5); // 5op .subtract (3); // 2op .multiply (6); // 12op .divide (3); // 4
ts
classOperator {protectedvalue : number;public constructor(value : number) {this.value =value ;}publicsum (value : number): void {this.value +=value ;}publicsubtract (value : number): void {this.value -=value ;}publicmultiply (value : number): void {this.value *=value ;}publicdivide (value : number): void {this.value /=value ;}}constop :Operator = newOperator (0);op .sum (5); // 5op .subtract (3); // 2op .multiply (6); // 12op .divide (3); // 4
演算ごとにステートメントを分ける必要があります。このような場合メソッドチェーンを使って処理を連続させることができます。
ts
classOperator {protectedvalue : number;public constructor(value : number) {this.value =value ;}publicsum (value : number):Operator {this.value +=value ;return this;}publicsubtract (value : number):Operator {this.value -=value ;return this;}publicmultiply (value : number):Operator {this.value *=value ;return this;}publicdivide (value : number):Operator {this.value /=value ;return this;}}constop :Operator = newOperator (0);op .sum (5).subtract (3).multiply (6).divide (3); // 4
ts
classOperator {protectedvalue : number;public constructor(value : number) {this.value =value ;}publicsum (value : number):Operator {this.value +=value ;return this;}publicsubtract (value : number):Operator {this.value -=value ;return this;}publicmultiply (value : number):Operator {this.value *=value ;return this;}publicdivide (value : number):Operator {this.value /=value ;return this;}}constop :Operator = newOperator (0);op .sum (5).subtract (3).multiply (6).divide (3); // 4
op.sum(), op.subtract(), op.multiply(). op.divide()
の戻り値の型をOperator
に変更しました。これによりメソッドチェーンが可能になりました。
ここで、このクラスOperator
を拡張して累乗の計算を追加したいとします。すると新しいクラスNewOperator
は次のようになるでしょう。
ts
classNewOperator extendsOperator {public constructor(value : number) {super(value );}publicpower (value : number):NewOperator {this.value **=value ;return this;}}
ts
classNewOperator extendsOperator {public constructor(value : number) {super(value );}publicpower (value : number):NewOperator {this.value **=value ;return this;}}
ですが、このクラスでは次の演算ができません。
ts
constop :NewOperator = newNewOperator (2);Property 'power' does not exist on type 'Operator'.2339Property 'power' does not exist on type 'Operator'.op .power (3).multiply (2).(3); power
ts
constop :NewOperator = newNewOperator (2);Property 'power' does not exist on type 'Operator'.2339Property 'power' does not exist on type 'Operator'.op .power (3).multiply (2).(3); power
これはop.multiply()
の戻り値がOperator
だからです。Operator
にはpower()
というメソッドがないためこのような問題が発生します。
このようなとき、戻り値にthis
を設定することができます。上記クラスの戻り値のOperator, NewOperator
をすべてthis
に置き換えると問題が解消されます。
ts
classOperator {protectedvalue : number;public constructor(value : number) {this.value =value ;}publicsum (value : number): this {this.value +=value ;return this;}publicsubtract (value : number): this {this.value -=value ;return this;}publicmultiply (value : number): this {this.value *=value ;return this;}publicdivide (value : number): this {this.value /=value ;return this;}}classNewOperator extendsOperator {public constructor(value : number) {super(value );}publicpower (value : number): this {this.value **=value ;return this;}}constop :NewOperator = newNewOperator (2);op .power (3).multiply (2).power (3); // 4096
ts
classOperator {protectedvalue : number;public constructor(value : number) {this.value =value ;}publicsum (value : number): this {this.value +=value ;return this;}publicsubtract (value : number): this {this.value -=value ;return this;}publicmultiply (value : number): this {this.value *=value ;return this;}publicdivide (value : number): this {this.value /=value ;return this;}}classNewOperator extendsOperator {public constructor(value : number) {super(value );}publicpower (value : number): this {this.value **=value ;return this;}}constop :NewOperator = newNewOperator (2);op .power (3).multiply (2).power (3); // 4096