ตอน 26 Method Overrding, name biding การทำ hiding และ Abstract

ไปหน้าแรก | สารบัญ | Laploy.comระเบียนบทความ | บทความจากลาภลอย

เว็บไซต์นี้เป็นตัวอย่างเนื้อหาบางตอนในหนังสือ "เรียนรู้ด้วยตนเอง OOP C# ASP.NET" ครอบคลุม บทที่ 1 ถึงบทที่ 6 (ในหนังสือมี 21 บท) ข้อเขียนในหนังสืออาจอาจแตกต่างจากใน Blog นี้ เพราะเป็นเนื้อหาที่ยังไม่ได้ตรวจแก้ขัดเกลา (edit)

กดที่นี่เพื่อดูรายละเอียดเนื้อหาในแต่ละบท

กดที่นี่เพื่อไปยังเว็บบอร์ด ถาม-ตอบ 

 

นี่คือตอน 3 ของหัวข้อ Inheritance และสิ่งที่ต่อเนื่องจาก Inheritance ในภาษา C#

 

method overriding

เมื่อรับ method เป็นมรดกจาก base class แล้วเราอาจพบว่าบาง method มีคุณสมบัติที่ไม่ตรงกับความต้องการ หากเป็นเช่นนั้นเราสามารถ override method นั้นเพื่อให้กลายเป็น method ที่มีคุณสมบัติตามความต้องการของเราได้

เฉพาะ method ใน base class ที่ประกาศไว้เป็นแบบ virtual เท่านั้นที่จะ override ได้ โปรดพิจารณาตัวอย่างโค้ดต่อไปนี้

1      class A
2      {
3           public void Foo() { }
4           public virtual void Goo() { }
5      }
6      class B : A
7      {
8           public void Foo() { }
9           public void Goo() { }
10          public override void Goo()
11          {
12              base.Goo();
13          }
14     }

โค้ดข้างบนคือนิยามคลาสสองคลาส คลาสแรกชื่อ A ภายในมี method สมาชิกสอง method อันแรกชื่อ Foo() อีก method ชื่อ Goo() ซึ่งถูกประกาศไว้ให้เป็น virtual อีกคลาสหนึ่งชื่อ B สืบสันดานจากคลาส A ภายในคลาสนี้มีนิยาม method สมาชิกสาม method คือ Foo(), Goo() และ Goo() แบบ override ตามลำดับ

 

ข้อสรุปเรื่อง method override

  • method ต้องเป็น virtual จึงจะถูก override ได้
  • method Foo() ของคลาส A (ต่อไปจะเรียก A.Foo())จะถูก override ไม่ได้เพราะไม่เป็น virtual
  • method Goo() ของคลาส A (ต่อไปจะเรียก A.Goo())จะถูก override ได้เพราะเป็น virtual (บรรทัดที่ 4)
  • การทำให้ method ใดๆ กลายเป็นแบบ virtual ทำได้โดยใส่ keyword virtual ไว้หน้า return type ของ method นั้นๆ
  • บรรทัดที่ 8 มีนิยาม method Foo() (ต่อไปจะเรียก B.Foo()) ซ้ำกับ A.Foo()ซึ่งไม่ดี เพราะจะบังนิยามของ A.Foo() ทำให้ A.Foo() ถูกบังไว้ เมื่อคอมไพล์แม้จะไม่ error แต่จะมี warning ว่านิยาม B.Foo() ซ้ำกับใน A.Foo()แก้ไขได้โดยใส่คำสั่ง new ไว้หน้า B.Foo() ซึ่งจะมีผลให้ A.Foo() ถูกบังการทำงานไป (ไม่ทำงาน)
  • บรรทัดที่ 9 อย่างนี้เมื่อคอมไพล์จะเกิด error เพราะถือเป็นการนิยาม method ใหม่เพื่อบัง A.Goo() แต่ A.Goo() มีภาวะเป็น virtual ซึ่งบังไม่ได้ (override ได้เท่านั้น)
  • บรรทัดที่ 10 เป็นการทำ override ที่ถูกต้อง
  • การนิยาม override method ต้องใช้คำสั่ง override
  • การนิยาม override method ต้องมี signature ตรงกับ method ใน base class
  • การนิยาม override method ต้องมี return type (int, string…) ตรงกับ method ใน base class
  • การนิยาม override method ต้องมี access modifier (public, protected…) ตรงกับ method ใน base class
  • การอ้างถึง method ใน base class ทำได้โดยใช้ keyword base (บรรทัดที่ 12)
  • ไม่สามารถ override method แบบ static ได้

 

Name binding

name binding คือการจับคู่ระหว่าง object กับตัวอ้างอิง ปรกติจะเกิดขึ้นระหว่างการคอมไพล์ แต่เมื่อมีการทำ inheritance ทำให้บางสถานการณ์ name binding ไม่สามารถทำได้เมื่อคอมไพล์

ในเอกสารเกี่ยวกับภาษา C# ท่านจะพบคำว่า static binding (early binding ก็ว่า) และ dynamic binding (late binding ก็ว่า) อยู่เสมอ สองคำนี้เป็นเรื่อง name binding ซึ่งเกี่ยวของกับ inheritance โดยตรง ดังนั้นผู้เขียนจึงจะอธิบายภายใต้หัวของ inheritance นี้

name binding คือการจับคู่ระหว่าง instance ของคลาสกับ reference variable ที่เราสร้างขึ้นเพื่อทำหน้าที่เป็นตัวอ้างอิง ยกตัวอย่างเช่น

A foo = new A();

อย่างนี้คือการนำตัวแปรชื่อ foo (ตัวอ้างอิง) ไปจับคู่กับ instance ที่ถูกสร้างจาก type A หากการจับคู่นี้เกิดก่อนโปรแกรมทำงาน (คือเกิดขณะ compile time) จะเรียกว่า static binding หากการจับคู่นี้เกิดขณะโปรแกรมทำงาน (runtime) จะเรียกว่า dynamic binding โปรดพิจารณาโค้ดต่อไปนี้

class A
{
    public virtual void WhoAreYou() { Console.WriteLine("I am an A"); }
}
class B : A
{
    public override void WhoAreYou() { Console.WriteLine("I am a B"); }
}

โค้ดข้างบนนิยามคลาสสองคลาสๆ A ทำหน้าที่เป็น base class พร้อม method แบบ virtual และ คลาส B ทำหน้าที่เป็น derived class และมี method ที่ override method ชื่อเดียวกันของ A

1 A foo = new A();
2 foo.WhoAreYou(); // ได้ผลลัพธ์เป็น “I am a A”
3
4 A Bar = new B();
5 Bar.WhoAreYou(); // ได้ผลลัพธ์เป็น “I am a B” 

บรรทัดที่ 1 และ 2 สร้าง reference object ชื่อ foo มี type เป็น A เมื่อเรียกให้ method WhoAreYou() ทำงานปรากฏว่า method WhoAreYou()ของคลาส A ทำงาน ในทำนองเดียวกัน บรรทัดที่ 4 และ 5 สร้าง reference object ชื่อ Bar มี type เป็น B เมื่อเรียกให้ method WhoAreYou() ทำงานปรากฏว่า method WhoAreYou()ของคลาส B ทำงาน

1 static void Use(A x)
2 {
3      x.WhoAreYou();
4 }
5 static void Main(string[] args)
6 {
7      Use(new A()); // ได้ผลลัพธ์เป็น “I am a A”
8      Use(new B()); // ได้ผลลัพธ์เป็น “I am a B”
9 }

ตัวอย่างโค้ดข้างบนผู้เขียนสร้างนิยาม method ใหม่ขึ้นมา (ในคลาส ที่ไม่ใช่ A หรือ B) ชื่อ Use รับ argument หนึ่งตัวมี type เป็น A จากนั้นเรียกใช้ method ที่ยังไม่รู้ว่าจะเป็นของคลาส A หรือ B (บรรทัดที่ 3) ในบรรทัดที่ 7 ผู้เขียนเรียก method Use โดยส่งพารามิเตอร์เป็น type A และบรรทัดที่ 8 ผู้เขียนเรียก method Use โดยส่งพารามิเตอร์เป็น type B

จะเห็นว่าโค้ดตัวอย่างนี้ เมื่อมีการเรียกใช้ method ที่ยังไม่รู้ว่าจะเป็นของคลาส A หรือ B (บรรทัดที่ 3) จึงยังไม่สามารถทำ name binding ได้ตอนคอมไพล์ (early binding) ต้องรอไปจนกระทั่งโปรแกรมทำงานที่บรรทัดที่ 7 และ 8 การ binding จึงจะเกิดขึ้นได้ (late binding)

 

ภาษา C# สนับสนุนการทำ late binding ซึ่งจะเกิดขึ้นในกรณีที่ compiler ไม่สามารถหา type ของ object ตอนคอมไพล์ได้ การทำ name binding จึงต้องรอไปทำตอน run-time โดย CLR เมื่อ object ถูกกำหนด type ที่แน่นอนแล้ว

 

ข้อสรุปเรื่อง name binding

  • name binding คือการจับคู่ระหว่าง object กับตัวอ้างอิง
  • หากเกิดตอนคอมไพล์เรียก early binding
  • หากเกิดตอน runtime เรียก late binding
  • late binding จะเกิดเมื่อต้องหาค่านิพจน์ก่อนจึงจะรู้ type ของ object

 

การทำ hiding

เมื่อทำ inheritance แล้วเราสามารถบัง method บาง method ของ base class ได้ ท่านคงจำได้จากหัวข้อ method overriding ว่าหากเรานิยาม method ใน derived class ชื่อเดียวกับ method ใน base class จะมีผลทำให้ method ใน base class ถูกบังไว้ (hiding) หากเราต้องการทำเช่นนั้นโดยเจตนาให้ใช้คำสั่ง new โปรดพิจารณาโค้ดตัวอย่างต่อไปนี้

class A
{
    public int x;
    public void F() { }
    public virtual void G() { }
}
class B : A
{
    public new int x;
    public new void F() { }
    public new void G() { }
} 

โค้ดข้างบนเป็นนิยามคลาส 2 คลาสๆ A ทำหน้าที่เป็น base class และคลาส B ทำหน้าที่เป็น derived class คลาส A มีสมาชิกสามตัว เป็น field หนึ่งตัวที่เหลือเป็น method โดยสมาชิกเหล่านี้ถูกบังไว้ทั้งหมดในคลาส B ต่อไปโปรดพิจารณาการนำ type ทั้งสองไปใช้

B b = new B();         // สร้าง object b จากกคลาส B
b.x = 12;              // ค่าที่ถูกเปลี่ยนคือ B.x
b.F();                 // เรียก method B.F()
b.G();                 // เรียก method B.G()
((A)b).x = 34;         // ค่าที่ถูกเปลี่ยนคือ A.x
((A)b).F();            // เรียก method A.F()
((A)b).G();            // เรียก method A.G()

ข้อสรุปเรื่อง hiding

  • hiding คือการซ่อนสมาชิกของ base class
  • hiding ทำทำได้กับสมาชิกแบบ method, constant, field, properties และ type
  • hiding ทำได้ทั้งในคลาสและ struct
  • method ใน derived class จะบัง properties, field และ type ชื่อเดียวกันใน base class
    ใช้ keyword new และ override พร้อมๆ กันกับสมาชิกตัวเดียวกันไม่ได้

 

Abstract class

ในการทำ inheritance บางครั้งเราต้องการสร้างคลาสเพื่อนำไปใช้เป็น base class โดยเฉพาะ ซึ่งทำได้โดยใช้คำสั่ง abstract

abstract คือ modifier ตัวหนึ่งที่นำไปใช้กับคลาสก็ได้ หรือจะใช้กับ method, properties, indexers และ event ก็ได้เช่นกัน เมื่อเราต้องการสร้างคลาสเพื่อนำไปใช้เป็น base class โดยเฉพาะ (ไม่ใช่คลาสที่มีไว้เพื่อสร้าง instance เป็น object) เราจะใช้ modifier abstract กำกับไว้หน้าชื่อคลาส ซึ่งมีผลให้คลาสนั้นกลายเป็น abstract class

abstract class เป็นคลาสพิเศษ เพราะภายใน abstract class อาจจะมีแต่ประกาศนิยามต่างๆ โดยไม่มีตัวโค้ดจริงๆ (คือมีแต่ส่วนหัว ไม่มีไส้ใน) คลาสที่นำ abstract class ไปใช้ (เพื่อสืบสันดาน คือ derived class) จะต้องใส่ส่วนโค้ดจริงๆ ของนิยามนั้น โปรดพิจารณาโค้ดตัวอย่างต่อไปนี้

    abstract class ShapesClass
    {
        abstract public int Area();
    }

นี่คือนิยาม abstract class ชื่อ ShapesClass ภายในคลาสนี้มีนิยาม method ชื่อ Area() ซึ่งมี modifier เป็น abstract เช่นเดียวกัน โปรดสังเกตว่า method นี้มีแต่ประกาศนิยามส่วนหัว ไม่มีส่วนโค้ดภายใน method แต่อย่างใด

    class Square : ShapesClass
    {
        int x, y;
        public override int Area()
        {
            return x * y;
        }
    }

โค้ดข้างบนเป็นนิยามคลาส Square ซึ่งสืบสันดานจาก ShapsClass โปรดสังเกตว่าคลาส Square จำเป็นต้องมีส่วนนิยามโค้ดของ method Area() มิฉะนั้นจะเกิด compile error

ข้อสรุปเรื่อง abstract class

  • abstract class คือคลาส

  • abstract class มีไว้ทำ base class โดยเฉพาะ
  • ใช้ abstract class สร้าง object ไม่ได้
  • ใน abstract class จะมีสมาชิกที่ไม่ใช่ abstract ก็ได้
  • Method ที่ไม่ใช่ abstract ต้องมีโค้ดนิยามการทำงาน
  • สมาชิกของ abstract class ไม่ได้เป็น abstract ไปโดยปริยาย
  • abstract method ต้องไม่มีส่วนโค้ดสำหรับการทำงาน
  • abstract method มีภาวะเป็น virtual โดยปริยาย
  • abstract method ต้องอยู่ภายใน abstract class เท่านั้น
  • derived class จะทำ override ต่อ method แบบ virtual ที่อยู่ใน abstract base class หรือไม่ก็ได้ 
  • Derived class ไม่จำเป็นต้องกำหนดโค้ดการทำงานให้ method แบบ virtual ของ abstract base class
  • Derived class จำเป็นต้องกำหนดโค้ดการทำงานให้ method แบบ abstract ของ abstract base class ให้ครบทุกตัว

ตอนต่อไป: Interface

Post a comment or leave a trackback: Trackback URL.

ความเห็น

ใส่ความเห็น

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / เปลี่ยนแปลง )

Twitter picture

You are commenting using your Twitter account. Log Out / เปลี่ยนแปลง )

Facebook photo

You are commenting using your Facebook account. Log Out / เปลี่ยนแปลง )

Google+ photo

You are commenting using your Google+ account. Log Out / เปลี่ยนแปลง )

Connecting to %s

%d bloggers like this: