概要
Javaの場合だと、抽象クラスを書く場合には”abstract”宣言をして、具体のクラスで実装すべきメソッドの宣言だけを行うが、CoffeeScript/Javascriptの場合はこれと異なる。
準備
まず、同じ名前executionのメソッドを持つ2つのクラスConcrete1とConcrete2を考える。メソッド名は同じだが、各クラスで処理内容は異なる。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Concrete1   execution: -> console.log("execution in Concrete 1") class Concrete2   execution: -> console.log("execution in Concrete 2") ins = new Concrete1() ins.execution() ins = new Concrete2() ins.execution() # 実行結果 # execution in Concrete 1 # execution in Concrete 2 | 
次にexecution()について共通な2つのクラスを抽象化したAbstractクラスを導入する。
抽象クラスの導入
CoffeeScript/Javascriptではabstractキーワードがなく、abstractなメソッドも中身が空のもので定義。ただし抽象クラスのままでインスタンス化したときの注意喚起のメッセージを実装。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class Abstract   execution: -> console.log("execution of Abstrac must be overridden") class Concrete1   execution: -> console.log("execution in Concrete 1") class Concrete2   execution: -> console.log("execution in Concrete 2") ins = new Abstract() ins.execution() ins = new Concrete1() ins.execution() ins = new Concrete2() ins.execution() # 実行結果 # execution of Abstrac must be overridden # execution in Concrete 1 # execution in Concrete 2 | 
Javaなどでは型指定が厳格なので、異なる2つのクラスをAbstractクラスで抽象化して、同じメソッドを呼び出すことでクラスごとに特有の処理をさせる。
CoffeeScript/Javascriptでは変数の型がないため、抽象化をしなくてもメソッド名を同じにしておくだけで同じ効果を得る。ただしコンパイル時のチェックなどがない点に注意。
共通部分がある場合
以下のように、2つのクラスで共通の処理と個別の処理が混在している場合を考える。
共通の処理を各クラスごとに実装しているが、コピー/ペースト操作で行うとしてもエラーが入り込みやすく、その後の保守性も悪い。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | class Concrete1   execution: ->     @commonExecution()     @specificExecution()   commonExecution: -> console.log("common execution")   specificExecution: -> console.log("execution for Concrete 1") class Concrete2   execution: ->     @commonExecution()     @specificExecution()   commonExecution: -> console.log("opening execution")   specificExecution: -> console.log("execution for Concrete 2") ins = new Concrete1() ins.execution() ins = new Concrete2() ins.execution() # 実行結果 # common execution # execution for Concrete 1 # opening execution # execution for Concrete 2 | 
共通部分の集約(1)
エラー回避と保守性の向上のため、共通部分を一つにまとめる。
- 抽象クラスAbstractを導入
- Abstractに- execution()メソッドのフレームを集約
- 共通処理部分commonExecution()をAbstractのメソッドとして実装
- 2つのクラスでAbstractを継承
- これらの子クラスで、それぞれの個別処理部分のみを実装
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class Abstract   execution: ->     @commonExecution()     @specificExecution()   commonExecution: -> console.log("common execution") class Concrete1 extends Abstract   specificExecution: -> console.log("execution for Concrete 1") class Concrete2 extends Abstract   specificExecution: -> console.log("execution for Concrete 2") ins = new Concrete1() ins.execution() ins = new Concrete2() ins.execution() # 実行結果 # common execution # execution for Concrete 1 # opening execution # execution for Concrete 2 | 
Concrete1とConcrete2がAbstractを継承したことによって2つの具象クラスがAbstractクラスのexecute()メソッドも継承され、具象クラスでexecute()メソッドを呼ぶと、Abstractのexecute()メソッドが実行される。
その後、共通部分のcommonExecution()はAbstractのメソッドが呼ばれ、specificExecution()については、各具象クラスでオーバーライドされたメソッドが呼ばれる。
共通部分の集約(2)
各具象クラスでexecution()のから書いていくこともできるが、この場合は共通処理が一括してsuper()で実行されるため、「共通の前処理と後処理の間に各クラス独自の処理を置きたい」という場合には適さない。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | class Abstract   execution: ->     @commonExecution()     @specificExecution()   commonExecution: -> console.log("common execution") class Concrete1 extends Abstract   execution: ->     super()     @specificExecution   specificExecution: -> console.log("execution for Concrete 1") class Concrete2 extends Abstract   execution: ->     super()     @specificExecution   specificExecution: -> console.log("execution for Concrete 2") ins = new Concrete1() ins.execution() ins = new Concrete2() ins.execution() # 実行結果 # common execution # execution for Concrete 1 # opening execution # execution for Concrete 2 |