11/08/2018, 23:17

Strategy Pattern trong Ruby

Như đã nhắc tới trong bài viết về Template Method Pattern trong Ruby , pattern này giúp chúng ta thay đổi 1 phần của thuật toán, tách đoạn xử lý phức tạp trong thuật toán ra cho các subclass xử lý, nó giúp chúng ta tối giản hoá thuật toán. Có thể nói pattern này xử lý khá hiệu quả, và đã đáp ứng ...

Như đã nhắc tới trong bài viết về Template Method Pattern trong Ruby, pattern này giúp chúng ta thay đổi 1 phần của thuật toán, tách đoạn xử lý phức tạp trong thuật toán ra cho các subclass xử lý, nó giúp chúng ta tối giản hoá thuật toán. Có thể nói pattern này xử lý khá hiệu quả, và đã đáp ứng được nguyên tắc đầu tiên trong thiết kế pattern "Separate out the things that change from those that stay the same.". Tuy nhiên, nó lại đang vi phạm nguyên tắc số 3 "Prefer composition over inheritance" vì cách này sử dụng nhiều tới kế thừa dẫn tới một số hạn chế và nhược điểm:

  • Các subclass sẽ bị rối theo superclass nếu bạn thiết kế không cẩn thận.
  • Hạn chế sự linh hoạt về thời gian thực thi
  • Một khi đã chọn được phần biến đổi trong thuật toán, tương ứng với việc chọn format của report, thì việc thay đổi lại sẽ gặp khó khăn. Vì nếu muốn tạo 1 report theo format khác, chúng ta sẽ phải tạo một report hoàn toàn mới.
reportHTML = HTMLReport.new
reportHTML.output_report
#Để tạo 1 report với format khác chúng ta phải tạo 1 đối tượng mới
reportPlain = PlainTextReport.new
reportPlain.output_report

Để giải quyết vấn đề này, chúng ta có thể áp dụng theo nguyên tắc 4 của GoF “Delegate, delegate, delegate.”

class Formatter
  def output_report( title, text )
    raise 'Abstract method called'
  end
end
#HTML
class HTMLFormatter < Formatter
  def output_report( title, text )
    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
#PlainText
class PlainTextFormatter < Formatter
  def output_report(title, text)
    puts("***** #{title} *****")
    text.each do |line|
      puts(line)
    end
  end
end

Với class Report, ta có thể loại bỏ các chi tiết format cho báo cáo vì nó đã được cài đặt trong class Format

class Report
  attr_reader :title, :text
  attr_accessor :formatter

  def initialize(formatter)
   @title = 'Monthly Report'
   @text = ['Things are going', 'really, really well.']
   @formatter = formatter
  end

  def output_report
   @formatter.output_report( @title, @text )
  end
end

Ý tưởng của pattern strategies là xác định tập các đối tượng làm những việc giống nhau (trong ví dụ việc này là Formatter).

Strategy Pattern

Bây giờ, chúng ta có thể dễ dàng chuyển đổi giữa các format report khác nhau mà không cần khởi tạo đối tượng mới

report = Report.new(HTMLFormatter.new)
report.output_report
####
report.formatter = PlainTextFormatter.new
report.output_report

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
  • Template Method Pattern trong Ruby
0