ที่มา: The Geek Asian เรามาดูปัจจัยพื้นฐานสี่ประการของการเขียนโปรแกรมเชิงวัตถุแล้วพยายามทำความเข้าใจวิธีการทำงาน การเขียนโปรแกรมเชิงวัตถุ (OOP) เป็นหนึ่งในกระบวนทัศน์การเขียนโปรแกรมหลัก อาจเป็นเรื่องง่ายและเรียบง่ายหรือในทางกลับกันซับซ้อนมาก ทุกอย่างขึ้นอยู่กับว่าคุณตัดสินใจพัฒนาแอปพลิเคชันของคุณอย่างไร
OOP มี 4 เสาหลัก:

- การห่อหุ้ม
- มรดก
- นามธรรม
- ความแตกต่าง
1. การห่อหุ้ม
เราทุกคนได้ศึกษาการห่อหุ้มเป็นการซ่อนองค์ประกอบข้อมูลและอนุญาตให้ผู้ใช้เข้าถึงข้อมูลโดยใช้วิธีสาธารณะ เราเรียกสิ่งเหล่านี้ว่า getters และ setters ทีนี้ เรามาลืมเรื่องนี้แล้วหาคำจำกัดความที่ง่ายกว่านี้กันดีกว่า การห่อหุ้มเป็นวิธีการจำกัดผู้ใช้จากการเปลี่ยนแปลงสมาชิกข้อมูลหรือตัวแปรคลาสโดยตรงเพื่อรักษาความสมบูรณ์ของข้อมูล เราจะทำเช่นนี้ได้อย่างไร? เราจำกัดการเข้าถึงตัวแปรโดยการสลับตัวแก้ไขการเข้าถึงเป็นแบบส่วนตัวและเปิดเผยวิธีการสาธารณะที่สามารถใช้เพื่อเข้าถึงข้อมูล ลองดูตัวอย่างที่เฉพาะเจาะจงด้านล่าง ซึ่งจะช่วยให้เราเข้าใจว่าเราสามารถใช้การห่อหุ้มเพื่อรักษาความสมบูรณ์ของข้อมูลได้อย่างไร ไม่มีการห่อหุ้ม:/**
* @author thegeekyasian.com
*/
public class Account {
public double balance;
public static void main(String[] args) {
Account theGeekyAsianAccount = new Account();
theGeekyAsianAccount.balance = -54;
}
}
ในข้อมูลโค้ดด้านบน เมธอด main()จะเข้าถึง ตัวแปร balanceโดยตรง ซึ่งช่วยให้ผู้ใช้สามารถตั้งค่าสองเท่าให้กับ ตัวแปรยอด คงเหลือ ของ คลาสบัญชีได้ เราอาจสูญเสียความสมบูรณ์ของข้อมูลได้โดยการอนุญาตให้ใครก็ตามกำหนดยอดคงเหลือให้กับตัวเลขที่ไม่ถูกต้อง เช่น -54 ในกรณีนี้ ด้วยการห่อหุ้ม:
/**
* @author thegeekyasian.com
*/
public class Account {
private double balance;
public void setBalance(double balance) {
if(balance >= 0) { // Validating input data in order to maintain data integrity
this.balance = balance;
}
throw new IllegalArgumentException("Balance cannot be less than zero (0)");
}
public static void main(String[] args) {
Account theGeekyAsianAccount = new Account();
theGeekyAsianAccount.setBalance(1); // Valid input - Allowed
theGeekyAsianAccount.setBalance(-55); // Stops user and throws exception
}
}
ในโค้ดนี้ เราได้จำกัดการเข้าถึง ตัวแปร balanceและเพิ่ม เมธอด setBalance()ที่ให้ผู้ใช้สามารถตั้ง ค่า สมดุลสำหรับAccountได้ ผู้ตั้งค่าจะตรวจสอบค่าที่ให้มาก่อนที่จะกำหนดให้กับตัวแปร หากค่าน้อยกว่าศูนย์ ข้อยกเว้นจะเกิดขึ้น สิ่งนี้ทำให้แน่ใจได้ว่าความสมบูรณ์ของข้อมูลจะไม่ถูกทำลาย หลังจากอธิบายตัวอย่างข้างต้นแล้ว ฉันหวังว่าคุณค่าของการห่อหุ้มซึ่งเป็นหนึ่งในสี่เสาหลักของ OOP นั้นชัดเจน
2. มรดก
การสืบทอดเป็นวิธีการรับคุณสมบัติของคลาสอื่นที่แชร์คุณสมบัติทั่วไป สิ่งนี้ช่วยให้เราเพิ่มความสามารถในการใช้ซ้ำและลดความซ้ำซ้อนของโค้ด วิธีการนี้ยังมีหลักการของการโต้ตอบระหว่างเด็กและผู้ปกครอง เมื่อองค์ประกอบลูกสืบทอดคุณสมบัติของผู้ปกครอง มาเจาะลึกสองตัวอย่างสั้นๆ และดูว่าการสืบทอดทำให้โค้ดง่ายขึ้นและนำกลับมาใช้ใหม่ได้อย่างไร โดยไม่มีมรดก:/**
* @author thegeekyasian
*/
public class Rectangle {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int getArea() {
return width * height;
}
}
public class Square {
private int width; // Duplicate property, also used in class Rectangle
public Square(int width) {
this.width = width;
}
public int getArea() { // Duplicate method, similar to the class Rectangle
return this.width * this.width;
}
}
ทั้งสองคลาสที่คล้ายกันจะแชร์คุณสมบัติความกว้างและ เมธอด getArea() เราสามารถเพิ่มการนำโค้ดกลับ มาใช้ใหม่ได้โดยการปรับโครงสร้างใหม่เล็กน้อย โดยที่ คลาส Squareจะสืบทอดมาจาก คลาส สี่เหลี่ยมผืนผ้า ด้วยมรดก:
/**
* @author thegeekyasian
*/
public class Rectangle {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int getArea() {
return width * height;
}
}
public class Square extends Rectangle {
public Square(int width) {
super(width, width); // A rectangle with the same height as width is a square
}
}
เพียงขยาย คลาส สี่เหลี่ยมผืนผ้าเราก็จะได้คลาสSquareเป็น ประเภท สี่เหลี่ยมผืนผ้า ซึ่งหมายความว่าจะสืบทอดคุณสมบัติทั้งหมดที่เหมือนกันกับSquareและสี่เหลี่ยมผืนผ้า ในตัวอย่างข้างต้น เราจะเห็นว่าการสืบทอดมีบทบาทสำคัญในการทำให้โค้ดสามารถนำมาใช้ซ้ำได้อย่างไร นอกจากนี้ยังอนุญาตให้คลาสสืบทอดพฤติกรรมของคลาสพาเรนต์ด้วย
3. สิ่งที่เป็นนามธรรม
นามธรรมเป็นเทคนิคในการนำเสนอเฉพาะรายละเอียดที่จำเป็นแก่ผู้ใช้โดยการซ่อนรายละเอียดที่ไม่จำเป็นหรือไม่เกี่ยวข้องของวัตถุ ช่วยลดความซับซ้อนในการปฏิบัติงานทางฝั่งผู้ใช้ Abstraction ช่วยให้เราสามารถจัดเตรียมอินเทอร์เฟซที่เรียบง่ายให้กับผู้ใช้โดยไม่ต้องขอรายละเอียดที่ซับซ้อนในการดำเนินการ พูดง่ายๆ ก็คือทำให้ผู้ใช้สามารถขับรถได้โดยไม่จำเป็นต้องเข้าใจว่าเครื่องยนต์ทำงานอย่างไร ลองดูตัวอย่างก่อนแล้วจึงอภิปรายว่านามธรรมช่วยเราได้อย่างไร/**
* @author thegeekyasian.com
*/
public class Car {
public void lock() {}
public void unlock() {}
public void startCar() {
checkFuel();
checkBattery();
whatHappensWhenTheCarStarts();
}
private void checkFuel() {
// Check fuel level
}
private void checkBattery() {
// Check car battery
}
private void whatHappensWhenTheCarStarts() {
// Magic happens here
}
}
ในโค้ดข้างต้น วิธีการ lock() , Unlock()และstartCar()เป็นแบบสาธารณะ และส่วนที่เหลือเป็นแบบส่วนตัวในชั้นเรียน เราได้ทำให้ผู้ใช้สามารถ "ขับรถ" ได้ง่ายขึ้น แน่นอนว่าเขาสามารถตรวจสอบcheckFuel()และcheckBattery() ได้ด้วยตนเอง ก่อนสตาร์ทรถด้วย startCar()แต่นั่นจะทำให้กระบวนการซับซ้อนขึ้น ด้วยโค้ดข้างต้น สิ่งที่ผู้ใช้ต้องทำคือใช้startCar()และคลาสจะจัดการส่วนที่เหลือ นี่คือสิ่งที่เราเรียกว่านามธรรม
4. ความแตกต่าง
เสาหลักสุดท้ายและสำคัญที่สุดในสี่เสาหลักของ OOP คือความหลากหลาย Polymorphism หมายถึง "หลายรูปแบบ" ตามชื่อของมัน มันเป็นฟังก์ชั่นที่ให้คุณดำเนินการได้หลายวิธีหรือต่างกัน เมื่อเราพูดถึงความหลากหลาย ไม่มีอะไรให้พูดคุยมากนักเว้นแต่เราจะพูดถึงประเภทของมัน ความหลากหลายมีสองประเภท:- วิธีการโอเวอร์โหลด - ความหลากหลายแบบคงที่ (Static Binding)
- การเอาชนะวิธีการ - ความหลากหลายแบบไดนามิก (Dynamic Binding)
วิธีการโอเวอร์โหลด - ความหลากหลายแบบคงที่:
Method Overloading หรือ Static Polymorphism หรือที่เรียกว่า Static Binding หรือ Compile-time Binding เป็นประเภทที่กำหนดการเรียกเมธอด ณ เวลาคอมไพล์ วิธีการโอเวอร์โหลดช่วยให้เรามีหลายวิธีที่มีชื่อเดียวกัน มีประเภทข้อมูลพารามิเตอร์ที่แตกต่างกัน หรือจำนวนพารามิเตอร์ต่างกัน หรือทั้งสองอย่าง แต่คำถามก็คือ เหตุใดวิธีการโอเวอร์โหลด (หรือความหลากหลายแบบคงที่) จึงมีประโยชน์ ลองดูตัวอย่างด้านล่างเพื่อทำความเข้าใจวิธีการโอเวอร์โหลดให้ดียิ่งขึ้น โดยไม่ต้องโอเวอร์โหลดวิธีการ:/**
* @author thegeekyasian.com
*/
public class Number {
public void sumInt(int a, int b) {
System.out.println("Sum: " + (a + b));
}
public void sumDouble(double a, double b) {
System.out.println("Sum: " + (a + b));
}
public static void main(String[] args) {
Number number = new Number();
number.sumInt(1, 2);
number.sumDouble(1.8, 2.5);
}
}
ในตัวอย่างข้างต้น เราสร้างสองวิธีโดยใช้ชื่อที่แตกต่างกัน เพียงเพื่อเพิ่มตัวเลขสองประเภทที่แตกต่างกัน หากเราดำเนินการแบบเดียวกันต่อไป เราจะมีหลายวิธีที่มีชื่อต่างกัน ซึ่งจะลดคุณภาพและความพร้อมใช้งานของโค้ด เพื่อปรับปรุงสิ่งนี้ เราสามารถใช้วิธีโอเวอร์โหลดได้โดยใช้ชื่อเดียวกันสำหรับวิธีการต่างๆ ซึ่งจะทำให้ผู้ใช้สามารถมีทางเลือกเดียวเป็นจุดเริ่มต้นในการรวมตัวเลขประเภทต่างๆ การโอเวอร์โหลดเมธอดจะทำงานเมื่อเมธอดตั้งแต่สองเมธอดขึ้นไปมีชื่อเดียวกันแต่มีพารามิเตอร์ต่างกัน ประเภทการคืนสินค้าอาจเหมือนหรือต่างกันก็ได้ แต่ถ้าสองวิธีมีชื่อเหมือนกัน พารามิเตอร์เดียวกัน แต่มีประเภทการส่งคืนต่างกัน จะทำให้เกิดการโอเวอร์โหลดและข้อผิดพลาดในการคอมไพล์! ด้วยวิธีการโอเวอร์โหลด:
/**
* @author thegeekyasian.com
*/
public class Number {
public void sum(int a, int b) {
System.out.println("Sum: " + (a + b));
}
public void sum(double a, double b) {
System.out.println("Sum: " + (a + b));
}
public static void main(String[] args) {
Number number = new Number();
number.sum(1, 2);
number.sum(1.8, 2.5);
}
}
ในโค้ดเดียวกัน มีการเปลี่ยนแปลงเล็กน้อยเล็กน้อย เราสามารถโอเวอร์โหลดทั้งสองวิธีได้ ทำให้ชื่อทั้งสองเหมือนกัน ขณะนี้ผู้ใช้สามารถระบุประเภทข้อมูลเฉพาะของตนเป็นพารามิเตอร์วิธีการได้ จากนั้นจะดำเนินการตามประเภทข้อมูลที่ให้ไว้ การผูกเมธอดนี้เสร็จสิ้นในเวลาคอมไพล์เนื่องจากคอมไพเลอร์รู้ว่าเมธอดใดที่จะถูกเรียกด้วยประเภทพารามิเตอร์ที่ระบุ นั่นเป็นเหตุผลที่เราเรียกมันว่าการผูกเวลาคอมไพล์
การเอาชนะวิธีการ - พหุสัณฐานแบบไดนามิก:
ไม่เหมือนกับการโอเวอร์โหลดเมธอด การแทนที่เมธอดทำให้คุณมีลายเซ็นเหมือนกันทุกประการกับเมธอดหลายวิธี แต่ต้องอยู่ในคลาสที่แตกต่างกันหลายคลาส คำถามก็คือ มีอะไรพิเศษเกี่ยวกับเรื่องนี้บ้าง? คลาสเหล่านี้มีความสัมพันธ์แบบ IS-A นั่นคือจะต้องสืบทอดจากกันและกัน กล่าวอีกนัยหนึ่ง ในการเอาชนะเมธอดหรือไดนามิกโพลีมอร์ฟิซึม เมธอดจะถูกประมวลผลแบบไดนามิกที่รันไทม์เมื่อมีการเรียกใช้เมธอด สิ่งนี้เสร็จสิ้นตามการอ้างอิงไปยังออบเจ็กต์ที่เริ่มต้น นี่เป็นตัวอย่างเล็กๆ น้อยๆ ของวิธีการเอาชนะ:/**
* @author thegeekyasian.com
*/
public class Animal {
public void walk() {
System.out.println("Animal walks");
}
}
public class Cat extends Animal {
@Override
public void walk() {
System.out.println("Cat walks");
}
}
public class Dog extends Animal {
@Override
public void walk() {
System.out.println("Dog walks");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.walk(); // Animal walks
Cat cat = new Cat();
cat.walk(); // Cat walks
Dog dog = new Dog();
dog.walk(); // Dog walks
Animal animalCat = new Cat(); // Dynamic Polymorphism
animalCat.walk(); // Cat walks
Animal animalDog = new Dog(); // Dynamic Polymorphism
animalDog.walk(); //Dog walks
}
}
ในตัวอย่างที่สำคัญนี้ เราได้กำหนดวัตถุประเภท "สุนัข" และ "แมว" ให้พิมพ์ "สัตว์" แบบไดนามิก สิ่งนี้ช่วยให้เราสามารถเรียกใช้เมธอดwalk()บนอินสแตนซ์ที่อ้างอิงแบบไดนามิกที่รันไทม์ เราสามารถทำได้โดยใช้วิธีการเอาชนะ (หรือความหลากหลายแบบไดนามิก) นี่เป็นการสรุปการสนทนาสั้น ๆ ของเราเกี่ยวกับเสาหลักทั้งสี่ของ OOP และฉันหวังว่าคุณจะพบว่ามีประโยชน์
GO TO FULL VERSION