Template Method Pattern trong Ruby
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á ...
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
: CÁC NGUYÊN TẮC TRONG DESIGN PATTERN