とある社内のエンジニアのmixinの使い方に関する一言からある議論になりました。
オブジェクト指向設計では、オブジェクト同士の関係を is a
の関係であるか、 has a
関係であるかを考えながら設計していくと良い設計になります。
この is a
と has a
を表現するために用いられる手法が、あります。代表的なものとしては、
is a: クラス継承
has a: コンポジションによる委譲
をそれぞれ使用して表現するのが一般的です。
しかし、rubyにはこれら両方の性質をもった mixin
というクラスをmodule単位で切り出して、適時合成する機能があります。
初めてrubyを触った時は、mixinをどのように扱うべきか悩みました。
mixin moduleは、includeするとクラス自体に合成されるため、クラス内のメンバ変数へのアクセスや、別のモジュールのメソッドを呼び出すことができるなど、かなり自由度の高い実装が可能で便利な機能です。
反面、何も考えずに書いてしまうと、メンテナンス性の低いモジュールが量産されてしまうことになります。
では、どのようにmixinを設計すべきなのでしょうか?
基本的に委譲を意識したmixinがよい
よくあるmixinのコードです。
class Dog
include Runnable
endclass Cat
include Runnable
endmodule Rannable
def run
end
endDog.new.run
同じコードをコンポジションを用いた委譲の形で書くとこうなります。
class Dog
def initialize
@leg = Leg.new
enddef run
@leg.run
end
endclass Leg
def run
end
endDog.new.run
そして、継承で書いてみます
class Dog < Leg
endclass Leg
def run
end
endDog.new.run
典型的な継承の失敗例になってしまっていますね。
やはり、has aを意識するほうがよさそうです。
実際にDHHなども ***able
というconcernをたくさん作っていますね
https://twitter.com/dhh/status/964244090224128001
結論: mixinは has a 関係を意識すると良い
・***able
のような名前で命名する
・include元の実装に依存しない
・ include元のメンバ変数は参照しない
・ include元の関数は呼ばない
というのを気をつけるとよいmixinの設計ができるのではないでしょうか!
リアルワールドではこれらを破った方が効率的なケースもあるので、そういう意味で柔軟性の高い仕様になっているのかと思います。
なので、理由が説明できれば、上記のようなルールから外れても良いと思います。
異議あり!という方や、こういった設計のお話が好きな方がいらっしゃいましたら、弊社ではエンジニアを募集中ですのでぜひお気軽にご連絡ください!カジュアル面談だけでもお待ちしております!