Theo Wikipedia, lập trình hướng đối tượng (OOP) là một paradigm lập trình dựa trên khái niệm "objects", có thể chứa dữ liệu và code: dữ liệu dưới dạng fields (thường gọi là attributes hoặc properties) và code dưới dạng procedures (thường gọi là methods).
Ruby là ngôn ngữ hướng đối tượng thuần túy, nghĩa là trong Ruby, mọi thứ đều là object. Các object này, bất kể là string, number, class, module, v.v., đều hoạt động trong một hệ thống gọi là The Object Model.
Ruby cung cấp phương thức object_id, có sẵn cho mọi object. Identifier này trả về integer và không bao giờ giống nhau cho bất kỳ hai object nào. Hãy vào irb để thực hành; bạn làm bằng cách gõ irb trong terminal.

Như thấy ở trên, string, integer, array, class, và thậm chí method đều là object, vì chúng có object ID.
Trong bài này, chúng ta sẽ đi sâu vào các khái niệm sau:
- Classes và instances
- Kế thừa (Inheritance)
- Public, private, và protected methods
- Mixins
- Modules
- Object hierarchy
Classes và Instances
Trong Ruby, classes là nơi định nghĩa attributes và attitudes (hành động) của một object. Nếu object là tròn (một attribute) và có thể nói chuyện (một hành động), chúng ta biết từ class nó thuộc về vì các attribute và hành động này được định nghĩa như methods trong class đó. Object thuộc về class gọi là instance của class đó và được tạo (instantiated) bằng .new. Hãy bắt đầu tạo class tên Human. Giả sử chúng ta đều là human, nên sẽ vui.
class Human
def initialize(name)
@name = name
end
end
Phương thức initialize xác định yêu cầu để tạo instance mới của class. Trong trường hợp trên, để tạo human mới, cần một name. Do đó, instance mới của human được tạo bằng lệnh Human.new(name), nơi name là bất cứ thứ bạn chọn. Trong trường hợp chúng ta, dùng 'Henry'. Để test trong môi trường irb, chỉ cần load file bằng lệnh load './path_to_filename' và tái sử dụng lệnh để chạy lại file khi có thay đổi. Trong trường hợp tôi, là load './Human.rb' vì irb mở trong thư mục chứa file. Để chạy code không qua irb, cần thêm statement puts trước mọi lệnh để kết quả hiển thị.

Khi thử tạo human mới không có name, ta nhận argument error vì name bắt buộc. Tuy nhiên, khi làm đúng, ta thấy human tên Henry được tạo và thuộc class Human. Henry do đó là instance của class Human.
Biến @name gọi là instance variable vì ký hiệu @ ở đầu, nghĩa là biến này có thể tham chiếu bởi bất kỳ phương thức nào trong class, miễn là instance class đó tồn tại. Ở đây, chúng ta tạo nó và gán bằng name dùng để khởi tạo object.
Tiếp tục định nghĩa traits và actions của bất kỳ object thuộc class này. Vì object được tạo gọi là instances của class, các phương thức định nghĩa hành vi của chúng gọi là instance methods. Human có tên, bộ phận cơ thể nhất định và có thể thực hiện hành động, nên hãy định nghĩa chúng.
def name
@name
end
def no_of_legs
2
end
def no_of_hands
2
end
def speak
'blablabla'
end
Chúng ta thêm phương thức lấy tên human, định nghĩa số chân và tay, và cho human khả năng nói. Chúng ta có thể gọi các phương thức này trên instance class bằng instance.method_name, như dưới đây.

Điều gì nếu đổi ý muốn đổi tên instance Henry? Ruby có built-in method để làm vậy, nhưng cũng có thể làm thủ công. Thủ công, ta có thể đổi phương thức name từ chỉ getter lấy name thành setter cũng gán giá trị nếu cung cấp.
def name=(new_name)
@name = new_name
end
Dùng built-in attr_accessor của Ruby, ta có thể bỏ phương thức name và thay bằng dòng attr_accessor :name:
class Human
attr_accessor :name
def initialize(name)
@name = name
end
# rest of the code
end
Bất kể phương pháp chọn, cuối cùng kết quả như sau.

Tất cả phương thức tạo cho đến nay gọi là instance methods vì chúng có thể gọi trên bất kỳ instance nào của class nhưng không phải class chính nó. Cũng có class method, có thể gọi trên chính class chứ không phải instances. Class methods được đặt tên bằng tiền tố self.. Ví dụ:
# within the Human class
def self.introduction
"I am a human, not an alien!"
end

Như thấy, class method chỉ dành cho class, không phải instances. Ngoài ra, như instance variables, ta có class variables, tiền tố hai ký hiệu @. Ví dụ:
class Human
attr_accessor :name
@@no_cars_bought = 0 #class variable
def initialize(name)
@name = name
end
def self.no_cars_bought
@@no_cars_bought
end
def buy_car
@@no_of_cars_bought += 1
"#{@name} just purchased a car"
end
end
Trong ví dụ này, thêm phương thức buy_car cho phép mỗi human mua xe. Tạo class variable @@no_of_cars_bought tăng 1 mỗi lần mua xe. Cuối cùng, tạo class method no_cars_bought lấy số xe đã mua. Xem cách hoạt động:

Tất cả phương thức định nghĩa cho đến nay gọi là public methods vì accessible bên ngoài class. Chúng ta cũng có thể định nghĩa private methods, chỉ truy cập được trong class. Thử ví dụ.
# at the bottom of the Human class
def say_account_number
"My account number is #{account_number}"
end
private
def account_number
"1234567890"
end
Điều này cho kết quả:

Khi gọi henry.account_number, ta nhận "NoMethodError" vì account_number là private method chỉ truy cập được từ trong class. Khi truy cập qua phương thức say_account_number như làm, không lỗi, vì phương thức này tồn tại trong cùng class với private method. Quan trọng lưu ý mọi instance method sau từ khóa private trở thành private method; do đó private methods nên định nghĩa ở cuối class sau tất cả public methods.
Bạn từng nghe về protected methods? Đúng rồi! Chúng cũng tồn tại, nhưng chúng ta sẽ nói sau khi hiểu khái niệm inheritance.
Kế thừa (Inheritance)
Bây giờ biết về classes và instances, hãy nói về inheritance. Để hiểu đúng kế thừa, tạo class mới Mammal vì human là mammal. Tôi nhớ cụm từ lớp khoa học: "Tất cả human là mammal, nhưng không phải mammal nào cũng là human". Tôi cũng nhớ đặc điểm mammal bao gồm lông hoặc lông thú và não phức tạp. Đưa thông tin này vào class Mammal.
class Mammal
def has_hair?
"Most certainly, Yes"
end
def has_complex_brain?
"Well, as a mammal, what do you expect?"
end
end
Bạn nhớ cụm từ lớp khoa học? Nếu vậy, quan trọng tạo mối quan hệ xác nhận câu đó. Vậy làm gì? Cho phép human class kế thừa properties của class Mammal bằng thêm < Mammal vào định nghĩa class.
class Human < Mammal
# all other code
end
Class kế thừa properties của class khác gọi là subclass, class được kế thừa gọi là superclass. Trong trường hợp, Human là subclass, Mammal là superclass. Quan trọng lưu ý nếu định nghĩa tất cả class trong một file như đang làm, định nghĩa class Mammal nên đặt trước class Human trong file, vì không muốn tham chiếu biến trước khi định nghĩa. Xem human có thêm tính năng gì.

Như trên, human giờ truy cập được tất cả attributes định nghĩa trong class Mammal. Nếu xóa kế thừa (bỏ < Mammal khỏi dòng code) và chạy henry.class.superclass, ta nhận "Object". Điều này cho thấy mọi class khi không trực tiếp kế thừa class khác có superclass là "Object", củng cố thêm rằng trong Ruby, ngay cả class cũng là object.

Bây giờ biết superclass, đây là lúc nói về từ khóa super. Ruby cung cấp từ khóa này để tái sử dụng và sửa đổi methods đã tồn tại trên superclass. Trong superclass Mammal, nhớ method has_hair?; nếu muốn thêm thông tin bổ sung đặc thù human khi method đó gọi? Đó là nơi dùng super keyword. Trong class Human, định nghĩa method cùng tên has_hair?.
def has_hair?
super + ", but humans can be bald at times"
end
Khi từ khóa super gọi, Ruby tìm method đó trong superclass và trả kết quả. Trong method trên, ta thêm thông tin thêm vào kết quả từ method has_hair? của superclass.

Ruby chỉ hỗ trợ single class inheritance, nghĩa là chỉ kế thừa properties từ một class. Tôi chắc bạn tự hỏi nếu muốn thêm nhiều properties không đặc thù class nào vào class. Ruby cũng dự phòng cho trường hợp này dưới dạng mixins, nhưng trước hãy nói về protected methods.
Protected Methods
Protected methods hoạt động như private methods: chúng có sẵn để gọi trong class và subclasses. Tạo protected method trong class Mammal.
#at the bottom of the Mammal class
def body_status
body
end
protected
def body
"This body is protected"
end

Như trên, không thể truy cập method body vì nó protected. Tuy nhiên, có thể truy cập từ method body_status, method khác trong class Mammal. Cũng có thể truy cập protected methods từ subclasses của class định nghĩa nó. Thử trong class Human.
# within the Human class
def check_body_status
"Just a human checking that " + body.downcase
end

Như trên, bất kể method body định nghĩa trong class Human và protected, class Human truy cập được vì nó subclass của Mammal. Truy cập subclass này cũng có thể với private methods; tuy nhiên, điều này dẫn đến câu hỏi khác biệt giữa các method này là gì.
Protected methods có thể gọi dùng explicit receivers, như receiver.protected_method, miễn receiver là từ khóa self hoặc cùng class với self. Tuy nhiên, private methods chỉ có thể gọi dùng explicit receivers khi explicit receiver là self. Sửa code bằng thay body.downcase thành self.body.downcase trong check_body_status và thay đổi private method call cũng include self.
def check_body_status
"Just a human checking that " + self.body.downcase # body method is protected
end
def say_account_number
"My account number is #{self.account_number}" # account_number method is private
end

P.S.: Trước Ruby 2.7, khi gọi private method lấy thông tin, dùng self là không thể.
Tiếp tục thay self bằng object cùng class với self. Trong trường hợp, self là Henry, người gọi methods, và Henry là instance của class Human, kế thừa từ Mammal.
def check_body_status
non_self = Human.new('NotSelf') #same class as self
puts "Just #{non_self.name} checking that " + non_self.body.downcase
# Mammal.new is also in the same class as self
"Just a human checking that " + Mammal.new.body.downcase
end
def say_account_number
non_self = Human.new('NotSelf')
"My account number is #{non_self.account_number}"
end

Như trên, có thể thay self trong protected methods bằng object cùng class với self, nhưng không thể với private methods.
Mixins
Mixins là tập code định nghĩa trong module, khi included vào hoặc extended đến bất kỳ class nào, cung cấp khả năng thêm cho class đó. Nhiều module có thể thêm vào class, không giới hạn, khác với class inheritance. Theo thông tin này, tạo module Movement thêm khả năng di chuyển cho human class.
module Movement
def hop
"I can hop"
end
def swim
"Ermmmm, I most likely can if I choose"
end
end
Bước tiếp theo include module này vào class Human. Làm bằng thêm cụm include <module_name> vào class. Trong trường hợp, thêm include Movement vào human class.
class Human < Mammal
include Movement
# rest of the code
end
Test code:

Như trên, methods mixed vào class qua module chỉ có sẵn cho instances của class, không phải class chính nó. Nếu muốn methods trong module có sẵn cho class chứ không phải instance? Thay "include" bằng "extend", như extend Movement.

Thuật ngữ .extend cũng dùng trên class instances nhưng cách khác. Tạo module khác Parent.
module Parent
def has_kids?
"most definitely"
end
end

Như trên, dùng .extend(Parent) trên biến dad làm method has_kids? có sẵn cho nó. Đặc biệt hữu ích khi không muốn methods của module mixed vào mọi instance. Vì vậy, extend chỉ dùng trên instance cụ thể chúng ta quan tâm.
Modules
Modules là nơi chứa classes, methods, constants, thậm chí modules khác. Ngoài việc dùng như mixins, modules có dùng khác. Chúng là cách tuyệt vời tổ chức code để tránh tên trùng lặp, vì cung cấp lợi ích namespacing. Trong Ruby, mọi class là module, nhưng không module nào là class, vì modules không thể instantiated cũng không thể inherited. Để hiểu namespacing, tạo module chứa constant, class, method, và module khác.
module Male
AGE = "Above 0"
class Adult
def initialize
puts "I am an adult male"
end
end
def self.hungry
puts "There's no such thing as male food, I just eat."
end
module Grown
def self.age
puts "18 and above"
end
end
end
Bạn có thể nhận thấy methods định nghĩa và gọi trong modules trên có tiền tố self.. Điều này vì modules không thể instantiated, và method nào định nghĩa trong chúng không có tiền tố self. chỉ có sẵn như mixin, như thấy trước khi thảo luận mixins. Để gọi method trên module, phải xác định trên tên method qua self.method_name. Nhớ chúng ta cũng dùng self trong class methods; nguyên tắc giống hệt.

Như trên, để truy cập classes, modules, constants định nghĩa trong module, dùng module_name::target_name. Để hiểu đúng namespacing, tạo module khác chứa class tên Adult, xem cách phân biệt hai class.
module Female
class Adult
def initialize
puts "I am an adult female"
end
end
def self.hungry
puts "Maybe there's such a thing as female food, I'm not sure. I just need to eat"
end
end

Như trên, có hai class mang tên "Adult". Bằng bọc chúng trong modules riêng, ta dùng tên này không nhầm lẫn về class Adult nào được gọi. Hơn nữa, code dễ đọc, tổ chức hơn, và thực hiện nguyên tắc separation of concerns, tách các block code khác nhau thành nhiều category dựa trên chức năng.
Object Hierarchy
Việc hiểu classes, instances, modules, và inheritance tuyệt vời, nhưng kiến thức chưa hoàn chỉnh nếu không hiểu object hierarchy trong Ruby. Điều này đề cập đến thứ tự tìm method trong Ruby. Một vài method giúp hiểu hierarchy; một là ancestors. Method này không dùng cho class instances nhưng gọi trên chính classes và ancestors. Ví dụ:

Từ kết quả Human.ancestors, method trả về class, modules included trực tiếp, parent class, modules included trực tiếp của parent class; vòng lặp tiếp tục đến Basic Object, gốc của tất cả objects.
Method khác là included_modules.

Như trên, human class có hai modules included: cái included trực tiếp bằng include Movement và cái included trong Object class. Nghĩa là mỗi class kế thừa properties từ ancestor classes, và mỗi module included trong chúng cũng included. Dựa trên thông tin, làm bài tập đơn giản xác nhận method lookup path trong Ruby và class nào ưu tiên. Định nghĩa methods cùng tên nhưng output khác đặt trong Human, Movement module, và Mammal.
# in the mammal class
def find_path
"Found me in the Mammal class path"
end
# in the Movement module
def find_path
"Found me in the Movement module path"
end
# in the Human class
def find_path
"Found me in the Human class path"
end
Bây giờ thực hiện bài tập.

Như trên, thứ tự ancestors khi gọi .ancestors trên class là path theo dõi khi tìm method gọi trên instance của class đó. Với instance Human tạo, Ruby tìm method trong class trước; nếu không thấy, đi đến modules included trực tiếp; nếu không thấy, đến superclass và vòng lặp tiếp tục đến BasicObject. Nếu method vẫn không thấy, "NoMethodError" trả về. Dùng .ancestors, ta xác định lookup path cho object và methods xuất phát từ đâu tại bất kỳ thời điểm.
Ruby cũng cung cấp method methods, xác định tất cả methods có sẵn cho object. Có thể trừ để thấy method nào từ parent, nào riêng. Ví dụ:

Như trên, biến Henry có nhiều methods. Tuy nhiên, khi trừ methods của class Object, chỉ còn lại những method định nghĩa cụ thể trong file, mọi method khác kế thừa. Tương tự cho mọi object, class, và ancestors. Tự do thử nhiều tổ hợp với Human.methods, Mammal.methods, Module.methods, và mọi class/module định nghĩa trước; bạn sẽ nắm vững Ruby object model.