Tuesday, January 8, 2013

new modifier, static binding และ dynamic binding

ในตอนนี้จะพยายเขียนให้เข้าใจง่ายที่สุด สำหรับผู้ที่เพิ่งเริ่มให้เข้าใจกระบวนการ instantiate object

คำสั่งในภาษา C# เช่นคำสั่ง new อาจมีการใช้งานได้เกินหนึ่งรูปแบบซึ่งก็คือ
  1. ใช้ในการทำ method hiding
  2. ใช้สร้าง object
ในหัวข้อนี้ เราจะขอพูดถึงอย่างหลังก่อนนะครับแล้วในตอนท้าย จะค่อยสาธิตการทำ method hinding

ในการสร้าง object เราสามารถใช้คำสั่งแบบนี้

    new Beer();


จะเห็นว่า หลังคำว่า new จะเป็นชื่อของ constructor ของ class ที่เราต้องการสร้าง นั้นก็คือ Beer() ซึ่ง constructor คือ method ของ class ที่จะทำงานเมือเราต้องการจะสร้าง class นั้นๆ เช่น class Beer ข้างล่าง


    class Beer
    {
        String Name;

        public Beer() : this("Noname")
        {
        }

        public Beer(string name)
        {
            this.Name = name;
        }
    }


จะเห็นว่า ใน class Beer มีสอง method ที่ไม่มี return type และมีชื่อเหมือนกันกับชื่อ class คือ "Beer()" และ "Beer(string name)" (constructor overload) นั้นหมายความว่า เราสามารถสร้าง object จาก constructor ตัวใดตัวหนึ่งจากสองตัวนี้ก็ได้ ทำให้การสร้าง object มีความยืดหยุ่น และสามารถประยุคเทคนิคในการสร้างและจัดการ object ได้หลากหลาย

จากตัวอยากข้างบน "new Beer()" code จะทำงานดังนี้

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       8 (0x8)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  newobj     instance void SampleDynamicBinding.Beer::.ctor()
  IL_0006:  pop
  IL_0007:  ret
} // end of method Program::Main


ข้างบนนี้คือ MSIL ที่ได้หลังจาก compile คำสั่ง new Beer(); การทำงานคร่าวๆ ก็คือ
  1. เรียก constructor เพื่อสร้าง instance ของ class Beer
  2. จองและจัดสรรนหน่วยความจำ เพื่อเก็บ instance ของ Beer ในที่นี้คือ managed heap
  3. จบ

อ่าว แล้วงี้เราจะไปใช้ไอ้ตัว object นี้ได้ไงอ่ะ คำตอบคือ เราก็สร้างตัวแปร เพื่อที่จะอ้างอิงไปหาไอ้ instance ที่อยู่ใน heap นั้นไงล่ะ, ง่ายๆ ด้วย syntax ที่คุ้นเคย แบบนี้

    Beer beer = new Beer();


จะเห็นว่า มีส่วนเพิ่มขึ้นมาสามส่วนจากหน้าไปหลังคือ
  1. Beer คือ ชื่อของ type นั้นก็คือ class Beer ซึ่งต้องมี type ตรงกันกับกับ object ที่เราสร้างด้วยคำสั่ง new
  2. beer คือชือของตัวแปร
  3. เครื่องหมาย "=" คือการ assign ค่าอ้างอิงไปยัง instance ของ Beer ที่เราสร้างดังที่อธิบายไปแล้ว

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       8 (0x8)
  .maxstack  1
  .locals init ([0] class SampleDynamicBinding.Beer beer)
  IL_0000:  nop
  IL_0001:  newobj     instance void SampleDynamicBinding.Beer::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ret
} // end of method Program::Main


MSIL ที่ compile ออกมาจะต่างออกไปหน่อย ซึ่งการทำงานคร่าวๆ จะเป็นประมาณนี้

  1. หลังจากที่มีการสร้าง instance ของ class และเก็บไว้ใน managed heap ไปแล้ว
  2. จองเนื้อที่ใน stack เพื่อเก็บตัวแปร "beer" ซึ่งมี type เป็น Beer
  3. ทำการกำหนดค่า reference ของ beer ไปยัง instance ในข้อ 1.
ทีนี้ เราก็สามารถใช้ instance ของ class Beer ได้แล้ว โดยการใช้ผ่านตัวแปร beer นั้นเอง ซึ่งการกำหนดค่าอ้างอิงของ instance ไปยังตัวแปรที่มี type ตรงกัน เราเรียกว่า name binding 

การ binding แบ่งออกเป็น 2 แบบคือ static binding และ dynamic binding
  1. Static binding คือ การ กำหนดค่าอ้างอิงไปยังตัวแปลได้เลย ขณะ compile time ดังตัวอย่างข้างต้น
  2. Dynamic binding คือ การกำหนดค่าอ้างอิงไปยังตัวแปลของ object ขณะ run-time ซึ่งวิธีนี้ ทำให้การออกแบบซอร์ฟแวรได้ยืดหยุ่น และหลากหลาย สนับสนุนหลักการ polymorphism เช่น dependency injection เป็นต้น
ตัวอย่างด้านล่าง จะเป็นการสาธิตการทำ dynamic binding และการทำ method hiding ด้วย new modifier นะครับ จาก class Beer เราจะเพิ่ม class ที่ชือว่า Singha เข้ามา ซึ่งนี้ implement class Beer อยู่ (inherit) 

    class Beer
    {
        string Name;

        public Beer() : this("Noname")
        {
        }

        public Beer(string name)
        {
            this.Name = name;
        }

        public void WriteName()
        {
            Console.WriteLine(this.Name);
        }
    }

    class Singha : Beer
    {
        string Name; 

        public Singha() : this("Singha")
        {
        }

        public Singha(string name)
        {
            this.Name = name;
        }

        public new void WriteName()
        {
            Console.WriteLine(this.Name);
        }
    }


บรรทัดข้างล่างนี้ เป็นการสร้าง instance ของ class Singha แล้ว assign ใส่ตัวแปลที่ชื่อว่า beer ซึ่งมี type เป็น Singha

    Singha beer = new Singha();


ซึ่งกระบวนการสร้าง object แบบนี้จะต่างจากแบบก่อนหน้านิดหน่อย เพราะว่า class Singha implement class Beer ทำให้เวลาสร้าง object Singha ใน heap จะมีการสร้างและเรียก constructor ของ class แม่มันด้วย ซึ่งก็คือ Beer ทำให้ใน managed heap มี instance อยู่สองตัว คือ
  1. instance ของ class Singha
  2. instance ของ class Beer
นั้นหมายความว่า ถ้าเราใช้ตัวแปร เป็น type ของ Beer เราก็สามารถที่จะเลือกเสียบไปยัง instance Singha หรือ Beer ใน heap ก็ได้ ซึ่งนั้นก็คือ dynamic binding เป็นผลเนื่องมาจาก compiler มีการทำ implicit type conversion

    Beer beer = new Singha();


แต่เราไม่สามารถทำแบบนี้ได้

    Singha beer = new Beer();


เพราะว่า เราเรียก constructor ของ Beer จะไม่มีการสร้าง instance ของ class Singha, การทำ type conversion จะเป็นจาก base class ไป derived class เท่านั้น  ไม่งั้นจะเกิด error แบบนี้
Cannot implicitly convert type 'SampleDynamicBinding.Beer' to 'SampleDynamicBinding.Singha'. An explicit conversion exists (are you missing a cast?)

ตัวอย่าง

    Singha beer = new Singha();
    beer.WriteName();               // dynamic type คือ Singha
    ((Beer)beer).WriteName();       // dynamic type คือ Beer


ผลรัน


    Singha
    Noname

นอกจาก method hiding ก็ยังมีการทำ method overriding อีกตัว ซึงเป็นการเปลี่ยนการทำงานของ method ใน derived class ต่างจากการทำ hiding ที่จะทับการทำงานเดิม

ตัวอย่าง

    Beer beer = new Singha();
    beer.WriteName();               // dynamic type คือ Singha
    ((Beer)beer).WriteName();       // dynamic type คือ Beer


ผลรัน

    Noname
    Noname

ผลรันเป็น Noname ทั้งสองบรรทัดเนื่องจาก class Singha มีการทำ method hinding ทำให้ complier มองเป็นคนละ method กัน ถึงแม้จะเรียกใช้ constructor ของ Singha ซึ่งจะมีการ  implicit type conversion จาก Beer เป็น Singha แต่ method ที่ถูกเรียกจะเป็น Beer.WriteName() 

ตัวอย่างด้านล่างคือการทำ method overriding

    class Beer
    {
        string Name;

        public Beer() : this("Noname")
        {
        }

        public Beer(string name)
        {
            this.Name = name;
        }

        public virtual void WriteName()
        {
            Console.WriteLine(this.Name);
        }
    }

    class Singha : Beer
    {
        string Name; 

        public Singha() : this("Singha")
        {
        }

        public Singha(string name)
        {
            this.Name = name;
        }

        public override void WriteName()
        {
            Console.WriteLine(this.Name);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Beer beer = new Singha();
            beer.WriteName();               
            ((Beer)beer).WriteName();       
        }
    }
 

ผลรัน


    Singha
    Singha

จะเห็นว่าการทำ method overriding จะเป็นการทับการทำงานของ method เดิม 

No comments:

Post a Comment