# 模組 ![](https://i.imgur.com/X1LJdPj.png) ## 何謂模組: 1. 任何希望表現得像鴨子類型並且有做角色扮演的物件都可以使用它 2. 可以定義一組被命名的方法,這些方法獨立於類別,並且可以混入到任何物件 ## 一個物件可回應的所有訊息包括有: * 自身實作的訊息 * 所有上游物件所實作的訊息 * 被包含的模組所實作的訊息 * 所有上游物件包含的模組所實作的訊息 ![](https://i.imgur.com/ozIKgyz.png) ```ruby= class Schedule attr_accessor :lead_days def initialize; end def scheduled?(target, _starting, _ending) if target.class == Bicycle lead_days = 1 elsif target.class == Mechanic lead_days = 4 elsif target.class == Vehicle lead_days = 3 end end def add(target, starting, ending); end def remove(target, starting, ending); end end ``` ![](https://i.imgur.com/lzQ48bb.png) ```ruby= class Bicycle attr_accessor :lead_days def initialize @lead_days = 1 end end class Schedule def initialize; end def scheduled?(target, _starting, _ending) target.lead_days # 可以實作lead_days則為Schedulable end def add(target, starting, ending); end def remove(target, starting, ending); end end ``` ### 以訊息角度思考 ```ruby= bicycle = Bicycle.new Schedule.new.scheduled?(bicycle, starting, ending) ``` 如果你對B物件(bicycle)感興趣,那麼你不應該被迫知道A(Schedule)物件。即使你只是使用它來尋找關於B物件的事情,也不應該這樣做。 **應為** ```ruby= bicycle.scheduled?(starting, ending) ``` **重構** ```ruby= class Schedule def schedule? false end end class Bicycle attr_reader :schedule, ... def initialize(args = {}) @schedule = args[:schedule] || Schedule.new end def schedulable?(start_date, end_date) !scheduled?(start_date-lead_days, end_date) end def scheduled?(start_date, end_date) schedule.scheduled?(self, start_date, end_date) end def lead_days 1 end end ``` ### Mechanic和Vehicle也都會扮演這個角色 -> DRY ```ruby= module Scheulable attr_writer :schedule def schedule @schedule || = ::Schedule.new end def schdeuled?(start_date, end_date) schedule.scheduled?(self, start_date, end_date) end def schedulable?(start_date, end_date) !scheduled?(start_date-lead_days, end_date) end def lead_days 0 #默認值 end end ``` ![](https://i.imgur.com/DOZJQNq.png) ![](https://i.imgur.com/7v4MVGk.png) ## 結論: 1. 里氏代替原則(Liskov Substitution Principle,LSP ) 2. 範本方法模式(Template method pattern)