ruby で継承と委譲の使い分けを考える
ことはじめ
継承と委譲に関して、それぞれなんとなく理解はしているものの、どう使い分けるのがよいのか、イマイチ実感が湧いていなかったので、整理がてらブログに書いてみます。
それぞれの定義
継承
既存の親クラスの構造(属性や振る舞い含む)をまるごと受け継ぎ、新しいクラスとして再定義する。
委譲
特定の振る舞いを別のクラスのメソッドに委ねる。
具体例で考える
継承、委譲の使い分けを、具体例を使いながら検討していきます。 ここでは、下記のようなエンジニアマネージャのクラスを考えます。
class EngineerManager def initialize end # エンジニアに共通する振る舞い def code end def debug end def test end # マネージャに共通する振る舞い(ただし assign は許可しない) def coach end def motivate end # 社員に共通する振る舞い def eat(type = 'lunch') end def attend(meeting_type) end end
ここで想定するエンジニアマネージャは、下記のようなロールだとします。
- エンジニアとしての振る舞いをする
- マネージャとしての振る舞いをする
※ただし、他のマネージャ職のメンバーとは異なり、アサイン組み替えの権限はない - 社員としての振る舞いをする
基本的な方針としては、下記の記事にもあるように、「is-a」の関係が綺麗に成り立つ場合は継承を使うのが良いと僕も思います。
継承は安易に用いると容易に複雑化し、バグの温床になりえるものの、その系において "よく" 抽象化された親クラスのもとで継承関係を作ることができれば、とても強力に機能すると思います。
例えば今回の例だと、エンジニアマネージャは社員の部分集合であるため、継承を用いてよいケースだと考えます。 社員に対して認められる振る舞いや属性は、漏れなくエンジニアマネージャも備えるべきです。
逆にエンジニアやマネージャに対してはそうではなく、それぞれの一部の振る舞いがエンジニアマネージャにも備わっているという状態であるため、まるごと受け継ぐ継承は不向きです。 エンジニアマネージャは "(エンジニアやマネージャが担う振る舞いのうち)どの振る舞いができるか" にのみ焦点を当て、その処理の具体的な内容は関知しない、という状態にします。
上記の思想のもと、下記のように実装してみました。
class EngineerManager < Worker extend Forwardable def_delegators :@engineer, *%i( code debug test ) def_delegators :@manager, *%i( coach motivate ) def initialize(name) @engineer = Engineer.new # Engineer.find(name: name) @manager = Manager.new # Manager.find(name: name) end end class Worker def initialize end def eat(type = 'lunch') puts "#{type} を食べました" end def attend(meeting_type) puts "#{meeting_type} に出席しました" end end class Engineer def initialize end def code puts 'code を書きました' end def debug puts 'debug をしました' end def test puts 'test をしました' end end class Manager def initialize end def coach puts 'コーチングしました' end def motivate puts 'モチベートしました' end def assign puts 'アサイン組み換えしました' end end
bob = EngineerManager.new('Bob') puts bob.code # => code を書きました puts bob.attend('朝会') # => 朝会 に出席しました puts bob.coach # => コーチングしました puts bob.assign # => undefined method `assign' for #<EngineerManager:0x007fe6ec8e3a70>
おわりに
なんとなく具体例を考えてみると思考が整理された気がします。
まだまだ思考実験の域を出ていないので、ツッコミなどありましたらぜひお願いします!