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.