Interface Inheritance #3: Versioning

    เรื่องนี้เป็นความเหนือชั้นของการเขียนโปรแกรมแบบ Interface ครับ จะดูดีแค่ไหนเรามาดูว่าเมื่อก่อนมันมีปัญหาอะไรบ้าง  

    เราลองนึกภาพดูนะครับสมมุติว่าเราสร้าง class ขึ้นมา class หนึ่ง compile เป็น .dll แล้วนำเอา .dll นั้นไปใช้ สมมุติว่า .dll ตัวนี้เป็นที่ยอดนิยมมาก ในบริษัทของคุณใครๆ ก็เอาไปใช้ แต่พอมาวันหนึ่งปรากฏว่า class ของคุณไม่สามารถรองรับบางอย่างได้ ก็ไม่มีปัญหาครับ คุณก็เพิ่มความสามารถใส่เข้าไปใน class นั้นแล้ว compile ใหม่ จากนั้นก็ deploy ไปทับ .dll ที่มีอยู่แล้วของทุกคน มันก็ทำงานได้ดีครับ เพราะคุณไม่ได้แก้ไข interface ที่เคยมีมา แค่เพิ่มเติมเท่านั้น code เก่าก็ใช้ interface ที่ตัวเองรู้จักเดิม แต่ code ใหม่นั้นก็จะใช้ interface เดิมบวกกับของที่มีมาใหม่

    แต่โลกมันไม่ได้ง่ายอย่างนั้นนะครับ ถ้าการแก้ไขนั้นมันกระเทือนกับ interface  ที่มีอยู่แล้ว ถึงแม้ว่าเราใช้แนวคิดในการเขียนโปรแกรมแบบ Interface ทำการแยก interface ออกมาจาก class แล้วก็กลั่นกรอง interface ให้ดูดีที่สุด ไม่ว่าคุณจะเป็น programmer ที่เก่งกาจแค่ไหนก็ตาม  มันก็ไม่ได้เป็นหลักประกันมามันจะสมบูรณ์ตลอดไป โลกมันยังหมุนอยู่อะไรมันก็มีการเปลี่ยนแปลงได้ พอคุณเอา .dll ที่แก้ interface ไป deploy ปรากฏว่าโปรแกรมใหม่ทำงานได้ครับ แต่โปรแกรมเก่ารวนหมด ภาษาทาง OOP จึงออกแบบการแก้เรื่องนี้โดยการใช้ function overload แต่นั่นมันก็ทำให้ interface บวมขึ้น

    ยิ่งไปกว่านั้น ถึงแม้ว่า interface เหมือนเดิม แต่เนื้อหาอาจจะไม่เหมือน พอเวลาเอา code ใหม่ไปใช้มันอาจกระทบกับของเดิมได้ นี่คือปัญหาใหญ่ที่สุดครับ

    ถ้าเราเขียนโปรแกรมแบบ Interface โดยการแยก Interface ออกมาจาก class ปัญหาเรื่องนี้จะถูกแก้ได้โดยไม่ยากครับ ยังจำเรื่อง multiple Inheritance ของ Interface ได้รึเปล่าครับ นั้นแหละครับทางแก้ปัญหา

    หลักการก็ของเราแยก interface ออกมา แล้ว class นั้นก็ inherit interface ดังกล่าวไปใช้ compile เป็น .dll code ของคุณก็จะถูกโปรแกรมอื่นนำไปใช้งานได้ครับ นั่นก็เหมือนกับข้างต้นใช่ไหมครับ พออยู่มาวันหนึ่ง เกิดจำเป็นต้องแก้ interface ขึ้นมา แทนที่เราจะเข้าไปแก้ตัว interface นั้น เราจะถือว่า interface ใดๆ ที่แจกจ่ายไปแล้ว ไม่ยอมให้มีการแก้อีก เราจะทำการสร้าง interface ใหม่ครับเป็นรุ่นอีก interface หนึ่ง แล้วนำไป inherit เข้าไปใน class เดิมทำให้ class นั้นมี 2 interfaces และเมื่อ compile เป็น .dll แล้ว โปรแกรมเก่าก็จะใช้ interface เดิม ส่วนโปรแกรมใหม่ จะใช้ interface ตัวใหม่แบบนี้ก็แก้ปัญหาได้อย่างสบายครับ

    เรามาลองดูตัวอย่างกัน

version1.cs

using System;
public interface IBackup
{
	void Backup();
	void Restore();
}
class SQLServer : IBackup
{
	void IBackup.Backup() { Console.WriteLine("IBackup.Backup"); }
	void IBackup.Restore() { Console.WriteLine("IBackup.Restore"); }
}
class Start
{ 	public static void OldProgram()
	{
		SQLServer s = new SQLServer();
		IBackup ib = s;
		ib.Backup();
		ib.Restore();
	}
	
	public static void Main()
	{
		OldProgram();
	}
}

DOS Prompt

C:\CS>version1
IBackup.Backup
IBackup.Restore

C:\CS>_

     มันก็ทำงานได้ตามปรกติ จนวันหนึ่งมีความจำเป็นต้องปรับปรุง class นี้เพื่อไปรองรับการ backup ที่ระบุชื่อ เราทำได้อย่างนี้ครับ

version2.cs

using System;
public interface IBackup
{
	void Backup();
	void Restore();
}
public interface IBackup2
{ 	
	void Backup();
	void Restore();
}
class SQLServer : IBackup, IBackup2
{
	void IBackup.Backup() { Console.WriteLine("IBackup.Backup"); }
	void IBackup.Restore() { Console.WriteLine("IBackup.Restore"); }
	void IBackup2.Backup() { Console.WriteLine("IBackup2.Backup"); }
	void IBackup2.Restore() { Console.WriteLine("IBackup2.Restore"); }
}
class Start
{ 	
	public static void OldProgram()
	{
		SQLServer s = new SQLServer();
		IBackup ib = s;
		ib.Backup();
		ib.Restore();
	}
	
	public static void NewProgram()
	{
		SQLServer s = new SQLServer();
		IBackup2 ib2 = s;
		ib2.Backup();
		ib2.Restore();
	}
	public static void Main()
	{
		OldProgram();
		NewProgram();
	}
}

DOS Prompt

C:\CS>version2
IBackup.Backup
IBackup.Restore
IBackup2.Backup
IBackup2.Restore

C:\CS>_

    จะเห็นได้ว่า โปรแกรมใหม่ก็จะใช้ interface ใหม่ นั่้นคือ IBackup2 นั่นเอง จะเห็นว่า class SQLServer จะรองรับทั้ง 2 Interfaces ซึ่งทำให้ code เก่าและใหม่ใช้ class เดียวกันได้ ดีไหมครับ

    เทคนิคนี้ถูกนำมาใช้อย่างกว้างขวางในเรื่องของ COM (Component Object Model) ของ Microsoft บางคนที่ไม่คุ้นเคยกับ Microsoft อาจจะงงว่า COM นี้มันอะไร มันพอเทียบเคียงได้กับ CORBA ของทาง Java หรือถ้ายังงงอีก คำว่า ActiveX เป็นยังไง ทาง Microsoft ใช้วิธีนี้แหละครับที่ควบคุม version ต่างๆ ของ code ตัวเอง ทำให้เวลาเอารุ่นใหม่ไปทับรุ่นเก่า ก็ไม่มีปัญหา (แต่ปัญหายังคงอยู่ครับ ถ้าเรา install โปรแกรมที่เขียนมานานแล้ว ใช้ .dll รุ่นเก่าๆ มันจะไปทับ .dll ของใหม่ ทำให้โปรแกรมที่ใช้ interface ตัวใหม่ทำงานไม่ได้)

    เรายังสามารถตรวจสอบได้ว่า object ที่เรากำลังใช้อยู่นี้ รองรับ interface ที่เราต้องการหรือไม่ ถ้าไม่รองรับ เราอาจไม่ยอม run หรืออาจจะเลือก interface เก่าไป มีสองคำสั่งครับที่เราสนใจครับ

    s is IBackup                 ประโยคนี้จะได้ค่าเป็น True ถ้า object s รองรับ Interface แบบ IBackup

    s as IBackup                ถ้า s รองรับ IBackup จะ return ตัวชี้ไปยัง object แบบ Ibackup แต่ถ้าไม่รองรับจะ return null
                                ส่วนมากเราจะใช้ตัวแปรไปรับดังนี้ครับ    IBackup ib = s as IBackup;
                                ib จะชึ้เป็นที่ object นั้น หรือเป็น null ถ้าไม่รองรับ interface นั้น

    ลองประยุกต์ใช้งานดูครับ