2012年12月20日木曜日

Scala Tips / トレイトと抽象クラス

クラスの多重継承の問題点は、クラスの継承関係が循環グラフになってしまった場合に、循環してかち合ってしまったクラスの扱いが非常に難しいということです。循環してかち合ってしまったクラスは一つにまとめたほうが良いのか、別管理にしておいた方が良いのか、用途によってどちらもあり得ます。これに付随して、初期化の順番やメソッド呼び出しの解決の順番など、プログラミングを複雑に解決が難しい難問があります。

こういった問題があるため、Javaでは多重継承はサポートしないという選択を行いました。また、「クラスの継承関係が循環グラフになる」という問題を持たないインタフェースという言語機能を用意して、多重継承的な用途を実装可能にしています。インタフェースは実装を持たないため、「実装部」が循環グラフになる問題が起きないわけです。

インタフェースは素晴らしい解決策でしたが、機能が絞りこまれているので用途がかなり限定されます。具体的には、実装の再利用の目的には使用できません。

トレイトは、実装部を持ちながら、継承関係の循環グラフ問題を以下の手法で回避しています。

  • コンストラクタを持たない。
  • 同じトレイトが複数指定されても、トレイト実装の実体は1つになる。

この制約をつけることで、ミックスインが可能になったのは非常に大きな成果ということができます。

一方、プログラミング時に意識しなければならないのは、トレイトにはコンストラクタがなく、コンストラクタを使っての初期化はできないということです。

この制約がどの程度のものなのか、「実装の再利用」とのトレードオフがペイしたものなのか、という点が気になりますね。

トレイトとケースクラス

以下のトレイトPartyと、このトレイトをミックスインしたケースクラスPerson, Companyの構造は頻出のパターンです。

トレイトPartで抽象変数として定義したnameを、ケースクラスPersonおよびCompanyの引数nameで"実装"しているため、このまま普通に動作します。子供(トレイトPersonやCompany)からコンストラクタ経由でパラメタを受け渡す必要はありません。

trait Party {
  val name: String
}

case class Person(name: String) extends Party
case class Company(name: String) extends Party

抽象クラスとケースクラス

これと同様のことを抽象クラスを用いて実装すると以下になります。

abstract class Party(val name: String) {
}

case class Person(n: String) extends Party(n)
case class Company(n: String) extends Party(n)

トレイト版と同じことができているだけですが、記述が少し冗長になります。特にパラメタをコンストラクタに積み直すところはプログラミング時にかなり面倒くさい部分です。

しかも、抽象クラス版では、抽象クラスであるために複数の親クラスを同時に継承、つまり多重継承を行うことができないという弱点があります。トレイト版が多重継承的なことが自在にできることと対照的ですね。

トレイト vs 抽象クラス

トレイトと抽象クラスの例を比べてみましたが、この例だけをみるとトレイトが圧倒的に便利という結論になります。

もちろん、親クラスに対して、コンストラクタで引数を渡して初期化を行うということが有用なケースも多々あるので、この極端な例だけを引き合いに出してトレイトが"圧倒的に便利"というわけにもいきません。

とはいえ、日々のScalaプログラミングの感想では、案外トレイトだけでも大丈夫なケースが多い、というのが実感です。特にイミュータブルオブジェクトの場合、コンストラクタでの初期化で複雑な処理は必要ないのが普通なので、トレイトで十分なケースが多いでしょう。

さらに、関数型プログラミング的な指向が強くなればなるほど、イミュータブルオブジェクトを多用するようになるので、トレイトの優位性が高まります。

そういうわけで、ボクが最近Scalaプログラミングをしながら着目しているのは、"抽象クラスでないといけない用途はなんだろう?"ということです。

トレイトではなく、抽象クラスでないといけない用途が明確になれば、日々のプログラミングで迷いなく抽象クラスとトレイトの選択を行うことができます。最近は、まずトレイトで実装してみて、抽象クラスでないといけないと分かった時に、抽象クラスに変更する、というアプローチをとっています。その結果、あまり抽象クラスに変換するケースはなく、案外トレイトで大丈夫という感触を得ています。

諸元

  • Scala 2.9.2

0 件のコメント:

コメントを投稿