370 likes | 468 Views
Object-Oriented Programs Design and Construction. #6: Dynamic Properties of Objects. Dynamic Properties of Objects.
E N D
Object-Oriented Programs Design and Construction #6: Dynamic Properties of Objects ITEC0610 Object-Oriented Programs Design and Construction
Dynamic Properties of Objects • คุณสมบัติของระบบเชิง OO ที่สำคัญอีกอย่างหนึ่งคือการที่กลไกการนำเอาวัตถุไปใช้งานนั้น กระบวนการใช้งานของวัตถุอาจถูกปรับเปลี่ยนหรือเปลี่ยนแปลงการตอบสนองไปตามวัตถุอีกตัวที่มีปฏิสัมพันธ์กัน (มีบทบาทเปลี่ยนแปลงไปตามวัตถุที่มีปฏิสัมพันธ์กัน) หรืออาจเปลี่ยนแปลงไปตามกลไกหรือรายละเอียดการเรียกใช้ (เปลี่ยนพารามิเตอร์) • คุณสมบัติแรกที่เราจะพิจารณากันคือความสามารถในการ overload ของเมธอด • จากนั้น เราจะมาดูเกี่ยวกับคุณสมบัติที่เรียกว่า polymorphism หรือความหลากหลายรูปแบบของวัตถุ โดยอาศัยกลไกของ virtual method และ abstract method • สุดท้ายในบทนี้ เราจะมาดูเกี่ยวกับคุณสมบัติที่เรียกว่า interface ในภาษา C# ซึ่งคือ abstract class ที่ช่วยเป็นทางออกให้ C# สามารถเลียนแบบการทำ multiple inheritance ได้ (เพียงแต่เราต้องนิยามเมธอดฝั่ง interface เพิ่มเติมเองด้วย) ITEC0610 Object-Oriented Programs Design and Construction
Overloading • ในภาษา C++/C# ได้นำเสนอหลักการของ redefinition ในลักษณะที่ผู้เขียนโปรแกรมจะสามารถนิยามกระบวนการให้ปฏิบัติอย่างเหมาะสมกับข้อมูลที่รับมาได้ • การที่กระบวนการหนึ่งๆ (หรือเมธอดหนึ่งๆ) ได้ถูกนิยามไว้อย่างใดอย่างหนึ่งไว้ก่อนแล้ว แต่ยังสามารถที่จะนิยามกระบวนการนั้นๆ ใหม่(หรือซ้ำซ้อน เพื่อให้ปฏิบัติ)กับข้อมูลประเภทใหม่ๆ เพื่อให้การตอบสนองต่อข้อมูลต่างชนิดกัน เกิดผลต่างกันนี้ เราเรียกกระบวนการดังกล่าวว่า overloading • เราสามารถกระทำ overloadingได้กับเมธอดและโอเปอร์เรเตอร์ ITEC0610 Object-Oriented Programs Design and Construction
Function Overloading & Redefinitionof Functions • การทำฟังก์ชันโอเวอร์โหลดดิง คือการนิยามเมธอดที่มีชื่อเดียวกัน แต่มีการทำงานที่แตกต่างไปจากเดิม โดยให้การทำงานใหม่นี้ มีลักษณะขึ้นกับพารามิเตอร์ที่เรานิยามเมธอดนั้นๆ • ดังนั้น การที่เมธอดตัวใด(ที่มีชื่อซ้ำกัน) จะทำงาน ก็จะขึ้นอยู่กับพารามิเตอร์ที่เข้ามา • การทำโอเวอร์โหลดดิง ยังสามารถกระทำกับเมธอดที่สืบทอดมาด้วยก็ได้ (มีการนิยามเมธอดนั้นไว้แล้วในคลาสแม่ แต่ก็มีการนิยามซ้ำซ้อนในคลาสลูก) • ในกรณีที่เรากำหนดชื่อเมธอดและพารามิเตอร์ซ้ำกันในระหว่างคลาสแม่และคลาสลูก) เราจะต้องใส่คำสงวน new นำการนิยามเมธอดในคลาสสืบทอด(คลาสลูก) เพื่อบ่งบอกว่าเมธอดดังกล่าวได้รับการ redefinition สำหรับคลาสลูก • ในกรณีที่เรากำหนดชื่อเมธอดซ้ำกันในคลาสหลักและคลาสสืบทอด เราสามารถเจาะจงเรียกเมธอดที่นิยามในคลาสแม่ได้โดยการใช้ baseเพื่ออ้างถึงสมาชิกในคลาสแม่ และ thisเพื่ออ้างถึงสมาชิกในคลาสสืบทอด ITEC0610 Object-Oriented Programs Design and Construction
Function Overloading & Redefinition of Functions กำหนดแบบไม่มี พารามิเตอร์ class aClass { public void aFunc() { Console.WriteLine("No parameter"); } public void aFunc(int a) { Console.WriteLine("One parameter:"+a); } public void aFunc(int a,int b) { Console.WriteLine("Two parameters:"+a+" and "+b); } public void aFunc(double a) { Console.WriteLine("One parameter in floating point format:"+a); } } class Class1 { [STAThread] static void Main(string[] args) { aClass myClass = new aClass(); myClass.aFunc(); myClass.aFunc(10); myClass.aFunc(10,-10); myClass.aFunc(10.5); } } จะเห็นถึงการนิยามซ้ำด้วยพารามิเตอร์ที่ต่างออกไป เรียกใช้ในลักษณะต่างๆ ITEC0610 Object-Oriented Programs Design and Construction
Function Overloading & Redefinition of Functions class superClass { public void aFunc() { Console.WriteLine("No parameter"); } public void aFunc(int a) { Console.WriteLine("(base function)One parameter:"+a); } } class subClass:superClass { public new void aFunc(int a) { Console.WriteLine("One parameter:"+a); } public void aFunc(int a,int b) { Console.WriteLine("Two parameters:"+a+" and "+b); } public void aFunc(double a) { Console.WriteLine("One parameter in floating point format:"+a); } public void getTwoAFunc(int a) { base.aFunc(a); this.aFunc(a); } } class Class1 { [STAThread] static void Main(string[] args) { subClass myClass = new subClass(); myClass.aFunc(); myClass.aFunc(10); myClass.aFunc(10,-10); myClass.aFunc(10.5); myClass.getTwoAFunc(5); } } นิยามครั้งแรกแบบไม่มีพารามิเตอร์ นิยามซ้ำซ้อนในคลาสสืบทอด ต้องมี new สังเกตการใช้ base/this นิยามชื่อซ้ำแต่พารามิเตอร์ไม่ซ้ำ ไม่ต้องมี new ITEC0610 Object-Oriented Programs Design and Construction
Operator Overloading • นอกจากการทำโอเวอร์โหลดดิงกับเมธอดแล้ว เรายังสามารถกระทำโอเวอร์โหลดดิงกับโอเปอร์เรเตอร์ได้ด้วยเช่นกัน • การทำโอเวอร์โหลดดิงกับโอเปอร์เรเตอร์ ใช้เพื่อการนิยามซ้ำ (redefinition) กับกระบวนการที่กระทำกับออปเจ็คต์ชนิดใดชนิดหนึ่งเป็นการเฉพาะ • ใช้คำสงวน operator เพื่อบ่งบอกการทำโอเวอร์โหลดดิงกับโอเปอร์เรเตอร์ และตามด้วยตัวโอเปอร์เรเตอร์ที่ต้องการทำโอเวอร์โหลดดิง ในการนิยามเมธอดเพื่อกระทำการโอเวอร์โหลดดิง • การใช้งานโอเปอร์เรเตอร์ที่ได้รับการโอเวอร์โหลดดิง ใช้งานในรูปเหมือนเดิม เพียงแต่ใช้กับตัวโอเปอร์แรนด์ที่มีชนิดสอดคล้องกับที่ได้นิยามไว้ • หมายเหตุ การนิยามซ้ำกับกระบวนการที่กระทำกับออปเจ็คต์ที่มีชนิดพื้นฐานก็กระทำได้ แต่ไม่ควรกระทำเพราะจะทำให้ผู้เขียนโปรแกรมคนอื่นอาจเกิดความสับสนต่อโค้ดของเรา ITEC0610 Object-Oriented Programs Design and Construction
Operator Overloading using System; namespace operatoroverloading { class complex { private double r; private double i; public double R { get{ return r;} set{ r=value;} } public double I { get{ return i;} set{ i=value;} } public complex() { this.r=this.i=0.0; } public complex(double r,double i) { this.r=r; this.i=i; } ตัวอย่างต่อไปนี้ เป็นการสร้างคลาส complex เพื่อใช้จัดการจำนวนเชิงซ้อน โดยมีการทำ overloading operator จำนวนสี่ตัวด้วยกันคือ บวก ลบ คูณ และหาร ส่วนการนิยามคลาส complex ส่วนนี้นิยาม properties และ constructor ของคลาส ITEC0610 Object-Oriented Programs Design and Construction
Operator Overloading static public complex operator +(complex a,complex b) { return new complex(a.R+b.R,a.I+b.I); } static public complex operator -(complex a,complex b) { return new complex(a.R-b.R,a.I-b.I); } static public complex operator *(complex a,complex b) { return new complex(a.R*b.R-a.I*b.I,a.R*b.I+a.I*b.R); } static public complex operator /(complex a,complex b) { return new complex((a.R*b.R+a.I*b.I)/(b.R*b.R+b.I*b.I), (a.I*b.R-a.R*b.I)/(b.R*b.R+b.I*b.I)); } } สังเกตการ สร้างโอเวอร์โหลดดิงโอเปอร์เรเตอร์ทั้งสี่ ITEC0610 Object-Oriented Programs Design and Construction
Operator Overloading จะเห็นว่าตัวคำตอบเราไม่ได้ สร้างอินสแตนต์ใหม่ เหตุเพราะในการนิยามโอเวอร์โหลดดิงโอเปอร์เรเตอร์ที่สร้างเองนั้น เราได้สร้างอินสแตนต์ใหม่และส่งค่า reference ของอินสแตนต์ดังกล่าวกลับมาแล้ว class Class1 { [STAThread] static void Main(string[] args) { complex x=new complex(2,4); complex y=new complex(5,3); complex resultadd; complex resultsub; complex resultmul; complex resultdiv; resultadd = x+y; resultsub = x-y; resultmul = x*y; resultdiv = x/y; Console.WriteLine("x+y=("+ resultadd.R + ")+i(" + resultadd.I+")"); Console.WriteLine("x-y=("+ resultsub.R + ")+i(" + resultsub.I+")"); Console.WriteLine("x*y=("+ resultmul.R + ")+i(" + resultmul.I+")"); Console.WriteLine("x/y=("+ resultdiv.R + ")+i(" + resultdiv.I+")"); } } } จะเห็นว่า ในการใช้งาน เราใช้งานโอเปอร์เรเตอร์นี้ ราวกับว่าเป็นการกระทำธรรมดา (แต่ในที่นี้เราได้ นิยามโอเปอร์เรเตอร์ใหม่แล้ว ITEC0610 Object-Oriented Programs Design and Construction
New and Inheritance ให้เรามาดูโค้ดต่อไปนี้ พิจารณาว่าคำตอบควรเป็นเช่นใด??? class Animal { public void Say() { Console.WriteLine("I am an animal"); } } class Dog:Animal { public new void Say() { Console.WriteLine("I am a dog"); } } class Bulldog:Dog { public new void Say() { Console.WriteLine("I am a Bulldog"); } } ITEC0610 Object-Oriented Programs Design and Construction
New and Inheritance • สิ่งแรกที่เราเห็นก็คือ แม้ว่าเราจะกำหนดคลาสมีชื่อแตกต่างกัน แต่เราพบว่าเราสามารถกำหนดให้อินสแตนต์ที่สร้างขึ้นนั้น มีโครงสร้างอิงจากรูปคลาสสืบทอดหลังสุดได้ ทั้งนี้โครงสร้างลูกถูกนิยามมี รายละเอียดของโครงสร้างแม่อยู่ภายในแล้ว class Class1 { [STAThread] static void Main(string[] args) { Animal dog1 = new Bulldog(); Dog dog2 = new Bulldog(); Bulldog dog3 = new Bulldog(); Bulldog dog4 = (Bulldog)dog1; dog1.Say(); dog2.Say(); dog3.Say(); dog4.Say(); } } ITEC0610 Object-Oriented Programs Design and Construction
New and Inheritance Animal dog1 = new Bulldog(); Dog dog2 = new Bulldog(); Bulldog dog3 = new Bulldog(); Bulldog dog4 = (Bulldog)dog1; • ประการต่อมา เราจะเห็นว่า แม้ว่าเราจะนิยามอินสแตนต์โดยอิงรูปจากคลาส Bulldog แต่เวลาเราเข้าถึงเมธอด ผลของเมธอดที่เรียกนั้น เป็นไปตามชนิดของ reference • เราสามารถถ่ายค่า reference มาจาก dog1 ซึ่งมีชนิดเป็น animal ได้ โดยการทำ casting (กระทำได้หากสืบทอดมาโดยตรงแบบนี้เท่านั้น) และสังเกตว่าแม้เราจะเข้าถึงอินสแตนต์ที่สร้างโดยเก็บค่า reference ที่ dog1 ผ่าน dog4 แต่จะเห็นว่าการเข้าถึงเมธอดจะเป็นไปตาม ชนิดของ reference อยู่ดี ITEC0610 Object-Oriented Programs Design and Construction
New and Inheritance dog1 Bulldog Dog (Animal) Animal say() say() say() dog4 (Bulldog) dog2 Bulldog Dog (Dog) Animal say() แม้ว่าจะรับค่าอ้างอิงมาจาก dog1 แต่การเข้าถึงเมธอดเป็นไปตามชนิดของมันเอง say() say() dog3 Bulldog Dog (Bulldog) Animal say() say() say() ITEC0610 Object-Oriented Programs Design and Construction
Virtual Methods class Animal { virtual public void Say() { Console.WriteLine("I am an animal"); } } class Dog:Animal { override public void Say() { Console.WriteLine("I am a dog"); } } class Bulldog:Dog { override public void Say() { Console.WriteLine("I am a Bulldog"); } } • หากเราต้องการให้เมธอดที่กำหนดไว้ในคลาสหลัก เป็นเมธอดที่มีความสำคัญรองลงไป โดย กำหนดให้เมธอดที่กำหนดไว้ในคลาสสืบทอดเป็นตัวที่จะถูกอ้างอิงเป็นหลัก เราจะใช้คำสงวน virtual นำหน้าเมธอดของคลาสหลัก และใช้ override นำหน้าเมธอดที่จะนิยามซ้ำในคลาสสืบทอด ให้นักศึกษาลองแก้โค้ดก่อนหน้า เป็นดังด้านซ้ายแล้วคอมไพล์ใหม่ ITEC0610 Object-Oriented Programs Design and Construction
Virtual and Override dog1 Bulldog Dog (Animal) Animal say() say() say() dog4 (Bulldog) dog2 Bulldog Dog (Dog) Animal say() say() say() dog3 Bulldog Dog (Bulldog) Animal say() say() say() ITEC0610 Object-Oriented Programs Design and Construction
Virtual Methods • ข้อกำหนดเกี่ยวกับการใช้ new/virtual/override ระหว่างคลาสหลักและคลาสสืบทอด • virtual ใช้กับคลาสหลักตัวฐานสุด • คลาสที่สืบทอดต่อมากี่ทอด ใช้ override ได้ตลอด • จากนั้นเป็น new ซึ่งจะมีอีกกี่ทอดก็ได้ • ให้นักศึกษาลองดูการสืบทอดในลักษณะที่คลาสลูกสองตัวสืบทอดจากคลาสแม่ร่วมกัน ลองดูว่าเกิดผลอย่างไร เมื่อเราใช้ virtual/override ในการนิยามคลาสต่างๆดังกล่าว ITEC0610 Object-Oriented Programs Design and Construction
Virtual Methods using System; namespace polymorph { class Thai { virtual public void Say() { Console.WriteLine("I am a Thai"); } } class ThaiNorth:Thai { override public void Say() { Console.WriteLine("I am a Northern Thai"); } } class ThaiSouth:Thai { override public void Say() { Console.WriteLine("I am a Southern Thai"); } } ITEC0610 Object-Oriented Programs Design and Construction
Virtual Methods class Class1 { [STAThread] static void Main(string[] args) { Thai man1=new Thai(); Thai man2=new ThaiNorth(); Thai man3=new ThaiSouth(); Thai man; man1.Say(); man2.Say(); man3.Say(); Console.Write("Who are you? "); man = man1; man.Say(); Console.Write("Who are you? "); man = man2; man.Say(); Console.Write("Who are you? "); man = man3; man.Say(); } } } จะเห็นว่า เรากำหนด man ขึ้นมาจากคลาส Thai แต่เมื่อเรานำเอาไปรับค่า reference จากอินสแตนต์ทั้งสามตัว ปรากฏว่าการเรียกใช้เมธอดเดียวกันแท้ๆ แต่กลับให้ค่าแตกต่างกันไปตาม อินสแตนต์ที่ man อ้างถึง !!! ITEC0610 Object-Oriented Programs Design and Construction
Polymorphism • คุณสมบัติ polymorphism คือคุณสมบัติที่การอ้างถึงองค์ประกอบหนึ่งๆ ของวัตถุ สามารถแปรเปลี่ยนไปได้ตามสภาพการอ้างถึงหรือนิยาม • เราได้เห็นจากหลายกรณีด้วยกัน ในกรณีแรก เราจะเห็นว่าเราสามารถนิยามชื่อเมธอดในคลาสสืบทอดซ้ำกับคลาสหลัก(โดยมีลักษณะพารามิเตอร์เหมือนกัน)ได้โดยการใช้ new ประกอบการนิยามในคลาสสืบทอด (ซึ่งการพิจารณาว่าจะใช้เมธอดใดจะขึ้นอยู่กับชนิดของ reference • ในกรณีถัดมา เราใช้ virtual/override เพื่อให้ผลของการอ้างเมธอด เป็นไปตามคลาสต้นแบบที่นำมาใช้สร้างอินสแตนต์ จะเห็นว่า แม้ว่าเราจะกำหนดชนิดของ reference แตกต่างกันไปแต่ก็จะไม่มีผลต่อกรณีหลังนี้ • ตัวอย่างต่อมาที่น่าสนใจก็คือ การที่เรานิยามชนิดของ reference ด้วยคลาสหลัก และนำไปรับค่า reference ของอินสแตนต์ต่างประเภทกันไป (ที่นิยามเมธอดด้วย virtual/override) เราจะเห็นว่าแม้ว่าเราเรียกใช้เมธอดเดียวกัน แต่ผลที่ออกมานั้นจะแตกต่างไปตามอินสแตนต์ที่อ้างถึง ITEC0610 Object-Oriented Programs Design and Construction
Abstract Class • ในตัวอย่างก่อนหน้า สมมติว่าเราสร้างคลาส ThaiUnknown ขึ้นมาสืบทอดจากคลาส Thai โดยไม่นิยามเมธอด Say() เมื่อเราสร้างอินสแตนต์จากคลาส ThaiUnknown แล้วเรียกใช้เม ธอด Say() จะเท่ากับไปเรียกใช้เมธอด Say() ที่ได้นิยามเอาไว้ในคลาส Thai ซึ่งเป็นคลาสหลัก ซึ่งเข้าลักษณะการสืบทอดตามปกติ • แต่ในการออกแบบคลาสในบางกรณี เราต้องการกำหนดขึ้นลอยๆ ถึงกระบวนการที่เป็นไปได้ไว้ภายใต้คลาสหลัก แล้วในคลาสสืบทอดต่างๆ ค่อยมานิยามรายละเอียดกระบวนการต่อไป โดยเรามุ่งหวังว่าเราไม่นำเอาคลาสหลักไปใช้สร้างอินสแตนต์ใดๆ โดยตรง และหวังว่าจะให้ใช้คลาสที่สืบทอดมาสร้างอินสแตนต์เท่านั้น การทำเช่นนี้จะทำให้การนิยามกระบวนการทำงานในเมธอดที่กำหนดไว้ในคลาสหลักนั้น เปล่าประโยชน์ • ในที่นี้เราจะกำหนดเมธอดดังกล่าวโดยนิยามไว้แต่หัวเมธอดโดยไม่นิยามสิ่งอื่นใดที่เป็นตัวเมธอด และใช้คำสงวน abstract กำกับเมธอดและคลาสที่มีเมธอดดังกล่าว เมธอดดังกล่าวเรียกว่า Abstract methodและคลาสดังกล่าวเรียกว่า Abstract Class ITEC0610 Object-Oriented Programs Design and Construction
Abstract Class abstract class Thai { abstract public void Say(); } class ThaiNorth:Thai { override public void Say() { Console.WriteLine("I am a Northern Thai"); } } class ThaiSouth:Thai { override public void Say() { Console.WriteLine("I am a Southern Thai"); } } หมายเหตุ ใน C# สามารถกำหนด abstract ได้กับการนิยามเมธอดและพรอปเพอร์ตี ITEC0610 Object-Oriented Programs Design and Construction
Abstract Class class Class1 { [STAThread] static void Main(string[] args) { Thai man1=new ThaiNorth(); Thai man2=new ThaiSouth(); Thai man; man1.Say(); man2.Say(); Console.Write("Who are you? "); man = man1; man.Say(); Console.Write("Who are you? "); man = man2; man.Say(); } } ITEC0610 Object-Oriented Programs Design and Construction
Interface • ในภาษาภายใต้แนวคิด OO บางตัว(เช่น C++) สามารถสืบทอดจากคลาสหลักได้มากกว่าหนึ่งคลาส ซึ่งเราจะเรียกว่า multiple inheritance • ใน C# ไม่ยอมให้มีการทำ multiple inheritance แต่จะยอมให้เราสามารถสืบทอดจาก abstract class ที่สมาชิกทุกตัวเป็น abstract ได้มากกว่าหนึ่ง • interface คือ abstract class ที่สมาชิกจะเป็น abstract properties หรือ abstract methods เท่านั้น โดยเราจะใช้คำสงวน interface นำหน้าการกำหนด และสมาชิกทุกตัวจะไม่มีส่วนการนิยามรายละเอียด มีแต่การนิยามชื่อและพารามิเตอร์เท่านั้น • การใช้งาน interface กระทำผ่านการกำหนดคลาสทั่วไปที่จะต้องอ้างถึง interface (หากมีการสืบทอดคลาสหรือมีอินเทอร์เฟสอื่นๆ อีก จะต้องคั่นด้วยเครื่องหมาย , ) และจะต้องมีการนิยามรายละเอียดของเมธอดหรือพรอปเพอร์ตีที่ปรากฏไว้ในอินเทอร์เฟส ให้เรียบร้อยครบถ้วนด้วย ITEC0610 Object-Oriented Programs Design and Construction
Interface using System; namespace animalinterface { interface IFlyable { void Fly(double kilometre); double WingSize { get; set; } } interface IWalkable { void Walk(double kilometre); int NumLeg { get; set; } } class Animal { void Say() { Console.WriteLine("I am an animal"); } } นิยามเมธอดและพรอปเพอร์ตีโดยไม่กำหนดรายละเอียด ITEC0610 Object-Oriented Programs Design and Construction
Interface class Bird:Animal,IFlyable,IWalkable { private double wingSpan; private int leg; public double WingSize { get{ return wingSpan; } set{ wingSpan = value; } } public int NumLeg { get{ return leg; } set{ leg = value; } } public void Fly(double kilometre) { Console.WriteLine("I am flying for "+kilometre+" km."); } public void Walk(double kilometre) { Console.WriteLine("I am jumping for "+kilometre+" km."); } } สังเกตว่าเราจะต้องนิยามรายละเอียดของ เมธอดและพรอปเพอร์ตีที่กำหนดไว้ในอินเทอร์เฟซให้เรียบร้อยด้วย ITEC0610 Object-Oriented Programs Design and Construction
Interface class Cat:Animal,IWalkable { private int leg; public int NumLeg { get{ return leg; } set{ leg = value; } } public void Walk(double kilometre) { Console.WriteLine("I am walking for "+kilometre+" km."); } } class Class1 { [STAThread] static void Main(string[] args) { Bird parrot = new Bird(); Cat thaiCat= new Cat(); parrot.Fly(10); parrot.Walk(12); thaiCat.Walk(4); } } } ใช้งานตามปกติ ITEC0610 Object-Oriented Programs Design and Construction
Interface • ในกรณีที่มีการนิยามเมธอดชื่อซ้ำกันจากต่างอินเทอร์เฟส เราสามารถกำหนดว่าจะเรียกเมธอดที่ถูกนิยามจากอินเทอร์เฟสแต่ละตัวได้โดยการอ้างชื่ออินเทอร์เฟซกำกับไว้ที่เมธอดในคลาสสืบทอด • ในกรณีที่มีชื่ออินเทอร์เฟสกำกับไว้กับเมธอดที่เรากำลังนิยาม ก็ไม่ต้องใส่คำสงวน public กำกับเมธอดที่จะต้องนิยามรายละเอียดไว้อีก • เวลาเรียกใช้งานเมธอดที่มีชื่อซ้ำกัน เราสามารถเจาะจงอินเทอร์เฟสที่ต้องการโดยกรรมวิธี casting ITEC0610 Object-Oriented Programs Design and Construction
Interface interface IFlyable { void Fly(double kilometre); void spreadMyLimb(); double WingSize { get; set; } } interface IWalkable { void Walk(double kilometre); void spreadMyLimb(); int NumLeg { get; set; } } class Animal { void Say() { Console.WriteLine("I am an animal"); } } class Cat:Animal,IWalkable { private int leg; public int NumLeg { get{ return leg; } set{ leg = value; } } public void Walk(double kilometre) { Console.WriteLine("I am walking for "+kilometre+" km."); } public void spreadMyLimb() { Console.WriteLine("I am spreading all of my legs."); } } ตัวอย่างนี้ปรับจากตัวอย่างก่อนหน้า โดยเพิ่ม เมธอด spreadMyLimb() ที่อินเทอร์เฟสทั้ง สองตัว สังเกตการนิยามในคลาส Bird ที่มี ความซับซ้อนขึ้นจากเดิม ITEC0610 Object-Oriented Programs Design and Construction
Interface class Bird:Animal,IFlyable,IWalkable { private double wingSpan; private int leg; public double WingSize { get{ return wingSpan; } set{ wingSpan = value; } } public int NumLeg { get{ return leg; } set{ leg = value; } } public void Fly(double kilometre) { Console.WriteLine("I am flying for "+kilometre+" km."); } public void Walk(double kilometre) { Console.WriteLine("I am jumping for "+kilometre+" km."); } void IFlyable.spreadMyLimb() { Console.WriteLine("I am spreading my wings"); } void IWalkable.spreadMyLimb() { Console.WriteLine("I am spreading my legs"); } } แก้ปัญหา ชื่อเมธอดต่างอินเทอร์เฟสชนกันด้วยการอ้างชื่ออินเทอร์เฟสด้วย ITEC0610 Object-Oriented Programs Design and Construction
Interface นิยาม reference ให้มีชนิดตามอินเทอร์เฟสที่ต้องการ และกำหนดให้อ้างไปยังอินสแตนต์ที่ต้องการ class Class1 { [STAThread] static void Main(string[] args) { Bird parrot = new Bird(); IFlyableflyingParrot = (IFlyable)parrot; IWalkablewalkingParrot = (IWalkable)parrot; Cat thaiCat= new Cat(); parrot.Fly(10); parrot.Walk(12); ((IWalkable)parrot).spreadMyLimb(); ((IFlyable)parrot).spreadMyLimb(); walkingParrot.spreadMyLimb(); flyingParrot.spreadMyLimb(); thaiCat.Walk(4); thaiCat.spreadMyLimb(); } } อ้างเมธอดที่นิยามไว้ใน อินเทอร์เฟสด้วยวิธีปกติ อาศัยการ casting เพื่อบ่ง ว่าจะเข้าถึงเมธอดภายใต้ อินเทอร์เฟสใด หากนิยาม reference เพื่อชี้ไปยังอินสแตนต์ที่ต้องการตามอินเทอร์เฟสที่ต้องการแล้ว สามารถเข้าถึงเมธอดนั้นๆได้ทันที ITEC0610 Object-Oriented Programs Design and Construction
Interface • นอกจากกลไกการใช้งานอินเทอร์เฟสในสองรูปแบบที่กล่าวมาแล้ว เรายังสามารถนิยามเมธอดกลางสำหรับใช้กับอินสแตนต์ใดๆ ที่มีคุณสมบัติตามอินเทอร์เฟสที่ได้นิยามไว้ภายใต้คลาส โดยกำหนดเพิ่มพารามิเตอร์อีกหนึ่งตัวในเมธอดกลางนี้ ให้มีชนิดเป็นอินเทอร์เฟสที่ต้องการ และเราส่งผ่านค่า reference ของอินสแตนต์ที่ต้องการเข้าไปยังอินเทอร์เฟสนี้ • ในส่วนคำสั่งการทำงานของเมธอดกลาง เราก็เข้าถึงเมธอดที่นิยามภายใต้อินเทอร์เฟสตามปกติ • การนิยามในลักษณะนี้ ทำให้ผู้เขียนโปรแกรมสามารถปรับปรุงพัฒนาเมธอดที่นิยามภายใต้อินเทอร์เฟสได้สะดวก ลดการผูกติดกับส่วนที่เรียกใช้เมธอด • ลองดูตัวอย่างต่อไปนี้ว่ามีการนิยามทำงานอย่างไร ITEC0610 Object-Oriented Programs Design and Construction
Interface interface IFlyable { void Fly(double kilometre); } class Animal { void Say() { Console.WriteLine("I am an animal"); } } class Bird:Animal,IFlyable { public void Fly(double kilometre) { Console.WriteLine("A bird is flying for "+kilometre+" km."); } } class Duck:Animal,IFlyable { public void Fly(double kilometre) { Console.WriteLine("A duck is flying for "+kilometre+" km."); } } ITEC0610 Object-Oriented Programs Design and Construction
Interface class TheMainClass { [STAThread] public static void Flying(IFlyable anAnimal,double kilometre) { anAnimal.Fly(kilometre); } static void Main(string[] args) { Bird parrot = new Bird(); Duck waterDuck = new Duck(); Flying(parrot,10); Flying(waterDuck,12); } } ผ่านค่า reference ของอินสแตนต์เข้ามาทางนี้ สังเกตการเรียกใช้เมธอดกลางโดยผ่านค่า reference ของอินสแตนต์เข้าไป ITEC0610 Object-Oriented Programs Design and Construction
Conclusion • การโอเวอร์โหลดดิงกระทำได้กับเมธอดและโอเปอร์เรเตอร์ • กระบวนการโอเวอร์โหลดดิง เป็นความสามารถพื้นฐานของภาษาเชิง OO ที่นำเสนอคุณสมบัติ redefinition • Polymorphism คือการที่เราสามารถนิยามเมธอดชื่อเดียวกันพารามิเตอร์เหมือนกัน ให้มีกระบวนการทำงานที่แตกต่างกันออกไป การที่จะมีการเรียกใช้กระบวนการใดนั้น ขึ้นอยู่กับกลไกการเรียกใช้และการนิยาม • เราเห็นคุณสมบัติของ Polymorphism โดยการนิยาม virtual method (การใช้ virtual/override/new) ซึ่งทำให้เราสามารถนิยามเมธอดที่มีชื่อเดียวกัน แต่มีกระบวนการทำงานที่แตกต่างกันภายใต้ อินสแตนต์ที่มีแม่แบบจากคลาสต่างกัน (แต่มีการสืบทอดร่วมกัน) ITEC0610 Object-Oriented Programs Design and Construction
Conclusion (Cont.) • เรายังได้เห็นคุณสมบัติของการทำ dynamic binding (หรือเรียกอีกอย่างว่า late binding) อันเป็นผลจากการเรียกใช้งานเมธอดที่มีคุณสมบัติ polymorphism โดยสังเกตจากตัวอย่างที่มีการนิยาม reference ขึ้นมา เมื่อนำเอาค่า reference value จากอินสแตนต์หนึ่งๆ มาใส้ไว้ในตัวแปร reference นี้และอาศัยตัวแปร reference นี้เข้าถึงเมธอดที่มีคุณสมบัติ polymorphism เราจะเห็นว่าแม้เราจะเรียกชื่อเมธอดเดิมๆ แต่เมื่อเราเปลี่ยนอินสแตนต์ที่เราจะเข้าถึง เราจะพบว่าผลการทำงานของโปรแกรมก็จะเปลี่ยนไปตามอินสแตนต์นั้นๆ ด้วยโดยอัตโนมัติ (กระบวนการดังกล่าวเกิดขณะที่โปรแกรมกำลังรัน ไม่ใช่อยู่ภายใต้โค้ดที่คอมไพล์ เราจึงเรียกว่าเป็น dynamic binding ของเมธอด) ITEC0610 Object-Oriented Programs Design and Construction
Conclusion (Cont.) • จากนั้น เราได้เรียนรู้ถึง Abstract Class ซึ่งมีประโยชน์ใช้สำหรับนิยามโครงร่างทั่วไปของคุณสมบัติที่จะใช้ร่วมกันในคลาสต่างๆ • จากนั้นเราเห็นถึงโครงสร้างทางภาษาที่นำเสนอ interface ซึ่งแท้ที่จริงแล้วก็คือลักษณะของ Abstract Class ที่ประกอบไปด้วยสมาชิกทุกตัวเป็น abstract method หรือ abstract property เท่านั้น • C# ไม่สนับสนุนการทำ multiple inheritance แต่เราสามารถเลียนแบบการกระทำดังกล่าวโดยอาศัยการใช้ interface (ซึ่งมีจุดด้อยกว่าการทำ multiple inheritance ใน C++ ตรงที่เราจะต้องนิยาม เมธอดที่กล่าวไว้ในอินเทอร์เฟส ในคลาสสืบทอดทุกตัว ส่วนใน C++ ไม่ต้องกระทำเช่นนี้) • สุดท้าย เราได้เรียนรู้การนิยามอินเทอร์เฟสในรูปแบบต่างๆ และการเรียกใช้เมธอดของอินสแตนต์ที่นิยามภายใต้อินเทอร์เฟสในลักษณะต่างๆ กัน • อินเทอร์เฟส นิยมใช้เพื่อนิยามคุณสมบัติร่วมที่มักจะมีในลักษณะข้ามความสัมพันธ์ที่ไม่ใช่ generalization abstraction แบบสายตรง ซึ่งตามปกติจะใช้การทำ inheritance ธรรมดา ITEC0610 Object-Oriented Programs Design and Construction