ตาม Wikipedia Object-Oriented Programming (OOP) เป็น Programming Paradigm ฐานบน Concept "Objects" ที่บรรจุ Data และ Code: Data เป็น Fields (Attributes/Properties) Code เป็น Procedures (Methods)
Ruby เป็น Pure Object-Oriented Language ทุกอย่างใน Ruby เป็น Object Objects เหล่านี้ ไม่ว่าจะเป็น Strings, Numbers, Classes, Modules เป็นต้น ทำงานใน System เรียกว่า The Object Model
Ruby มี Method object_id ให้ทุก Object Return Integer ไม่มี 2 Objects ใดๆ ที่ ID เหมือนกัน ลอง IRB (irb ใน Terminal):

เห็นว่า Strings, Integers, Arrays, Classes, Methods ล้วนเป็น Objects เพราะมี Object ID
บทความนี้ครอบคลุม:
- Classes และ Instances
- Inheritance (การสืบทอด)
- Public, Private, Protected Methods
- Mixins
- Modules
- Object Hierarchy
Classes และ Instances
ใน Ruby Classes คือที่ Define Attributes และ Actions (Attitudes) ของ Object ถ้า Object กลม (Attribute) และพูดได้ (Action) เรารู้จาก Class เพราะ Attributes/Actions ถูก Define เป็น Methods ใน Class Object ที่เป็นของ Class เรียกว่า Instance สร้างด้วย .new สร้าง Class Human:
class Human
def initialize(name)
@name = name
end
end
initialize ระบุ Requirements สำหรับ Instance ใหม่ ต้องมี Name สร้าง Instance: Human.new(name) ใช้ 'Henry' ทดสอบใน IRB: load './Human.rb' (Re-run เมื่อแก้ไฟล์) ไม่ใช้ IRB ใส่ puts หน้า Commands

สร้าง Human ไม่ให้ Name -> Argument Error ให้ถูกต้อง -> Henry สร้างเสร็จ เป็น Instance ของ Class Human
@name = Instance Variable (ขึ้นต้น @) เรียกใช้ได้จาก Methods อื่นใน Class ตราบใด Instance ยังมีอยู่ ตั้งค่าเท่ากับ Name ที่ Initialize
Define Traits/Actions (Instance Methods):
def name
@name
end
def no_of_legs
2
end
def no_of_hands
2
end
def speak
'blablabla'
end
เรียก Methods: instance.method_name

อยากเปลี่ยนชื่อ Henry? Ruby มี Built-in แต่ Manual ก็ได้: เปลี่ยน name Method ให้เป็น Setter ด้วย:
def name=(new_name)
@name = new_name
end
หรือใช้ attr_accessor :name แทน:
class Human
attr_accessor :name
def initialize(name)
@name = name
end
# rest of code
end
ผลลัพธ์เหมือนกัน:

Methods จนตอนนี้ = Instance Methods เรียกบน Instances ได้ ไม่ใช่ Class มี Class Methods เรียกบน Class โดย Prefix self.:
# within Human class
def self.introduction
"I am a human, not an alien!"
end

Class Method ใช้ได้กับ Class เท่านั้น ไม่ใช่ Instances มี Class Variables Prefix @@:
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_cars_bought += 1
"#{@name} just purchased a car"
end
end
buy_car ให้ Human ซื้อรถ Class Variable @@no_cars_bought เพิ่ม 1 ทุกครั้ง Class Method no_cars_bought ดึงจำนวนรถที่ซื้อ:

Methods จนตอนนี้ = Public Methods Accessible Outside Class มี Private Methods เรียกได้แค่ใน Class:
# at bottom of Human class
def say_account_number
"My account number is #{account_number}"
end
private
def account_number
"1234567890"
end

henry.account_number -> NoMethodError (Private) แต่ say_account_number เรียกได้เพราะอยู่ใน Class เดียว ทุก Instance Method หลัง private Keyword กลายเป็น Private ควร Define Private Methods ท้าย Class หลัง Public Methods
Protected Methods มีอยู่จริง แต่พูดหลังเข้าใจ Inheritance
Inheritance (การสืบทอด)
สร้าง Class Mammal (Humans are Mammals) Characteristics: Hair/Fur, Complex Brain:
class Mammal
def has_hair?
"Most certainly, Yes"
end
def has_complex_brain?
"Well, as a mammal, what do you expect?"
end
end
Human Inherit Mammal: class Human < Mammal
Human = Subclass Mammal = Superclass ถ้า Define ใน File เดียว Mammal ต้องอยู่ก่อน Human ลองดู Features ใหม่:

Human เข้าถึง Mammal Attributes ได้ทั้งหมด ถ้าลบ Inheritance (< Mammal) รัน henry.class.superclass -> "Object" ทุก Class ไม่ Inherit อื่น Superclass = "Object" ยืนยันว่าใน Ruby Classes ก็เป็น Objects

Keyword super Reuse/Modify Methods จาก Superclass ใน Mammal มี has_hair? อยากเพิ่ม Info เฉพาะ Human:
def has_hair?
super + ", but humans can be bald at times"
end
super เรียก Method เดียวกันใน Superclass Return Result เราเพิ่ม String ต่อท้าย:

Ruby Single Class Inheritance เท่านั้น (Inherit จาก Class เดียว) อยากเพิ่ม Properties หลายอย่างไม่ Specific Class ใช้ Mixins แต่ก่อนหน้านั้น...
Protected Methods
Protected Methods เรียกได้ใน Class และ Subclasses (คล้าย Private) สร้าง Protected Method ใน Mammal:
# at bottom of Mammal class
def body_status
body
end
protected
def body
"This body is protected"
end

body Protected เรียกไม่ได้โดยตรง แต่ เรียกผ่าน body_status ใน Mammal ได้ และ Subclasses เข้าถึงได้ ลองใน Human:
# within Human class
def check_body_status
"Just a human checking that " + body.downcase
end

Human (Subclass) เรียก Protected Method ของ Mammal ได้ Private Methods ก็ Subclass Access ได้ แต่ต่างอย่างไร?
Protected Methods เรียกด้วย Explicit Receiver (receiver.protected_method) ได้ถ้า Receiver เป็น self หรือ Same Class As self แต่ Private Methods เรียกด้วย Explicit Receiver ได้เฉพาะ Receiver = self เท่านั้น
ทดสอบ: แก้ body.downcase -> self.body.downcase และ Private Method ใส่ self:
def check_body_status
"Just a human checking that " + self.body.downcase # protected
end
def say_account_number
"My account number is #{self.account_number}" # private
end

P.S. ก่อน Ruby 2.7 เรียก Private Method ด้วย self ไม่ได้
ลองแทน self ด้วย Object Same Class As self (self = Henry Instance Human Subclass 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 also 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

Protected: แทน self ด้วย Object Same Class ได้ Private: ไม่ได้
Mixins
Mixins = Code Set ใน Module Include/Extend ให้ Class ให้ Capabilities เพิ่ม Modules หลายตัว Include ได้ (ต่าง Class Inheritance ที่ Single) สร้าง Module Movement:
module Movement
def hop
"I can hop"
end
def swim
"Ermmmm, I most likely can if I choose"
end
end
Include ใน Human: include Movement
class Human < Mammal
include Movement
# rest of code
end

Methods จาก Module ใช้ได้กับ Instances เท่านั้น ไม่ใช่ Class อยากให้ Class ใช้ได้ -> extend Movement

extend ใช้กับ Instance ได้อีกแบบ สร้าง Module Parent:
module Parent
def has_kids?
"most definitely"
end
end

dad.extend(Parent) ทำให้ has_kids? ใช้ได้กับ Instance นั้นเฉพาะเจาะจง Useful เมื่อไม่อยาก Mixin ให้ทุก Instance
Modules (โมดูล)
Modules = Housing สำหรับ Classes, Methods, Constants, Modules อื่นๆ นอกจาก Mixins แล้ว Modules Organize Code ป้องกัน Name Clashing ด้วย Namespacing ใน Ruby ทุก Class เป็น Module แต่ Module ไม่ใช่ Class (Modules ไม่ Instantiate/Inherit ได้) เข้าใจ Namespacing: สร้าง Module มี Constant, Class, Method, Module ย่อย:
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
Methods ใน Modules Prefix self. เพราะ Modules ไม่ Instantiate Method ไม่มี self. จะ Available แค่ Mixin เรียก Method บน Module ต้อง self.method_name เหมือน Class Methods

เข้าถึง Classes/Modules/Constants ใน Module: module_name::target_name ทดสอบ Namespacing: สร้าง Module อื่น มี Class Adult เหมือนกัน ดู Differentiation:
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

2 Classes ชื่อ "Adult" Wrap ใน Modules -> ใช้ชื่อซ้ำได้ไม่ Confused Code Readable, Organized, Separation of Concerns
Object Hierarchy (ลำดับชั้นวัตถุ)
เข้าใจ Classes, Instances, Modules, Inheritance ยังไม่พอ ต้องเข้าใจ Object Hierarchy ใน Ruby = Order ที่ Method ถูก Search มี Methods ช่วย:
ancestors Method ไม่ใช้กับ Instances ใช้กับ Classes และ Ancestors:

Human.ancestors Return: Class Itself, Directly Included Modules, Parent Class, Parent's Included Modules... Loop จน BasicObject (Root ของ All Objects)
included_modules:

Human มี 2 Modules: Movement (Direct Include) และ Module ใน Object Class หมายถึงแต่ละ Class Inherit Properties จาก Ancestors และ Modules ที่ Included ใน Ancestors ก็ Included ในมัน
Exercise: Define Method ชื่อ find_path เหมือนกัน Output ต่างกัน ใน Human, Movement, Mammal:
# in mammal class
def find_path
"Found me in the Mammal class path"
end
# in Movement module
def find_path
"Found me in the Movement module path"
end
# in Human class
def find_path
"Found me in the Human class path"
end
ทดสอบ:

Order ของ .ancestors = Lookup Path เรียก Method บน Instance: หาใน Class ก่อน -> Directly Included Modules -> Superclass -> Loop จน BasicObject ไม่เจอ -> NoMethodError .ancestors ระบุ Lookup Path และ Methods มาจากไหนได้
methods Method ระบุ All Methods Available To Object สามารถ Subtract ได้ว่า มาจาก Parent หรือ Peculiar ตัวเอง:

Henry มี Methods เยอะ ลบด้วย Object Methods -> เหลือแค่ที่ Define เอง ทุก Method อื่น Inherited ทำได้กับทุก Object, Class, Ancestors ลองเล่น Human.methods, Mammal.methods, Module.methods จะเข้าใจ Ruby Object Model ยิ่งขึ้น