หลายๆคนน่าจะเคยใช้หรือรู้จัก Generic กันมาบ้างแล้ว ถือว่าเป็น Mechanic สำคัญของการเขียนโปรแกรมหลายๆภาษาเลย ซึ่งใน Java อาจจะเจอความยากลำบากในการใช้งาน Generic ในบางสถานการณ์อยู่บ้าง แต่ Kotlin มีทางออกแบบสวยๆด้วย inline function ให้นะเออ จะสวยจะเท่อย่างไรมาลองอ่านกันเลย

ถ้าใครที่ยังไม่รู้จัก Generic แนะนำให้อ่านบทความเหล่านี้ก่อนนะครับ (Java, Kotlin)

เรื่องมันเริ่มมาจาก บางครั้งเราต้องการทำ function ที่สามารถอ่าน json text แล้วเก็บไว้ในตัวแปรของใน code ของเรา แน่นอนว่าเราใช้เครื่องมือที่คุ้นกันดีคือ Gson เครื่องมือที่หลายๆคนก็ใช้กันบ่อยๆ โดยเฉพาะเมื่อต้องต่อ API และใช้ Retrofit2 โค้ด Java ที่เขียนเพื่ออ่าน json text  ก็จะหน้าตาประมาณนี้

ดูไม่มีปัญหาอะไรเรียกใช้ได้ตามปกติ แต่ปัญหาจะเกิดตอนที่เรามี Data model เยอะมากๆ คิดเล่นๆแค่ 10 Class เราก็ต้องมี method 10 method บาปแน่นอน มัน WET (Write Everything Twice) มากๆ วิธีแก้ก็ต้องทำให้ DRY (Don’t repeat yourself) ไม่ควรมีการทำงานซ้ำๆกันอยู่ในโค้ดของเรา
ดังนั้นโจทย์คือ เราจะทำอย่างไรให้ยุบ 10 method ให้เหลือแค่ 1 method พระเอกขี้ม้าขาวของเราก็ไม่ใช่ใครที่ไหนไกลตัว แต่อยู่ในหัวข้อของเราในวันนี้ คือ Genenic นั้นเอง อะไรที่ซ้ำให้เป็นเป็น Generic ให้หมด นั้นก็คือชื่อ Data model class นั้นเอง

เท่กว่าเดิมขึ้นเยอะฟังก์ชันเดียวสามารถรองรับได้ทุก Data model แต่เดี๋ยวก่อน พอลองรันดูปรากฎว่าเจอ ClassCastException

Caused by: java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.model.network.response.MyDataModel

พอ Debug ดูก็เจอต้นตอมาจาก

Type type = new TypeToken<T>(){}.getType();

คำสั่งข้างบนนี้ไม่สามารถรู้ได้ว่า type ที่ส่งมาคืออะไรเลยมองว่าเป็น T เอาดื้อๆเลย แทนที่จะมองว่ามันคือ MyDataModel เลยไม่สามารถ Cast ได้นั้นเอง แต่ไม่เป็นไรในเมื่อออกทางนี้ไม่ได้เราก็จะไปออกทางอื่นโดยใช้ Class parameter แทน เลยได้ผลเป็นแบบนี้


ถึงแม้จะใช้งานได้ไม่มี error อะไรตามมาแล้วก็ตาม แต่เราต้องส่ง Class เข้าไปเป็น Parameter เลย ซึ่งทำให้ความเท่ๆหายไปราวๆ 50% วิธีนี้ถ้าใครคุ้นๆมันคือวิธีเดียวกันกับคำสั่ง startActivity() นั้นเอง เลยลองคิดว่า Kotlin น่าจะมีทางออกที่ดีกว่านี้หรือปล่าวนะ เรามาเริ่มในแบบง่ายๆกันก่อนดีกว่าคือการ Fix class ตายตัวไปเลย


ด้วยฟีเจอร์ Extension function ของ kotlin ทำให้เราลดการส่ง parameter context ลงไปได้ 1 ตัว ความเท่จึงเพิ่มขึ้นเป็น 70% แต่ก็ยังติดปัญหา ที่มันไม่ DRY เพราะเรา Fix class ไว้นั้นเอง งั้นเรามาลองเปลี่ยน Class ให้เป็น Generic ดู


ปรากฎว่ามีปัญหาตอน compile time เลยที่ T::class.java เพราะ Generic ไม่สามารถเรียกใช้ในแบบ Class ที่ทำแบบนี้ได้ MyDataModel::class.java ถ้าจะใช้ TypeToken แบบ Java ก็จะติดปัญหา ClassCastException อีกแน่นอน และถ้าจะส่ง Class ในรูปแบบของ Parameter ก็จะดูไม่เท่ไม่เก๋อีก(ทำไมเราต้องห่วงความเท่ขนาดนี้ ปัญหาเยอะจริงๆ 555)

val type = object : TypeToken<T>() {}.type // ClassCastException

จึงเป็นที่มาของ inline function และ reified ที่จะมาคู่กันเพื่อทำให้เราสามารถเรียกใช้ Generic เสมือนกับว่าเป็น Class parameter นั้นเอง จนได้แบบสุดท้าย(สักที)ที่เท่ๆ แบบนี้


คราวนี้สามารถใช้งาน T::class.java ได้โดยที่ compiler ไม่โวยวายอีกต่อไป

นอกจากจะใช้กับ Gson แล้วยังประยุกต์กับการทำ startActivity(), findViewById(), ect ให้สวยสดงดงามตามจินตนาการของเราได้อีกด้วยนะ

สรุปข้อดี

  • ไม่ต้องเพิ่ม parameter ที่ส่งเข้าไปใน method
  • โค้ดดีสวยขึ้น ตอนเรียกใช้ก็อ่านแล้วเข้าใจง่ายขึ้น

นอกเรื่องแพร้บ

การ findViewById เป็นสิ่งที่ Android developer ทุกคนต้องทำเป็นอย่างแน่นอน ชนิดที่ว่าลืมอะไรก็ลืมได้แต่ห้ามมี bind id ส่วนตัวเห็นวิวัฒนาการของคำสั่งนี้มาเรื่อยๆตั้งแต่

  • (MyView) findViewById(resId) แบบดั่งเดิมเลยที่ใช้หลักการ Cast object
  • Butter knife อันนี้ต้องเสียเวลาติดตั้งก่อนเพราะว่าเป็น Library แต่มันก็มีความสามารถมากกว่าการ binding id อะนะ
  •  <MyView> findViewById(resId) ท่านี้เท่ขึ้นมาหน่อยโดยใช้ generic ช่วยทำให้ไม่ต้องกังวลว่า type จะไม่ตรงกัน จะพบเห็นได้เฉพาะโปรเจ็คที่อัพ buildToolVersion 26+ แล้วเท่านั้น
  • kotlinx (extenstion) อันนี้พิมพ์ชื่อ id ไปเลย เดี๋ยวมันไปจัดการใช้เอง ส่วนตัวแล้วชอบแบบนี้และใช้แบบนี้อยู่ครับ

ก่อนจบตรงนี้ไม่มีอะไรแค่ต้องการจะสื่อว่า ถ้าใครที่เปลี่ยนมาใช้ kotlin แนะนำให้ลองใช้ kotlin extension ได้นะ อย่างน้อยก็ช่วยเรื่อง bind id ให้ดูเท่ขี้นได้แน่นอน

ปล. ถ้าตกหล่นตรงไหนสามารถแนะนำได้เลยนะครับ เพื่อให้คนที่เข้ามาอ่านทีหลังเข้าใจอย่างถูกต้องมากขึ้นครับ

เพิ่มเติมเกี่ยวกับ inline, reified

https://antonioleiva.com/reified-types-kotlin/

Advertisements