Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Ngăn Chặn Unused Classes Trong Ruby: Hướng Dẫn Clean Object Modeling

Vì Ruby là object-oriented language, ta tend model world as set of objects. Nói two integers (x, y) là Point, Line có hai Points.

Approach này often useful, nhưng one big problem. Nó privileges one interpretation data over all others. Assumes x, y always Point và never need act as Cell hoặc Vector.

Khi need Cell? Point owns data. Add cell methods to Point. Over time Point class = junk drawer loosely-related methods.

Junk-drawer classes common đến mức accept as inevitable và tack on "refactoring" step. But what if avoid problem first place?

First Rule Web Development: We Don't Talk About User Class

Heart của battle-hardened production Rails app: gigantic monstrosity class User.

Start innocently. Want people log in. Store username, password, create class:

class User
  attr_accessor :username, :password, :email, :address
  def authenticate!(password) ... end
end

Works well, eventually people want give money. "OK, User basically same Subscriber, add few attributes/methods."

class User
  ...
  attr_accessor :payment_processor_token, :subscription_plan_id, ...etc
  def charge_cc ... end
end

Great! Making real money! CEO muốn export VCards contact info cho sales team import SalesForce. Fire up vim:

class User
  ...
  def export_vcard ... end
end

What Have We Done?

Start User class, purpose chỉ handle authentication. Add methods/attributes → User/Subscriber/Contact Frankenstein hybrid.

Tại sao? Không respect ourselves as developers? Parents không love enough? Hay setup failure bằng believe data really is a thing, object?

Tại sao nói username, password, email combo là User? Tại sao không Subscriber? Contact? SessionHolder?

Truth About Data: It's Just Data

Data just data. Today treat như boyfriend. Tomorrow ex-boyfriend.

Approach functional languages như Elixir. Data stored simple structures như Ruby hashes, arrays, strings. Want do something với data → pass to function. Results returned từ function.

Sounds simple, nhưng approach này makes easy separate concerns vào different modules.

Cartoon construct User system 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)

Vì data separate từ code, no module có privileged interpretation data.

Bringing It Back To Ruby

Approach works well Elixir, why not adopt Ruby? Nothing stopping make User simple wrapper data, pull business logic vào modules.

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

Could even make User struct, hoặc add code make (kind-of) immutable.

So What?

Seem trivial change? Point?

Typical OO approach problems as ages. Saying class owns data → problems khi need use data other way.

Modular approach, adding behavior no problems. Create new module interpret data whatever way needs. Easy vì data và functionality completely separate.

Other Approaches

Other approaches prevent junk-drawer problem. Traditionally OOP: inheritance và careful refactoring. 2012 Sandi Metz published Practical Object Oriented Design in Ruby convinced many compose objects dùng dependency injection. Recently, functional programming popularity caused Rubyists experiment immutable "data objects".

All approaches create clean, beautiful code. However fact classes typically own data → always tension giữa what class is và what class does với data.

Suspect success these approaches avoid junk-drawer từ decoupling data từ code.