ในฐานะ Senior Architect การออกแบบระบบที่ยืดหยุ่น (Flexible) และสามารถขยายตัวได้ง่าย (Scalable) คือหัวใจสำคัญของงานวิศวกรรมซอฟต์แวร์ แนวคิดที่ช่วยให้เราบรรลุเป้าหมายนี้คือ Polymorphism หรือ ‘การมีหลายรูปแบบ’ ซึ่งเป็นหลักการสำคัญใน Object-Oriented Programming (OOP)
Polymorphism อนุญาตให้เราใช้ชื่อเมธอดหรือตัวแปรเดียว แต่สามารถทำงานที่แตกต่างกันไปตามบริบทของการเรียกใช้งาน ในภาษา Java (และแนวคิดที่นำมาประยุกต์ใช้ในระบบฐานข้อมูลเชิงวัตถุอย่าง PostgreSQL) เราแบ่งกลไกนี้ออกเป็นสองประเภทหลัก คือ Compile-time Polymorphism (Overloading) และ Runtime Polymorphism (Overriding) การเข้าใจความแตกต่างนี้คือกุญแจสำคัญในการออกแบบระบบที่แข็งแกร่งและดูแลรักษาง่าย
💡 1. Compile-time Polymorphism: Method Overloading
Overloading เกิดขึ้นเมื่อเรามีเมธอดหลายตัวในคลาสเดียวกันที่มีชื่อเหมือนกัน แต่รับพารามิเตอร์ (Signature) ที่แตกต่างกัน (เช่น จำนวนหรือชนิดของข้อมูล) คอมไพเลอร์จะสามารถตัดสินใจได้ตั้งแต่ขั้นตอนการคอมไพล์ว่าควรเรียกใช้เมธอดใดโดยดูจากประเภทและจำนวนของ Argument ที่ส่งเข้าไป
แนวคิดเชิงสถาปัตยกรรม: การออกแบบ API ที่มีหลายเวอร์ชันสำหรับงานที่คล้ายกัน แต่ถูกรวมไว้ภายใต้ชื่อเรียกหลักเดียว ทำให้ผู้ใช้งานเรียกใช้ฟังก์ชันได้ง่ายโดยไม่ต้องจำชื่อฟังก์ชันเฉพาะเจาะจง
ตัวอย่างการจำลอง Overloading ใน PostgreSQL (PL/pgSQL):
เราสามารถจำลอง Overloading โดยการสร้างฟังก์ชันที่มีชื่อเหมือนกันแต่รับ Argument ที่แตกต่างกัน:
SQL
-- ฟังก์ชันสำหรับประมวลผลสตริง (รับ 1 argument)
CREATE OR REPLACE FUNCTION process_data(input_text TEXT) RETURNS TEXT AS $$
BEGIN
RETURN 'Processed Text: ' || input_text;
END;
$$ LANGUAGE plpgsql;
-- ฟังก์ชันสำหรับประมวลผลตัวเลข (รับ 2 arguments)
CREATE OR REPLACE FUNCTION process_data(a INTEGER, b INTEGER) RETURNS INTEGER AS $$
BEGIN
RETURN a + b;
END;
$$ LANGUAGE plpgsql;
-- การเรียกใช้งาน: คอมไพเลอร์รู้ว่าจะเรียกใช้ฟังก์ชันไหน
SELECT process_data('apple,banana,orange'); -- เรียกใช้เวอร์ชัน TEXT
SELECT process_data(10, 20); -- เรียกใช้เวอร์ชัน INTEGER
💡 2. Runtime Polymorphism: Method Overriding
Overriding เกิดขึ้นเมื่อคลาสลูก (Subclass) มีการเขียนเมธอดที่มีชื่อและ Signature เดียวกันกับคลาสแม่ (Superclass) แต่ต้องการให้มีพฤติกรรมที่แตกต่างออกไป การตัดสินใจว่าจะเรียกใช้เมธอดใดจะเกิดขึ้นในขณะที่โปรแกรมกำลังทำงาน (Runtime) โดยขึ้นอยู่กับประเภทจริงของ Object ที่ถูกเรียกใช้งาน
แนวคิดเชิงสถาปัตยกรรม: ระบบต้องสามารถจัดการกับ Object ที่มีหลายรูปแบบ (เช่น Product, Service) โดยใช้ Interface หรือ Parent Class เดียวกัน แต่เมื่อเรียกใช้ฟังก์ชัน จะต้องรู้ว่า Object นั้นเป็นประเภทใดเพื่อเรียกใช้พฤติกรรมที่ถูกต้อง
การจำลอง Overriding ใน PostgreSQL (Conceptual):
ใน PostgreSQL เราจำลองแนวคิดนี้โดยการใช้ฟังก์ชันที่รับข้อมูลประเภท Record หรือ JSON ซึ่งให้ความยืดหยุ่นในการจัดการพฤติกรรมตามข้อมูลที่ป้อนเข้ามา:
SQL
-- สมมติว่าเรามีฟังก์ชันหลักที่ประมวลผล 'Item'
CREATE OR REPLACE FUNCTION calculate_cost(item_type TEXT, price NUMERIC, quantity INTEGER) RETURNS NUMERIC AS $$
BEGIN
-- พฤติกรรมที่แตกต่างกันตามประเภท (Runtime Decision)
IF item_type = 'PRODUCT' THEN
RETURN price * quantity * 1.1; -- มีภาษี
ELSIF item_type = 'SERVICE' THEN
RETURN price * quantity; -- ไม่มีภาษี
ELSE
RETURN 0;
END IF;
END;
$$ LANGUAGE plpgsql;
-- การเรียกใช้งาน: ระบบตัดสินใจพฤติกรรม ณ Runtime
SELECT calculate_cost('PRODUCT', 100.00, 2); -- ผลลัพธ์: 220.00 (มีภาษี)
SELECT calculate_cost('SERVICE', 50.00, 3); -- ผลลัพธ์: 150.00 (ไม่มีภาษี)
✅ สรุปความแตกต่างทางสถาปัตยกรรม
ความแตกต่างหลักคือ Overloading คือการตัดสินใจที่เกิดขึ้นตั้งแต่คอมไพล์ไทม์ (Compile-time) โดยดูจาก Signature ของเมธอด ส่วน Overriding คือการตัดสินใจที่เกิดขึ้นในขณะรันไทม์ (Runtime) โดยดูจากประเภทจริงของ Object หรือข้อมูลที่ถูกส่งเข้ามา
การเข้าใจหลักการเหล่านี้ช่วยให้เราออกแบบระบบฐานข้อมูลและแอปพลิเคชันที่สามารถรองรับการขยายตัวของฟังก์ชัน (Scalability) และมีความยืดหยุ่นสูง (Flexibility) โดยที่โค้ดหลักไม่จำเป็นต้องรู้รายละเอียดของทุกส่วนประกอบย่อย