เรียนรู้ภาษา Dart ก่อนไปเริ่มใช้ Flutter (Part 2) ว่าด้วยเรื่องของ Class Constructor และ Class Inheritance

Dart Feb 2, 2021

มาต่อกันเลยกับภาคต่อของภาษา Dart ชุดเริ่มต้น ก่อนไปใช้งาน Flutter วันนี้เราจะมาพูดกันถึงเรื่องของ Class ในแง่ที่เป็นคุณสมบัติต่างๆของมัน ได้แก่ Constructor, Class Inheritance ว่าแล้วตอนนี้ ก็มาเริ่มกันเลยดีกว่า

ในบทความที่แล้ว เรามีการเกริ่นไปถึงคลาสบ้างแล้ว ในเรื่อง Getters/Setters, Private Variable และ Constructor ในส่วนนี้ จะขอขยายความ Constructor ต่อไปอีก เนื่องจาก มี syntax หลายๆอย่างที่จำเป็นต้องเรียนรู้ เพื่อนำไปสร้างแอพได้จริง

Constructor

จาก syntax ทั่วไปของ Constructor ที่มองในรูปแบบ header/body มีการแบ่งออกมาเป็นแบบนี้

//constructor head
Class_name(parameter_list) { 
   //constructor body 
}

Constructor ถูกสร้างขึ้นมาโดยไม่ต้องกำหนด data type เพื่อเป็นตัวแทนของ class ในส่วน body ใช้สร้าง statement เพื่อกำหนดค่าให้ field เมื่อสร้าง object จากคลาส จะสามารถใส่พารามิเตอร์ให้กับ object ตั้งแต่ตอนที่สร้างขึ้นมาเลย เช่น หากมีการสร้างคลาส Employee ที่มีการกำหนดตัวแปรสามตัวคือ ชื่อ อายุ และที่อยู่ แล้วสร้าง Constructor ที่ทำหน้าที่รับพารามิเตอร์แต่ละค่าเอามาเก็บไว้ในตัวแปรนั้นๆ

class Employee {
  // fields
  String name;
  int age;
  String address;

  // Constructor
  Employee(name, age, address) {
    this.name = name;
    this.age = age;
    this.address = address;
  }
}

การใช้ this เพราะต้องการให้ Constructor รู้ว่า ค่าพารามิเตอร์ที่รับเข้ามา ให้เอาไปเก็บที่ fields ของคลาสนะ เนื่องจากโปรแกรมจะค้นหา fields ของฟังก์ชั่นก่อน fields ของคลาส
ถ้าไม่มี this โปรแกรมจะเข้าใจว่าพารามิเตอร์ที่รับเข้ามาจะถูกกำหนดให้ local scope fields ของ Constructor ซึ่งในที่นี้เราไม่มีการสร้างเอาไว้ จะทำให้ field ทั้งหมดของคลาสยังคงเป็น null

เวลาจะสร้าง object จากคลาส Employee แล้วเก็บ object ไว้ในตัวแปรชื่อว่า employee_1 จะใช้ syntax แบบนี้

var employee_1 = new Employee(“John Doe”, 29, “Califonia”);

หรือ จะละ new ก็ได้

var employee_1 = Employee(“John Doe”, 29, “Califonia”);

คำเตือน : ต้องใส่พารามิเตอร์ให้ครบทุกค่าตามที่ฟังก์ชั่น Constructor กำหนด มิเช่นนั้น จะเกิด error!!!

นี่เป็นรูปแบบทั่วไปของการสร้าง Constructor ซึ่งเราสามารถกำหนดรูปแบบอื่นๆได้อีก เช่น

Named Parameter

สามารถนำเอาแนวคิดของ Named Parameter มาผสมกับ Constructor ได้ เมื่อพารามิเตอร์มีหลายตัว

class Employee {
  String name;
  int age;
  String address;

  Employee({this.name, this.age, this.address});
}

ตอนสร้าง object ก็ต้องใส่พารามิเตอร์แบบนี้

var man1 = Employee(name: 'John', age: 19, address: 'Bangkok');

สังเกตว่า ตอนประกาศฟังก์ชั่น ต้องใส่ {} ครอบพารามิเตอร์ ส่วนตอนใช้งานฟังก์ชั่น ไม่ต้องใส่
สามารถใส่พารามิเตอร์สลับตำแหน่งกันได้ด้วย เช่น

var man1 = Employee(address: 'Bangkok', name: 'John', age: 19);

การใช้ Named Parameter ถือว่าเป็น Optional ซึ่งถ้าไม่ใส่ค่า จะได้ null

var man1 = Employee(address: 'Bangkok', age: 19);
print(man1.name);
// output is null

สำหรับ field ที่กำหนดเป็น private ต้องประกาศฟังก์ Constructor ชั่นแบบนี้

class Employee {
  String _name;
  int _age;
  String _address;

  Employee({String name, int age, String address}) {
    this._name = name;
    this._age = age;
    this._address = address;
  }
}

หรือละ this แบบนี้

class Employee {
  String _name;
  int _age;
  String _address;

  Employee({String name, int age, String address}) {
    _name = name;
    _age = age;
    _address = address;
  }
}

จากนั้นก็สามารถประกาศตัวแปร object ได้เหมือนเดิม

แต่ถ้า field กำหนดเป็น final ล่ะ

class Employee {
  final String _name;
  final int _age;
  final String _address;

  Employee({String name, int age, String address}) {
    _name = name;
    _age = age;
    _address = address;
  }
}

จะติด error ยาวเป็นพรืดแน่นอน ถึงจะเอา final ไปใส่ใน Constructor ก็ยัง error อยู่ดี ว่าแต่ทำไมกันนะ?

เราไปหาคำตอบมาจากที่นี่ เค้าบอกว่า construction state มันจบก่อนที่จะทำงานตรง body ดังนั้น เราจึงต้อง กำหนด fianl ให้จบตั้งแต่ header ก่อนที่ body จะเริ่มทำงาน โดย initialize (ประกาศ) Constructor เป็นแบบนี้

class Employee {
  final String _name;
  final int _age;
  final String _address;

  Employee({String name, int age, String address}) :
    this._name = name,
    this._age = age,
    this._address = address;
}

สังเกตว่า เราจะเอา {} ที่ครอบ body ออก และเพิ่ม : เพื่อเชื่อม _name _age _address เนี่ย ให้เป็นเนื้อเดียวกันกับ header และใช้ , คั่นระหว่างกลาง

แน่นอนว่า เราก็สามารถละ this ได้เช่นเดียวกัน

class Employee {
  final String _name;
  final int _age;
  final String _address;

  Employee({String name, int age, String address}) :
    _name = name,
    _age = age,
    _address = address;
}

มาสร้าง object และ ปริ๊นต์ค่ากันหน่อย

var man1 = Employee(name: 'John', age: 19, address: 'Bangkok');
print(man1._name);
// output is John

Named Constructor

Dart ได้ให้ความสามารถในการสร้าง Constructor หลายๆตัว เรียกว่า Named Constructor มีรูปแบบ syntax แบบนี้

Class_name.constructor_name(param_list)

ยกตัวอย่าง

class Tourist {
  String name;
  String address;

  //Default Constructor
  Tourist(name) {
    this.name = name;
    }
  
  //Named Constructor
  Tourist.touristAddress(name, address) {
    this.name = name;
    this.address = address;
    print('I am ${this.name} and I come from ${this.address}');
  }
}

เมื่อถึงเวลาสร้าง object 2 ตัว จากคลาส Tourist

void main() {
  var tourist = Tourist('Bonnie');
  print(tourist.name);
  var touristPresentation = Tourist.touristAddress('Puppey', 'Estonia');
}
/* output
Bonnie
I am Puppey and I come from Estonia
*/

เราสามารถสร้าง object โดยใช้ Constructor ตัวไหนก็ได้ จากตัวอย่าง object tourist ใช้ default constructor ส่วน touristPresentation ใช้ named constructor ซึ่งเวลาใช้ต้องเรียกออกมาตาม syntax คือ Tourist.touristAddress('Puppey', 'Estonia'); นั่นเอง

Class Inheritance

ต่อมา เราจะพูดถึงการสืบทอดคลาส (Inheritance) ซึ่งเป็นคุณสมบัติหนึ่งของคลาส โดยสืบทอดคลาส จากคลาสแม่ (Parrent or Super Class) ไปยัง คลาสลูก (Child or Sub Class) ทำให้ คลาสลูกจะได้ คุณสมบัติ (Fields) และเมธอดทั้งหมดจากคลาสแม่มาด้วย

การ inherit จะใช้ extends เป็น keyword ซึ่งมี syntax แบบนี้

class child_class_name extends parent_class_name

ยกตัวอย่าง

class Alcohol {
  Alcohol() {
    print('Cheers!!');
  }
}

class Beer extends Alcohol {}

void main() {
  var pileOfBeer = Beer();
}

/*
Cheers!!
*/

การสืบทอดคลาส ปกติในภาษาโปรแกรมแบบ Imperative ประเภท OOP จะมีการสืบทอดคลาสใน 3 ลักษณะ ได้แก่
-Single คือ สืบทอดมาจากคลาสแม่เดียว
-Multiple คือ สืบทอดมาจากคลาสแม่หลายคลาส
-Multi-level คือ สืบทอดมาจากคลาสลูกอันอื่น

Dart ไม่สนับสนุนแบบ Multiple

Class Inheritance and Method Overriding

การใช้คุณสมบัติหนึ่งจากการสืบทอดคลาสคือ การเอา Method ของคลาสแม่มาดัดแปลงเป็นของตัวเอง เรียกว่า Method Overriding สามารถใช้ได้ผ่าน keyword @override

ยกตัวอย่างเช่น

void main() { 
   Child c = new Child(); 
   c.m1(12); 
} 
class Parent { 
   void m1(int a){ print("value of a ${a}");} 
}  
class Child extends Parent { 
   @override 
   void m1(int b) { 
      print("value of b ${b}"); 
   } 
}

/*
value of b 12
*/

จากตัวอย่าง เราสร้างคลาสลูกสืบทอดจากคลาสแม่ ในคลาสแม่มี method m1 เมื่อเอามาเป็น method ของลูกแล้ว สามารถดัดแปลงได้ ใส่ @override ข้างบน ก่อนทำการแปลง
ข้อจำกัดคือ
1.data type ของ method จะต้องเหมือนกัน ทั้งของแม่ของลูก
2.จำนวณพารามิเตอร์ใน method จะต้องเหมือนกัน ทั้งของแม่ของลูก
3.data type ของพารามิเตอร์ใน method จะต้องเหมือนกัน ทั้งของแม่ของลูก

ในตัวอย่าง m1 ของแม่ เป็น void มีพารามิเตอร์ 1 ตัว เป็น int ซึ่งของลูกก็ต้องเป็น void และรับค่าพารามิเตอร์ 1 ตัว เป็น int เช่นเดียวกัน

ยกตัวอย่างกรณีที่ data type ของพารามิเตอร์ใน method แม่ลูกต่างกัน

void main() { 
   Child c = new Child(); 
   c.m1(12); 
} 
class Parent { 
   void m1(int a){ print("value of a ${a}");} 
} 
class Child extends Parent { 
   @override 
   void m1(String b) { 
      print("value of b ${b}");
   }
}
/*
Error: The parameter 'b' of the method 'Child.m1' has type 'String', which does not match the corresponding type, 'int', in the overridden method, 'Parent.m1'.
Change to a supertype of 'int', or, for a covariant parameter, a subtype.
*/

ถ้าเป็นกรณีจำนวณพารามิเตอร์ไม่เท่ากัน จะ throw error ออกมา

void main() { 
   Child c = new Child(); 
   c.m1(12); 
} 
class Parent { 
   void m1(int a){ print("value of a ${a}");} 
} 
class Child extends Parent { 
   @override 
   void m1(String b, int d) { 
      print("value of b ${b} and ${d}");
   } 
}
/*
Unhandled exception:
Class 'Child' has no instance method 'm1' with matching arguments.
NoSuchMethodError: incorrect number of arguments passed to method named 'm1'
*/

ในบทความนี้ บอกรายละเอียดพื้นฐานของคลาส ในส่วนที่เป็น Constructor และ Inheritance เนื่องจากมีรายละเอียดที่จำเป็นต้องทำความเข้าใจและยกตัวอย่างมาประกอบจำนวณมาก เลยเขียนมาซะยืดยาวเลย จึงขอยกคุณสมบัติอื่นของคลาสเช่น Static Keyword, Super Keyword และเรื่องอื่นๆ ทั้ง Library หรือ Package , Typedefs ไปจนถึง generic ไปยังบทความหน้า เพื่อไม่ให้บทความมันยืดเกินไปครับ

Reference

https://www.tutorialspoint.com/dart_programming/dart_programming_classes.htm

Tags