Encapsulation ในภาษา JavaScript

28 February 2022

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

  • Encapsulation คืออะไร
  • Private properties
  • Private methods

Encapsulation คืออะไร

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

  • Public: เป็นสมาชิกที่สามารถเข้าถึงได้ทั้งจากภายในคลาสและนอกคลาส (นี่เป็นค่าเริ่มต้น)
  • Private: เป็นสมาชิกที่สามารถเข้าถึงจากภายในคลาสเท่านั้น

ดังนั้นเพื่อปกป้องข้อมูลในออบเจ็ค เราจะกำหนดระดับการเข้าถึงของ Property หรือเมธอดของคลาสให้มีระดับการเข้าถึงเป็นแบบ Private และการดำเนินการกับข้อมูลเหล่านี้จะทำผ่านเมธอดที่เป็นแบบ Public แทน

Private properties

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

music_player.js
class MusicPlayer {
    #soundLevel = 0;

    constructor(song) {
        this.song = song;
    }

    setVolume(value) {
        if (value < 0) {
            this.#soundLevel = 0; 
        } else if (value > 30) {
            this.#soundLevel = 30; 
        } else {
            this.#soundLevel = value;
        }
    }

    getVolume() {
        return this.#soundLevel;
    }

    play() {
        console.log(`Song starts playing...`);
    }

    stop() {
        console.log(`Song has stopped playing.`);
    }
}

let player = new MusicPlayer("Automatica - Nigel Standford");
player.setVolume(25);

player.play();
console.log(`Currently playing: '${player.song}'`);
console.log(`Volume was set to: ${player.getVolume()}`);
player.stop();

นี่เป็นผลลัพธ์การทำงานของโปรแกรม

Song starts playing...
Currently playing: 'Automatica - Nigel Standford'
Volume was set to: 25
Song has stopped playing.

ในตัวอย่างนี้ เป็นโปรแกรมจำลองการทำงานของเครื่องเล่นเพลงที่มีสอง Property คือ song สำหรับเก็บชื่อเพลงที่ต้องการเล่น และ #soundLevel สำหรับเก็บระดับความดังของเครื่องเล่นเพลงโดย Property นี้มีระดับการเข้าถึงเป็นแบบ Private เนื่องจากเราต้องการปกปิดการเข้าถึงมันจากภายนอกออบเจ็ค

setVolume(value) {
    if (value < 0) {
        this.#soundLevel = 0; 
    } else if (value > 30) {
        this.#soundLevel = 30; 
    } else {
        this.#soundLevel = value;
    }
}

getVolume() {
    return this.#soundLevel;
}

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

player.setVolume(25);
...
console.log(`Volume was set to: ${player.getVolume()}`);

นั่นหมายความว่าในการกำหนดค่าและอ่านค่าจาก #soundLevel จะต้องทำผ่านเมธอดที่มอบให้เท่านั้น นอกจากนี้ ภายในเมธอด setVolume ยังได้ตรวจสอบระดับเสียงว่าจะต้องอยู่ในช่วงที่กำหนดเท่านั้นคือ 0-30 นี่จะทำให้ไม่มีใครสามารถเปลี่ยนแปลงค่าของ Property นี้โดยตรงได้ผ่านทางออบเจ็ค ยกตัวอย่างเช่น

// This will give an error
player.#soundLevel = 50;

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

console.log(`Currently playing: '${player.song}'`);
// or
player.song = "Gavity - Nigel Standford";

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

Private methods

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

นี่เป็นตัวอย่างโปรแกรมจำลองการทำงานของรถยนต์ โดยการจำลองการทำงานพื้นฐานของรถยนต์ให้เป็นทั้งเมธอดที่เป็นแบบ Public และ Private ต่อไปมาดูตัวอย่างในการเขียนโปรแกรม

car_example.js
class Car {
    #fuelLevel = 0;

    constructor(name) {
        this.name = name;
    }

    // Public methods
    start() {
        console.log(`${this.name} engine has started.`);
    }

    stop() {
        console.log(`${this.name} engine has stopped.`);
    }

    fillUpFuel(amount) {
        this.#fuelLevel += amount;
        console.log(`${this.name}'s fuel has increased by ${amount}.`);
    }

    getFuelLevel() {
        return this.#fuelLevel;
    }

    move(direction, speed) {
        let fuelToUse = speed * 0.5;
        if (this.#fuelAvailable(fuelToUse)) {
            this.#consumeFuel(fuelToUse);
            console.log(`${this.name} is moving ${direction} at ${speed} MPH.`);
        } else {
            console.log(`Fuel is not enoght for ${this.name} to move.`);
        }
    }

    // Private methods
    #consumeFuel(amount) {
        this.#fuelLevel -= amount;
        console.log(`${this.name} fuel has been consumed by ${amount}.`);
    }

    #fuelAvailable(fuelToUse) {
        return this.#fuelLevel >= fuelToUse;
    }
}

// Let's ride the car
let myCar = new Car("Tesla Model X");

myCar.start();
myCar.fillUpFuel(100);

myCar.move("forward", 56);
console.log(`How much fuel left for this car? ${myCar.getFuelLevel()}`);

myCar.move("backward", 20);
console.log(`How much fuel left for this car? ${myCar.getFuelLevel()}`);

myCar.move("forward", 200);
myCar.stop();

และนี่เป็นผลลัพธ์การทำงานของโปรแกรม


ในตัวอย่างนี้ เราได้สร้างคลาส Car ซึ่งเป็นคลาสของรถยนต์ที่มีสอง Property คือ name ที่มีระดับการเข้าถึงแบบ Public และ #fuelLevel ที่มีระดับการเข้าถึงเป็นแบบ Private จากนั้นภายในคลาสประกอบไปด้วยเมธอดทั้งแบบ Public และ Private ต่อไปมาดูว่าแต่ละเมธอดทำงานอะไร

start() {
    console.log(`${this.name} engine has started.`);
}

stop() {
    console.log(`${this.name} engine has stopped.`);
}

มาเริ่มจากสองเมธอดแรกคือเมธอด start และ stop ที่เป็น Public เมธอด มันใช้สำหรับเริ่มต้นและหยุดการทำงานของเครื่องยนต์ตามชื่อของมัน เมธอดเหล่านี้เพียงแค่แสดงข้อความเพื่อแจ้งสถานะการทำงานพร้อมกับชื่อของรถยนต์ที่กำลังทำงานอยู่เท่านั้น

fillUpFuel(amount) {
    this.#fuelLevel += amount;
    console.log(`${this.name}'s fuel has increased by ${amount}.`);
}

getFuelLevel() {
    return this.#fuelLevel;
}

สองเมธอดต่อมาทำหน้าที่เป็น Getter และ Setter เมธอดสำหรับทำงานกับ Property #fuelLevel ที่มีระดับการเข้าถึงเป็นแบบ Private เมธอดเหล่านี้ใช้สำหรับเติมเชื้อเพลิงให้กับรถยนต์ และรับเอาค่าของเชื้อเพลงปัจจุบันที่รถยนต์มีอยู่

#consumeFuel(amount) {
    this.#fuelLevel -= amount;
    console.log(`${this.name} fuel has been consumed by ${amount}.`);
}

#fuelAvailable(fuelToUse) {
    return this.#fuelLevel >= fuelToUse;
}

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

move(direction, speed) {
    let fuelToUse = speed * 0.5;
    if (this.#fuelAvailable(fuelToUse)) {
        this.#consumeFuel(fuelToUse);
        console.log(`${this.name} is moving ${direction} at ${speed} MPH.`);
    } else {
        console.log(`Fuel is not enoght for ${this.name} to move.`);
    }
}

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

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

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

myCar.move("forward", 56);

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

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

บทความนี้เป็นประโยชน์หรือไม่? Yes · No