以前に書いたエントリーで、Scalaにおける菱形継承問題を取り上げた。
Scalaでは、トレイトを用いて菱形継承関係を作った場合、クラス定義の際の記述の順番によって最終的な具象クラスの振る舞いが決まる、ということだった。
つまり、
"class MyPet extends DogTrait with CatTrait" と書けばMyPetは猫の振る舞いを優先し、
"class MyPet extends CatTrait with DogTrait" と書けばMyPetは犬の振る舞いを優先する。
ここで一つ問題が生じる。MyPetの状態によって、犬と猫の振る舞いを使い分けたい場合はどうすれば良いのか。
というか、そもそもそんなシチュエーションが存在するのだろうか。
これは、菱形継承の根本に関わるような気がしていて、いろいろ自分なりに考えた結果、こういう考えで良いのではないか、という結論に至ったのでちょっと書いてみたいと思う。
異論、反論は全然OKなので、「お前、そりゃ考えが足りんよ」などのご意見があれば是非いただきたい。
■新しいマジンガーを作ってみよう。
今回は概念のお話なので、具体的なコードは書かない。
そこで、今回の概念を表すクラス図をご覧いただこう。
僕はこれから新しいマジンガーを作りたい。
新しいマジンガーは、マジンガーZとグレートマジンガー、両方の性質を備えさせたいので、マジンガーZとグレートマジンガーを多重継承しようと思う。これら二つのクラスは、そもそもスーパーロボットからの派生である。
スーパーロボットといえば空を飛ばねばならない。しかし、ロボットによって飛び方は様々なので、スーパーロボットクラスにおける「空を飛ぶ」という振る舞いは抽象メソッドであり、それぞれの具象クラスで具体的に実装されている。
マジンガーZは、ジェットスクランダーという外部装置を使って空を飛ぶ。
グレートマジンガーは、内蔵されているスクランブルダッシュという装置を使って空を飛ぶ。
さて、ここで新しいマジンガーを見てみよう。
我らが新しいマジンガーは、「ブレストファイヤー」「サンダー・ブレイク」という二種類の必殺技を使うことができる。これぞ多重継承の醍醐味。その辺の悪役ロボットは一瞬にして倒せてしまいそうだ。独自の必殺技メソッドをさらに追加実装しても良いかもしれない。
さて、ここで皆さんお分かりのとおり、問題が生じる。
新しいマジンガーは、どうやって空を飛ぶのか。つまり、ジェットスクランダーを使うのか、スクランブルダッシュを使うのか、という問題だ。
これが菱形継承における曖昧さの問題である。
この曖昧さに対しては、以下のアプローチがあると思う。
・ジェットスクランダーを使って飛びたい。
→class 新しいマジンガー extends グレートマジンガー with マジンガーZと書け!
・スクランブルダッシュを使って飛びたい。
→class 新しいマジンガー extends マジンガーZ with グレートマジンガーと書け!
・新しい方式で飛びたい
→「空を飛ぶ」メソッドをオーバーライドしろ!
・状況に応じてジェットスクランダーとスクランブルダッシュを使い分けたい。
→そもそも、要求として「空を飛ぶ」という振る舞いが共通の意味を持たなくなっているのだから、マジンガーZとグレートマジンガー双方にて「ジェットスクランダーで飛ぶ」「スクランブルダッシュで飛ぶ」というようにメソッドをリファクタリングしろ!
こういう事で良いのではないだろうか?
ここでのポイントは、最後の「使い分けたい場合どうすれば良いか」という部分である。
使い分けたい、という要求が発生した時点で、「空を飛ぶ」というメソッドはそれぞれのクラスにおいて共通の意味を持たなくなっている。つまり、「どのように飛べば良いのか」、という新しい意味付けがメソッドに対して行われていることになる。
それはつまり、継承ではなくなっているという事だ。
ちなみに、我らが新しいマジンガーは、オーバーライドして反重力エンジンを用いた、翼の不要な飛び方を実装してみようと思う。
と、ここまで書いておいて一つ大きな問題に気づいた。
上記クラス図に、「歩く」というメソッドを追加したモデルで考えみよう。
新しいマジンガーに対して、「マジンガーZのように歩き、グレートマジンガーのように飛べ」という要求があった場合どうすれば良いのだろう……。
適切なモデリングが思い浮かばないので、ちょっと宿題とさせていただきたい…。
当ブログはamazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。
2011年4月17日日曜日
2011年4月15日金曜日
今、しんどいと思ってる人に伝えたい僕の話
僕が24歳のときに父親が亡くなった。
結婚する前の年になるのかな。
ちょうどその頃は仕事でも人生最大級のデスマーチ中で、夜中の病院で父親の臨終を迎えた瞬間に現場から電話が鳴るようなクソッタレな状況だった。
あの頃が今のところ人生で一番辛かったときかな。
転職とか、結婚とかいろいろと人生の転機について考えていた時期で、そのときにデスマーチと父親の死が重なって、心のなかはグチャグチャだった。
オレの人生なんなんだろうって思ったよ。やっと大人になって、お酒飲みながら父親と語り合いたいとか思ってたのに、そういう機会は結局無かったし、残業代もつかない安月給で何百時間も働かされて、顔とかアレルギーが出てひどかったし。
お葬式の日は本当にしんどかった。
息子を亡くしたおばあちゃんの気持ち、旦那を亡くした母親の気持ち、父親を亡くした僕と弟の気持ち。
そんな事を考えていると、気が狂いそうになった。
おそらく、家族皆が似たような気持ちだったかもしれない。
お葬式が終わって、最後に母親がお坊さんにこんな事を聞いた。
「お葬式が終わって、主人は安らかに目を閉じてますけど、わたしたちはちっとも救われていません。どうしたらいいんですか?」
するとお坊さんはこんな事を言う。
「はい。今、この瞬間に残された人が救われることはありません。でも、騙されたと思って、2年だけ我慢してください。大変長い2年間に思われるかもしれないけれど、その期間だけ我慢してください。人間の悲しみとか、そういうものを癒すのは時間だけなんです」
その時は、にわかに信じられなかった。
で、あれから8年経った。今でも時々父親の事を思い出して、少しだけ悲しかったり、寂しい気持ちになるけれど、あの日の気の狂いそうな辛さはもうすっかり無くなった。これが「癒された」というのかどうかは分からないけれど。
あの経験があったから、割とつらいことは乗り越えられるようになった。ちょっとの間我慢すれば、辛さは通り過ぎたり、気にならなくなったりするってわかったから。
最近、自分や、友人や、周りの人達がつらそうにしているのを見て、なんとなくそんなことを思い出したので書いてみた。
結婚する前の年になるのかな。
ちょうどその頃は仕事でも人生最大級のデスマーチ中で、夜中の病院で父親の臨終を迎えた瞬間に現場から電話が鳴るようなクソッタレな状況だった。
あの頃が今のところ人生で一番辛かったときかな。
転職とか、結婚とかいろいろと人生の転機について考えていた時期で、そのときにデスマーチと父親の死が重なって、心のなかはグチャグチャだった。
オレの人生なんなんだろうって思ったよ。やっと大人になって、お酒飲みながら父親と語り合いたいとか思ってたのに、そういう機会は結局無かったし、残業代もつかない安月給で何百時間も働かされて、顔とかアレルギーが出てひどかったし。
お葬式の日は本当にしんどかった。
息子を亡くしたおばあちゃんの気持ち、旦那を亡くした母親の気持ち、父親を亡くした僕と弟の気持ち。
そんな事を考えていると、気が狂いそうになった。
おそらく、家族皆が似たような気持ちだったかもしれない。
お葬式が終わって、最後に母親がお坊さんにこんな事を聞いた。
「お葬式が終わって、主人は安らかに目を閉じてますけど、わたしたちはちっとも救われていません。どうしたらいいんですか?」
するとお坊さんはこんな事を言う。
「はい。今、この瞬間に残された人が救われることはありません。でも、騙されたと思って、2年だけ我慢してください。大変長い2年間に思われるかもしれないけれど、その期間だけ我慢してください。人間の悲しみとか、そういうものを癒すのは時間だけなんです」
その時は、にわかに信じられなかった。
で、あれから8年経った。今でも時々父親の事を思い出して、少しだけ悲しかったり、寂しい気持ちになるけれど、あの日の気の狂いそうな辛さはもうすっかり無くなった。これが「癒された」というのかどうかは分からないけれど。
あの経験があったから、割とつらいことは乗り越えられるようになった。ちょっとの間我慢すれば、辛さは通り過ぎたり、気にならなくなったりするってわかったから。
最近、自分や、友人や、周りの人達がつらそうにしているのを見て、なんとなくそんなことを思い出したので書いてみた。
2011年4月14日木曜日
ちょっとだけ休んでまた歩く。
個人的に気持ちの整理をつけたくて、ちょっとつまらないエントリーを書いてみる。
この数日、社会人になってからあまり経験したことがないくらい落ち込んでいた。
人とあまり関わり合いになりたくなくて、TwitterとかFacebookにもログインしないようにしていたし、会社でも極力周りと会話しないようにしていた。とはいっても仕事をするうえで最低限やるべきことはやってたけど。
数ヶ月ほど、いろいろなことがあって、それは主に仕事上のことなんだけれど、それでどうしても気持ちが後ろを向いていた。
これではいかんな、と思って、前を向きたくて、踏ん張って、ちょっとしたチャレンジをしてみようと思った。
自分の中では、少し背伸びが必要なチャレンジ。
どのようなチャレンジなのかは、まだ詳細を書けるほど気持ちの整理ができてないから書かないけれど、今までの人生で自分が積み上げてきた色々な知識とか、経験とか、そういうリソースを結構めいっぱい使わなきゃいけないような、そんなチャレンジ。
思い切って、勇気を出して、「自重は悪だ」という信念を胸に、飛び込んでみた。
世の中には、自分でも信じられないくらい、自身のキャパシティを大きく超えた成果を手にする奇跡のようなこともあれば、反対に自分の全部をつぎ込んでもそれらがすべて無駄になるような残酷なこともある。
今回の結果は後者だった。まぁ、人生ってそんなものだ。
片思いの初恋がうまくいったなんていう話をあまり聞いたことがないように、憧れて、必死で手を伸ばして、努力しても得られないものはある。世界は残酷で、無慈悲だ。
「自分の思い通りにいかない事」なんて、30年以上も人生過ごしていれば何百回と経験してきたはずなのに、それでもやっぱりしんどい。単に自分の能力が無いだけのことなんだけれどね。
で、仕事から帰って、ヤケ酒飲んで、妻に心配されたりするんだ。
彼女の「何かあったの?」という言葉が余計に辛くて、ちょっと自分の機嫌が悪くなったりして、さらに自己嫌悪に陥るというありがちな負のパターンに堕落していく。
なんだコレ?
どんなにうずくまってても、お腹は空くし、眠くなるし、仕事に行かなきゃいけないし、そうやって生活は続くわけだ。
そうして続いていく日々を、僕はどれだけの時間、下を向いて過ごすのだろうか……。
前を向くための力が欲しい。空を見上げるための自信が欲しい。
ちょっとだけ休んで、また頑張るよ。
だって笑って生きていたいからね。
この数日、社会人になってからあまり経験したことがないくらい落ち込んでいた。
人とあまり関わり合いになりたくなくて、TwitterとかFacebookにもログインしないようにしていたし、会社でも極力周りと会話しないようにしていた。とはいっても仕事をするうえで最低限やるべきことはやってたけど。
数ヶ月ほど、いろいろなことがあって、それは主に仕事上のことなんだけれど、それでどうしても気持ちが後ろを向いていた。
これではいかんな、と思って、前を向きたくて、踏ん張って、ちょっとしたチャレンジをしてみようと思った。
自分の中では、少し背伸びが必要なチャレンジ。
どのようなチャレンジなのかは、まだ詳細を書けるほど気持ちの整理ができてないから書かないけれど、今までの人生で自分が積み上げてきた色々な知識とか、経験とか、そういうリソースを結構めいっぱい使わなきゃいけないような、そんなチャレンジ。
思い切って、勇気を出して、「自重は悪だ」という信念を胸に、飛び込んでみた。
世の中には、自分でも信じられないくらい、自身のキャパシティを大きく超えた成果を手にする奇跡のようなこともあれば、反対に自分の全部をつぎ込んでもそれらがすべて無駄になるような残酷なこともある。
今回の結果は後者だった。まぁ、人生ってそんなものだ。
片思いの初恋がうまくいったなんていう話をあまり聞いたことがないように、憧れて、必死で手を伸ばして、努力しても得られないものはある。世界は残酷で、無慈悲だ。
「自分の思い通りにいかない事」なんて、30年以上も人生過ごしていれば何百回と経験してきたはずなのに、それでもやっぱりしんどい。単に自分の能力が無いだけのことなんだけれどね。
で、仕事から帰って、ヤケ酒飲んで、妻に心配されたりするんだ。
彼女の「何かあったの?」という言葉が余計に辛くて、ちょっと自分の機嫌が悪くなったりして、さらに自己嫌悪に陥るというありがちな負のパターンに堕落していく。
なんだコレ?
どんなにうずくまってても、お腹は空くし、眠くなるし、仕事に行かなきゃいけないし、そうやって生活は続くわけだ。
そうして続いていく日々を、僕はどれだけの時間、下を向いて過ごすのだろうか……。
前を向くための力が欲しい。空を見上げるための自信が欲しい。
ちょっとだけ休んで、また頑張るよ。
だって笑って生きていたいからね。
2011年4月10日日曜日
Scalaにおける菱形継承問題を調べてみた。
昨日、エンジニアな皆さんたちと京都の二条城にお花見に行ってきた。
その後の飲み会の席で、こんな話題がでた。
「Scalaって菱形継承をどう解決してんの?」
Scalaを勉強中とはいえ、まだまだ勉強が足りない僕は、その質問にきちんと答えることができなかったので、ちょっと調べてみた。
■菱形継承問題とは
菱形継承問題とは、多重継承を認めているプログラム言語において、クラスが上記の図のような継承関係にあり、BとCがAのメソッドをそれぞれ異なる形でオーバーライドしていた場合、DのクラスはBかC、いずれのメソッドを継承するのか、という問題である。
JavaやC#などの言語では、そもそも多重継承を認めておらず、インターフェイスも最終的に具象化すべきクラス(上記図でいうとD)にて具体的な実装がなされるため、菱形継承は問題にならない。
■Scalaのトレイトではどうなる?
ScalaもJavaやC#と同様、言語仕様としてクラスの多重継承を認めていない。その一方でインターフェイスという概念も存在せず、代わりにトレイトという考え方が存在する。
トレイトとは、基本的にはインターフェイスと同様の考え方であるのだが、それ自体に具体的な実装を施すことができる。
(ちなみにインターフェイスでいうinplementsはトレイトではmixinと呼ぶ)
トレイトはインターフェイスと同様、複数のトレイトをクラスにmixinできるため、JavaやC#では起こり得なかった菱形継承問題が発生することになる。
■実際に動かしてみた
ではScalaで菱形継承を実装するとどうなるのか、実験してみよう。
B、Cに当たるのは、Animalクラスを継承したトレイト、DogTraitとCatTrait。それぞれhowl()メソッドに具体的な実装を施している。
最後に、DogTraitとCatTraitを多重mixinしたMyPetクラス。
DriverでMyPetクラスのインスタンスを生成し、howl()メソッドを呼び出した。
ビルドしてみたところ、正常終了。実行したら以下の結果が出た。
CatTraitのメソッドを継承するようだ。
続いて、コードをこのように変えてみた。
"class MyPet extends DogTrait with CatTrait"のExtends 〇〇 with △△を入れ替え、"class MyPet extends CatTrait with DogTrait"としてみる。
実行すると…。
結果が変わった!
Scalaで菱形継承を実装した場合、Extends 〇〇 with △△ で定義された継承関係のうち、右側に位置するトレイトから優先して継承される(右側にあるトレイトが左側のトレイトを上書きする)ようだ。
ちなみに、菱形継承でなく、単純に同名のメソッドを有するトレイトを多重mixinした場合はこうなった。
これをビルドすると…。
howl()をオーバーライドしろ、というエラーになる。
その後の飲み会の席で、こんな話題がでた。
「Scalaって菱形継承をどう解決してんの?」
Scalaを勉強中とはいえ、まだまだ勉強が足りない僕は、その質問にきちんと答えることができなかったので、ちょっと調べてみた。
■菱形継承問題とは
引用元:wikipedia 菱形継承問題
菱形継承問題とは、多重継承を認めているプログラム言語において、クラスが上記の図のような継承関係にあり、BとCがAのメソッドをそれぞれ異なる形でオーバーライドしていた場合、DのクラスはBかC、いずれのメソッドを継承するのか、という問題である。
JavaやC#などの言語では、そもそも多重継承を認めておらず、インターフェイスも最終的に具象化すべきクラス(上記図でいうとD)にて具体的な実装がなされるため、菱形継承は問題にならない。
■Scalaのトレイトではどうなる?
ScalaもJavaやC#と同様、言語仕様としてクラスの多重継承を認めていない。その一方でインターフェイスという概念も存在せず、代わりにトレイトという考え方が存在する。
トレイトとは、基本的にはインターフェイスと同様の考え方であるのだが、それ自体に具体的な実装を施すことができる。
(ちなみにインターフェイスでいうinplementsはトレイトではmixinと呼ぶ)
トレイトはインターフェイスと同様、複数のトレイトをクラスにmixinできるため、JavaやC#では起こり得なかった菱形継承問題が発生することになる。
■実際に動かしてみた
ではScalaで菱形継承を実装するとどうなるのか、実験してみよう。
package diamondProblem abstract class Animal { def howl() } trait DogTrait extends Animal { override def howl() = println("bow wow") } trait CatTrait extends Animal { override def howl() = println("mew") } class MyPet extends DogTrait with CatTrait { } object Driver { def main(args: Array[String]) { var pet = new MyPet pet.howl() } }前述した菱形継承の図と対応させると、AのクラスがAnimalクラスに該当する。これはhowl()メソッドを定義した抽象クラスである。
B、Cに当たるのは、Animalクラスを継承したトレイト、DogTraitとCatTrait。それぞれhowl()メソッドに具体的な実装を施している。
最後に、DogTraitとCatTraitを多重mixinしたMyPetクラス。
DriverでMyPetクラスのインスタンスを生成し、howl()メソッドを呼び出した。
ビルドしてみたところ、正常終了。実行したら以下の結果が出た。
% scala diamondProblem.Driver mew
CatTraitのメソッドを継承するようだ。
続いて、コードをこのように変えてみた。
package diamondProblem abstract class Animal { def howl() } trait DogTrait extends Animal { override def howl() = println("bow wow") } trait CatTrait extends Animal { override def howl() = println("mew") } class MyPet extends CatTrait with DogTrait { } object Driver { def main(args: Array[String]) { var pet = new MyPet pet.howl() } }
"class MyPet extends DogTrait with CatTrait"のExtends 〇〇 with △△を入れ替え、"class MyPet extends CatTrait with DogTrait"としてみる。
実行すると…。
% scala diamondProblem.Driver bow wow
結果が変わった!
Scalaで菱形継承を実装した場合、Extends 〇〇 with △△ で定義された継承関係のうち、右側に位置するトレイトから優先して継承される(右側にあるトレイトが左側のトレイトを上書きする)ようだ。
ちなみに、菱形継承でなく、単純に同名のメソッドを有するトレイトを多重mixinした場合はこうなった。
package diamondProblem trait DogTrait { def howl() = println("bow wow") } trait CatTrait { def howl() = println("mew") } class MyPet extends CatTrait with DogTrait { } object Driver { def main(args: Array[String]) { var pet = new MyPet pet.howl() } }
これをビルドすると…。
% scalac diamondProblem.scala diamondProblem.scala:11: error: overriding method howl in trait CatTrait of type ()Unit; method howl in trait DogTrait of type ()Unit needs `override' modifier class MyPet extends CatTrait with DogTrait { ^ one error found
howl()をオーバーライドしろ、というエラーになる。