Python singledispatchmethod: แยก Handler ตามชนิดโดยยังเป็น Method เดียว
เมธอดที่รับ command หลายชนิดมักโตเป็น if isinstance(...) ยาว ๆ แล้วทุกครั้งที่เพิ่มชนิดใหม่ต้องแก้ศูนย์กลาง functools.singledispatchmethod ให้เราเก็บชื่อเมธอดเดียว แต่ลงทะเบียน implementation ตามชนิดของ argument ตัวแรกที่ไม่ใช่ self หรือ cls ครับ
import unittest
from functools import singledispatchmethod
class Encoder:
@singledispatchmethod
def encode(self, value: object) -> str:
raise TypeError(f"unsupported: {type(value).__name__}")
@encode.register
def _(self, value: int) -> str:
return f"count={value}"
@encode.register
def _(self, value: list) -> str:
return ",".join(str(item) for item in value)
class EncoderTests(unittest.TestCase):
def test_dispatches_on_first_non_self_argument(self):
encoder = Encoder()
self.assertEqual(encoder.encode(7), "count=7")
self.assertEqual(encoder.encode([2, 4]), "2,4")
def test_unknown_type_uses_base_implementation(self):
with self.assertRaises(TypeError):
Encoder().encode({"unexpected": True})
if __name__ == "__main__":
unittest.main()
เมื่อไรเหมาะ เมื่อไรไม่เหมาะ
เหมาะเมื่อ operation เดียวมีความหมายร่วมกัน แต่ชนิดข้อมูลต้องการ algorithm ต่างกัน เช่น serialize event หลาย class หรือแปลง domain command เป็นข้อความ การลงทะเบียนแยกทำให้เพิ่ม implementation โดยไม่แตะเงื่อนไขเดิม และ subclass ยังต่อยอด registry ได้
อย่างไรก็ตาม dispatch ดูเฉพาะ runtime type ของ argument หลัก ไม่เลือกจากหลาย argument พร้อมกัน และไม่เข้าใจเงื่อนไขเชิงค่า เช่น จำนวนติดลบ ถ้ากฎขึ้นกับสองแกนหรือ state ธุรกิจ explicit strategy/table อาจอ่านง่ายกว่า นอกจากนี้ list[int] กับ list[str] เป็น list เหมือนกันตอน runtime จึงแยกด้วย generic parameter ไม่ได้
เมื่อใช้ร่วมกับ @classmethod หรือ decorator อื่น เอกสารกำหนดให้ singledispatchmethod อยู่ชั้นนอกสุดเพื่อให้ attribute register ยังเข้าถึงได้ เขียน test ที่เรียกผ่าน instance จริงและมีเคส fallback เสมอ เพราะ annotation ที่เปลี่ยนโดย refactor สามารถเปลี่ยน registry โดยไม่เห็นจาก call site
Recap
- dispatch ตาม argument แรกที่ไม่ใช่
self/cls - เก็บ default implementation ให้ fail ชัดเมื่อไม่รองรับ
- generic parameter ไม่ช่วยแยก runtime type
- ใช้ explicit strategy ถ้ากฎขึ้นกับหลายค่า ไม่ใช่ชนิดเดียวครับ