💖 Polymorphism

🤔 มันคืออะไร ?

คำว่า Polymorphism มีใช้อยู่ในหลายวงการเลย แต่ในวงการซอฟต์แวร์ใน Wikipedia ถูกเขียนไว้ว่า

Polymorphism is the provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types.

😑 ผมเชื่อว่าถ้าไม่เคยรู้จักมันมาก่อนจริงๆอ่านไปก็ไม่เข้าใจหรอก แต่ก็ถอดหัวใจสำคัญของมันออกมาได้ว่า

Polymorphism คือทำให้ object เปลี่ยนรูปได้

🤨 ก็ยัง งง อยู่ดีขอตัวอย่างหน่อย

😢 ปัญหา

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

public class SavingAccount
{
public void Withdraw(double amount) { ... }
}
public class CurrentAccount
{
public void Withdraw(double amount) { ... }
}

แล้วสมมุติว่าเราต้องเขียนให้มัน ถอนเงิน ให้กับบัญชีทั้ง 2 แบบนี้ได้ล่ะ เราจะเขียนยังไง?

ถ้าเอาแบบง่ายก่อน ก็สร้าง method ให้มันถอนเงินได้ใช่ป่ะ ตามโค้ดด้านล่าง

static void Main(string[] args)
{
var sa = new SavingAccount();
Withdraw(sa, 500);
var ca = new CurrentAccount();
Withdraw(ca, 700);
}
static void Withdraw(SavingAccount account, double amount)
{
account.Withdraw(amount);
}
static void Withdraw(CurrentAccount account, double amount)
{
account.Withdraw(amount);
}

มันก็ทำงานได้นะ แต่ลองคิดดูว่าถ้าเรามีบัญชีอีกหลายๆประเภทเข้ามาล่ะ? เราจะมี method พวกนี้กี่ตัว?

🤔 วิเคราะห์ปัญหากันนิสนุง

ปัญหาที่เราเจออยู่ตอนนี้คือ object ที่เราสร้างขึ้นมา มันจะต้องใช้กับ data type ที่มันเป็นอยู่เท่านั้น มันเลยทำให้เราไม่สามารถส่ง object ของ SavingAccount ไปยัง CurrentAccount ได้ และทางตรงข้ามกันเราก็ส่ง object ของ CurrentAccount ไปยัง SaveingAccount ไม่ได้ด้วยเช่นกัน เพราะ object มันผูกกับ data type ที่มันเป็นอยู่นั่นเอง

😄 วิธีแก้ปัญหา

ตามหัวข้อคือเราจะใช้ Polymorphism มาแก้ปัญหาในรอบนี้ยังไงล่ะ ซึ่งการใช้ใช้ Polymorphism ได้เราจะต้องใช้หลักของ Inheritance เข้ามาช่วยด้วยดังนั้นก็แก้โค้ดให้มันเป็นแม่ลูกกันหน่อยนึง ตามตัวอย่างในเรื่อง Inheritance เลย (ถ้าจำไม่ได้กดที่ชื่อเพื่อกลับไปทบทวนซะ) ซึ่งเราก็จะได้ออกมาราวๆนี้

public class BankAccount { ... }
public class SavingAccount : BankAccount { }
public class CurrentAccount : BankAccount { ... }

ซึ่งหลักจากที่เราทำ Inheritance มาเรียบร้อย เราจะได้ความสามารถของ Polymorphism มาด้วย นั่นคือ

การเปลี่ยนรูปได้ (ผมชอบใช้คำว่าโค้ดมันดิ้นได้) นั่นหมายความว่า object นั้นๆมันจะไม่ได้ผูกติดกับ data type ที่มันอยู่แล้วนั่นเอง

ดังนั้นเราจะสามารถเขียนโค้ดแบบนี้ได้

BankAccount acc1 = new SavingAccount();
BankAccount acc2 = new CurrentAccount();

จะเห็นว่าตัว acc1 และ acc2 ทั้งคู่มันเป็นคลาส์ BankAccount แต่มันก็สามารถเก็บ object ที่ไม่ใช่ BankAccount ได้นั่นเอง ดังนั้นเราก็สามารถที่จะทำให้โค้ดของเรารองรับการทำงานของ บัญชีหลายๆรูปแบบในอนาคตได้แล้ว โดยการเขียนโค้ดเพียงแค่ตัวนี้เท่านั้น

static void Withdraw(BankAccount account, double amount)
{
account.Withdraw(amount);
}

ไหนทดลองทำงานดูดิ๊

static void Main(string[] args)
{
BankAccount acc1 = new SavingAccount { OwnerName = "(Saving) Saladpuk" };
Withdraw(acc1, 500);
Console.WriteLine($"{acc1.OwnerName}, has THB {acc1.Balance}.");
BankAccount acc2 = new CurrentAccount { OwnerName = "(Current) Saladpuk" };
Withdraw(acc2, 700);
Console.WriteLine($"{acc2.OwnerName}, has THB {acc2.Balance}.");
}

Output (Saving) Saladpuk, has THB 0. (Current) Saladpuk, has THB -700.

จะเห็นว่ามันทำงานได้ตามปรกติแถมยังทำงานถูกต้องตามแต่ละประเภทบัญชีด้วย บัญชีออมทรัพย์ถอนเงินเกินในบัญชีไม่ได้ แต่บัญชีกระแสรายวันถอนเงินเกินได้

😵 Polymorphism คือไรกันแน่ ?

ก่อนอื่นผมอยากให้เข้าใจตรงกันก่อนว่า โดยปรกติตัวแปรมันจะทำงานตามที่ type ที่มันเป็นอยู่เท่านั้น เช่น เรามีคลาส Dog กับ Cat

public class Dog { }
public class Cat { }

ถ้าเราจะไปสร้าง object ของคลาส Dog ออกมา เราก็ต้องใช้ตัวแปรที่เป็นคลาส Dog มาเก็บมันเท่านั้น

Dog d1 = new Dog();

ไม่สามารถสร้าง object ของคลาส Dog ไปเก็บไว้กับตัวแปรที่เป็นคลาส Cat ได้เลย

Cat c1 = new Dog(); // Error

นี่แหละคือสิ่งที่เรียกว่า object มันผูกกับ data type ที่มันเป็นอยู่ นั่นเอง

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

public class Animal { }
public class Dog : Animal { }
public class Cat : Animal { }

มันเลยทำให้คลาส Dog และ Cat สามารถ เปลี่ยนรูป ตัวมันเองให้ตัวแปรที่เป็น Animal สามารถเก็บ object เหล่านั้นได้

Animal dog = new Dog();
Animal cat = new Cat();

การเปลี่ยนรูป

จากตัวอย่างด้านบนคือการเปลี่ยนรูป จาก Dog หรือ Cat กลายเป็น Animal เลยทำให้ตัวแปร Animal สามารถทำงานร่วมกัน Dog และ Cat ได้นั่นเอง โดยมันมีกฏอยู่ว่า

Base Class สามารถเก็บ Sub Class ได้ แต่ทำตรงข้ามกันไม่ได้

ถ้าเราแปลงโค้ดด้านบนเป็นรูปแล้วเราจะได้ออกมาเป็นแบบนี้

นั่นหมายความว่า Animal สามารถเก็บ object ที่เป็น Sub Class ของมันได้นั่นคือ Dog และ Cat แต่เราไม่สามารถทำตรงข้ามกันได้ นั่นคือ Dog และ Cat ไม่สามารถเก็บ object ที่เป็น Animal ได้นั่นเอง

Dog dog = new Animal(); // Error
Cat cat = new Animal(); // Error

และถ้าเรามองย้อนกลับไปถึงลำดับชั้นของการทำ Inheritance แล้วเราจะพบว่ามันเป็นรูปนี้

เลยเป็นผลว่าทำไม Object เลยสามารถเก็บตัวแปรทุกอย่างในโค้ดเราได้นั่นเอง (มันเป็น base class สูงสุดนั่นเอง)

object animal = new Animal();
object dog = new Dog();
object cat = new Cat();

Casting

นอกจากที่เราจะเปลี่ยนรูปจาก Sub class ไปให้ Base class เก็บไว้ได้แล้ว เรายังสามารถเปลี่ยนรูปมันกลับมาก็ได้นะ เช่น

public class Animal
{
public string Name { get; set; }
}
public class Dog : Animal
{
// พลักในการกัด
public int BitePower { get; set; }
}
static void Main(string[] args)
{
Animal animal = new Dog
{
Name = "Saladpuk",
BitePower = 50,
};
Dog dog = (Dog)animal;
Console.WriteLine($"Name: {dog.Name}, BitePower: {dog.BitePower}");
}

Output Name: Saladpuk, BitPower: 50

จากโค้ดด้านบนจะเห็นว่าแม้ Dog จะถูกเปลี่ยนรูปไปเป็น Animal ในบรรทัดที่ 14 แล้ว แต่เราก็สามารถแปลงมันกลับมาเป็น Dog ได้ตามปรกติ ในบรรทัดที่ 19

Interface

อีกตัวอย่างหนึ่งในการทำ Polymorphism นั่นคือการใช้ Interface นั่นเอง เช่น เครื่องใช้ไฟฟ้าสามารถเปิด/ปิดได้ โดยมีทีวีและมือถือเป็นเครื่องใช้ไฟฟ้า

จากรูปเราก็สามารถทำ Polymorphism ได้เช่นกัน ตามนี้

public interface IElectronic
{
void TunrOn();
void TunrOff();
}
public class Television : IElectronic
{
public void TunrOff() => Console.WriteLine("Tv - off");
public void TunrOn() => Console.WriteLine("Tv - on");
}
public class Mobile : IElectronic
{
public void TunrOff() => Console.WriteLine("Mobile - off");
public void TunrOn() => Console.WriteLine("Mobile - on");
}

ลองใช้งานมันดู

IElectronic e1 = new Television();
e1.TunrOn();
e1.TunrOff();
IElectronic e2 = new Mobile();
var mobile = (Mobile)e2;
mobile.TunrOn();
mobile.TunrOff();

Output Tv - on Tv - off Mobile - on Mobile - off

เกร็ดความรู้ Polymorphism มันมีหลามันมาจากคำ 2 คำรวมกันนั่นคือ (Poly = หลากหลาย) + (Morph = รูปร่าง) ดังนั้นพอรวมกันแล้วเลยเป็นกลายทำให้โค้ดของเรา เปลี่ยนรูป ได้นั่นเอง ซึ่งมันจะช่วยให้โค้ดของเรายืดหยุ่นขึ้นยังไงล่ะ

🎥 วีดีโอประกอบความเข้าใจ

ทฤษฎี

ตัวอย่างการใช้งาน