12/08/2018, 13:02

Design Pattern - Decorate

Decorate là gì? Một trong số những câu hỏi cơ bản của lập trình là: Làm thế nào có thể thêm được tính năng cho code hay chương trình của bạn mà không cần truyền thêm một mớ hỗ độn vào khiến nó cồng kềnh thêm và khó quản lý. Có một số design pattern sẽ giúp bạn làm được điều này khá tốt như: ...

Decorate là gì?

Một trong số những câu hỏi cơ bản của lập trình là: Làm thế nào có thể thêm được tính năng cho code hay chương trình của bạn mà không cần truyền thêm một mớ hỗ độn vào khiến nó cồng kềnh thêm và khó quản lý.

Có một số design pattern sẽ giúp bạn làm được điều này khá tốt như: Observer pattern, Composites và Iterators. Tuy nhiên nếu bạn chỉ muốn đơn giản là thay đổi một chút về cách làm việc hay hiển thị của chương trình thì Decorator pattern là lựa chọn hoàn hảo cho bạn.

Hãy tưởng tượng rằng bạn có một số văn bản cần phải được ghi vào một tập tin. Nghe có vẻ đơn giản, nhưng trong hệ thống của bạn đôi khi bạn chỉ muốn viết text đơn thuần thông thường, trong khi ở thời điểm khác bạn muốn đánh số mỗi dòng như nó khi xuất chúng ra. Đôi khi bạn muốn thêm một thời gian để mỗi dòng vào các tập tin. Đôi khi bạn cần một tổng kiểm tra từ các văn bản để sau này bạn có thể đảm bảo rằng nó đã được viết và lưu trữ đúng cách...

Chúng ta có đoạn code sau giúp xử lý vấn đề này:

class EnhancedWriter
  attr_reader :check_sum
  def initialize(path)
    @file = File.open(path, "w")
    @line_number = 1
  end

  def write_line(line)
    @file.print(line)
    @file.print("
")
  end

  def checksumming_write_line(data)
    data.each_byte {|byte| @check_sum = (@check_sum + byte) % 256 }
    @check_sum += "
"[0] % 256
    write_line(data)
  end

  def timestamping_write_line(data)
    write_line("#{Time.new}: #{data}")
  end

  def numbering_write_line(data)
    write_line("%{@line_number}: #{data}")
    @line_number += 1
  end

  def close
    @file.close
  end
end

Với đoạn code này, bạn có thể xuất ra dạng plain text

writer = EnhancedWriter.new('out.txt')
writer.write_line("A plain line")

hay thêm checksum

writer.checksumming_write_line('A line with checksum')
puts("Checksum is #{writer.check_sum}")

hoặc thêm timestamp ở mỗi dòng

writer.timestamping_write_line('with time stamp')
writer.numbering_write_line('with line number')

Cách làm này đáp ứng được yêu cầu đặt ra nhưng có một vấn đề. Đó là phương pháp này ném tất cả mọi thứ vào 1 class.Nó sẽ khiến mọi thứ trở nên mất kiểm soát và đôi khi là thừa vì tất cả mọi thứ đều được ném vào, không cần biết nó có được sử dụng hay không.

Bạn có thể nghĩ tới việc tách chúng ta bằng cách sử dụng một base class và các sub class giống hình dưới decorate1.PNGdecorate2.PNG

Việc sinh ra quá nhiều subclass kế thừa như vậy thực sự không thể bao quát hết tất cả những thay đổi những sự kết hợp khác nhau trong thiết kế. Có một giải pháp tốt hơn cho việc này.

Đầu tiên xây dựng một class có chức năng cơ bản

class SimpleWriter
  def initialize(path)
    @file = File.open(path, 'w')
  end

  def write_line(line)
    @file.print(line)
    @file.print("
")
  end

  def pos
    @file.pos
  end

  def rewind
    @file.rewind
  end

  def close
    @file.close
  end
end

Bây giờ nếu bạn muốn đánh số các dòng, hãy thêm vào 1 đối tượng trung gian giữa SimpleWriter và Client

class WriterDecorator
  def initialize(real_writer)
    @real_writer = real_writer
  end

  def write_line(line)
    @real_writer.write_line(line)
  end

  def pos
    @real_writer.pos
  end

  def rewind
    @real_writer.rewind
  end

  def close
    @real_writer.close
  end
end

class NumberingWriter < WriterDecorator
  def initialize(real_writer)
    super(real_writer)
    @line_number = 1
  end

  def write_line(line)
    @real_writer.write_line("#{@line_number}: #{line}")
    @line_number += 1
  end
end

Class NumberingWriter thể hiện core interface tương tự như plain text, về cơ bản cả hai cách đều giống nhau. Ví dụ để đánh số dòng, chúng ta chỉ cần đặt SimpleWriter vào NumberingWriter:

writer = NumberingWriter.new(SimpleWriter.new('final.txt'))
writer.write_line('Hello out there')

Formal Decorate

decorate3.PNG ConcreteComponent là object sẽ implement các chức năng cơ bản. Nó chính là SimpleWritertrong ví dụ trước.

Class Decorator thì tham chiếu tới một Component, và nó implement tất cả các phương thức của Component. Trong ví dụ, 3 class Decorate là NumberingWriter, CheckSummingWriter, TimeStampingWriter.

Mỗi Decorate sẽ tác động theo các cách khác nhau để biến đổi các method có sẵn.

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
0