ตอน 27 Interface

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

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

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

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

 

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

 

Interface

ผู้เรียนภาษา C# (และภาษา Java ด้วย) บางคนพอได้ยินคำว่า Interface แล้วขวัญบิน เพราะคิดว่าเป็นศาสตร์การเขียนโค้ดระดับสูง คงมีแต่ศาสตราจารย์ทางคอมพิวเตอร์เท่านั้นที่จะเข้าใจได้ นั่นเป็นความเข้าใจผิด อันที่จริง interface เป็นเรื่องเรียบง่ายที่แม้แต่แม่บ้านทุกคนก็สามารถทำความเข้าใจได้

ลองคิดเปรียบเทียบว่าคลาสเป็นเครื่องใช้ไฟฟ้าในครัวเรือน เครื่องใช้ไฟฟ้าก่อนจะทำงานได้ต้องเสียบปลั๊ก ยกตัวอย่างเช่น เครื่องซักผ้ากับเครื่องดูดฝุ่น แม้จะแตกต่างกันมาก แต่ก็เป็นเครื่องใช้ไฟฟ้าเหมือนกัน จึงต้องเสียบปลั๊กเหมือนกัน เครื่องใช้ไฟฟ้าไม่ว่าจะเป็นชนิดก็ตาม ต้องมีปลั๊กที่ตรงตามข้อกำหนด มิฉะนั้นจะเสียบกับปลั๊กที่ฝาผนังไม่ได้ ดังเช่นที่ท่านอาจพบว่าเครื่องใช้ไฟฟ้าที่ซื้อมาจากประเทศอังกฤษไม่สามารถเสียบกับปลั๊กในประเทศไทยได้

ข้อกำหนดของปลั๊กไฟนี้ เทียบได้กับ interface มันคือข้อกำหนดซึ่งทำหน้าที่ระบุเพียงว่าปลั๊กไฟต้องมีลักษณะเช่นใด (คือมีขนาดเท่าใด มีกี่ขา แต่ละขามีขนาดเท่าใดเป็นต้น) แต่ไม่ได้กำหนดว่าจะเครื่องใช้ไฟฟ้านั้นๆ นำไฟฟ้าไปใช้อย่างไร ยกตัวอย่างเช่น เครื่องซักผ้ากับเครื่องดูดฝุ่น แม้จะมีปลั๊กไฟที่ตรงตามข้อกำหนดทั้งคู่ แต่วิธีที่มันนำไฟฟ้าไปประยุกต์ใช้นั้นแตกต่างกันมาก

โปรดคิดเปรียบเทียบว่า ไฟฟ้าคือคลาสหลัก หรือตัวโปรแกรมหลัก เครื่องใช้ไฟฟ้าเปรียบเหมือนคลาสบริการ คลาสบริการจะต้องต่อเชื่อมกับคลาสหลักได้ ข้อกำหนด หรือข้อตกลงของการเชื่อมต่อระหว่างคลาสหลักกับคลาสบริการคือ interface ยกตัวอย่างเช่นเรามีคลาสหลักทำหน้าที่แสดงรายงาน และมีคลาสบริการหลายคลาส ทำหน้าที่เรียงข้อมูล คลาสบริการแต่ละคลาสใช้วิธีเรียงข้อมูลแตกต่างกัน (เช่น quick sort, bubble sort, merge sort และ radix sort) interface คือข้อกำหนดที่ทุกคลาสบริการต้องทำตาม เพื่อให้คลาสหลักสามารถเรียกคลาสบริการเพื่อการเรียงข้อมูลได้ โดยคลาสหลักไม่จำเป็นต้องสนใจว่าคลาสบริการมี method ชื่ออะไร พารามิเตอร์เป็นเช่นไร หรือแม้แต่รับ-ส่งข้อมูลเป็น type อะไร

ภาษา C# สนับสนุนแนวคิด component based programming อย่างเต็มที่ เพราะแนวคิดนี้ช่วยให้การสร้างซอฟท์แวร์มีประสิทธิภาพ ท่านคงยังจำได้จากที่อ่านไปแล้วในบทที่ 1 ว่า แนวคิดนี้เหมือนการต่อ เลโก้ หรือการนำดิจิตอลไอซี มาต่อกัน หากออกแบบการเชื่อมต่อไว้ให้ดี จะทำให้สามารถนำชิ้นส่วนเล็กๆ หลายๆ แบบมาต่อกันให้กลายเป็นระบบใหญ่ที่มีความซับซ้อนได้โดยง่าย ในภาษา C# การเชื่อมต่อนี้คือ interface

หลักการ OOP มีเป้าหมายให้การสร้างซอฟท์แวร์มีความยืดหยุ่น นำกลับมาใช้ใหม่ได้ และบำรุงรักษาได้ง่าย แต่กลไก OOP อื่นๆ ในภาษา C# ช่วยให้บรรลุเป้าหมายนี้ได้เพียงแค่ครึ่งเดียว และ interface เติมเต็มส่วนที่เหลือ ประโยชน์ของ interface มีสามประการดังนี้

• จัดกลุ่มงานที่สัมพันธ์กัน
• ความสะดวกเรื่อง type
• multiple inheritance

จัดกลุ่มงานที่สัมพันธ์กัน: เราใช้ interface เพื่อรวบรวมสิ่งต่างๆ ที่หลายคลาสใช้ร่วมกัน โดยคลาสเหล่านั้นไม่จำเป็นต้องมีความสัมพันธ์กัน (คือไม่สืบสันดานกัน) ยกตัวอย่างเช่นคลาสเครื่องซักผ้า และคลาสเครื่องดูดฝุ่น แม้จะมีความสามารถในการปรับความเร็วในการทำงาน ทั้งคู่ แต่การนำสองคลาสนี้มาเป็น derived class ของคลาส “ปรับความเร็ว” ย่อมไม่สมเหตุสมผล (เพราะการปรับความเร็วของเครื่องซักผ้าและคลาสเครื่องดูดฝุ่นมีความแตกต่างกันมากเกินไป) ที่ถูกคือทั้งคู่ควรเป็น derived class ของคลาส “เครื่องใช้ไฟฟ้า” แล้วจัดให้มี interface การ “ปรับความเร็ว” ที่คลาสทั้งสองใช้ร่วมกัน (แต่มีวิธีปฏิบัติเป็นของตนเอง)

ความสะดวกเรื่อง type: นอกจากนั้น interface ยังช่วยให้เราใช้ object ได้ โดยไม่จำเป็นต้องรู้ type ของมัน ยกตัวอย่างเช่นหากเรารู้สึกว่าความเร็วในการทำงานสูงเกินไป เราสามารถสั่งให้ผู้ที่กำลังทำงานลดความเร็วลงได้ โดยเราไม่จำเป็นต้องรู้ว่าผู้ที่กำลังทำงานเป็น “เครื่องซักผ้า” หรือ “เครื่องดูดฝุ่น” ขอยกอีกตัวอย่างที่เป็นเรื่องของการเขียนโค้ดจริงๆ คืออัลกอริทึมเพื่อเรียงข้อมูล (sorting algorithm) ที่ตามปรกติจะรับ object ที่มี type เป็น interface แบบ IComparable ทำให้มันสามารถจัดเรียงข้อมูลได้ โดยไม่จำเป็นต้องสนใจว่า object ที่รับมามี type อะไร

multiple inheritances: ภาษา C# ไม่สนับสนุนการทำ multiple inheritance (การสืบสันดานจากหลาย base class) เพราะอาจทำให้เกิดข้อผิดพลาดที่ดีบักยาก งานใดที่จำเป็นต้องอาศัย multiple inheritance สามารถทำได้โดยใช้ interface

โปรดดูตัวอย่างโค้ดแสดงการทำงานของ interface ต่อไปนี้

interface I1
{
    void MyFunction();
}
interface I2
{
    void MyFunction();
} 

โค้ดข้างบนคือนิยาม interface class สอง class ชื่อ I1 และ I2 ตามลำดับ โปรดสังเกตว่า interface class คล้าย abstract class ในแง่ที่ว่า ภายในคลาสมีนิยามสมาชิกเฉพาะส่วนหัวเท่านั้น ไม่มีโค้ดที่ทำงานจริง

class Test : I1, I2
{
    public void MyFunction()
    {
        Console.WriteLine("This is I1");
    }
    void I2.MyFunction()
    {
        Console.WriteLine("This is I2");
    }
}

โค้ดข้างบนคือนิยามคลาสธรรมดาชื่อ Test เป็นคลาสที่สืบสันดานจาก interface class สองคลาส (multiple inheritances) ภายในคลาส Test มีโค้ดทำงานของ method ที่รับมรดกมาจากการ interface คือ method MyFunction() โดยมีทั้ง MyFunction() ของ I1 และ MyFunction() ของ I2

    class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();
            I1 i1 = (I1)t;
            i1.MyFunction();
            I2 i2 = (I2)t;
            i2.MyFunction();
        } 
    }

โค้ดข้างบนคือโค้ดที่สาทิตการใช้งาน interface class I1, I2 และคลาสที่สืบสันดานจากสองคลาสนี้ (คือ Test) ขั้นแรกเราสร้างตัวแปร object ชื่อ t ให้มี type เป็น test และสร้าง instance ของคลาส test จากนั้นสร้างตัวแปร object ชื่อ i1 มี type เป็น I1 และสร้าง instance ของคลาส I1 โดยใช้การ casting จาก type test (คำสั่ง I1 i1 = (I1)t;) บรรทัดถัดมาลองเรียกใช้ method MyFunction(); ดูท่านคิดว่าจะเห็นข้อความว่าอะไร

คำสั่งสองบรรทัดสุดท้ายสร้างตัวแปร object ชื่อ i2 ให้มี type เป็น I2 และสร้าง instance ของคลาส I2 โดยอาศัยการ casting จาก type test (คำสั่ง I2 i2 = (I2)t;) บรรทัดถัดมาลองเรียกใช้ method MyFunction(); ดูท่านคิดว่าจะเห็นข้อความว่าอะไร

ผลลัพธ์จากการรันโค้ดตัวอย่างนี้คือ

This is I1
This is I2

 

เมื่อใดจึงควรใช้ Interface

ถ้าท่านเขียนโปรแกรมเล็กๆ ไว้ใช้เองคนเดียว และเขียนโค้ดด้วยตนเองทั้งหมด คงไม่จำเป็นต้องใช้ interface แต่ถ้าเขียนโปรแกรมซับซ้อนขึ้น หรือทำงานเกี่ยวข้องกับหลายฝ่าย หลายหน่วยงาน บางครั้งการใช้ interface ก็เป็นสิ่งจำเป็น หรือเป็นสิ่งที่ช่วยให้เขียนโปรแกรมได้กระชับขึ้น หรือมีมาตรฐานดีขึ้น หากเป็นซอฟท์แวร์ระบบงานขนาดใหญ่ ที่เขียนโดยนักเขียนโค้ดหลายกลุ่ม ผู้ควบคุมงานหรือผู้ออกแบบซอฟท์แวร์อาจใช้ interface เป็นเครื่องมือช่วยควบคุมการเขียนโค้ดของแต่ละฝ่ายให้ประสานกันได้

ต่อไปลองพิจารณาเหตุการณ์ต่อไปนี้ เราเขียนคลาสหลักคลาสหนึ่ง (สมมุติว่าชื่อคลาส BackUp) ทำหน้าที่เก็บสำรองข้อมูล โดยมีจุดหนึ่งในคลาส BackUp ที่เราต้องการบีบอัดข้อมูลก่อนนำไปเก็บ สมมุติว่าเราสร้างคลาสบริการชื่อ MakeZip ทำหน้าที่บีบอัดข้อมูลแบบ zip เมื่อต้องการบีบข้อมูลเราจึงใช้คำสั่งดังนี้

    MakeZip myZip = new MakeZip();
    myZip.Compress(data);

เมื่อทำงานไปได้ระยะหนึ่งเราต้องการบีบข้อมูลโดยใช้อัลกอริทึมแบบ rar เราจึงต้องสร้างคลาสบริการอีกคลาสหนึ่งชื่อ MakeRar ทำหน้าที่บีบอัดข้อมูลแบบ rar เมื่อต้องการบีบข้อมูลเราจึงใช้คำสั่งดังนี้

MakeRar myRar = new MakeRar()
myRar.Compress(data); 

เมื่อทำงานไปได้ระยะหนึ่งเราต้องการบีบข้อมูลโดยใช้อัลกอริทึมใหม่ๆ เพิ่มอีกห้าแบบคือ cab, tar, arj, gz และ arc เราจึงต้องสร้างคลาสบริการอีกห้าคลาส ทำให้เรามีคลาสบริการที่ทำหน้าที่บีบข้อมูลเจ็ดคลาสที่เวลาเรียกใช้ต้องสร้าง object เจ็ด object และเรียก method ของมันที่แตกต่างกันไปเจ็ดแบบซึ่งไม่ค่อยสะดวกนัก ในกรณีที่มีนักเขียนโค้ดหลายคน แบ่งงานกันนิยามคลาสเหล่านี้ นักเขียนโค้ดแต่ละคนย่อมนิยามวิธีเรียกใช้ method แตกต่างกันไป เพราะไม่มีสิ่งใดทำหน้าที่กำหนดมาตรฐานให้ตรงกัน

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

interface ICompress
{
    string Compress(string data);
}
class MakeZip : ICompress
{
    public string Compress(string data)
 {
      return data + " compressed using Zip"
 }
}
class MakeRar : ICompress
{
    public string Compress(string data)
 {
      return data + " compressed using Rar"
 }
}
class Program
{
    static void Main(string[] args)
    {
        ICompress myCompress = new MakeZip();
        Console.WriteLine(myCompress.Compress("data"));
        Console.ReadLine();
        myCompress = new MakeRar();
        Console.WriteLine(myCompress.Compress("data"));
        Console.ReadLine();
    }
} 

ผลลัพธ์ของการทำงานคือ

Data compressed using Zip
Data compressed using Rar

จากโค้ดข้างบนจะเห็นว่าผู้เขียนนิยาม interface ชื่อ ICompress เพื่อเป็นข้อกำหนดให้คลาสบริการ (คลาสทำหน้าที่บีบข้อมูล) ทุกตัวดำเนินการตาม ในตัวอย่างนี้มีสมาชิกเพียงตัวเดียวคือ method Compress (แต่ในการใช้งานจริงปรกติแล้วจะซับซ้อนกว่านี้มาก) คลาส MakeZip และคลาส MakeRar นำ ICompress ไปใช้ราวกับว่ามันคือ base class แบบ abstract แล้วคลาสบริการทุกคลาสต้องนิยามโค้ดการทำงานให้แก่ method Compress

คลาส Program ทำหน้าที่ทดสอบการทำงานโดยสร้าง object ชื่อ myCompress จากคลาส MakeZip โดยระบุให้มี type เป็น ICompress จากนั้นเรียก method Compress ซึ่งมีผลให้ method ชื่อเดียวกันในคลาส MakeZip ทำงาน บรรทัดต่อมาเปลี่ยน myCompress ให้อ้างอิงไปยัง object ตัวใหม่ก็สามารถทำได้ เพราะถือว่าทั้ง MakeZip และ MakeRar มี type เป็น ICompress เหมือนกัน และการเรียกใช้ method Compress ก็ยังคงมีวิธีใช้คงเดิม เพราะทั้ง MakeZip และ MakeRar ถูกสร้างขึ้นตามข้อตกลงเดียวกัน

 

 

ตัวอย่างงานที่มีคลาสบีบข้อมูลหลายแบบ (แต่ละแบบใช้อัลกอริทึมบีบข้อมูลแตกต่างกัน) หากคลาสเหล่านี้ถูกเขียนขึ้นโดยนักเขียนโค้ดหลายคน การนิยามวิธีเรียกใช้ method และ data type ย่อมแตกต่างกันออกไปตามคตินิยมของนักเขียนโค้ดแต่ละคน หาเรานิยาม interface ไว้ก่อน แล้วให้นักเขียนโค้ดทุกคนดำเนินการตามข้อตกลงใน interface ผู้นิยามคลาสที่ต้องการบีบข้อมูลจะสามารถเรียกใช้ method บีบข้อมูลได้โดยไม่ต้องกังวลถึงวิธีเรียกใช้

 

ความแตกต่างระหว่า interface กับ abstract class

ท่านคงจำได้จากเรื่อง abstract class ที่เรียนไปแล้วในหัวข้อก่อนหน้านี้ในบทนี้ ว่า abstract class อาจมีแต่สมาชิกแบบ abstract ซึ่งไม่มีโค้ดนิยามการทำงาน สิ่งนี้ทำให้มันคล้าย interface แต่หน้าที่และประโยชน์ของ interface กับ abstract class ย่อมไม่เหมือนกัน interface และ abstract class แตกต่างกันดังนี้

 

  • เราทำ multiple inheritance ต่อ Interface ได้ แต่ทำต่อ abstract class ไม่ได้
  • สมาชิกทุกตัวของ interface ต้องไม่มีนิยามการทำงาน แต่ abstract class สามารถมีได้ (คือ concrete method)
  • Interface มี constant ได้เฉพาะแบบ static final ส่วน abstract class มีได้ทั้งที่เป็นแบบ instance (แบบธรรมดา) และที่เป็น static
  • เราใช้ interface เพื่อจัดกลุ่มคุณสมบัติย่อยๆ ที่มีร่วมกันในหลายคลาส โดยคลาสเหล่านั้นไม่ได้ inherit จาก base class เดียวกัน แต่เราใช้ abstract class เพื่อสร้าง base class
  • Interface ทำงานช้ากว่า abstract class
  • หากเราเพิ่ม method ใน interface เราต้องแก้ไขคลาสที่ใช้งาน interface ทุกตัว แต่ถ้าเราเพิ่ม method แบบ virtual ใน abstract class เราไม่จำเป็นต้องแก้ derived class แต่อย่างใด

 

Polymorphism โดยใช้ Interface

ท่านคงยังจำได้จากที่เรียนไปแล้วในบทที่ 1 ว่า polymorphism เกี่ยวข้องกับการเปลี่ยนแปลง method ของ base class โดยการทำ method overriding เพื่อให้ method มีการทำงานที่แตกต่างไปใน derived class ซึ่งถือว่าเป็นการทำ polymorphism แบบที่พบเห็นได้ทั่วไป เรียกว่า “การทำ polymorphism ทาง inheritance”

นอกจากนั้นในบทที่ 4 ท่านได้เรียนการทำ method overloading ซึ่งเป็นการนิยาม method ชื่อเดียวกันหลายๆ แบบ โดยมี signature แตกต่างกัน ทำให้ method ชื่อเดียวสามารถทำงานกับข้อมูล หรือ type ได้หลายแบบ ซึ่งถือว่าเป็นการทำ polymorphism แบบที่เรียกว่า “การทำ polymorphism ขณะ compile-time”

ในบทนี้ จากโค้ดตัวอย่างข้างบน ท่านจะเห็นว่าตัวแปรอ้างอิง myCompress ซึ่งทำหน้าที่อ้างอิง interface type ICompress มีผลให้ตัวแปรนี้ใช้อ้างอิง object ที่ถูกสร้างจากคลาส MakeZip หรือ MakeRar ก็ได้ สลับกลับไปกลับมาก็ได้ ทำให้ method ที่ตัวแปรนี้อ้างถึงเปลี่ยนไปได้ด้วย ยกตัวอย่างเช่น ขณะที่ myCompress อ้างถึง MakeZip เมื่อเราใช้คำสั่ง myCompress.Compress() method Compress จะทำงานแตกต่างจากเมื่อ myCompress อ้างถึง MakeRar การที่ method ชื่อเดียวกัน มี signature เหมือกัน แต่เปลี่ยนแปลงการทำงานไป-มาได้เช่นนี้ ถือว่าเป็น polymorphism ด้วยเช่นกัน เรียกว่า “การทำ polymorphism ผ่านทาง interface”

 

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

  • interface คือข้อตกลง หรือสัญญา หรือข้อกำหนดกลาง ที่หลายๆ คลาสนำไปใช้ร่วมกัน
  • interface ทำให้เกิดความสม่ำเสมอในการเชื่อมต่อระหว่างคลาส
  • interface คือการสนับสนุนแนวคิด component base programming
  • interface รวมกลุ่มคุณสมบัติที่มีร่วมกันในหลายๆ คลาส โดยที่คลาสเหล่านั้นไม่ได้ inherit จาก base class เดียวกัน
  • interface ทำให้เราปฏิบัติต่อกลุ่มของ object ได้ราวกับว่าพวกมันมี type เดียวกัน
  • ตัว interface เองก็คือคลาส
  • interface คล้ายคลาสแบบ abstract
  • เราใช้ interface แทน abstract เพื่อทดแทนการทำ multiple inheritance
  • คลาสที่ไม่เป็น abstract ซึ่งสืบสันดานจาก interface ต้องใส่โค้ดทำงานให้สมาชิกทุกตัว
  • นำ interface ไปสร้าง object โดยตรงไม่ได้
  • ภายใน interface มีสมาชิกต่อไปนี้ได้ methods, properties, indexers, events
  • method ใน interface มีโค้ดทำงานไม่ได้ (มีได้เพียงประกาศส่วนหัว จะมีโค้ดที่เป็นไส้ในไม่ได้)
  • คลาส และ struc อาจสืบสันดานจาก interface ได้มากกว่าหนึ่งคลาส
  • ตัว interface เองก็อาจสืบสันดานจาก interface ได้มากกว่าหนึ่งคลาสเช่นกัน
  • การใช้งาน interface อาจทำให้เกิดภาวะ polymorphism ได้

ตอนต่อไป: IEnumerator และ Indexer

Post a comment or leave a trackback: Trackback URL.

ความเห็น

  • jued  On มกราคม 19, 2007 at 2:37 pm

    เขียนได้ละเอียดดีครับ เป็นกำลังใจให้ครับผม
     
    จะเข้ามาอ่านเรื่อยๆนะครับ

  • ยุวนันท์  On สิงหาคม 11, 2007 at 1:45 pm

    -ขอบคุณสำหรับบทความดีๆน่ะค่ะ 

  • tua  On ตุลาคม 14, 2007 at 10:33 pm

    ขอบคุณนะค่ะ เป็นประโยชน์มาก ๆ เลย

  • มารูโกะ ป้าค่ะ VS  On ตุลาคม 26, 2007 at 1:49 pm

    เขียนได้ละเอียดทำไปใช้ประโยชน์ได้ค่ะ

ใส่ความเห็น

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: