Polymorphism #2: The Syntax

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

    การทำ Polymorphism ผ่าน Inheritance นั้น ก็คือการที่ code ฝั่งตัวเรียกนั้น จะมอง codeตัวถูกเรียกเฉพาะ่ base class ไม่ว่า class นั้นจะ inherit ไปเป็น derived class ใดๆ ยังไงมันต้องรองรับ interface ของ base class นั่นหมายถึงว่า คุณต้องออกแบบให้มันมีความสัมพันธ์แบบ IS-A นั่นเอง เรามาลองดูตัวอย่างกัน

    

using System;
class Animal
{ 	
	public void say()
	{ 		
		Console.WriteLine("Animal Say");
	} 		
}

    เราสร้าง class นี้มาเป็นต้นแบบครับ เป็น class สัตว์ ซึ่งเรากำลังจะ inherit มันให้เป็น หมา แมว วัว ดังนี้ครับ

class Dog : Animal
{ 	
	public void say()
	{
		Console.WriteLine("bok.. bok...");
	}
}
class Cat : Animal
{
	public void say()
	{
		Console.WriteLine("meow.. meow...");
	}
}
class Cow : Animal
{
	public void say()
	{
		Console.WriteLine("mu... mu...");
	}
}

    จะเห็นได้ว่า เราสร้าง class ขึ้นมาใหม่ 3 ตัว โดยที่ในแต่ละ class นั้น inherit มาจาก Animal ต่างคนต่างเอา method say() มาประยุกต์ให้เหมาะกับตัวเอง ถ้าคุณสร้าง main() เปล่าๆ แล้วลอง compile ดังนี้

class Start
{
	public static void Main()
	{
	}
}

 จะสามารถ compile ได้แต่จะเจอ warning ไม่ต้องสนใจมันครับ มัน compile ผ่านก็แล้วกัน  ในตอนนี้เรามาดูเฉพาะในส่วนของ Main()

public static void Main()
{
	Dog bobby = new Dog();
	Cat kitty = new Cat();
	Cow mali  = new Cow();
	
	bobby.say();
	kitty.say();
	mali.say();
}

ถ้าเขียน Main() แบบนี้จะได้ 

Dos Prompt

C:\CS>animal
bok.. bok...
meow.. meow...
mu... mu...

C:\CS>_

    ซึ่งก็ไม่ได้แปลกอะไร มันเป็นเรื่อง Inheritance ธรรมดา เรื่องนี้ยังไม่ใช่ polymorphism ครับ จำได้รึเปล่าว่าถ้าเป็น polymorphism มันต้อง reuse ฝั่งผู้เรียก ไม่ใช่ผู้ถูกเรียก ฝั่งผู้เรียกจะเรียกผ่าน base class ครับ ดังนี้

public static void Main()
{
	Dog bobby = new Dog();
	Animal an;
	an = (Animal)bobby;
}

     ในเรื่อง Syntax ของ OOP เกือบทุกภาษาจะยอมให้เราสามารถใช้ตัวชี้ของ base object ให้ไปชี้ยัง derive object ได้ เช่นในกรณีข้างบนเราสร้าง object ตัวหนึ่งจาก class Dog ซึ่ง Dog เอง นั้น Derive มาจาก Animal เราจึงสามารถสร้างตัวชี้ชนิด Animal ให้ชี้ไปยัง object ของ Dog ตัวนั้นได้ ตามตัวอย่าง an ซึ่งเป็นตัวชี้แบบ Animal จึงสามารถชี้ไปยัง bobby ซึ่งเป็น instance ของ Dog ได้

    ทำไมถึงยอม คุณอาจรู้สึกแปลกๆ ที่ยอมก็เพราะ Polymorphism ยึดหลัก IS-A ที่เราเคยเรียนกัน Dog IS-A Animal ดังนั้นถ้า Animal ทำอะไรได้ Dog ก็ย่อมต้องทำได้เช่นกัน Animal เป็น class กลางที่ สัตว์ต่างๆ มา derive ไป ดังนั้น Animal จึงเก็บเฉพาะ property/method ที่ share ด้วยกันทั้งหมดของสัตว์ทุกชนิด ซึ่งพอ derive มาเป็น Dog มันก็สามารถเพิ่ม Methods อื่นๆ เฉพาะของ Dog ได้เช่น คาบจาน เป็นต้น 

    ดังนั้นคุณจะเห็นได้ว่า เราสามารถสร้างตัวชี้ชนิด Animal ไปชี้ไปยัง Dog, Cat หรือ Cow ได้ และถึงแม้ มี Inheritance จาก Cat ให้เป็น PersianCat  ถ้าคุณสร้าง object จาก PersianCat คุณก็สามารถสร้างตัวชี้เป็น Animal หรือ Cat ไปชี้ยัง object นั้นได้

    แต่ในทางกลับกัน

public static void Main()
{
	Animal an = new new Animal()
	Dog bobby;
	bobby = (Dog)an;
}

    ประโยคสีแดงผิดครับ คุณคงเดาได้ว่าเป็นเพราะอะไร ถูกแล้วครับ เวลาคุณ new() เป็น Animal คุณมี Methods จำกัดมาก ถ้าคุณเอาตัวชี้แบบ Dog ไปชี้มัน พอเวลาคุณสั่ง Method คาบจาน มันก็ error เพราะ object แบบ Animal จริงๆ ไม่มี Method คาบจานครับ

    คราวนี้น่าสนใจ เราสามารถเอา an ชี้ไปที่ object ของ Dog แล้วถ้าเรียก Method say() หละ version มันจะถูกเรียก

public static void Main()
{
	Dog bobby = new Dog();
	Animal an;
	an = (Animal)bobby;
	an.say();
}

Dos Prompt

C:\CS>animal
Animal Say

C:\CS>_

    ผิดหวังผสมกับงงใช่ไหมครับ มันไม่ polymorphism จริงนี่ ไหนบอกว่า code ตัวเรียกสามารถเรียก code ของตัวที่ plug-in ได้ทำไมมันเรียก code ของตัวเองอีก ทีมันเป็นเช่นนี้ คุณจำได้ไหมผมเคยบอกแล้วว่า การ Derive นี้มันไม่ได้รวมเป็น object เดียวกันจริงๆ แต่มันเอา object ของ base และ derive ผูกติดกันแล้วกำหนดสิทธิ์ในการเข้าถึงเท่านั้น พอเวลาคุณเรียก object ผ่านตัวชี้แบบ Animal มันจึงมองว่าเป็น Animal ดังนั้น code ที่คุณเขียนมันไม่เป็น polymorphism โดยธรรมชาติครับ คุณต้องเพิ่ม code เอง

    หมายเหตุ: ถ้าคุณเอาตัวแปรชนิดที่เป็น base class กว่า มารับ object ชนิดที่ derive กว่า จะทำได้เลยไม่ต้อง cast ก่อน เช่น

Animal an = new Dog();

   แต่ในทางกลับกันถ้า คุณเอาตัวแปรชนิดที่ derive กว่าไปรับตัวแปรที่ base กว่า คุณต้อง cast ดังนี้ครับ

Dog dog = (Dog)an;

 

    เราต้องปรับ code ครับ Method ไหนที่เราต้องการให้มีคุณสมบัติของ Polymorphism เราต้องเพิ่ม modifier ที่ชื่อว่า virtual นำหน้าใน base class ดังนี้ครับ

using System;
class Animal
{ 	
	virtual public void say()
	{ 		
		Console.WriteLine("Animal Say");
	} 		
}

    ส่วน class ที่ derived นั้น ต้องนำหน้าด้วยคำว่า override เช่น Dog เป็นตัวอย่าง ดังนี้ครับ 

class Dog : Animal
{ 	
	override public void say()
	{
		Console.WriteLine("bok.. bok...");
	}
}

 ถ้าทำตามนั้นได้ เรามาลอง run code นี้ดูใหม่

public static void Main()
{
	Dog bobby = new Dog();
	Animal an;
	an = (Animal)bobby;
	an.say();
}

Dos Prompt

C:\CS>animal
bok.. bok...

C:\CS>_

   เรามาดูกันว่า ทำไมมันถึงทำงานได้ เริ่มต้นนะครับ เมื่อคุณใช้ตัวชี้ an ซึ่งเป็น class Animal ชี้ไปยัง object Dog ใน object ของ Dog มันมีทั้ง say() ของ Animal และ say() ของ Dog อยู่ใน object เดียวกัน

    ดังนั้นมันจะเข้าไปทำงานที่ say() ของ Animal เพราะตัวชี้ของเราชนิด Animal มันจึงหลงผิดคิดว่าเราต้องการ say() ของ Animal

    แต่ say() ของ Animal() มี modifier คำว่า virtual อยู่ หมายความว่ามันอาจจะมี รุ่นไหม่ที่เหมาะสมกว่ามัน มันจึงเข้าไปหา derived class แล้วมองหา say() ที่ มี modifier ว่า override ซึ่งมันก็พบ แต่มันก็ไม่หยุดเท่านั้นมันพยายามไปหา derive class ของ Dog อีก ดูว่า มี say() ที่เป็น override อีกหรือเปล่า แต่ใน object ไม่มี derive อะไรลึกลงไปกว่า Dog อีก มันจึงเลือกใช้ Derive class ของ Dog ซึ่งก็ได้คำตอบที่ถูกต้อง

    คุณอาจจะดูงงๆ แต่ลองตั้งโจทย์ทำเองดูดู แล้วคุณจะเข้าใจโดยไม่ยากครับ

abstract

    ในเรื่องของ Polymorphism นั้น คุณสังเกตว่า Base class ตามตัวอย่างข้างบนคือ Animal เราไม่ใช้มัน new() เลย ตัวนี้ new() จะเป็นตัวที่ Derive จากมันอีกที ถ้าเกิดคุณสร้าง plug-in แล้วลืม override say() คุณก็จะได้ code ที่ผิด อย่านึกว่ามันจะไม่เกิดนะครับ เพราะในงานจริงๆ คนเขียน code ตัวเรียก และตัวถูกเรียกอาจจะเป็นคนละคน ถ้าการสื่อสารไม่ดีพอ จะมี Bug เต็มไปหมด

    C# จึงแก้ให้ครับ ให้คุณสร้าง abstract class คือ class นั้นจะไม่สามารถ new() ได้ ถ้าพยายาม new() มันจะเกิด error compile ไม่ผ่าน  และถ้าคุณสร้าง Derive class ใหม่เช่น Pig ถ้า Inherit มาจาก Animal แล้วไม่ได้ สร้าง override Method มันจะ compile ไม่ผ่านบังคับให้คุณต้องสร้าง Method นั้น เราต้องเขียนแบบนี้ที่ Base class ครับ

abstract class Animal
{ 	
	abstract public void say();
		
}

    จะเห็นว่าในเมื่อเราทำ abstract แล้ว เป็นการบอกว่าเราจะไม่มี code ตัวที่ derive ไปถึงจะบังคับให้มี ดังนั้นใน Base class จึงไม่มี code เราจะเห็นว่า เราไม่มี code block { } เราใช้ semicolon เปิดท้ายไปเลย

    การใช้ abstract เป็นสิ่งดีครับ ป้องกันการเกิด bug ได้มาก