Dart: Extension Methods - มาเพิ่มความสามารถให้กับ Third-party Library กันเถอะ

Dart Aug 4, 2020

ปฏิเสธไม่ได้เลยว่าการพัฒนาโปรแกรมในปัจจุบันนั้น การใช้งาน Third-party Library นั้นมีความสำคัญเป็นอย่างมาก โดยเฉพาะในแง่ของการลดความซับซ้อนของการพัฒนาโปรแกรมลง ซึ่งช่วยให้เราสามารถพัฒนาระบบต่าง ๆ ได้รวดเร็วมากยิ่งขึ้น

อย่างไรก็ตามหลายครั้งที่เรามักมีความรู้สึกว่า "ถ้ามี xxx มาด้วยก็ดีสินะ" หรือ "อยากให้ xxx ทำแบบนี้มากกว่าแบบที่ให้มาจังเลย" ปัญหาเหล่านี้ หากเป็น Library ที่เราพัฒนาขึ้นมาเอง หรือเป็นส่วนหนึ่งของโปรแกรมของเราก็คงไม่ใช่ปัญหาอะไรที่จะไปแก้ไขโค้ดในส่วนนั้น ๆ ให้มีความสามารถมากยิ่งขึ้น

ถึงกระนั้นการแก้ไข Third-party Library นั้น แทบจะเป็นไปไม่ได้เลย หรืออาจทำให้เกิดความวุ่นวายในการแก้ไขเกินความจำเป็น วิธีปกติที่เราใช้กันเพื่อเพิ่มความสามารถ ก็อาจจะเป็นการ Extends class นั้น ๆ ออกมาเป็น Class ของเราเองและเพิ่มความสามารถเข้าไปให้มีความสามารถตามที่เราตั้งใจไว้

แต่จุดนี้เองที่หลายครั้งมักทำให้โปรแกรมของเรามีการความซับซ้อน เข้าใจได้ยากมากยิ่งขึ้น รวมไปถึงโอกาสในการเกิดข้อผิดพลดาต่าง ๆ รวมถึงเพิ่มระดับความยากในการ Maintain โค้ดชุดนั้น ๆ อีกด้วย

Dart เล็งเห็นปัญหานี้ ดังนั้นตั้งแต่เวอร์ชัน 2.7 เป็นต้นมา Dart ได้มาพร้อมกับฟีเจอร์ที่เรียกว่า Extension Methods ซึ่งช่วยให้ชีวิตนักพัฒนาอย่างเรา ๆ ดีขึ้นไปอีก โดยเฉพาะหากใครที่เคยเขียน Swift ในฝั่ง iOS มาน่าจะคุ้นเคยกันเป็นอย่างดีกับ Pattern นี้


Extension Methods ส่วนขยายเพิ่มความสามารถ

ฟีเจอร์ Extension Methods นั้นเป็นฟีเจอร์ที่เข้ามาเพื่อช่วยให้เราสามารถเพิ่มความสามารถของ Class นั้น ๆ ได้ โดยไม่ต้องอาศัยกับ Extends ออกไปเป็น Subclass นั่นหมายความว่าความสามารถใด ๆ ก็ตามที่เราเพิ่มเข้าไปนั้น จะสามารถเรียกใช้งานผ่าน Class เดิมได้เลยนั่นเอง

โดย Syntax สำหรับการใช้งาน Extension Methods นั้นก็เป็นดังนี้

extension <extension name> on <type> {
  (<member definition>)*
}

โดย Extension name นั้นเราสามารถละไว้ได้ แต่ที่ Dart แนะนำให้เราตั้งชื่อด้วยนั้น เนื่องจากเราไม่รู้ว่านักพัฒนาคนอื่น ๆ ในทีมจะบังเอิญตั้งชื่อ Method ใน Extension เหมือนกับเราหรือไม่ ซึ่งอาจทำให้เกิด Conflict ได้

ดังนั้นก็ตั้งชื่อเสมอย่อมเป็นเรื่องที่ดี เนื่องจากเวลาเรา Import เราสามารถใช้ show, hide, as เข้ามาช่วยในการจัดการ Conflict เหล่านี้ได้ หรือหากเราต้องการให้ใช้งานทั้ง 2 Method ก็อาจจะใช้การทำ Wrapper เข้ามาเพื่อระบุได้ เช่น

<extension name>(<literal value>).<extended method>()

// For example
StringExtension('123').parseInt();
StringExtension2('456').parseInt();

ตัวอย่าง: ลูกอม

สมมติว่าเรามี Class ที่เราใช้งานจาก Third-party Library ใช้ในการสร้าง ลูกอม (Candy) ขึ้นมา สำหรับโรงงานของเรา ดังนี้

class Candy {
  final List<String> _flavors = [];
  final int _sweetness;

  Candy(this._sweetness);

  String get detail =>
      'This candy has degree of sweetness at $_sweetness and has the following flavor(s): $_flavors';

  void addFlavor(String flavor) {
    _flavors.add(flavor);
  }
}

แต่แล้ววันดีคืนดี โรงงานของเราตัดสินใจว่าลูกอมจะต้องมีลูกเล่นใหม่ ๆ เพื่อให้แข่งขันกับคู่แข่งได้ โดยการที่รสชาติของลูกอมนั้นสามารถหายไปได้ แต่เนื่องจากโรงงานของเราใช้งานเครื่องจักรผลิตลูกอมที่สั่งมาจากต่างประเทศ (ใช้งาน Third-party Library) เราจึงไม่สามารถแก้ไขลูกอม (Class) นั้นได้โดยตรง

แต่เราก็นึกขึ้นได้ว่าเราลูกอมของเราเขียนขึ้นในภาษา Dart เราสามารถ Extension ออกมาได้ โดยที่ไม่ต้องทำให้ลูกอมนั้นเป็นประเภทพิเศษ​ (Extends ให้กลายเป็น Subclass) เราจึงเขียนโค้ดเพิ่มขึ้นมาดังนี้

extension on Candy {
  void removeFlavor(String flavor) {
    _flavors.remove(flavor);
  }
}

เพียงเท่านี้เราก็สามารถเรียกใช้งาน candyInstance.removeFlavor("Mint"); ได้แล้ว อย่างไรก็ตาม ข้อควรระวังหนึ่งก็คือ เราไม่สามารถเพิ่ม Instance Variables ให้กับ Extension Methods ได้ตามที่ชื่อนั้นบอกใบ้เราไว้เลย

หากเราต้องการเพิ่ม Instance Variables แล้วละก็ เราอาจพิจารณาใช้การ Extends ไปเป็น Subclass แทน ซึ่งเป็นวิธีที่มีความเหมาะสมมากกว่า


ใช้ Extension Methods กับ Built-in Class

จริง ๆ แล้ว Extension Methods นั้นไม่ได้ใช้งานเฉพาะกับ Class ที่มากับ Third-party Library เท่านั้น แต่สามารถใช้ได้กับทุก Class รวมไปถึง Class ที่เป็น Built-in Class ใน Dart ด้วยเช่นกัน เช่น หากเราต้องการให้ String มีความสามารถในการ Return Reversed Uppercase String เราก็สามารถใช้งาน Extension Methods ในการเพิ่มความสามารถนั้นได้เช่นกัน เช่น

extension ExtendedString on String {
  String get uppercaseReversed => this.toUpperCase().split('').reversed.join();
}

ซึ่งจะทำให้เราสามารถเรียกใช้งานได้แบบนี้

print('Hello, Dart!'.uppercaseReversed);
// OUTPUT: !TRAD ,OLLEH

Final Thought

สำหรับ Extension Methods นั้น ไม่ได้มีความจำเป็นสำหรับการเรียนรู้ที่ต้องเข้าใจสำหรับผู้ที่พึ่งเริ่มศึกษา Dart อย่างไร (แต่ไหน ๆ ก็อาจมาถึงตรงนี้แล้ว ก็อาจจะไม่ทันแล้ว -.-) และไม่ใช่ฟีเจอร์ที่ Dart บังคับให้ทุกคนใช้ เป็นเพียงทางเลือกหนึ่งเท่านั้น ที่นำไปใช้ หรือไม่ใช้ก็ได้ตามที่เห็นสมควร

นอกจากนี้ยังมีอีกหลากหลายวิธีสำหรับการประยุกต์ใช้เช่นกัน เช่น วิธีหนึ่งที่มักจะเห็นได้ใน iOS คือ การใช้งาน Extension เพื่อแยกส่วนของโค้ดออกเป็นส่วน ๆ ให้มีความเป็นระเบียบ และในแต่ละ Extension มีความจำเพาะต่อส่วนของ Logic

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


📚 Hope you enjoy reading! 📚


Tags

Pittawat Taveekitworachai

A CS student who passionate about web and mobile technology with the belief that technology can enhance people's life.

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.