เนื้อหาต่อไปนี้ เราจะพูดถึงการสืบทอดของ class หรือที่เรียกว่า
Inheritance ซึ่งยังอยู่ในโหมดของ OOP ในภาษา Dart
สามารถทบทวนเนื้อหาเกี่ยวกับการใช้งาน class เบื้องต้น
ในภาษา Dart ได้ที่บทความ http://niik.in/942
สามารถทดสอบการเขียนโปรแกรมผ่านเว็บไซต์ DartPad
การสืบทอด Inheritance
Inheritance หรือการสืบทอด เป็นวิธีการที่ทำให้ object หนึ่งสามารถใช้งาน property และ method จาก parent
class ที่ทำการสืบทอดได้ หรือเข้าใจอย่างง่ายคือ class หนึ่งๆ สามารถสืบทอด property และ method จาก อีก class หนึ่งได้
สมมติเช่น class A สืบทอด class B เราจะเรียก class A และ class B ได้เป็นดังนี้
class B ก็คือ SUPER class หรือ BASE class หรือ PARENT class
class A ก็คือ SUB class หรือ CHILD class
โดยในการใช้งานการสืบทอด จะใช้ extends keyword ในการกำหนด อย่างกรณีตามตัวอย่างด้านบน ก็จะได้เป็น
class B{ } class C{ } // class A สืบทอดจาก class B class A extends B{ }
เราสามารถทำการสืบทอด class ได้เพียง class เดียวเท่านั้น กล่าวคือ ไม่สามารถสืบทอด class A จาก class B
และ class C พร้อมกันได้
// class A extends B,C{} // error
เพื่อให้เข้าใจการสืบทอดง่ายขึ้น ขอยกตัวอย่างเกียวกับ Animal class, Dog class และ Cat class ประกอบคำอธิบาย
class Animal{ String color; // สี void eat(){} // กินอาหารได้ } class Dog{ String color; // สี String breed; // สายพันธ์ void bark(){} // เห่าได้ } class Cat{ String color; // สี int age; // อายุ void meaw(){} // ร้องเหมียวได้ }
class ทั้ง 3 ล้วนมี property และ method ของตัวเองซึ่งเป็นได้ทั้ง instance variable หรือ instance method
โดยทั้ง Dog และ Cat มี property ที่ชื่อว่า color ซึ่งสามารถใช้ property นี้จาก Animal class ได้ ดังนั้น เราสามารถทำการสืบทอด
Animal class ไปยัง Dog / Cat class ได้ดังนี้
class Animal{ String color; // สี void eat(){} // กินอาหารได้ } class Dog extends Animal{ String breed; // สายพันธ์ void bark(){} // เห่าได้ } class Cat extends Animal{ int age; // อายุ void meaw(){} // ร้องเหมียวได้ }
เมื่อทำการสืบทอด class จะทำให้ Child class ได้รับ หรือสามารถเรียกใช้ property และ method ของ Parent class ได้ นั่นคือ Dog และ Cat
จะมี color และ eat() ที่เป็น variable และ method ของ Animal class ที่ได้รับสืบทอดมานั่นเอง
ดูตัวอย่างการเรียกใช้งาน
void main () { var dog = Dog(); dog.breed = 'Pug'; dog.color = 'black'; // color property จาก Animal class dog.eat(); // eat() method จาก Animal class dog.bark(); var cat = Cat(); cat.age = 4; cat.color = 'white'; // color property จาก Animal class cat.eat(); // eat() // method จาก Animal class cat.meaw(); }
ก่อนไปรายละเอียดต่อไป ขอทบทวนอีกครั้งว่า ในภาษา Dart ทุกอย่างล่วนเป็น object และทุกๆ class ในภาษา Dart ก็ล้วนสืบทอดมาจาก
Object class ซึ่งเป็น Super class อีกที ดังนั้น ทกๆ class จะได้รับการสืบทอด property ต่อไปนี้มาได้ ได้แก่
- toString() // method ที่คืนค่าข้อความที่อธิบายถึง object นั้นๆ
- hashCode // ตัวเลขค่า Hash Code หรือรหัสเฉพาะตัวของ object นั้นๆ ไว้ใช้ในการเปรียบเทียบค่า
- opperator หรือตัวดำเนินการ ( == ) สำหรับเปรียบเทียบ สอง object ใดๆ
เรามาทดสอบลองเรียกใช้งาน property และ method ทั้ง 3 ร่วมกับ Dog และ Cat object คำอธิบายแสดงในโค้ด จะได้เป็นดังนี้
void main () { var dog = Dog(); print(dog.hashCode); // hashCode property จาก Object super class // output: 790618802 ค่าตัวเลขแสดงสภาวะของ object ไว้ใช้ในการเปรียบเทียบ == // ค่า hashCode จะเปลี่ยนแปลงทุกครั้ง print(dog.toString()); // toString() method จาก Object super class // output: Instance of 'Dog' var cat = Cat(); print(cat.hashCode); // hashCode property จาก Object super class // output: 130547424 ค่าตัวเลขแสดงสภาวะของ object ไว้ใช้ในการเปรียบเทียบ == // ค่า hashCode จะเปลี่ยนแปลงทุกครั้ง print(cat.toString()); // toString() method จาก Object super class // output: Instance of 'Cat' if(dog == cat){ print("Same"); }else{ print("not same"); } // output: not same var dog2 = dog; if(dog == dog2){ print(dog.hashCode); // ค่าจะเท่ากับ hashCode ของ dog2 print(dog2.hashCode); // ค่าจะเท่ากับ hashCode ของ dog print("Same"); }else{ print("not same"); } // output: // 790618802 // 790618802 // Same }
การกำหนด Overriding
การ Overriding เป็นวิธีที่ให้ Child class สามารถที่จะกำหนด property และ method ที่ทำการสืบทอดจาก Parent class ให้มีคุณลักษณะ หรือการ
ทำงานที่แตกต่างจาก Parent class ได้ ยกตัวอย่าง ดังนี้
void main () { var dog = Dog(); print(dog.color); // output: black dog.eat(); // output: Animal is eating.... } class Animal{ String color = 'black'; void eat(){ print('Animal is eating....'); } } class Dog extends Animal{ String breed; void bark(){ print('Dog is barking....'); } }
ตัวอย่างโค้ดข้างต้น เราจะเห็นว่า Dog class ทำการสืบทอดจาก Animal class ทำให้ Dog class มี color และ eat() ที่ได้ทำการสืบทอดมา
ดังนั้น เมื่อเราทำการเรียกใช้คำสั่งแสดงข้อมูล และทดสอบการทำงาน ก็จะได้ค่า color เท่ากับ black และ eat() method ก็ทำการแสดงข้อความ
ว่า Animal is eating.... ออกมา
ทีนี้ เราต้องการกำหนดค่าให้กับ color และ eat() ของ Dog class เอง โดยไม่ใช้ค่าจากที่ทำการสืบทอดมา เราเรียกวิธีการนี้ว่า การ Overriding
โดยทำการกำหนดค่าเป็นดังนี้
void main () { var dog = Dog(); print(dog.color); // output: white dog.eat(); // output: Dog is eating.... } class Animal{ String color = 'black'; void eat(){ print('Animal is eating....'); } } class Dog extends Animal{ String breed; @override String color = 'white'; @override void eat(){ print('Dog is eating....'); } void bark(){ print('Dog is barking....'); } }
โดยการ override เราต้องกำหนด property และ method ให้เป็นประเภทข้อมูลเดียวกันกับใน parent class อย่าง color ก็ต้องเป็น String
เช่นเดียวกัน โดยทั้ง color และ eat() เรามีการกำหนดค่าในรูปแบบเดียวกัน แต่ให้มีค่าและการทำงานที่ต่างไปจาก parent class ข้างต้น
เรากำหนด color เป็น white และ กำหนด eat() ให้ทำการพิมพ์ข้อความ Dog is eating..... แทน
สังเกตว่า ในการทำการ overriding เรามีการใช้งาน meatada @override เพื่อเป็นการระบุหมายเหตุกำกับเพิ่มเติมให้ทราบว่า property และ
method นั้นๆ เป็นการ override ค่าจาก parent อีกที
ถึงตอนนี้เกิดคำถามว่า เราสามารถเรียกใช้ color property และ eat() method ใน parent class ได้ไหม ในเมื่อทำการ override ไปแล้ว
คำตอบก็คือ เราสามารถ เรียกใช้ property และ method ของ parent class ที่ทำการ override ไปแล้วด้วย การใช้ super keyword ตามด้วย property
หรือ method ที่จะใช้งาน โดย super ก็คือ parent class ที่เราทำการสืบทอดมานั่นเอง ดูตัวอย่างการใช้งาน ตามตัวอย่างโค้ดด้านล่าง
void main () { var dog = Dog(); print(dog.color); // output: white dog.eat(); // output // Animal is eating.... // Dog is eating.... // Animal is eating.... // black } class Animal{ String color = 'black'; void eat(){ print('Animal is eating....'); } } class Dog extends Animal{ String breed; @override String color = 'white'; @override void eat(){ super.eat(); // output: Animal is eating.... print('Dog is eating....'); // output: Dog is eating.... super.eat(); // output: Animal is eating.... print(super.color); // output: black } void bark(){ print('Dog is barking....'); } }
การสืบทอดกับการใช้งาน Constructor
จากตัวอย่างการใช้งาน Animal และ Dog class ข้างต้น เราจะเห็นว่า class ทั้งสองมีการใช้งาน default constructor หรือก็คือส่วนที่ทำงาน
ทันที เมื่อมีการสร้าง object จาก class สามารถทบทวนเนื้อหาเกี่ยวกับการใช้งาน constructor เพิ่มเติมได้ที่บทความ http://niik.in/942
เราจะทดลอง กำหนด default constructor ให้กับ class ที่มีการสืบทอด เพื่อดูทำงานดังนี้
void main () { var dog = Dog(); // สร้าง instance object // output // Animal default constructor // Dog default constructor } class Animal{ String color; // default constructor Animal(){ print("Animal default constructor"); } void eat(){ print('Animal is eating....'); } } class Dog extends Animal{ String breed; // default constructor Dog(){ print("Dog default constructor"); } void bark(){ print('Dog is barking....'); } }
สังเกตว่า ทันทีที่มีการสร้าง object ขึ้นมา constructor ของ class ที่มีการสืบทอดกัน จะเริ่มทำงานทันที ตามตัวอย่างเราลองให้ทำการ print
ข้อความเพื่อดูว่า เมื่อทำการสร้าง Dog object จาก Dog class ที่สืบทอดมาจาก Animal class จะเกิดอะไรขึ้น
ผลที่ได้ก็คือ default constructor ของ Animal class ที่เป็น parent class จะเริ่มทำงานก่อน แล้วตามด้วย default constructor ของ Dog class
ตามลำดับ ทั้งนี้เนื่องจากใน default constructor ของ child class มีการเรียกใช้งาน default constructor ของ parent class เป็นค่าเริ่มต้นในรูปแบบ
// default constructor แบบกำหนดการเรียกใช้งาน constructor ของ parent class แบบชัดเจน Dog() : super(){ // มีค่าเท่ากับ Dog() ที่ไม่กำหนดการเรียกใช้งาน constructor ของ parent class print("Dog default constructor"); }
นั่นหมายความว่า Dog() constructor จะมีการเรียกใช้งาน super() ซึ่งเป็น ของ Animal class ที่เป็น parent อัตโนมัติโดยที่เราจะกำหนดการเรียกใช้งาน
: super() หรือไม่ก็ตาม ดังนั้น ผลลัพธ์ของ Dog() และ Dog() : super() จึงมีค่าเท่ากัน คือมีการเรียกใช้งาน default constructor ของ parent class ก่อนแล้ว
ทำการเรียกใช้งาน default contructor ของ child class ตามลำดับ
ทำการเรียกใช้งาน default contructor ของ child class ตามลำดับ
มาดูตัวอย่างต่อ สมมติว่า parent class มีการกำหนดเป้น parameter constructor เราจะทำการส่งค่าจาก child class อย่างไร
ดูตัวอย่างโค้ดด้านล่างประกอบ
void main () { // มีการส่งค่า argument 2 ค่าในการสร้าง dog object var dog = Dog('Poodle','white'); // สร้าง instance object } class Animal{ String color; // parameter constructor // มีการกำหนด parameter 1 ค่า คือ color Animal(String color){ this.color = color; print("Animal parameter constructor"); } } class Dog extends Animal{ String breed; // parameter constructor // มีการกำหนด parameter 2 ค่า และทำการส่งค่าตัวแปร color ไปยัง // parameter constructor ของ parent class โดยใช้ super(color) Dog(String breed, String color) : super(color){ print("Dog parameter constructor"); } }
จากตัวอย่างข้างต้น จะเห็นว่า ทั้ง parnent class และ child class มีการใช้งาน parameter constructor โดยใน parent class มีการกำหนด
parameter 1 ค่าคือ color ในขณะที่ child class มีการกำหนด parameter 2 ค่าคือ breed และ color โดยมีการส่งต่อค่า parameter color ไปยัง
constructor ของ parent class โดยใช้คำสั่ง : super(color)
ต่อไป มาดูตัวอย่างเพิ่มเติม เกี่ยวกับการใช้งาน named constructor
void main () { // มีการส่งค่า argument 2 ค่าในการสร้าง dog object var dog = Dog('Poodle','white'); // สร้าง instance object // สร้าง instance object จาก named constructor var dog2 = Dog.myNameConstructor(); } class Animal{ String color; // parameter constructor // มีการกำหนด parameter 1 ค่า คือ color Animal(String color){ this.color = color; print("Animal parameter constructor"); } // กำหนด named constructor ใช้ชื่ออะไรก็ได้ตามต้องการ Animal.myNameConstructor(){ print("Animal named constructor"); } // กำหนด named constructor แบบมี paramter Animal.myNameConstructor2(String color){ print("Animal named constructor with parameter"); } } class Dog extends Animal{ String breed; // parameter constructor // มีการกำหนด parameter 2 ค่า โดยเรียกใช้งาน named constructor // ของ parent class แบบไม่มีการส่งค่า parameter โดยใช้ super.myNameConstructor() Dog(String breed, String color) : super.myNameConstructor(){ print("Dog parameter constructor"); } // ตัวอย่างกรณี named paramter ของ parent class มีการใช้งาน parameter จะได้เป็น // Dog(String breed, String color) : super.myNameConstructor(color){ // print("Dog parameter constructor"); // } // named constructor // ไม่มีการกำหนด paramter และเรียกใช้งาน named constructor // ของ parent class แบบไม่มีการส่งค่า parameter โดยใช้ super.myNameConstructor() Dog.myNameConstructor() : super.myNameConstructor(){ print("Dog named constructor"); } }
ตัวอย่างข้างต้น ใน parent class มีการกำหนด parameter constructor , named constructor with paramter และ named constructor
without parameter โดยในกรณีที่ parent class มีการกำหนด parameter constuctor เราจะกำหนด Dog constructor แบบไม่มี parameter ไม่ได้
ทั้งนี้เพราะ เราต้องส่งค่าเข้าไปใช้งานใน Animal constructor ดูกรณีต่างๆ เพิ่มเติมด้านล่าง
// error เพราะ parent มีการใช้งาน parameter ต้องส่งค่าเข้าไป // Dog(){ } // Dog() : super(){}
อย่างน้อยต้องส่งค่า color เข้าไป
// ไม่ error มีการส่งค่าไปให้ parent ที่มีการใช้งาน parameter Dog(String color) : super(color){}
กรณีใช้งาน named constructor แบบไม่มี parameter
// ไม่ error เพราะ parent มีการใช้งาน named constructor แบบไม่มีการกำหนด parameter // จึงไม่จำเป็นต้องส่งค่าใดๆ เข้าไป Dog() : super.myNameConstructor(){}
กรณีใช้งาน named constructor แบบมี paramter
// error มีการใช้งาน named constructor ใน parent แบบมี paramter // ต้องส่งค่า paramter เข้าไป Dog() : super.myNameConstructor2(){}
// ไม่ error มีการใช้งาน named constructor ใน parent แบบมี paramter // และมีการส่งค่า paramter เข้าไป Dog(String color) : super.myNameConstructor2(color){}
สามารถทบทวนเนื้อหาเกี่ยวกับการใช้งาน constructor เพิ่มเติมได้ที่บทความ http://niik.in/942
หวังว่าจะเป็นแนวทางทำความเข้าใจ เกี่ยวกับการใช้งานการสืบทอดของ class เบื่องต้น เพื่อทำความเข้าใจในส่วนอื่นๆ ต่อไป