O/R Mapping:  Mapping Inheritance

เกริ่นนำ

     เมื่อคราวที่แล้ว ผมให้ Link ของ Sun DAO Pattern ไป มีคนสนใจเอาไป Implement เป็น .NET จริงๆ ลำพัง DAO อย่างเดียวนั้น มันทำ O/R Mapping ไม่ได้ เป้าหมายจริงๆ ของ DAO นั้นเป็นเพียงให้เราอิสระจาก Data Source ต่างๆ หรือกล่าวอีกนัยหนึ่งก็คือ DAO เป็น Layer ที่สูงกว่า O/R Mapping ครับ จะเห็นได้ว่า DAO ที่ผมแสดงให้ดูเป็นตัวอย่างนั้น ภายในมันซ่อน O/R Mapping เอาไว้อีกที มีเครื่องมือหลายตัวที่ใช้แนวคิดของ DAO เช่น IBatis หรือ Spring เป็นต้น ถ้ามีโอกาสผมอาจจะเอามาแนะนำให้รู้จักกันครับ

     วันนี้ผมขอต่อเรื่อง O/R Mapping ครับ วันนี้ผมจะชี้ให้เห็นถึงปัญหาความเข้ากันไม่ค่อยได้ของ Object-Oriented และ Relational Database มีปัญหาอยู่หลายข้อเหมือนกันที่เราต้องพิจารณา แต่แรงน้อยครับ วันนี้เอาแค่เรื่องเดียวก่อน นั่นคือ Inheritance ส่วนเรื่องที่เหลือยกยอดเอาไว้คราวหน้าครับ

    คราวที่แล้วผม Map class book ซึ่งตรงไปตรงมา มาวันนี้เรามาลองดูที่มันวุ่นวายหน่อยครับ สมมุติว่า ผมมี Class หนึ่งที่ชื่อว่า Person ผมก็สร้าง PersonDAO เพื่อจัดการ object นี้ดูก็ไม่มีปัญหาอะไร แต่มาวันหนึ่ง ผมเล่น Inherit Class Person ออกมาเป็น Class Police ทีนี้ปัญหาก็เริ่มเกิดแล้วครับ Inheritance มันมีความสัมพันธ์ของหลาย class อยู่ในนั้น แต่ ทางด้าน Relational Database มันจะมีความสัมพันธ์แบบนั้นด้วยหรือไม่ เรามาลองติดตามกันดูครับ

วิธี 1: 1 table ต่อ 1 class

    ถ้าใครมีความรู้ด้าน Relational Database ยุคใหม่ พอเห็นปัญหานี้ ก็คงนึกถึง Subtype หรือ Category ทันที ซึ่งวิธีการทำ Category นี้ ก็ถือได้ว่าเป็น การทำ Inherit Table ถ้าใครไม่คุ้นเคยกับ Subtype หรือ Category ก็ไม่เป็นไรครับ ผมขอใช้พื้นที่ตรงนี้อธิบายให้รับทราบกัน

    ผมขอเริ่มกันที่ Person ก็แล้วกัน ถ้าเราต้องการเก็บประวัติของบุคคล แต่บุคคลนั้นมีหลายประเภทเช่นเป็นตำรวจ เป็นครูหรืออื่นๆ ถ้าเป็นตำรวจก็จะมี field บาง field ที่เฉพาะตำรวจต้องกรอก เช่น field ยศ เป็นต้น คนที่เชี่ยวชาญเรื่อง Subtype ก็จะออกแบบเป็นอย่างนี้ครับ

    ถ้าพูดในแง่ของ Relational Database แบบเก่า ที่ไม่รู้จัก SubType ไม่ต้องกังวลครับ ลองดูที่ Table Police และ Student มันไม่มี Primary Key เป็นของตัวเอง มันยืมเอามาจาก Person นั่นเอง มันจึงเป็นทั้ง Primary key และ Foreign key ในตัวเดียวกัน ส่วน type นั้นทำหน้าที่เป็นตัวที่เรียกว่า discriminator หรือตัวแยกประเภทนั่นเอง เช่นถ้า type เป็นตำรวจ ใน Field type อาจจะเก็บ "Police" แต่ถ้าเป็นนักเรียน ก็อาจจะเก็บ "student" เป็นต้น

    ถ้าเราต้องการข้อมูลของ Police เราต้องดึงการ join table person เข้ากับ Police หรืออาจกล่าวได้ว่า ข้อมูลของคนที่เป็นตำรวจคนนี้ กระจายอยู่ในทั้ง Table Police และ Person นั่นเอง ถ้าเราดึงข้อมูลผ่าน Person มา ตัว discriminator จะเป็นตัวบอกเราว่า เราต้อง Join เอา Table ไหนมาใช้งาน

    วิธีนี้ เป็นวิธีที่ตรงไปตรงมา เรา mapหนึ่ง class ต่อหนึ่ง Table กันเลยทีเดียว ง่ายต่อความเข้าใจ แต่ข้อเสียของวิธีนี้ก็มีอยู่สองเรื่อง นั่นก็คือ การทำให้โปรแกรมทำงานค่อนข้างช้า เนื่องจากต้องยิงคำสั่ง SQL หลายๆ ตัว เพราะเรามีหลาย Table (อาจจะใช้การ join นั่นก็ขึ้นอยู่กับการเขียน แต่การ join ก็ช้าอยู่ดีครับ) กับอีกเรื่องก็คือ การเขียนโปรแกรมเพื่อรองรับมันค่อนข้างยุ่งยาก เราต้องฉีด (inject) ข้อมูลของ Table Police ลงใน object Police และ ต้องฉีดข้อมูล Table Person ลงใน object Police แต่ลงในส่วนที่เป็น Base class ของ Police นั่นคือ Person นั่นเอง

วิธี 2 : Table เดียวทั้งโครงสร้าง

    วิธีนี้เป็นวิธีมาตรฐานที่คนส่วนมากจะนึกถึงกัน แทนที่จะแยก Table ออกมาเป็น 3 tables เราเล่นรวบเอาเป็น table เดียว นั่นคือ Table Person นั่นเอง โครงสร้างก็จะได้ดังนี้ครับ

   

    วิธีนี้สะดวกมาก ไม่ว่าเราจะ inherit ไปกี่ชั้นก็ตาม มันก็ใช้ Table Person ร่วมกัน ภายในนั้น ก็ union ข้อมูลของทุก class เข้ามารวมหมดอยู่ในที่เดียวกัน และมี discriminator นั่นก็คือ field type นั่นเอง การ implement ก็ง่าย object ของ แต่ละ class ก็ใช้ข้อมูลบางส่วนใน table นี้ร่วมกัน

    แต่วิธีนี้มีข้อเสียก็คือ record แต่ละ record มันจะบวมมาก ยิ่ง inherit ออกไปมาก class เท่าไร ก็ยิ่งมี field ที่ไม่ได้ใช้งานมากขึ้นเป็นเงาตามตัว ถ้าใครตามผมทัน คงตอบได้ว่า จะไม่มี Record ใดใน Table นี้ที่มีข้อมูลครบทั้งหมด บาง field มันต้องว่างไว้ เพราะเป็นของ class อื่น

วิธี 3 : 1 Table ต่อหนึ่ง Concrete Class

    เพื่อลดอาการบวมของวิธีที่ผ่านมา ก็เลยมีคนคิดการ Map อีกแบบ วิธีนี้ไม่ต้องการ Discriminator แต่อย่างใด class ใดที่ new ได้ class นั้นจะถูก map เข้ากับ Table เฉพาะเลย ภายใน Table นั้น จะมีข้อมูลที่ class นั้นต้องการใช้ทั้งหมด ไม่ต้องไปหาที่ Table อื่นเลย ดู Table Diagram นี้จะเข้าใจได้ทันทีครับ

    วิธีนี้ไม่มี Table Person แต่มี Police และ Student แทน class Police ก็ map โดยตรงกับ Table Police และ Student ก็เช่นกัน ข้อดีของวิธีนี้ก็คือการเขียนโปรแกรมง่ายที่สุดครับ map กันแบบตรงไปตรงมาก แต่ข้อเสียก็น่ากลัวครับ ถ้า base class มีการปรับเปลี่ยนข้อมูล มันจะกระทบ Table ต่างๆ เป็นลูกโซ่ สรุปก็คือการ maintain ค่อนข้างยุ่งครับ

 วิธี 4 : 4 Tables ครอบจักรวาล

    วิธีนี้ใช้งานเพียงแค่ 4 Tables เท่านั้น ไม่ว่าคุณจะสร้าง class มีกี่ class ก็ตาม Project ทั้ง Project ใช้ table แค่เพียง 4 tables เท่านั้น ดังรูปนี้ครับ

     ถ้าเราต้องการสร้าง class Police ขึ้นมา เราก็ไปสร้าง Record ใน Table MetaType ชื่อ Police จากนั้น class Police มี Field อะไรบ้าง เราก็ไปสร้างใน MetaField โดยที่ 1 Field ต่อ 1 Record

     จากนั้น ถ้าเราต้องการสร้าง Object ของ Police เราก็ไปสร้าง Record ใน DataRecord จากนั้นข้อมูลแต่ละ Field เราสร้างใน DataField ครับ ผมคงไม่แจงรายละเอียดอะไรมากนักสำหรับวิธีนี้ เพราะการใช้ยุ่งยากพอสมควร แต่วิธีนี้เหมาะกับการเขียนโปรแกรมแบบ Auto-Generation ครับ ให้เครื่องมันสร้าง class ให้ เช่นการ Drawing บนจอภาพ แล้ว generate มาเป็น code วิธีนี้เหมาะสมมากครับ

แนวคิดในการใช้งาน   

    โดยมากคนส่วนใหญ่มักเริ่มจากวิธี Table เดียวทั้งโครงสร้าง แต่ในอนาคตถ้ามันชักจะบวมมาก ก็มักปรับให้เป็น 1 ต่อ 1 หรือ 1 Table ต่อหนึ่ง Concrete Class ก็แล้วแต่ถนัดครับ

ทิ้งท้าย   

    คราวหน้าเรามาดูเรื่องความสัมพันธ์ระหว่าง class กัน