目標
Scala の Trait がどううれしいかについて論じます.
注意
Scala の Trait は厳密には Mixin なので,以降の文では Mixin という言葉を使いますが,特に気にしないという人は Mixin のことを Trait に置き換えて読んでください.
Mixin の簡単な説明
あるクラスに複数の別のクラスの機能を持たせるために多重継承するとき,もし複数の親クラスが同じ名称のメンバー変数や関数を持つとき,子クラスはこの名称のメンバへアクセスするときにどちらの親クラスのメンバにアクセスすればよいか曖昧になる. Mixin を用いることで,継承の順序関係を明らかにでき,さらに子クラスで Mixin の継承先を決定できるため,継承先のクラスを子クラスが柔軟に決定することができる.
使用例
電子マネーのウォレットのような,外部サーバと API を通じて連携するアプリケーションの実装を想定する.ウォレットは残高を保持し,他のウォレットのアドレスを指定してお金を送金する機能を有する.この機能は, API を通じて他のウォレットの残高を増やし,その後自身の残高を減らす.
この例では,外部サーバと連携する代わりにテスト用のサーバと連携する API を使うテスト用のウォレットを実装するとき, Mixin 機能が有効に働くことをクラス図を用いて示す.
テスト用のウォレット(TestModeWallet)は,本番用の Wallet と似たような機能を提供することに加え,テスト用のサーバと連携する TestMode の機能も提供したい.そのため,TestModeWallet は二つのクラスを継承したいのだが,二つは callPayAPI という同名のメソッドを持つため,多重継承の際にどちらのクラスのメソッドを優先するかの曖昧さが生じる.ここで,Mixin を導入することで親クラスの継承関係に順序をつけることができる.継承関係は TestModeWallet <: TestMode <: Wallet となる.
さらに, Wallet とは別に API を通じて外部サーバと連携する Account クラスが存在し,これのテスト用を作るように拡張する際は, TestMode の継承先を Account クラスになるように TestModeAccount を定義することができる.継承関係は TestModeAccount <: TestMode <: Account となる.
Mixin を使うと,このように子クラスが Mixin の継承先を決めることができるので,いろんなクラスに Mixin で定義した機能を提供できる.
コーディング例:
// テストモードにしたいクラスはこの Mixin を継承すると,テスト用の API を使うようになる.
trait TestMode {
def callPayAPI(amount: Int, address: String){
TestAPI.pay(amount, address)
}
def callSetIDAPI(oldID: Int, newID: Int){
TestAPI.setID(oldID, newID)
}
}
// テスト用のサーバの挙動.
object TestAPI {
def pay(amount: Int, address: String){} // TestMode causes nothing.
def setID(oldID: Int, newID: Int) {}// TestMode causes nothing.
}
class Wallet {
var balance = 0
def pay(amount: Int, address: String){
callPayAPI(amount, address)
balance = balance - amount
}
def callPayAPI(amount, address){
API.pay(amount, address) // This calls actual API.
}
}
class Account {
val id = 1
def setID(newID: Int){
callSetIDAPI(id, newID)
id = newID
}
def callSetIDAPI(oldID: Int, newID: Int){
API.setID(oldID, newID) // This calls actual API.
}
}
// Wallet class for test mode.
class TestModeWallet extends Wallet with TestMode {}
// Account class for test mode.
class TestModeAccount extends Account with TestMode {}
Mixin 機能がない場合の考察
Mixin の機能がない場合を想定すると,いくつかの実装上の制約が生じる.
もし Mixin がなく, TestModeWallet が TestMode と Wallet を多重継承するよう実装する場合,TestMode と Wallet 両方に callPayAPI メソッドが存在するため,メソッドの呼び出し先に曖昧さが生じる.もし,TestMode をなくし,TestModeWallet で callPayAPI メソッドを定義する場合, TestAPI を別のテスト用の API に差し替える際に複数のクラス定義で実装を変更しなければならない問題が生じる.