Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

Deep Dive Ruby Object Model: คู่มือครบครัน

ตาม 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):

Deep Dive Ruby Object Model: คู่มือครบครัน

เห็นว่า 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

Deep Dive Ruby Object Model: คู่มือครบครัน

สร้าง 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

Deep Dive Ruby Object Model: คู่มือครบครัน

อยากเปลี่ยนชื่อ 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

ผลลัพธ์เหมือนกัน:

Deep Dive Ruby Object Model: คู่มือครบครัน

Methods จนตอนนี้ = Instance Methods เรียกบน Instances ได้ ไม่ใช่ Class มี Class Methods เรียกบน Class โดย Prefix self.:

# within Human class
def self.introduction
  "I am a human, not an alien!"
end

Deep Dive Ruby Object Model: คู่มือครบครัน

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 ดึงจำนวนรถที่ซื้อ:

Deep Dive Ruby Object Model: คู่มือครบครัน

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

Deep Dive Ruby Object Model: คู่มือครบครัน

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 ใหม่:

Deep Dive Ruby Object Model: คู่มือครบครัน

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

Deep Dive Ruby Object Model: คู่มือครบครัน

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 ต่อท้าย:

Deep Dive Ruby Object Model: คู่มือครบครัน

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

Deep Dive Ruby Object Model: คู่มือครบครัน

body Protected เรียกไม่ได้โดยตรง แต่ เรียกผ่าน body_status ใน Mammal ได้ และ Subclasses เข้าถึงได้ ลองใน Human:

# within Human class
def check_body_status
  "Just a human checking that " + body.downcase
end

Deep Dive Ruby Object Model: คู่มือครบครัน

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

Deep Dive Ruby Object Model: คู่มือครบครัน

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

Deep Dive Ruby Object Model: คู่มือครบครัน

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

Deep Dive Ruby Object Model: คู่มือครบครัน

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

Deep Dive Ruby Object Model: คู่มือครบครัน

extend ใช้กับ Instance ได้อีกแบบ สร้าง Module Parent:

module Parent
  def has_kids?
    "most definitely"
  end
end

Deep Dive Ruby Object Model: คู่มือครบครัน

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

Deep Dive Ruby Object Model: คู่มือครบครัน

เข้าถึง 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

Deep Dive Ruby Object Model: คู่มือครบครัน

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:

Deep Dive Ruby Object Model: คู่มือครบครัน

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

included_modules:

Deep Dive Ruby Object Model: คู่มือครบครัน

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

ทดสอบ:

Deep Dive Ruby Object Model: คู่มือครบครัน

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 ตัวเอง:

Deep Dive Ruby Object Model: คู่มือครบครัน

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