最近讀書會在讀深入淺出設計模式,趁這個機會複習一下設計模式,試著舉出簡單的例子並且用非模式與模式的方式來實作,比較它們的差異與優缺點。
現在有兩個log記錄器,分別為 SystemLogger(系統log記錄器) 與 ApplicationLogger(應用程式log記錄器),如下所示:
class SystemLogger
def initialize
@sys_logs = []
end
def add(msg)
timestamp = Time.now.strftime('%Y%m%d_%H%M%S_%N')
@sys_logs << [timestamp, msg]
end
def logs
@sys_logs
end
end
class ApplicationLogger
def initialize
@app_logs = {}
end
def add(msg)
timestamp = Time.now.strftime('%Y%m%d_%H%M%S_%N')
@app_logs[timestamp] = msg
end
def logs
@app_logs
end
end
這兩個Logger做的事幾乎一樣,同樣都有add(加入新的log)與logs(列出目前儲存的log)兩個method,但最大的差別就在於logs回傳的格式不同。SystemLogger回傳的是一個Array裡面每一個項目是一個由 timestamp(時間標記)與msg(訊息) 組成的pair,例如:[[timestamp1, msg1], [timestamp2, msg2], ...]
;而ApplicationLogger則是利用一個hash來儲存log,以timestamp為key,msg為value,例如:{timestamp1 => msg1, timestamp2 => msg2, ...}
。
現在有一個LogCollector,負責收集這兩個Logger產生的log並將它們的log整合到一個array中,那要怎麼實作LogCollector呢?
class LogCollector
def initialize(loggers)
@loggers = loggers
end
def collect
all_logs = []
@loggers.each do |logger|
if logger.is_a?(SystemLogger)
logger.logs.each do |log|
all_logs << log.last
end
end
if logger.is_a?(ApplicationLogger)
logger.logs.each do |_timestamp, log|
all_logs << log
end
end
end
all_logs
end
end
sys_logger = SystemLogger.new
app_logger = ApplicationLogger.new
sys_logger.add('Segmentation fault.')
app_logger.add('This is a pen, this is an apple.')
sys_logger.add('The server is on fire.')
app_logger.add('Kait is not a cat.')
log_collector = LogCollector.new([sys_logger, app_logger])
p log_collector.collect
module LogIterator
def next
fail 'You should implement "next" method.'
end
def has_next?
fail 'You should implement "has_next?" method.'
end
end
class SysLogIterator
include LogIterator
def initialize(logs)
@logs = logs.map(&:last)
@counter = 0
end
def next
log = @logs[@counter]
@counter += 1
log
end
def has_next?
(@counter < @logs.size)
end
end
class AppLogIterator
include LogIterator
def initialize(logs)
@logs = logs.values
@counter = 0
end
def next
log = @logs[@counter]
@counter += 1
log
end
def has_next?
(@counter < @logs.size)
end
end
module IteratorLogger
def iterator
fail 'You should implement "iterator" method.'
end
end
class SystemLogger
include IteratorLogger
def initialize
@sys_logs = []
end
def add(msg)
timestamp = Time.now.strftime('%Y%m%d_%H%M%S_%N')
@sys_logs << [timestamp, msg]
end
def iterator
SysLogIterator.new(@sys_logs)
end
end
class ApplicationLogger
include IteratorLogger
def initialize
@app_logs = {}
end
def add(msg)
timestamp = Time.now.strftime('%Y%m%d_%H%M%S_%N')
@app_logs[timestamp] = msg
end
def iterator
AppLogIterator.new(@app_logs)
end
end
class LogCollector
def initialize(loggers)
@loggers = loggers
end
def collect
all_logs = []
@loggers.each do |logger|
iterator = logger.iterator
while iterator.has_next?
all_logs << iterator.next
end
end
all_logs
end
end
sys_logger = SystemLogger.new
app_logger = ApplicationLogger.new
sys_logger.add('Segmentation fault.')
app_logger.add('This is a pen, this is an apple.')
sys_logger.add('The server is on fire.')
app_logger.add('Kait is not a cat.')
log_collector = LogCollector.new([sys_logger, app_logger])
p log_collector.collect
在ruby中,我們比較熟悉存取一連串資料的方式是用each來做,而要讓class有each的功能就必須include Enumerable這個module,下面是一個例子:
class SysLogIterator
include Enumerable
def initialize(logs)
@logs = logs.map(&:last)
end
def each(&block)
@logs.each(&block)
end
end
class AppLogIterator
include Enumerable
def initialize(logs)
@logs = logs.values
end
def each(&block)
@logs.each(&block)
end
end
module IteratorLogger
def iterator
fail 'You should implement "iterator" method.'
end
end
class SystemLogger
include IteratorLogger
def initialize
@sys_logs = []
end
def add(msg)
timestamp = Time.now.strftime('%Y%m%d_%H%M%S_%N')
@sys_logs << [timestamp, msg]
end
def iterator
SysLogIterator.new(@sys_logs)
end
end
class ApplicationLogger
include IteratorLogger
def initialize
@app_logs = {}
end
def add(msg)
timestamp = Time.now.strftime('%Y%m%d_%H%M%S_%N')
@app_logs[timestamp] = msg
end
def iterator
AppLogIterator.new(@app_logs)
end
end
class LogCollector
def initialize(loggers)
@loggers = loggers
end
def collect
all_logs = []
@loggers.each do |logger|
logger.iterator.each do |log|
all_logs << log
end
end
all_logs
end
end
sys_logger = SystemLogger.new
app_logger = ApplicationLogger.new
sys_logger.add('Segmentation fault.')
app_logger.add('This is a pen, this is an apple.')
sys_logger.add('The server is on fire.')
app_logger.add('Kait is not a cat.')
log_collector = LogCollector.new([sys_logger, app_logger])
p log_collector.collect
其中要注意的是,一旦include Enumerable,就必須實作each這個method,以SysLogIterator為例,實作each的方式其實就是將each導向@logs的each。因為都是each,所以可以直接將傳給each的block用參數的方式傳給裡面的each:
def each(&block)
@logs.each(&block)
end
上面的寫法可能不容易了解,其實上面做的事情就跟下面的程式做的是一樣的,也就是將資料一個個取出並當做參數傳入block中:
def each(&block)
@logs.each do |log|
block.call(log)
end
end
Iterator Pattern - 反覆器(疊代器)模式
當我們需要從一連串的資料集合(Aggregate)中將資料一個個取出來使用時,利用 Iterator(LogIterator) 抽象化存取資料的界面,讓 client(LogCollector) 端只需要操作 Iterator 而不用去了解底層是如何儲存資料的。另一方面,Iterator 也統一個存取資料的界面,只要有實作並提供 Iterator 的 Aggregate(SystemLogger, ApplicationLogger) 就都可以讓 client 端取得資料。