Encapsulation ในภาษา Ruby

ในบทนี้ เราจะพูดเกี่ยวกับ Encapsulation ในภาษา Ruby เราจะแนะนำให้คุณรู้จักว่า Encapsulation คืออะไร และมันถูกนำไปใช้อย่างไรในการเขียนโปรแกรม หลังจากนั้นเราจะพูดถึงคำสั่งที่ใช้สำหรับกำหนดการเข้าถึงของเมธอดในภาษา Ruby และการใช้งานเมธอด Setter และ Getter ซึ่งเป็นเมธอดที่ใช้สำหรับสร้างเมธอดเพื่อกำหนดค่าและรับค่าจากแอตทริบิวส์ของออบเจ็ค ก่อนที่เราจะเริ่มมาทำความรู้จักกับ Encapsulation กันก่อน

Encapsulation ในภาษา Ruby

Encapsulation คืออะไร

การห่อหุ้มข้อมูล (Encapsulation) คือคุณสมบัติของการเขียนโปรแกรมเชิงวัตถุ (OOP) โดยมีแนวคิดที่ว่าข้อมูลจะผูกอยู่กับเมธอดที่จัดการข้อมูลนั้นๆ หรือเป็นการป้องกันการเข้าถึงข้อมูลหรือส่วนประกอบของออบเจ็คโดยตรงจากภายนอก ในภาษา Ruby แอตทริบิวต์ทั้งหมดนั้นจะถูกห่อหุ้มอยู่ภายในคลาส ในการเข้าถึงแอตทริบิวต์จากภายนอกคลาสนั้น จะต้องเข้าถึงผ่าน public เมธอดที่ถูกประกาศไว้ในคลาส ลองพิจารณาตัวอย่างต่อไปนี้

class Person

    def initialize(first_name, last_name)
        @first_name = first_name
        @last_name = last_name  
    end

    def full_name
        return @first_name + " " + @last_name
    end

end

p = Person.new("Matteo", "Marcus")
puts p.full_name

ในตัวอย่าง เราได้สร้างคลาสที่ชื่อว่า Person ที่มีสองแอตทริบิวส์คือ first_name และ last_name ที่ใช้สำหรับเก็บชื่อและนามสกุลตามลำดับ ภายในคลาสนั้นมีเมธอด initialize ซึ่งเป็นเมธอดที่ใช้สำหรับกำหนดค่าให้กับออบเจ็คตอนที่มันถูสร้าง และเมธอด full_name สำหรับรับชื่อแบบเต็มของออบเจ็ค

จากทั้งสองเมธอดที่เราได้ประกาศไปนั้น คุณคิดว่ามันเพียงพอแล้วหรือยัง แล้วถ้าเราต้องการเข้าถึงค่าแอตทริบิวต์เพื่อเปลี่ยนแปลงและอ่านค่าละ เราจะต้องทำอย่างไร ยกตัวอย่างเช่น

# Direct access to attributes
person.first_name = "Luciano"
puts person.first_name

จากสองคำสั่งด้านบน เราได้พยายามเข้าถึงแอตทริบิวส์ first_name โดยตรงเพื่อกำหนดค่าและอ่านค่าให้กับแอตทริบิวส์ดังกล่าว ในภาษา Ruby เราไม่สามารถทำเช่นนี้ได้ เพื่อเข้าถึงแอตทริบิวส์ของออบจ็คจากภายนอกนั้น เราจะต้องทำมันผ่านเมธอดที่เราสร้างขึ้นมา นี่เป็นตัวอย่าง

class Person

    # Setter methods
    def first_name=(value)
        @first_name = value
    end

    def last_name=(value)
        @last_name = value
    end

    # Getter methods
    def first_name
        return @first_name
    end

    def last_name
        return @last_name
    end

    def full_name
        return @first_name + " " + @last_name
    end

end

person = Person.new
person.first_name = "Matteo"
person.last_name = "Marcus"

puts "Full name: #{person.first_name} #{person.last_name}"
puts "Via full_name method: #{person.first_name} #{person.last_name}"

ในตัวอย่างต่อมา เนื่องจากเราต้องการเข้าถึงแต่ละแอตทริบิวส์เพื่อกำหนดค่าและอ่านค่า ดังนั้นเราจึงได้กำหนดเมธอดสำหรับกำหนดค่าและอ่านค่าให้กับแอตทริบิวส์ first_name และ last_name ในตัวอย่างนี้ เราได้นำเมธอด initialize ออกไป เพราะเราจะใช้เมธอดใหม่ที่เราประกาศมาแทน

# Setter methods
def first_name=(value)
    @first_name = value
end

def last_name=(value)
    @last_name = value
end

# Getter methods
def first_name
    return @first_name
end

def last_name
    return @last_name
end

ภายในคลาสนั้นเราได้สร้างสี่เมธอดสำหรับกำหนดค่าและรับค่าจากแอตทริบิวส์ทั้งสอง โดยทั่วไปแล้วเมธอดเหล่านั้นเรียกว่า Setter และ Getter เมธอด และเมื่อเราประกาศเมธอดแล้ว เราสามารถใช้เมธอดเหล่านี้สำหรับกำหนดค่าและรับค่าจากแอตทริบิวส์เหมือนกับว่าเราเข้าผ่านแอตทริบิวส์โดยตรง

person = Person.new
person.first_name = "Matteo"
person.last_name = "Marcus"

puts "Full name: #{person.first_name} #{person.last_name}"
puts "Via full_name method: #{person.first_name} #{person.last_name}"

หลังจากนั้นเรานำคลาสมาสร้างออบเจ็ค และกำหนดค่าให้กับแอตทริบิวส์และอ่านค่าจากแอตทริบิวส์ด้วยเมธอด Setter และ Getter ที่เราได้สร้างขึ้นมา

Full name: Matteo Marcus
Via full_name method: Matteo Marcus

นี่เป็นผลลัพธ์การทำงานของโปรแกรม ในการใช้งานเมธอด Setter และ Getter สำหรับกำหนดค่าและอ่านข้อมูลจากแอตทริบิวส์ภายในออบเจ็คที่ละค่า และนอกจากนี้ เรายังคงใช้เมธอด full_name สำหรับอ่านชื่อพร้อมกับนามสกุล

คำสั่งกำหนดการเข้าถึงในภาษา Ruby

ในการเขียนโปรแกรมในภาษา Ruby เราสามารถกำหนดการเข้าถึงให้กับเมธอดของคลาสได้ ซึ่งในภาษา Ruby นั้นมีคำสั่งสำหรับกำหนดการเข้าถึงอยู่สามคำสั่ง ดังที่แสดงในตารางด้านล่างนี้

คำสั่ง คลาส คลาสย่อย ภายนอกคลาส
public สามารถเข้าถึงได้ สามารถเข้าถึงได้ สามารถเข้าถึงได้
protected สามารถเข้าถึงได้ สามารถเข้าถึงได้ ไม่สามารถเข้าถึงได้
private สามารถเข้าถึงได้ สามารถเข้าถึงได้ ไม่สามารถเข้าถึงได้

จากตารางนั้นเป็นคำสั่งสำหรับกำหนดการเข้าถึงของเมธอด เราสามารถใช้คำสั่งเหล่านี้เพื่อกำหนดว่าเมธอดจะสามารถเข้าถึงได้อย่างไร ยกตัวอย่างเช่น public เมธอดจะสามารถเข้าถึงได้ทุกที่ ในขณะที่ private เมธอดจะสามารถเข้าถึงได้จากคลาสที่มันถูกประกาศเท่านั้น ต่อไปมาดูตัวอย่างการใช้งานในแต่ละคำสั่ง

คำสั่ง public และ private

คำสั่ง public และ private ใช้สำหรับกำหนดการเข้าถึงของเมธอดเช่นเดียวกัน แต่สิ่งที่แตกต่างกันคือ เมธอดที่สร้างจากคำสั่ง public จะสามารถเข้าถึงได้จากภายนอกของคลาส ในขณะที่เมธอดที่สร้างจากคำสั่ง private จะสามารถเข้าถึงได้จากคลาสที่ประกาศมันเท่านั้น โดยปกติแล้วเมธอดที่ประกาศในคลาสจะเป็น public ถ้าหากคุณไม่ได้ระบุคำสั่งใดเลย นอกจากเราจะกำหนดให้มันเป็นอย่างอื่น

ต่อไปมาดูตัวอย่างของคลาสของรถยนต์ ซึ่งคลาสนี้มีแอตทริบิวส์และเมธอดที่เป็นทั้งแบบ public และ private นี่เป็นโค้ดของโปรแกรม

class Car

    def initialize(model, color, fuel, speed)
        @model = model
        @color = color
        @fuel = fuel
        @speed = speed
    end

    public
    def console
        puts "Model: #{@model}"
        puts "Color: #{@color}"
        puts "Fuel level: #{@fuel}"
        puts "Current speed: #{@speed}"
    end

    def start
        puts "Car has started"
    end

    def stop
        puts "Car has stopped"
    end

    def drive
        consume_fuel
        push_engine
        spin_wheel
        puts "Car is running"
    end

    private
    def consume_fuel
        puts "Consuming fuel"
    end

    def push_engine
        puts "Pushing engine to work"
    end

    def spin_wheel
        puts "Wheel is spinning "
    end

end

c = Car.new("Porsche", "Green", 100, 240)
c.start
c.drive
c.stop
c.console

ในตัวอย่าง เราได้สร้างคลาสที่มีชื่อว่า Car คลาสนี้มีแอตทริบิวส์สำหรับเก็บค่าโมเดล สี ระดับน้ำมันเชื้อเพลิง และความเร็วของรถยนต์ ซึ่งแอตทริบิวส์เหล่านี้จะถูกกำหนดผ่านเมธอด initialize ซึ่งเป็นคอนสตรัคเตอร์ของคลาส

public
def console
    puts "Model: #{@model}"
    puts "Color: #{@color}"
    puts "Fuel level: #{@fuel}"
    puts "Current speed: #{@speed}"
end

def start
    puts "Car has started"
end

def stop
    puts "Car has stopped"
end

def drive
    consume_fuel
    push_engine
    spin_wheel
    puts "Car is running"
end

หลังจากนั้นเป็นการประกาศ public เมธอด สังเกตว่าเราได้ใช้คำสั่ง public ก่อนบรรทัดของการประกาศเมธอด console นั่นหมายความว่าเมธอดที่ประกาศหลังจากคำสั่งดังกล่าวจะเป็น public เมธอด

ในตัวอย่าง เราได้ประกาศสี่เมธอด เมธอด console ใช้สำหรับแสดงข้อมูลเกี่ยวกับรถยนต์ผ่านทางคอนโซลรถ เมธอด drive ใช้สำหรับขับเพื่อให้รถยนต์เคลื่อนที่ไป ส่วนเมธอด start และ stop ใช้สำหรับสตาร์ทและดับเครื่องยนต์

private
def consume_fuel
    puts "Consuming fuel"
end

def push_engine
    puts "Pushing engine to work"
end

def spin_wheel
    puts "Wheel is spinning "
end

ต่อมาเป็นการใช้คำสั่ง private สำหรับประกาศเมธอดให้เป็น private เมธอดที่ถูกประกาศหลังจากคำสั่งนี้จะเป็น private เมธอด ซึ่งเป็นเมธอดที่สามารถเข้าถึงได้แค่ภายในคลาสที่ได้ประกาศมันขึ้นมา

def drive
    consume_fuel
    push_engine
    spin_wheel
    puts "Car is running"
end

เราได้เรียกใช้งาน private เมธอดจากเมธอด drive ซึ่งอยู่ภายในคลาสเดียวกัน ในตัวอย่างแสดงให้เห็นว่า ในการที่รถยนต์เคลื่อนที่ เราจะต้องสั่งการโดยเรียกจากเมธอด drive หลังจากนั้นการนำมันเชื้อเพลิงมาใช้ การทำใช้เครื่องยนต์ทำงาน และการทำให้ล้อหมุนจะถูกทำงานอัตโนมัติด้วย private เมธอด

c = Car.new("Porsche", "Green", 100, 240)
c.start
c.drive
c.stop
c.console

หลังจากนั้นเราได้นำคลาสมาสร้างออบเจ็ค และเรียกใช้งานเมธอดต่างๆ เพื่อให้รถยนต์นั้นทำงาน สังเกตว่าเมธอดทั้งที่เราเรียกนั้นเป็น public เมธอดทั้งหมด และเราไม่สามารถเรียกใช้งาน private เมธอดจากภายนอกของคลาสได้

Car has started
Consuming fuel
Pushing engine to work
Wheel is spinning
Car is running
Car has stopped
Model: Porsche
Color: Green
Fuel level: 100
Current speed: 240

นี่เป็นผลลัพธ์การทำงานของโปรแกรม ในตัวอย่างได้แสดงให้คุณเห็นถึงวิธีการใช้งานคำสั่ง public และ private สำหรับกำหนดการเข้าถึงของเมธอด

ตัวอย่างนี้แสดงให้เห็นว่า เมื่อคุณขับรถยนต์ คุณไม่จำเป็นต้องรู้ว่าเบื้องหลังมันทำงานอย่างไร ในการที่จะนำเชื้อเพลงมาใช้ การสั่งให้เครื่องยนต์ทำงาน หรือการสั่งให้ล้อหมุน ซึ่งสิ่งเหล่านี้เป็นสิ่งที่เป็นนามธรรมกับผู้ใช้งาน ทั้งหมดที่คุณต้องทำก็คือเหยียบคันเร่ง และการทำงานเหล่านี้จะเกิดขึ้นอัติโนมัติ ซึ่งเราเรียกว่าการปกปิดหรือห่อหุ้มการทำงานเอาไว้จากภายนอกนั่นเอง

คำสั่ง protected

คำสั่ง protected เป็นสั่งที่ใช้สำหรับกำหนดให้เมธอดสามารถเข้าถึงได้จากภายในคลาสของมันเอง และภายในคลาสที่สืบทอดไปเท่านั้น เราไม่สามารถเรียกเมธอดชนิดนี้ได้จากภายนอกของคลาส ต่อไปมาดูตัวอย่างการใช้งานคำสั่ง protected ในภาษา Ruby

class Rectangle

    def initialize(width, height)
        @width = width
        @height = height
    end

    protected
    def area
        return @width * @height
    end

end

class Cube < Rectangle

    def initialize(width, height, dept)
        super(width, height)
        @dept = dept
    end

    def volume
        return area * @dept
    end

end

c = Cube.new(3, 4, 5)
puts c.volume

ในตัวอย่าง เราได้สร้างคลาส Rectangle ซึ่งเป็นคลาสของพื้นที่สี่เหลี่ยม และคลาส Cube ซึ่งเป็นคลาสของกล่องลูกบาศก์ เนื่องจากออบเจ็คทั้งสองประเภทนี้มีคุณสมบัติบางอย่างที่เหมือนกัน นั่นก็คือความยาวและความสูง ดังนั้นเราจึงได้สืบทอดคลาส Cube มาจากคลาส Rectangle เพื่อที่จะรับเอาคุณสมบัติดังกล่าวจากคลาสหลักของมัน

protected
def area
    return @width * @height
end

ในคลาส Rectangle เราได้ประกาศเมธอด area ซึ่งเป็น protected เมธอด นั่นทำให้เมธอดนี้สามารถเข้าถึงได้ภายในคลาสที่ประกาศมัน และคลาสที่ได้สืบทอดจากคลาส Rectangle ไป

def volume
    return area * @dept
end

ในเมธอด volume ของคลาส Cube เราได้เรียกใช้เมธอด area เพื่อคำนวณหาพื้นที่ในระนาบสองมิติซึ่งเป็นสิ่งที่กล่องลูกบาศก์ต้องมีอยู่แล้ว และสิ่งที่เพิ่มเติมเข้ามาก็คือความลึกของลูกบาศก์นั่นเอง

c = Cube.new(3, 4, 5)
puts c.volume

หลังจากนั้นเราได้นำคลาส Cube มาสร้างออบเจ็คลูกบาศก์โดยการกำหนดความกว้าง ความสูง และความลึก และแสดงปริมาตรของลูกบาศก์ออกมาทางหน้าจอผ่าน public เมธอด volume

60

นี่เป็นผลลัพธ์การทำงานของโปรแกรมจากการใช้งานคำสั่ง protected สำหรับกำหนดการเข้าถึงให้กับเมธอดในภาษา Ruby

และอย่างที่เราได้บอกไป protected เมธอดนั้นจะไม่สามารถเข้าถึงจากภายนอกคลาสได้ นั่นทำให้คำสั่งต่อไปนี้ไม่ทำงาน

r = Rectangle.new(2, 3)
puts r.area

c = Cube.new(3, 4, 5)
puts c.area

ในตัวอย่างจะเกิดข้อผิดพลาดขึ้น เนื่องจากเราได้เรียกใช้งานเมธอด area ซึ่งเป็น protected เมธอดจากภายนอกของคลาส ถึงแม้ว่ามันเข้าท่าที่จะเรียกใช้งานเมธอด area บนออบเจ็ค Rectangle แต่เนื่องจากเมธอดดังกล่าวได้ถูกประกาศเป็น protected นั่นจึงทำให้เราไม่สามารถเข้าถึงได้ และเพื่อที่จะทำให้มันสามารถเข้าถึงได้ คุณต้องประกาศมันให้เป็น public เมธอดแทน

เมธอด Setter และ Getter ในภาษา Ruby

สำหรับเนื้อหาในส่วนสุดท้ายของบทนี้เราจะแนะนำให้คุณรู้จักกับเมธอด Setter และ Getter ในภาษา Ruby ซึ่งเมธอดเหล่าใช้เพื่อสร้างเมธอดสำหรับกำหนดค่าและรับค่าจากแอตทริบิวต์ของออบเจ็ค ซึ่งในภาษา Ruby นั้นมีสามเมธอดให้เราได้ใช้งาน

attr_accessor(:attr_name, ...)
attr_writer(:attr_name, ...)
attr_reader(:attr_name, ...)

ในตัวอย่างเป็นรูปแบบการใช้งานเมธอดเพื่อสร้างเมธอด Setter และ Getter ในภาษา Ruby และทั้งสามเมธอดนั้นรับค่าอาร์กิวเมนต์ตั้งแต่หนึ่งถึงหลายอาร์กิวเมนต์ ซึ่งเป็น Symbol ของแอตทริบิวต์ที่เราต้องการสร้างเมธอด Setter และ Getter ให้กับมัน

เมธอด attr_accessor นั้นใช้สำหรับสร้างเมธอด Setter และ Getter ให้กับแอตทริบิวต์ และเมธอด attr_writer นั้นใช้สำหรับสร้างเมธอด Setter เพียงอย่างเดียว ในขณะที่เมธอด attr_reader นั้นใช้สำหรับสร้างเมธอด Getter เพียงอย่างเดียว ต่อไปมาดูตัวอย่างการใช้งานของเมธอดเหล่านี้

class Fruit

    attr_accessor(:name)
    attr_writer(:color)
    attr_reader(:weight)

    def initialize(name, color, weight)
        @name = name
        @color = color
        @weight = weight
    end

    def info
        puts "Name: #{@name}"
        puts "Color: #{@color}"
        puts "Weight: #{@weight} grams"
    end

end

f1 = Fruit.new("Apple", "Red", 100)
f1.info

f1.name = "Orange"
f1.color = "Orange"
puts "New name: #{f1.name}"

f1.info

ในตัวอย่าง เราได้สร้างคลาส Fruit ซึ่งเป็นคลาสของผลไม้ และในคอนสตรัคเตอร์ของคลาสนั้นมีการรับแอตทริบิวส์สามอย่างคือ ชื่อ สี และน้ำหนักของผลไม้

attr_accessor(:name)
attr_writer(:color)
attr_reader(:weight)

ภายในคลาส เราได้สร้างเมธอดสำหรับกำหนดค่าและรับค่าให้กับแอตทริบิวส์ โดยที่แอตทริบิวส์ name จะมีเมธอดสำหรับกำหนดค่าและรับค่า แอตทริบิวส์ color จะมีเมธอดสำหรับกำหนดค่าเพียงอย่างเดียว และสุดท้ายแอตทริบิวส์ weight จะมีเมธอดสำหรับรับค่าเพียงอย่างเดียว

จากในตัวอย่างก่อนหน้านั้น ให้ผลลัพธ์การทำงานที่เหมือนกับการประกาศเมธอดในคลาสในตัวอย่างต่อไปนี้

class Fruit

    def initialize(name, color, weight)
        @name = name
        @color = color
        @weight = weight
    end

    # Equivalent to attr_accessor
    def name=(value)
        @name = value
    end

    def name
        return @name
    end

    # Equivalent to attr_writer
    def color=(value)
        @color = value
    end

    # Equivalent to attr_reader
    def weight
        return @weight
    end

end

และอย่างที่คุณเห็นทั้งเมธอด attr_accessor, เมธอด attr_writer และเมธอด attr_reader นั้นเป็นเมธอดสำหรับอำนวยความสะดวกเพื่อให้เราสามารถสร้างเมธอด Setter และ Getter ได้อย่างรวดเร็วและสะดวกมากขึ้นโดยที่ไม่ต้องประกาศขึ้นมาเอง คุณแค่เรียกใช้เมธอดเหล่านี้ตามด้วยชื่อ Symbol ของแอตทริบิวส์ที่ต้องการ และ Ruby จะสร้างให้คุณในตอนที่โปรแกรมทำงาน

ในบทนี้ คุณได้เรียนรู้เกียวกับ Encapsulation ในภาษา Ruby ซึ่งเป็นคุณสมบัติการห่อหุ่มในการเขียนโปรแกรมเชิงวัตถุที่ให้เราสามารถกำหนดการเข้าถึงเมธอดในรูปแบบต่างๆ ได้ นี่เป็นแนวในการปกปิดข้อมูลและปกปิดการทำงานจากภายนอกของคลาสหรือออบเจ็ค