Template Method Pattern trong Ruby
Bài đăng này đã không được cập nhật trong 3 năm
Hãy tưởng tượng bạn có một đoạn code phức tạp, có thể đó là một thuật toán, hoặc mã hệ thống, hay cũng có thể là đoạn mã đủ khó mà bạn chỉ muốn code 1 lần. Vấn đề ở đây là chỉ có phần chính giữa của đoạn code phức tạp là thay đổi.
-> Làm sao để giải quyết mà không phải thay đổi đoạn code quá nhiều trong tương lai
Để cụ thể hơn, chúng ta sẽ xây dựng một chương trình xuất báo cáo như sau:
class Report
def initialize
@title = 'Monthly Report'
@text = [ 'Things are going', 'really, really well.' ]
end
def output_report
puts('<html>')
puts(' <head>')
puts(" <title>#{@title}</title>")
puts(' </head>')
puts(' <body>')
@text.each do |line|
puts(" <p>#{line}</p>" )
end
puts(' </body>')
puts('</html>')
end
end
Ở đây báo cáo được xuất theo định dạng HTML với các thẻ được gắn cố định. Đoạn mã này sẽ xử lý tốt nếu chúng ta muốn xuất báo cáo định dạng HTML, tuy nhiên nếu chúng ta muốn thay đổi định dạng xuất báo cáo thì đoạn mã này không thể dùng lại được.
Vậy cách xử lý ở đây là gì? Điều đâu tiên chúng ta nghĩ tới và đơn giản nhất là xử lý bằngIF ELSE
class Report
def initialize
@title = 'Monthly Report'
@text = ['Things are going', 'really, really well.']
end
def output_report(format)
if format == :plain
puts("*** #{@title} ***")
elsif format == :html
puts('<html>')
puts(' <head>')
puts(" <title>#{@title}</title>")
puts(' </head>')
puts(' <body>')
else
raise "Unknown format: #{format}"
end
@text.each do |line|
if format == :plain
puts(line)
else
puts(" <p>#{line}</p>" )
end
end
if format == :html
puts(' </body>')
puts('</html>')
end
end
end
Việc dùng IF ELSE
sẽ khiến đoạn code của chúng ta dài dòng và có nguy cơ sửa chữa nhiều khi phải thêm 1 đoạn xử lý cho định dạng report mới. Việc sửa chữa code đã vi phạm quy tắc thiết kế design pattern.
Áp dụng quy tắc đầu tiên Separate the Things That Stay the Same
Để ý đoạn code report, chúng ta có thể thấy dù định dạng xuất khác nhau nhưng về cơ bản format template của report vẫn giống nhau:
- header information
- title
- từng line của report
- trailing stuff
Với mỗi 1 phân đoạn của report, chúng ta có thể tách ra thành các function của class abstract như sau:
class Report
def initialize
@title = 'Monthly Report'
@text = ['Things are going', 'really, really well.']
end
def output_report
output_start
output_head
output_body_start
output_body
output_body_end
output_end
end
def output_body
@text.each do |line|
output_line(line)
end
end
def output_start
raise 'Called abstract method: output_start'
end
def output_head
raise 'Called abstract method: output_head'
end
def output_body_start
raise 'Called abstract method: output_body_start'
end
def output_line(line)
raise 'Called abstract method: output_line'
end
def output_body_end
raise 'Called abstract method: output_body_end'
end
def output_end
raise 'Called abstract method: output_end'
end
end
Từ đây với mỗi loại định dạng xuất report khác nhau, chúng ta có thể dễ dạng tạo thêm 1 subclass tương ứng với mỗi định dạng từ class abstract.
#HTML FORMAT
class HTMLReport < Report
def output_start
puts('<html>')
end
def output_head
puts(' <head>')
puts(" <title>#{@title}</title>")
puts(' </head>')
end
def output_body_start
puts('<body>')
end
def output_line(line)
puts(" <p>#{line}</p>")
end
def output_body_end
puts('</body>')
end
def output_end
puts('</html>')
end
end
#Plain text Format
class PlainTextReport < Report
def output_start
end
def output_head
puts("**** #{@title} ****")
puts
end
def output_body_start
end
def output_line(line)
puts(line)
end
def output_body_end
end
def output_end
end
end
Cách xử lý trên thực chất chính là Template Method Pattern
Ý tưởng của pattern này là xây dựng một class base với 1 method xương sống (còn được gọi là template method). Method này sẽ điều hướng các bộ xử lý bằng cách gọi các methods abtract, các method này sẽ được cài đặt lại ở subclass.
Tham khảo
Github (updating):https://github.com/ducnhat1989/design-patterns-in-ruby
Sách: “DESIGN PATTERNS IN RUBY” của tác giả Russ Olsen
Bài viết liên quan: CÁC NGUYÊN TẮC TRONG DESIGN PATTERN
All rights reserved