Training

Encapsulation คืออะไร

17 Jun 2013

เวลาเราเรียน OOP ไม่ว่าจะเป็นหลักสูตร Programming ผ่านภาษาต่างๆ หรือหลักสูตรที่มุ่งเน้นเพียงแค่แนวคิดและการออกแบบ เรามักจะได้ยินชื่อหัวเรื่องเท่ๆหรูๆดังต่อไปนี้ Encapsulation, Inheritance, และ Polymorphism ใช่ครับมันคือฟีเจอร์ทางภาษาของภาษาแบบออบเจกต์ ซึ่งถ้าเรานำมันมาใช้อย่างถูกต้อง ระบบจะมีความยืดหยุ่นสูง อย่างไรก็ตามมีผู้คนจำนวนมากที่ยังไม่เข้าใจว่าฟีเจอร์เหล่านี้คืออะไร ทำงานอย่างไร เราจะใช้มันเมื่อไหร่ แล้วเราจะได้อะไรจากมันมา ดังนั้นในบทความนี้ผมจะมาพูดถึงฟีเจอร์เหล่านี้ โดยจะเริ่มต้นที่ Encapsulation พร้อมทั้งยกตัวอย่างประกอบด้วย Java ส่วนฟีเจอร์อื่นๆนั้นจะเขียนในบทความถัดๆไป (หากคุณยังไม่เข้าใจว่า OOP คืออะไร ผมแนะนำให้ไปอ่านบทความ Object-Oriented Programming (OOP) คืออะไรกันแน่? และ Objects คืออะไร? เสียก่อนครับ)

Information Hiding

ก่อนอื่นเลยผมขอถามก่อนนะครับว่า Encapsulation แปลว่าอะไร เอาความหมายที่ไม่เกี่ยวข้องกับการเขียนโปรแกรมนะครับ ตอบได้มั๊ยครับ? ถ้าคุณตอบได้ ความหมายที่เขาใช้ในเรื่องของการเขียนโปรแกรมก็ไม่หนีไปจากนั้นหรอกนะครับ !!! หัวเรื่องมันต้องสัมพันธ์กับเนื้อเรื่องด้วยจริงมั้ยครับ เอาหล่ะจะตอบไม่ได้หรือตอบได้แล้วยังงงอยู่ก็ไม่เป็นไรครับ เรามาหาคำตอบกันโดยผมขอเริ่มจากกรณีศึกษาก่อนนะครับ อย่างที่เราทราบกัน ออบเจกต์ประกอบไปด้วยตัวแปร (Instance Variable) หรือแอททริบิวต์ (Attribute) และเจ้าตัวแปรนี่แหละที่เก็บค่าข้อมูลซึ่งจะถูกนำไปประมวลผลในภายหลัง อาทิเช่น ออบเจกต์ Employee ประกอบไปด้วยตัวแปร id, name, และ salary เป็นต้น ตัวอย่างที่ 1 (Employee.java) แสดงโค้ดตัวอย่างของคลาส Employee

ตัวอย่างที่ 1: Employee.java
public class Employee{
  public int id;
  public String name;
  public double salary;
}

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

Employee emp1 = new Employee();
emp1.id = 1;
emp1.name = "James";
emp1.salary = 1_000_000; // _ is a new feature of Java 7

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

เราสามารถใช้ private Modifier เพื่อปกป้องข้อมูลของเราได้ เพราะว่าเจ้า Modifier ตัวนี้จะไม่ยอมให้ตัวแปรถูกเข้าถึงจากภายนอกคลาสได้ Compiler จะฟ้อง Error ทันที ลองดูตัวอย่างกัน

public class Employee{
  private double salary;
  . . .
} // end of class

public class TestEmployee{
  public static void main(String args[]){
    Employee emp = new Employee();
    emp.salary = 10000000; // error, cann't access
    . . .
  }
} // end of class

เพียงเท่านี้เราก็มั่นใจได้ว่าจะไม่มีใครมาใส่ข้อมูลที่ผิดๆถูกๆให้กับตัวแปรในออบเจกต์ของเราได้แล้ว และการเขียนโปรแกรมรูปแบบนี้แหละที่เขาเรียกว่า Information Hiding หรือก็คือการซ่อนข้อมูลนั่นเอง เพราะว่าออบเจกต์อื่นๆจะไม่เห็นว่ามีตัวแปร private เหล่านี้อยู่ หากคุณใช้ IDE เช่น Netbeans หรือ Eclipse แล้วละก็ เวลากดจุด (.) คุณก็จะไม่เห็นตัวแปรเหล่านี้ปรากฏใน Auto-complete เอ๊ะแล้วมันเกี่ยวอะไรกับ Encapsulation หล่ะเนี่ย เกี่ยวแน่นอนครับแต่ต้องอ่านต่อไป

(เคยสังเกตุมั้ยครับว่า ภาษา Programming (Static Typing Language) มันกำหนดได้แต่ชนิดของตัวแปร แต่มันกำหนดขอบเขต (range) ของค่าข้อมูลไม่ได้ !!! )

Encapsulation

การทำ Information Hiding ฟังดูมันก็โอเคใช้ได้ดีนะ เพราะข้อมูลของเราจะต้องปลอดภัยแน่ๆ แต่แล้วเราจะใช้มันในความเป็นจริงได้อย่างไร ในเมื่อตัวแปรมันไม่สามารถเข้าถึงได้เลย จะเซตค่าให้ก็ไม่ได้ จะดึงค่าข้อมูลเอามาโชว์เอามาใช้ก็ไม่ได้ !!! แล้วมันจะไม่ขัดแย้งกับหลักการเบื้องต้น (Basic Principle) ของ OOP หรือ? ในเมื่อหัวใจหลักของ OOP คือ การแบ่งระบบออกเป็นออบเจกต์ย่อยๆ แต่ละออบเจกต์มีบทบาทหน้าที่เพียงอย่างเดียว และสุดท้ายทุกๆออบเจกต์ทำงานร่วมกันออกมาเป็นระบบที่สมบูรณ์ เช่นถ้าเราจะออกแบบให้ออบเจกต์ Employee มีบทบาทเป็น Model (หรือ Entity) ในระบบ แล้วให้ออบเจกต์อื่นนำค่าข้อมูลใน Model ไปใช้ แต่เราดันมาทำ Information Hiding ซะนี่ มันก็เลยเอาไปใช้ไม่ได้ !!! ครั้นจะไม่ทำเดี๋ยวก็มีคนเอาไปใส่ค่าผิดๆอีก เฮ้อ...

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

public class Employee{
  private double salary;
  . . .

  public double getSalary(){
    return salary;
  }
  public boolean setSalary(double sal){
    salary = sal;
  }
}

ตัวอย่างข้างต้น เราสร้างเมธอด getSalary() และ setSalary() ขึ้นมา เพื่อเป็นช่องทางในการเข้าถึงข้อมูล หากอยากได้ค่าข้อมูลมาใช้ก็ให้เรียก get() หากอยากเซตค่าใหม่ก็ให้เรียก set() ซึ่งทั้งสองเป็น public เมธอด ออบเจกต์อื่นๆจึงสามารถเรียกใช้ได้ เอ๊ะแต่ว่าข้อมูลของเราก็ยังคงถูกกำหนดค่าให้ผิดๆได้เหมือนเดิมนินา ไม่ต้องกังวลครับ เพราะภายในเมธอดเราสามารถควบคุมขอบเขตและความถูกต้องของข้อมูลได้แล้ว คือเราสามารถเขียน Control Logic ได้ไงครับ ตัวอย่างเช่นถ้าผมต้องการให้เงินเดือนอยู่ในช่วงของ 15,000 ถึง 100,000 เท่านั้น ก็สามารถทำได้ดังนี้

  public boolean setSalary(double sal){
    if(sal >= 15_000 && sal <= 100_000){
      salary = sal;
      return true;
    }else{
      return false;
    }
  }

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

และนั่นก็เป็นเพียงตัวอย่างหนึ่งของ Encapsulation ครับ การทำ Encapsulation ไม่จำเป็นที่เมธอดจะต้องเป็นชื่อ get และ set เสมอไป เพียงแต่ว่าเรานิยมตั้งชื่อกันแบบนั้น เพราะในการเข้าถึงค่าข้อมูลมันก็มีอยู่สองจุดประสงค์หลักๆคือ i) เพื่อเอาค่าไปใช้ ii) เพื่อเซตค่าใหม่ นอกจากนี้ เรายังไม่จำเป็นที่จะต้องมีทั้ง get และ set ในทุกๆแอททริบิวต์อีกด้วย ขึ้นอยู่กับเงื่อนไขของระบบ อย่างเช่นในกรณีตัวอย่างของ id ถ้าเราไม่ต้องการให้มันมีการเปลี่ยนแปลงได้อีก เราก็เปิดเฉพาะเมธอด get เพียงอย่างเดียวพอ แค่นี้ก็เซตค่าให้ใหม่ไม่ได้แล้ว อ่าวแล้วถ้าอย่างนั้นเราจะกำหนดค่าให้กับมันครั้งแรกได้อย่างไรหล่ะ? คำตอบคือทำผ่าน Constructor ครับ ดังนี้

public class Employee{
  private int id;
  public Employee(int initId){
    id = initId; // or maybe auto-gen
  }
  public int getId(){
    return id;
  }
  . . .
}

เรื่องการทำ Encapsulation ด้วย get/set นี่ ผมขออธิบายเพิ่มเติมอีกสักนิดนะครับ ในตัวอย่างที่ผ่านมาเรามีการกำหนดชนิด Return Type ของเมธอด set ให้เป็น boolean ซึ่งอันที่จริงแล้วในแพลตฟอร์มของ Java เราไม่นิยมทำเช่นนั้น แต่เรานิยมที่จะ Return void มากกว่า ทั้งนี้ทั้งนั้นก็เพื่อให้สอดคล้องกับสไตล์การตั้งชื่อของ JavaBeans นั่นเอง ซึ่งจะมีลักษณะดังนี้คือ

  • modifier type varName;
  • public type getVarName();
  • public void setVarName(type newVar);

แล้วทำไมเราจะต้องออกแบบตามสไตล์ของ JavaBeans ด้วยหล่ะ ทั้งนี้ทั้งนั้นก็เพื่อให้รองรับกับการใช้ JavaBeans ในอนาคต อาทิเช่น ในเทคโนโลยี Servlet/JSP, EJB เป็นต้น (การ Return void เราก็สามารถแจ้งข้อผิดพลาดได้นะครับ โดยทำผ่านการ Throw Exception แทน) หากคุณใช้ IDE ไม่ว่าจะของค่ายไหน ผมก็มั่นใจว่า IDE ที่คุณใช้เขามีฟีเจอร์การทำ Encapsulation ให้เราโดยอัตโนมัตินะครับ โดยมันจะอยู่ในเมนูของหมวด Refactor ครับ คลิ๊กสองสามทีคุณก็ได้ get/set มาครบทุกแอททริบิวต์ที่เราต้องการเลย :)

นอกจากนี้การทำ Encapsulation นั้น ไม่จำเป็นจะต้องทำในรูปของ get/set ก็ได้ ลองดูตัวอย่างของคลาส Account ในตัวอย่างที่ 2 กัน

ตัวอย่างที่ 2: Account.java
public class Account{
  private double balance; // information hiding

  // for first opening account
  public Account(double initBalance){
    balance = initBalance;
  }

  public boolean deposit(double amt){
    if(amt > 0){
      balance = balance + amt;
      return true;
    }else{
      return false;
    }
  } // end of deposit()

  public boolean withdraw(double amt){
    if(amt <= balance){
      balance = balance - amt;
      return true;
    }else{
      return false;
    }
  } // end of withdraw()

  public double getBalance(){
    return balance;
  } // end of getBalance()

} // end of class

จากตัวอย่างข้างต้น เราทำ Information Hiding ให้กับตัวแปร balance เพราะเราไม่ต้องการให้มีใครมาเข้าถึงข้อมูลเราได้โดยตรง และเนื่องจากบัญชีเงินฝากก็ต้องมีการฝากถอนได้ ดังนั้นจึงให้ทำผ่านเมธอด deposit() และ withdraw() ไม่ใช่ให้ไปบวกลบกับตัวแปร balance ได้เลยโดยตรง ดังนั้นเราจึงสามารถควบคุมความถูกต้องของข้อมูลได้ ดังจะเห็นว่าในตัวอย่างนี้ไม่ให้มีการถอนเงินเกินยอดที่มีในบัญชี เป็นต้น เห็นมั๊ยครับว่าการทำ Encapsulation ไม่จำเป็นต้องเป็น get/set เสมอไป แต่ในตัวอย่างนี้เราต้องการจะเช็คยอดเงินฝากของเราได้ด้วย ก็เลยมีเมธอด getBalance() นั่นเอง เท่ากับว่า 1 แอททริบิวต์แต่มี 3 Encapsulation Methods เลย (แนวคิดหน่ะเหมือนเดิม แต่การปฏิบัตินั้นประยุกต์ได้)

บทสรุปของ Encapsulation

พอเข้าใจกันมากขึ้นมั๊ยครับ ผมขอสรุปเรื่อง Information Hiding และ Encapsulation ดังนี้นะครับ การทำ Information Hiding คือการซ่อนและเพื่อปกป้องข้อมูลของเรา (ก็ตัวแปรนั่นแหละ) แต่การทำ Information Hiding อย่างเดียวจะไม่มีประโยชน์เลย ถ้าไม่ทำควบคู่กับ Encapsulation เพราะจะไม่มีใครหรือออบเจกต์ใดๆเลยที่สามารถเข้าถึงข้อมูลเหล่านั้นได้ และการทำ Encapsulation ก็คือการสร้างช่องทางในการเข้าถึงข้อมูลที่ได้ทำ Information Hiding ไป เราจะเรียกมันว่า Data Encapsulation ก็ได้นะครับ คือในท้องเรื่องนี้เราเน้นทำ Encapsulation กับตัวแปรนั่นเอง (อาจมีการทำ Encapsulation ในท้องเรื่องอื่นๆก็ได้) ในทางกลับกันการทำ Encapsulation เพียงอย่างเดียวไม่เกิดประโยชน์เช่นกัน มีแต่จะพิมพ์กันเหนื่อยเปล่า ถ้าเราไม่ทำ Information Hiding ด้วย เอ๊ะมันเป็นไปได้ด้วยหรือ? งั้นมาดูตัวอย่างกันดังนี้

public class Employee{
  public double salary;
  . . .

  public double getSalary(){
    return salary;
  }
  public boolean setSalary(double sal){
    if(sal >= 15_000 && sal <= 100_000){
      salary = sal;
      return true;
    }else{
      return false;
    }
  }
}

ตัวอย่างข้างต้น เราทำ Encapsulation แล้ว แต่เรายังเปิดตัวแปร salary ให้เป็น public อยู่เลย ดังนั้นการทำ Encapsulation จึงไม่ได้การันตีความถูกต้องของข้อมูลเลย และก็ไม่ผิดที่จะทำแบบนี้ด้วยเพราะ Compiler ก็ไม่ว่าอะไร แต่เราจะทำแบบนี้ไปทำไมหล่ะครับ ดังนั้นที่หลายๆคนเข้าใจกันว่า Encapsulation คือการปกป้องข้อมูลนั้นไม่ใช่นะครับ มันคือแนวคิดของ Information Hiding ต่างหาก (แม้แต่ความหมายในดิกชันนารีก็ไม่ได้บอกว่า Encapsulation มันเกี่ยวข้องกับการปกป้องแต่อย่างใด) แต่ทว่าเนื่องจากมันต้องทำไปพร้อมๆกันถึงจะมีประโยชน์ และก็ร่ำเรียนและถ่ายทอดกันมาไม่ครบถ้วน อาศัยจำและเขียนโค้ดตามๆกันมา พอเวลาผ่านไปความหมายมันก็เลยผิดเพี้ยนไปบ้างครับ จบแล้วครับสำหรับบทความนี้ ผมหวังว่ามันจะช่วยให้คุณเข้าใจเรื่อง Encapsulation ได้มากขึ้น แล้วพบกับบทความ Inheritance และ Polymorphism ในโอกาสถัดไปครับ มีความสุขกับการเขียนโปรแกรมกันนะคร้าบ ^^

(หมายเหตุ: การทำ Encapsulation ไม่จำเป็นต้องทำ Data Validation เสมอไปนะครับ เรื่องนี้พูดยากครับ บางคนก็ถือว่าทำ Validation ที่ออบเจกต์ View แล้ว ไม่ต้องทำที่ Model อีก บ้างก็ว่าทำที่ Service แน่นอนสุด บ้างก็ว่าทำมันทั้งสองที่เลยทั้ง Service และ View บ้างก็ว่าทำมันทุกที่และที่ Model ก็ต้องทำ... อันนี้เราต้องดูจากภาพรวมของระบบนะครับ ว่าอะไรที่เหมาะสมที่สุด อย่างไรก็ตามจะทำ Validation หรือไม่นั้น การทำ Encapsulation ก็ยังมีความจำเป็นอยู่ดีนะครับ อย่างน้อยก็ดีกว่ามาทำทีหลัง ให้มันต้อง Refactor กันทั้งโปรเจคจนปวดหัวไม่ได้หลับได้นอนครับ)


Books By Me

JavaScript Programming Guide book cover

JavaScript Programming Guide
Thai language
Kontentblue Publishing

About This Site

I use this site to share my knowledge in form of articles. I also use it as an experimental space, trying and testing things. If you have any problems viewing this site, please tell me.

→ More about me

→ Contact me

→ Me on facebook

Creative Commons Attribution License

creative commons logo

This license lets you distribute, remix, tweak my articles, even commercially, as long as you credit me for the original creation.

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