Forum
Who's Online
We have 2 guests online| Delphi 2009 Generic |
|
|
|
| Written by olarn u |
| Tuesday, 09 September 2008 14:41 |
หนึ่งใน feature ที่น่าสนใจที่สุดที่มีมากับ Delphi 2009 ก็คือเรื่อง Generic เพราะนี่ถือว่าเป็นการ update ภาษาของตัว Object Pascal เองหลังจากที่ไม่มีใหม่ๆ อะไรเพิ่มเติมมาซะนาน ซึ่ง Generic จะช่วยให้โปรแกรมเมอร์สามารถทำ polymorphism ได้ยืดหยุ่นมากขึ้นนอกเหนือไปจากการทำ override และ overload method ที่จริง Generic นั้นมีมานานแล้วในภาษา C++ ต่อมาภาษา Java ก็ support ใน version 1.5 และ C# ก็ support ใน version 2.0 เช่นกัน เราลองมาดูครับว่ามันเป็นยังไง และมีประโยชน์ยังไงสำหรับการถามตอบหรือ discussion ก็ไป post ไว้ที่นี่นะครับ http://www.thaideveloperexpert.org/forum/index.php?topic=45.msg256#new The land before Generic สมมุติว่าเรามี class การเรียงลำดับก็แล้วกัน โดยปกติถ้าไม่ได้ใช้ dataset เก็บข้อมูลเป็นตาราง เราก็ต้องใช้ array หรือ dynamic array เก็บค่าต่างๆ ตัวอย่าง class ก็น่าจะประมาณนี้ TMySorted = class private FElements : array of integer; // เก็บ integer ไว้ใน array FCount : integer; // เก็บจำนวนของ element procedure FSort; // method ในการ sort จะถูก call จาก AddElement ทุกครั้งที่มีการ add element ใหม่ public procedure AddElement(e : integer); // add element ใหม่เข้าไป จะไปลงตรงไหนไม่รู้ เพราะจะถูก sort เก็บไว้ function GetElement(position : integer = -1) : integer; // เอา element ตำแหน่ง position ออกมา ถ้าไม่ใส่คือเอาตัวแรกสุด end; สังเกตุว่า class นี้จะรองรับ element เฉพาะ integer เท่านั้น ถ้าต้องการให้รับ type อื่นๆ ด้วย ก็ต้องมี class TMySorted เฉพาะ type นั้นๆ ไป เช่น TMySortedReal, TMySortedString, etc. ทำให้เกิด code ซ้ำๆ กัน เพราะใช้ algorithm แบบเดียวกันหมด ถ้าต้องการเปลี่ยน algorithm ก็ต้องแก้ code ทุก class เลยเพราะแต่ละ class มี code ที่ซ้ำๆ กันอยู่ เราอาจจะแก้ปัญหาด้วยแนวคิดของ polymorphism ก็ได้ คือแทนที่จะกำหนด type เป็น integer ก็กำหนด type เป็น TObject แทน เพราะทุก type ถูก inherit มาจาก TObject อยู่แล้ว แต่ TObject ก็ไม่ support native type เพราะ Delphi เป็นภาษาที่ต่อยอดมาจาก Pascal ซึ่ง integer, real, etc. เป็น ตัวอย่าง class ในกรณีที่ใช้ TObject TMySortedObject = class private FElements : array of TObject; // เป็น object แทน integer FCount : integer; // procedure FSort; // public procedure AddElement(e : TObject); // add object แทน integer function GetElement(position : integer = -1) : TObject; // return object แทน end; เวลาเรียกใช้ก็ var mySortedObj : TMySortedObject; person: TPerson; // เปลี่ยนจาก interger มาเก็บ object ของ class TPerson แทน begin mySortedObj := TMySortedObject.Create(); person := TPerson.Create; mySortedObj.AddElement( person as TObject ); // ทำการ type cast บังคับให้เป็น object แทน person := (mySortedObj.GetElement(0) as TPerson); // บังคับ object ที่ return ออกมาเป็น integer อีกที end; การ casting object แบบนี้เรียกว่า late binding คือไปเชื่อม type กันตอน runtime ซึ่งตอน compile (design time) ไม่มีปัญหาอะไร แต่จะเกิดปัญหาตอน runtime เพราะเป็นไปได้ที่คนอื่นเอา class นี้ไปใช้แล้วอาจเกิด add object ของ TPerson แต่ตอน get กลับ cast เป็น TAnimal หรืออาจจะ add เข้าไปหลายๆ type แต่ get ออกมา ก็ต้องใช้ is operator ทดสอบว่า เป็น string ไหม ก็ cast string, แต่ถ้าเป็น TPerson ก็ต้อง cast ให้เป็น TPerson อีก ซึ่งทำให้การ test ทำได้ยาก และ code จะซับซ้อนเกินไปด้วย (มี if เยอะ) Genericคราวนี้เรามาดูว่า ถ้าเอา Generic มาใช้ เราจะเขียนยังไง ตัวอย่าง class ... TMySortedGeneric <T> = class // กำหนด T ขึ้นมาเป็น anonymous type ก่อน // เมื่อ T ถูก reference ที่ไหนใน class จะถือว่าเป็น type เดียวกัน private FElements : array of T; // ให้ array เก็บ element ของ T FCount : integer; // procedure FSort; // public procedure AddElement(e : T); // add type T (ขึ้นอยู่กับว่าตอนเอาไปใช้ T จะเป็นอะไร) function GetElement(position : integer) : T; // return object ของ type T ออกมา end; หมายเหตุ สำหรับใครที่ยังไม่รู้ ในส่วน body ของ code ให้ใช้ Delphi ช่วยสร้าง body ของ code ให้นะครับ ไม่ต้องเขียนเอง โดย click ไปที่ method ที่ยังไม่ได้เขียน code แล้วกด ctrl + shift + c บน keyboard ครับ เวลาเรียกใช้ก็ var mySorted : TMySortedGeneric <string>; // เอา string ไปแทนค่า T เพราะฉะนั้นใน T ใน class ก็จะเป็น string หมด s : string; begin s := 'Test1'; mySorted := TMySortedGeneric <string>.Create; mySorted.AddElement(s); // add ได้ตรงๆ s := mySorted.GetElement(0); // get ได้เลย ไม่ต้อง cast mySorted.AddElement( 20 ); // compile ไม่ผ่าน เพราะ compiler จะเช็ค type ตอน compile เลย end; นั่นคือ class TMySortedGeneric จะสามารถรองรับ type อะไรก็ได้ ขึ้นอยู่กับการนำไปใช้งาน เช่น mySorted := TMySortedGeneric <int>.Create; mySorted := TMySortedGeneric <real>.Create; เป็นต้น ในกรณีที่เรามี anonymous type มากกว่า 1 type เราสามารถประกาศได้ แบบนี้ TMySortedGeneric <T1, T2> = class … end; จากตัวอย่างที่ผ่านมา เป็นการกำหนดให้ class นั้นเป็น Generic class ในกรณีที่เราต้องการให้ class ของเรา support Generic โดยที่ไม่ได้กำหนดทั้ง class ก็ได้ เราก็เขียน method นั้นให้ support Generic แทน เช่น TMySortedGeneric = class … Public Function AddItem<T>(item: T): integer; // Generic method โดยที่ Type T จะมี scope อยู่ใน // method AddItem เท่านั้น … end; Generic and Polymorphismบางครั้งเราต้องการเขียน Generic class หรือ method ให้รับ type ที่อยู่ใน type กลุ่มเดียวเท่านั้น เช่น TPerson = class End; TEmployee = class(TPerson) End; TManager = class(TManager) End; ซึ่ง TPerson เป็น class แม่, TEmployee inherit มาจาก TPerson และ TManager inherit มาจาก TEmployee และ class TClaculateBonus เป็น class ที่เก็บ business logic ในการคำนวณโบนัสของพนักงาน TCalculateBonus<TPerson> = class End; การเขียน code แบบนี้ เรากำหนด class ที่จะให้เป็น generic class เป็น TPerson ไปเลย หมายความว่า TCalculateBonus จะยอมรับ object ของ class TPerson หรือ class ที่ inherit มาจาก TPerson เท่านั้น ตามกฏการทำ polymorphism ของ OOP ลองดูตัวอย่าง code ครับ var cal: TCalculateBonus <TPerson>; // <-- ประกาศตัวแปรชื่อ cal เป็น type TCalculateBonus รับเฉพาะ TPerson p: TPerson; // <-- ตัวแปรที่จะเอามา test e: TEmployee; m: TManager; begin cal := TCalculateBonus<TPerson>.Create; // <--สร้าง object cal ขึ้นมา p := TPerson.Create; e := TEmployee.Create; m := TManager.Create; cal.Calculate(p); // รับ p เข้าไป ไม่มีปัญหาเพราะ p เป็น TPerson cal.Calculate(e); // รับ e ได้ เพราะ e เป็น TEmployee ซึ่ง inherit มาจาก TPerson อีกที cal.Calculate(m); // รับ m ได้ เพราะ m เป็น TManager ซึ่ง inherit มาจาก TEmployee และ TPerdon cal.Calculate(cal); // *** ERROR เพราะ cal ไม่ได้เป็น class ที่อยู่ในตระกูลเดียวกับ TPerson end; หรือเราอาจประกาศแบบนี้ก็ได้ TCalculateBonus<T: TPerson> = class // T เป็น anonymous class ซึ่งเป็น type ของ TPerson (T “is a” TPerson) Public procedure Calculate(p: T); // กำหนด type เป็น T แทน end; คิดว่าคงจะพอเห็นภาพนะครับ ด้วยวิธีของ Generic จะช่วยให้เราเขียน code ได้สะอาดขึ้น เพราะเราสามารถ reuse code ที่ซ้ำๆ กันได้ดีกว่า รวมทั้งยังปลอดภัยขึ้น มี bug น้อยลงด้วย เพราะถ้า type ที่เรากำหนดไม่เข้ากฏเกณฑ์ที่กำหนดไว้ compiler จะฟ้อง error ตอน compile เลย ไม่ต้องไปรอ error ตอน runtime ครับ Olarn U. |
| Last Updated ( Tuesday, 09 September 2008 15:50 ) |




หนึ่งใน feature ที่น่าสนใจที่สุดที่มีมากับ Delphi 2009 ก็คือเรื่อง Generic เพราะนี่ถือว่าเป็นการ update ภาษาของตัว Object Pascal เองหลังจากที่ไม่มีใหม่ๆ อะไรเพิ่มเติมมาซะนาน ซึ่ง Generic จะช่วยให้โปรแกรมเมอร์สามารถทำ polymorphism ได้ยืดหยุ่นมากขึ้นนอกเหนือไปจากการทำ override และ overload method ที่จริง Generic นั้นมีมานานแล้วในภาษา C++ ต่อมาภาษา Java ก็ support ใน version 1.5 และ C# ก็ support ใน version 2.0 เช่นกัน เราลองมาดูครับว่ามันเป็นยังไง และมีประโยชน์ยังไง