もちろん出来レースです。Ruby には負けてもらいます。w
Ruby ではモジュールを、それを継承させたいクラスに include することでミックスインできます。module M1
def m1; p "M1#m1" end
end
class C
include M1
end
C.new.m1 #=> M1#m1
オープンクラス機構を有する Ruby は include したモジュールにメソッドを後から追加しても、その変更は即座に反映されます。さすが Ruby 。すごく動的ですね。
module M1
def m2; p "M1#m2" end
end
C.new.m2 #=> M1#m2
モジュールには別のモジュールもミックスインできます。当然、その結果はすでに当該モジュールを include 済みのクラスやインスタンスにも反映されるはず……ん?
module M2
def m3; p "M2#m3" end
end
module M1
include M2
end
C.new.m3 #=> NoMethodError: undefined method `m3'
なのですが、どうやらそうはならないみたいですね。
[2020-03 追記] Rubyで長らく放置されてきたこの“不具合”は、最新の 2.8.0-dev で解消が試みられているようですので、互換性面からの強いクレームや既存機能との整合性等に特に目立った問題がなければ近々是正される…はず^^;
なぜか Ruby のモジュールは、いったん include すると、それに別のモジュールを include してもその結果は既に include 済みのクラス等に対しては反映されない仕様になっている模様。
反映させるためには改めて include してやる必要があるみたいです。
class C
include M1
end
C.new.m1 #=> M1#m1
C.new.m2 #=> M1#m2
C.new.m3 #=> M2#m3
ClassDescription >> installTraitsFrom: aTraitCompositionがしかし、実際に変更を加えてみると…
"Install the traits from the given composition. This method implements
the core composition method - all others are just optimizations for
particular cases. Consequently, the optimized versions can always fall
back to this method when things get too hairy."
| allTraits methods |
(self traitComposition isEmpty and: [aTraitComposition isEmpty]) ifTrue: [^self].
"Check for cycles"
allTraits := aTraitComposition gather: [:t | t allTraits copyWith: t].
(allTraits includes: self) ifTrue:[^self error: 'Cyclic trait definition detected'].
self traitComposition: aTraitComposition.
methods := self assembleTraitMethodsFrom: aTraitComposition.
self installTraitMethodDict: methods.
self isMeta ifFalse:[self classSide updateTraitsFrom: aTraitComposition].
| T1 C T2 |
T1 := Trait named: #T1 uses: {} category: 'UserObjects'.
T1 compile: 'm1 ^''T1>>#m1'''.
C := Object newSubclass uses: T1; rename: #C.
C new m1. "=> 'T1>>#m1 ' "
T1 compile: 'm2 ^''T1>>#m2'''.
C new m2. "=> 'T1>>#m2' "
T2 := Trait named: #T2 uses: {} category: 'UserObjects'.
T2 compile: 'm3 ^''T2>>#m3'''.
T1 uses: T2.
C new m3. "=> 'T2>>#m3' "
{C. T1. T2} do: #removeFromSystem "for clean up"
と、普通に動的に反映してくれます。勝負ありましたね(出来レース乙)。
もちろん Smalltalk のことですから、泥臭く一生懸命差し替えることで、この動的性を実現していることは言うまでもありません。
実にけなげですね。