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

Tránh các lớp ngăn kéo rác trong Ruby

Bởi vì Ruby là một ngôn ngữ hướng đối tượng, chúng ta có xu hướng mô hình hóa thế giới như một tập hợp các đối tượng. Chúng ta nói rằng hai số nguyên (x và y) là một Điểm và một Đường có hai trong số chúng.

Mặc dù cách tiếp cận này thường hữu ích, nhưng nó có một vấn đề lớn. Nó có đặc quyền giải thích dữ liệu hơn tất cả những thứ khác. Nó giả định rằng x và y sẽ luôn là một Điểm và bạn sẽ không bao giờ cần chúng hoạt động như một Ô hoặc Vectơ.

Điều gì xảy ra khi bạn cần một Cell? Chà, Point sở hữu dữ liệu. Vì vậy, bạn thêm một phương thức ô của mình vào Point. Theo thời gian, bạn kết thúc với một lớp Point là một ngăn kéo rác chứa các phương thức liên quan đến lỏng lẻo.

Các lớp ngăn kéo rác phổ biến đến mức chúng ta thường chấp nhận chúng như là điều không thể tránh khỏi và thực hiện một bước "tái cấu trúc" để phát triển. Nhưng điều gì sẽ xảy ra nếu chúng ta có thể tránh được vấn đề ngay từ đầu?

Quy tắc đầu tiên của phát triển web là chúng tôi không nói về lớp Người dùng

Trung tâm của bất kỳ ứng dụng Rails sản xuất có tính chiến đấu nào là sự quái dị khổng lồ của một lớp được gọi là User .

Nó luôn luôn bắt đầu một cách ngây thơ. Bạn muốn cho phép mọi người đăng nhập. Bạn cần lưu trữ tên người dùng và mật khẩu của họ, vì vậy bạn tạo một lớp:

class User
  attr_accessor :username, :password, :email, :address

  def authenticate!(password)
    ...
  end
end

Điều đó hoạt động tốt đến mức cuối cùng mọi người muốn cung cấp cho bạn tiền. "Được," chúng tôi nói "Người dùng về cơ bản giống với Người đăng ký nên chúng tôi sẽ chỉ thêm một vài thuộc tính và một vài phương pháp."

class User
    ...

    attr_accessor :payment_processor_token, :subscription_plan_id, ...etc

    def charge_cc
      ...
    end
end

Tuyệt quá! Bây giờ chúng tôi đang kiếm một số tiền thực sự! Giám đốc điều hành đã quyết định rằng họ muốn có thể xuất VCards với thông tin liên hệ của người dùng để nhóm bán hàng có thể dễ dàng nhập chúng vào SalesForce. Đã đến lúc khởi động khí lực:

class User
    ...

    def export_vcard
      ...
    end
end

Chúng tôi đã làm gì?

Chúng tôi bắt đầu với một lớp Người dùng, có mục đích duy nhất là xử lý xác thực. Bằng cách thêm các phương thức và thuộc tính bổ sung vào nó, chúng tôi đã biến nó thành một kết hợp Người dùng / Người đăng ký / Liên hệ với Frankenstein.

Tại sao chúng tôi sẽ làm điều này? Chúng ta không tôn trọng mình với tư cách là nhà phát triển? Cha mẹ của chúng ta đã không đủ yêu thương chúng ta? Hay chúng tôi đã tự thiết lập cho mình sự thất bại khi tin rằng dữ liệu này thực sự một sự vật, một đối tượng?

Tại sao chúng tôi nói rằng tổ hợp tên người dùng, mật khẩu và địa chỉ email là Người dùng? Tại sao không phải là Người đăng ký? Hay một Liên hệ? Hay một SessionHolder?

Sự thật về dữ liệu là dữ liệu đó

Dữ liệu chỉ là dữ liệu. Hôm nay bạn có thể cần phải đối xử với nó như một người bạn trai. Ngày mai có thể là bạn trai cũ.

Đây là cách tiếp cận mà các ngôn ngữ chức năng như Elixir thực hiện. Dữ liệu được lưu trữ trong các cấu trúc đơn giản giống như hàm băm, mảng, chuỗi của Ruby, v.v. Khi bạn muốn làm điều gì đó với dữ liệu, bạn chuyển nó vào một hàm. Mọi kết quả đều được trả về từ chức năng đó.

Nghe có vẻ đơn giản, nhưng cách tiếp cận này giúp bạn dễ dàng tách các mối quan tâm thành các mô-đun khác nhau.

Đây là bức tranh biếm họa về cách chúng tôi có thể xây dựng hệ thống Người dùng của mình trong Elixir:

my_user = %{username: "foo", password: ..., phone: ..., payment_token: ...}
my_user = Authentication.authenticate(my_user)
my_user = Subscription.charge(my_user)
my_user = Contact.export_vcard(my_user)

Bởi vì dữ liệu tách biệt với mã, không mô-đun nào có đặc quyền giải thích dữ liệu đó.

Đưa nó trở lại Ruby

Vì cách tiếp cận này hoạt động rất tốt trong Elixir, tại sao không áp dụng nó trong Ruby? Không có gì ngăn cản chúng tôi tạo User thành một trình bao bọc đơn giản cho dữ liệu của nó và kéo tất cả logic nghiệp vụ vào các mô-đun.

class User
  attr_accessor :username, :password, :email, :address
end

module Authentication
  def self.authenticate!(user)
    ..
  end
end

module Subscription
  def self.charge(user)
    ..
  end
end

module Contact
  def self.export_vcard(user)
    ..
  end
end

Bạn thậm chí có thể tạo User lớp vào một cấu trúc hoặc thêm mã để làm cho nó (loại-of) bất biến.

Vậy thì sao?

Đây có vẻ như là một thay đổi nhỏ? Vấn đề là gì?

Như chúng ta đã thấy, cách tiếp cận OO điển hình có vấn đề khi nó già đi. Bằng cách nói rằng một lớp nhất định sở hữu dữ liệu, chúng tôi gặp sự cố khi chúng tôi cần sử dụng dữ liệu đó theo cách khác.

Tuy nhiên, trong cách tiếp cận mô-đun, việc thêm hành vi không có vấn đề gì. Chúng tôi chỉ cần tạo một mô-đun mới có thể diễn giải dữ liệu theo bất kỳ cách nào mà nó cần. Thật dễ dàng để làm như vậy vì dữ liệu và chức năng hoàn toàn riêng biệt.

Các phương pháp tiếp cận khác

Có những cách tiếp cận khác để ngăn chặn vấn đề ngăn kéo rác. Theo truyền thống, trong lập trình hướng đối tượng, bạn sẽ cố gắng làm như vậy thông qua kế thừa và tái cấu trúc cẩn thận. Vào năm 2012, Sandi Metz đã xuất bản Thiết kế hướng đối tượng thực tế trong Ruby điều này đã thuyết phục nhiều người bắt đầu soạn các đối tượng bằng cách sử dụng tính năng tiêm phụ thuộc. Thậm chí gần đây, sự phổ biến của lập trình chức năng đã khiến các Rubyists thử nghiệm với các "đối tượng dữ liệu" bất biến.

Tất cả các cách tiếp cận này có thể được sử dụng để tạo mã đẹp, sạch. Tuy nhiên, thực tế là các lớp thường sở hữu dữ liệu có nghĩa là sẽ luôn có sự căng thẳng giữa lớp và lớp làm gì với dữ liệu.

Tôi nghi ngờ rằng bất cứ thành công nào mà các phương pháp này mang lại trong việc tránh vấn đề ngăn kéo rác là kết quả từ việc tách dữ liệu khỏi mã của chúng.