Đống khổng lồ if statement kia cứ nhìn chằm chằm vào mặt bạn. Bạn cảm thấy bạn nên có thể đơn giản hóa nó, trừ Business Logic đó cứ mãi cản đường.
Ví dụ, nói bạn có platform bán hàng nơi bạn build Quotes, có nhiều LineItems. Trừ khi, bạn có thể có quote với duplicate line item nếu chúng là quảng cáo (ads), nhưng nếu bạn có nhiều website, bạn phải cộng giá lại với nhau và hiển thị thành một line item đơn. À và cũng thế này, nếu bạn mua website và đã có năm quảng cáo trong quote, bạn phải cho họ giảm 20% trên website.
Tôi nghe thấy bạn ném laptop qua cửa sổ từ đây đến đó.
Bạn có thể viết đống if statement để xử lý quy tắc này:
class Quote attr_accessor :line_items ... def add_line_item(line_item) if line_item.kind_of?(Ad) self.line_items << line_item elsif line_item.kind_of?(Website) if @line_items.select {|item| item.kind_of?(Ad) }.length >= 5 # TODO: Put the fractions of a cent into a bank account # I have set up line_item.price *= 0.8 end existing_website = self.line_items.detect { |item| item.kind_of?(Website) } if existing_website existing_website.price += line_item.price else self.line_items << line_item end end end end Nhưng tôi nghĩ chúng ta đồng ý rằng đó chỉ tồi tệ. Làm sao bạn có thể gỡ rối thứ như vậy?
Bạn có thể phân rã method thành nhiều method nhỏ hơn, nhưng đó giống như nhét tất cả đồ chơi vào tủ áo để mẹ nghĩ bạn dọn phòng. Và những kind_of? đó vẫn khiến tôi bực mình.
Nhưng nếu bạn bắt đầu nhìn từ góc độ của line item, thay vì quote? Thay vì hỏi line item thuộc loại gì và thêm nó vào quote, bạn chỉ bảo line item thêm chính nó vào quote?
Đảo ngược method!
Một trong những cách refactor yêu thích của tôi là thử đảo ngược caller và callee. Đây là ví dụ, dùng code trên:
class Quote ... def add_line_item(line_item) line_item.add_to_quote(self) end end class Ad < LineItem ... def add_to_quote(quote) quote.line_items << self end end class Website < LineItem def add_to_quote(quote) if quote.line_items.select {|item| item.kind_of?(Ad) }.length >= 5 # TODO: Put the fractions of a cent into a bank account # I have set up self.price *= 0.8 end existing_website = quote.line_items.detect { |item| item.kind_of?(Website) } if existing_website existing_website.price += self.price else quote.line_items << self end end end Chưa hoàn hảo. website.rb vẫn cần rất nhiều giúp đỡ refactor, và tôi không hài lòng về việc đảo ngược method làm vỡ encapsulation của line_items.
Nhưng bạn đã gỡ bỏ lớp phức tạp đầu tiên. Bây giờ bạn có thể đặt code ở LineItem hoặc Quote, tùy nơi hợp lý nhất. Object LineItem có thể dùng inheritance và mixin để xử lý sự giống và khác nhau giữa các subclass LineItem. Thêm vào đó, giờ rất dễ thêm subclass LineItem mới mà không làm phồng method add_line_item.
Code của bạn sạch hơn một chút, và linh hoạt hơn rất nhiều. Vì vậy nói chung, tôi coi đó là thắng lợi.
Nơi bạn có thể không muốn dùng pattern này
Mặc dù Reversing Method hữu ích, có lý do bạn có thể không muốn dùng pattern này:
-
Nó có thể phá vỡ encapsulation. Bạn có thể phải expose attribute trên object
Quotemà bạn không muốn public. -
Nó có thể tăng coupling. Cả
QuotevàAdgiờ cần biết nhau. Và tùy vào mức độ họ cần biết nhau, nó có thể làm code phức tạp hơn. -
Nó có thể vi phạm Single Responsibility Principle trên
Ad, vì giờAdcó trách nhiệm biết cách thêm chính nó vàoQuote.
Bạn thường có thể workaround những vấn đề này. Nhưng bạn nên nhận thức chúng, vì bạn không muốn refactoring làm code tồi hơn!
Tại sao nó là một trong những yêu thích của tôi
Dù có những vấn đề đó, đây vẫn là một trong những refactoring yêu thích. Code tôi viết sau khi dùng pattern này xu hướng rõ ràng và tự tin hơn.
Nhưng ngay cả khi không, dùng pattern này khiến tôi suy nghĩ về mối quan hệ giữa object theo cách khác. Khi tôi rơi vào hầm "tính năng này tồi, không tin được phải viết code tồi để xử lý", nó đá não bộ tôi vào thấy cách mới giải quyết vấn đề. Nó buộc tôi suy nghĩ về cách cấu trúc code khác nhau, và điều đó cực kỳ hữu ích.
Hãy thử trong code của bạn
Giống nhiều pattern yêu thích, tôi lần đầu gặp Reversing Method trong Smalltalk Best Practice Patterns, và nó luôn là công cụ quý giá.
Lần sau khi bạn khó xử lý object tương tự với hành vi hơi khác, hãy thử! Nếu bạn thích code mới hơn, giữ nó. Dù bạn không thích, nó cũng sẽ dẫn tâm trí bạn xuống con đường đến code tốt hơn.