Computer >> Máy Tính >  >> Lập trình >> Ruby

Một Decorator so với một Subclass

Trong bài viết gần đây nhất của mình, tôi đã đề cập đến một tính năng mới tuyệt vời trong Rails 5.1, delegate_missing_to . Với delegate_missing_to , bất kỳ phương thức nào mà bạn không thể tìm thấy trên một đối tượng sẽ được gọi trên một đối tượng khác, thay vào đó :

class Player
  delegate_missing_to :@user

  def initalize(user)
    @user = user
  end

  def points
    Game.points_for_user(user.id)
  end
end

Player.new(user).name # calls user.name

Nhưng, giống như Gavin đã đề cập trong các bình luận, đây có vẻ như là một cách kỳ quặc để tránh thừa kế. Tại sao không chỉ sử dụng một lớp con? Bạn sẽ nhận được hiệu ứng tương tự và bạn không cần phải thêm một tính năng hoàn toàn mới. Có vẻ như một điều kỳ lạ cần thêm vào.

Phải có lý do delegate_missing_to đã được thêm vào, mặc dù. Và đối với các tính năng của Rails, các yêu cầu kéo là một cách tuyệt vời để tìm ra những lý do đó. Trong yêu cầu kéo này, DHH đã đề cập lý do tại sao anh ấy đề xuất tính năng:

Dưới đây là một mẫu phổ biến nếu bạn muốn xây dựng một người trang trí:

Đó có vẻ là một nơi khá tốt để bắt đầu đào.

Tại sao nên trang trí?

Khi bạn xây dựng một trình trang trí, bạn đang thay đổi cách một đối tượng hoạt động mà không cần tạo một lớp con mới. Ví dụ:trong mã trước đó:

class Player
  delegate_missing_to :@user

  def initalize(user)
    @user = user
  end

  def points
    Game.points_for_user(user.id)
  end
end

Bạn sẽ nói rằng “Trình phát trang trí người dùng ”vì Người chơi hầu như hoạt động giống như Người dùng, nhưng có thêm một phương thức, points . Và nó thực hiện điều này mà không cần kế thừa.

Tại sao bạn cần một cái gì đó như thế này? Đó là một câu hỏi khó trả lời, bởi vì giống như nhiều mẫu thiết kế, không phải lúc nào bạn cũng rõ ràng muốn sử dụng nó ở đâu thay vì thứ khác.

Khi nào bạn sử dụng trình trang trí?

Decorator có thể là một cách phức tạp hơn để thực hiện kế thừa. Ý tôi là, dòng mã nào trong hai dòng mã này tốt hơn?

player = Player.new(User.new(name: "Justin")) # Player decorates User
player = Player.new(name: "Justin")           # Player subclasses User

Rõ ràng là cái thứ hai, phải không? Ở đây, việc tạo Trình phát làm trình trang trí thay vì lớp con chỉ là một sự lãng phí mã.

Nhưng đôi khi, bạn muốn thêm chức năng vào một đối tượng sau này, cách xa nơi bạn đã tạo đối tượng. Ví dụ, nếu bạn có mã như thế này thì sao?

user = User.find(1)

... some time later ...

player = Player.new(user)

Bây giờ, bạn có thể tạo người dùng theo bất kỳ cách nào bạn muốn, bằng bất kỳ phương pháp nào bạn muốn. Mã tạo đối tượng Người dùng không biết hoặc không quan tâm đến việc lớp Người chơi thậm chí còn tồn tại. Và bạn vẫn có thể sử dụng đối tượng Người dùng ban đầu nếu bạn không muốn các phương thức bổ sung đó nữa.

Điều này giúp bạn tách hành vi thành các lớp khác nhau. Mỗi lớp có thể tập trung vào cách đối tượng Người dùng sẽ được sử dụng trong một tình huống cụ thể - Người chơi, Nhân viên, Nhà phát triển. Với tính kế thừa, rất dễ để tất cả những thứ này trở nên lộn xộn với nhau.

MrChris đã đề cập đến một lợi ích khác cho người trang trí trong phần bình luận:

Khi bạn trang trí một đối tượng, bạn chỉ có thể gọi các phương thức công khai trên đối tượng đó. Khi bạn phân lớp, bạn có thể gọi bất kỳ phương thức nào, ngay cả những phương thức riêng tư. Điều đó có thể khiến các lớp con bị hỏng thường xuyên hơn, vì chúng có thể vô tình dựa vào chi tiết triển khai của cha mẹ chúng. Những chi tiết đó thường sẽ thay đổi thường xuyên hơn so với các phương pháp công khai.

Trình trang trí có thể đặc biệt hữu ích khi bạn chia nhỏ các lớp lớn. Với người trang trí, việc tuân theo Nguyên tắc một người chịu trách nhiệm trở nên dễ dàng hơn - mỗi người trang trí có thể làm một việc và làm tốt việc đó và bạn có thể kết hợp những người trang trí để có được hành vi phức tạp hơn.

Có rất nhiều cách để chia sẻ hành vi trong Ruby. Bạn có thể phân lớp, bạn có thể kết hợp các mô-đun, thậm chí bạn có thể lấy các phương thức ra khỏi một lớp và gắn chúng vào một lớp khác nếu bạn muốn. Tuy nhiên, mẫu trang trí mang lại cho bạn một chút gì đó khác biệt. Bạn chỉ đang sử dụng các biến phiên bản và lệnh gọi phương thức, các khối xây dựng của bất kỳ ngôn ngữ hướng đối tượng nào. Từ những nguyên tắc cơ bản đó, bạn có thể có hành vi linh hoạt trong khi ứng dụng của bạn đang chạy - tất cả mà không làm phức tạp mã của bạn.