top
logo

Who's Online

We have 2 guests online

Home Delphi Coding Technique Delphi 2009 Generic
Delphi 2009 Generic PDF Print E-mail
Written by olarn u   
Tuesday, 09 September 2008 14:41
Delphi 2009หนึ่งใน 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 )
 

bottom

Powered by Joomla!. Designed by: Joomla 1.5 Templates, web hosting. Valid XHTML and CSS.