البرمجة بالمنحى الكائني
خطوة خطوة (1)

خالد الشقروني   22 06 2016

أنت تريد أن تتعرف على أساسيات البرمجة بالمنحى للكائن Object Oriented Programming

أنت لديك بعض الخبرة بالبرمجة و تريد أن تتعرف على المنحى للكائن و تفهمه استنادا إلى خبرتك هذه وانطلاقا مما تعرفه من تقنيات برمجية.

أن تريد أن تتلمس بيدك كيف تكون البرمجة بالمنحى للكائن ، و ليس مجرد تعريفات ومصطلحات غامضة وشروحات طويلة مملة.

إذا، و بدون مقدمات ، وبدون تمهيد نظري، دعنا نبدأ:

الجولة الأولى

ابدأ مشروعا جديدا في دلفي، ضع زرا Button على نموذج الشاشة ثم نقرة مزدوجة double click ، نحن أمام إجرائية مناولة حدث OnClick للزر. من هنا نضع خربشاتنا للتجريب و الاستكشاف .

نعلم جميعا أنه يوجد في البرمجة مفهوم إسمه متغيرات Variables يمكن أن نحمل عليها قيم، مثل المتغير الذي سنعرفه الآن:

procedure TForm1.Button1Click(Sender: TObject);
var
  FirstName: string;
begin

end;

قمنا بتعريف متغير إسمه FirstName من نوع string، (كلمة نوع تسمى في دلفي Type)، يمكنك الآن أن تسند أية قيمة نصية لهذا المتغير، كأن نقول مثلا: FirstName := 'Ahmad';

procedure TForm1.Button1Click(Sender: TObject);
var
  FirstName: string;
begin
  FirstName := 'Ahmad';
end;

ويمكننا التأكد من ذلك بطبع القيمة التي يحملها أو يشير إليها هذا المتغير FirstName على سطح النموذج:

begin
  FirstName := 'Ahmad';

  Canvas.TextOut(10, 10, FirstName);
end;

نقوم أيضا بإضافة المزيد من المتغيرات:

procedure TForm1.Button1Click(Sender: TObject);
var
  FirstName: string;
  LastName: string;
  BirthDate: TDateTime;
begin
  FirstName := 'Ahmad';
  LastName := 'Hamza';
  BirthDate := EncodeDate(1980, 3, 15);

  Canvas.TextOut(10, 10, FirstName);
  Canvas.TextOut(10, 30, LastName);
  Canvas.TextOut(10, 50, DateToStr(BirthDate));
end;

كما هو واضح أعلاه بالإضافة إلى FirstName وضعنا متغيرات جديدة: اللقب من نوع string وتاريخ الميلاد من نوع TDate ، ثم خصصنا قيما لهذه المتغيرات، المتغيرات الثلاث معا تمثل حالة فرد ، والقيم تعبر عن فرد بذاته إسمه أحمد حمزة.

ثم كتبنا على شاشة النموذج القيم التي تحملها هذه المتغيرات.

البرنامج بعد التشغيل يكون شكله كالتالي:

الجولة الثانية

المتغيرات التي حددناها سابقا هي من أنواع معرفة مسبقا داخل دلفي مثل string و integer و TDate وكل نوع له خصائصه، فنوع string يحمل سلسلة من أحرف نصية، و integer يحمل قيمة بعدد صحيح، و نوع TDate يحمل قيمة من نوع تاريخ. وكل متغير نقوم بتحديده سيحمل قيمة توافق النوع الذي انبثق من المتغير. هذا كلام معروف ومفهوم و من أساسيات البرمجة.

الآن سوف ندفع بهذه المتغيرات إلى مستوى أعلى. ونستخدم نوعا جديدا من أنواع البيانات.

المتغيرات التي عرفناها سابقا؛ سنقوم بضمها في هيكل واحد. هذه الهيكلية تسمى في دلفي record ، كالتالي:

procedure TForm1.Button1Click(Sender: TObject);

type
  TPerson = record
    FirstName: string;
    LastName: string;
    BirthDate: TDateTime;
  end;

var
  Person: TPerson;
begin

في دلفي ، ومثل أية لغة أخرى تقريبا، يمكننا أن ننشئ أنواعا أخرى خاصة بنا، وهذا ما صنعناه أعلاه حيث قمنا بضم المتغيرات السابقة في هيكل واحد، ليعطينا نوعا جديدا أسميناه TPerson ، هذا النوع الجديد هو نوع مركب، أي أنه نوع يتكون من مجموعة أنواع أخرى.

كل تعريف داخل الهيكلية record يسمى حقلا Field ، فما كان متغيرات سابقا، أصبحت داخل الهيكلية حقولا لنوع TPerson .

ثم قمنا بإنشاء متغير جديد Person من نوع TPerson. أي أنه بإستطاعته تمثيل وحمل البيانات التي يتكون منها النوع TPerson.

والآن و بإستخدام المتغير Person يمكننا وضع القيم فيه كالتالي:

var
  Person: TPerson;
begin
  Person.FirstName := 'Ahmad';
  Person.LastName := 'Hamza';
  Person.BirthDate := EncodeDate(1980, 3, 15);

إذا، بدلا من وضع بيانات الفرد في ثلاث متغيرات متفرقة، أصبح لدينا متغيرا واحدا يحمل هذه البيانات. وهو Person ، وللوصول لكل حقل في هذا المتغير؛ نكتب اسم المتغير ثم نقطة ثم إسم الحقل: Person.FirstName تماما مثل استخدامنا للخصائص في المكونات.

طريقة عرض قيم هذا المتغير ستكون بالطريقة التالية:

  Canvas.TextOut(10, 10, Person.FirstName);
  Canvas.TextOut(10, 30, Person.LastName);
  Canvas.TextOut(10, 50, DateToStr(Person.BirthDate));
end;

حتى تكون الصورة واضحة نعيد سرد الإجرائية بالكامل:

procedure TForm1.Button1Click(Sender: TObject);
type
  TPerson = record
    FirstName: string;
    LastName: string;
    BirthDate: TDateTime;
  end;
var
  Person: TPerson;
begin
  Person.FirstName := 'Ahmad';
  Person.LastName := 'Hamza';
  Person.BirthDate := EncodeDate(1980, 3, 15);

  Canvas.TextOut(10, 10, Person.FirstName);
  Canvas.TextOut(10, 30, Person.LastName);
  Canvas.TextOut(10, 50, DateToStr(Person.BirthDate));
end;

ملاحظات سريعة:

لاحظ أن الإسم الذي أعطيناه للنوع TPerson يبدأ بحرف T وذلك للدلالة على أنه Type وهذا عرف متبع في لغة دلفي.

لاحظ أيضا أننا قبل تعريفنا لهذا النوع وضعنا التعليمة type ، وهذ أمر تتطلبه دلفي كلما أردنا إنشاء أنواعا خاصة. (حقيقة لا أعلم ما الحكمة من هذا)

ما قبل الجولة الثالثة

قبل أن ننتقل للجولة الثالثة أقترح القيام ببعض الإجراءات التنظيمية حتى لا تزدحم لدينا الأمور.

أولا:

تعريف TPerson بدلا من أن يكون محصورا في إجرائية Button1Click وخاص بها؛ نقوم بنقله إلى ملف وحدة جديدة، نسميها uOO ، حتى تتعرف عليه بقية الإجرائيات، ولفوائد أخرى سوف تتكشف لنا. وذلك كالتالي:

unit uOO;

interface
uses
  SysUtils;

type
  TPerson = record
    FirstName: string;
    LastName: string;
    BirthDate: TDateTime;
  end;

implementation

end.

كما هو واضح في السرد أعلاه، إسم الوحدة uOO ، قمنا بوضع تعريف النوع TPerson قبل قسم implementation ، كما قمنا بوضع التعليمة type قبل تعريف النوع للدلالة على أن ما يلي هذه التعليمة هي تعريفات لأنواع.

أيضا، نقوم بوضع إسم الوحدة uOO ضمن قائمة الاستخدام uses في وحدة نموذج الشاشة، كالتالي:

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
  Forms,Dialogs, StdCtrls,
  uOO;

الآن أصبح شكل مقدمة الإجرائية Button1Click كالتالي:

procedure TForm1.Button1Click(Sender: TObject);
var
  Person: TPerson;
begin

ثانيا:

ننقل التعليمات الخاصة بعرض البيانات ووضعها في إجرائية خاصة منفصلة. نقوم بتعريف الإجرائية تحت قسم private كالتالي:

  private
    procedure ShowPerson(P: TPerson);

ثم جسم الإجرائية:

procedure TForm1.ShowPerson(P: TPerson);
begin
  Canvas.TextOut(10, 10, P.FirstName);
  Canvas.TextOut(10, 30, P.LastName);
  Canvas.TextOut(10, 50, DateToStr(P.BirthDate));
end;

أي أن إجرائية العرض ShowPerson تستقبل محدد parameter إسمه P من نوع TPerson .

وتعليمة الإستدعاء لها كالتالي داخل Button1Click:

  ShowPerson(Person);
end;

وأصبح شكل إجرائية Button1Click كالتالي:

procedure TForm1.Button1Click(Sender: TObject);
var
  Person: TPerson;
begin
  Person.FirstName := 'Ahmad';
  Person.LastName := 'Hamza';
  Person.BirthDate := EncodeDate(1980, 3, 15);

  ShowPerson(Person);
end;

هل الأمور واضحة إلى حد الآن.

إذا لم تكن كذلك، فقد يعني هذا أنك لست معتادا على إستخدام أنواع البيانات المركبة، لذلك وقبل الدخول للجولة الثالثة، أقترح إعادة مراجعة الجولة الثانية وإستخدام أمثلة من عندك لإنشاء أنواعا أخرى باستخدام هيكلية record واستخدامها بأكثر من طريقة حتى تتمكن منها.

الجولة الثالثة

هذه هي الجولة المنتظرة.. سندخل الآن عالم المنحى للكائن ..

في الجولة الأولى عبرنا عن بيانات الفرد من خلال ثلاث متغيرات بأنواع بيانات مختلفة:

var
  FirstName: string;
  LastName: string;
  BirthDate: TDateTime;

وفي الجولة الثانية عبرنا عن بيانات الفرد بنوع بيانات جديد TPerson ببنية record :

type
  
  TPerson = record
    FirstName: string;
    LastName: string;
    BirthDate: TDateTime;
  end;

في هذه الجولة، سنطور نوع TPerson أعلاه من بنية record إلى صنفية Class. هل قلت Class نعم Class أي أننا الآن سنتحدث بالمنحى الكائني.

كيف نحول بنية record إلى صنفية. التغيير بسيط كالتالي:

type

  TPerson = class(TObject)
     FirstName: string;
     LastName: string;
     BirthDate: TDateTime;
 end;

بدلا من تعريف TPerson على أنه بنية record ؛ عرفناه على أنه صنفية class

الآن أصبح لدينا Class. صحيح شكلها بدائي، و لكنها صنفية مكتملة، إنها صنفية بإسم TPerson ومشتقة من صنفية أخرى إسمها TObject التي تعد أصل جميع الصنفيات في دلفي.

في دلفي كل صنفية Class لابد أن تكون مشتقة من صنفية أخرى، وقد اخترنا TObject لتكون أساس الاشتقاق لصنفيتنا TPerson.

الآن سوف نرى كيفية استخدام هذه الصنفية.

procedure TForm1.Button1Click(Sender: TObject);
var
  Person: TPerson;
begin

  Person := TPerson.Create;

  Person.FirstName := 'Ahmad';
  Person.LastName := 'Hamza';
  Person.BirthDate := EncodeDate(1980, 3, 15);
  
  ShowPerson(Person);

  Person.Free;
end;

لقد قمنا أولا ببث الروح في المتغير Person بخلق الكائن الذي سيمثله من خلال المنهاج Create ، وقد عبرنا عنه بالأمر: Person := TPerson.Create .

المتغير Person قبل استخدامه هو مجرد جسم ميت مشكل بحسب القالب TPerson و عندما يتم إنشائه أو إحيائه بأمر Create يصير كائنا Object حيا في الذاكرة، وجاهزا للإستخدام.

عند الإنتهاء من استخدامه، و انتفاء الحاجة إليه، يجيب علينا أن ننهيه و نميته من خلال المنهاج Free، كالتالي: Person.Free

إضافة العمليات على الصنفية

الصنفية كما عرفناها حتى الآن تحوي بيانات فقط. والتي تعبر عنها الحقول التي داخل الصنفية مثل FirstName، الآن ماذا لو احتوت الصنفية على عمليات Operation أو بمصطلح آخر إجرائيات. لنجرب.

داخل الصنفية و بعد تعريف الحقول نقوم بتعريف الإجرائية أو الدالة GetFullName كالتالي:

  TPerson = class(TObject)
    FirstName: string;
    LastName: string;
    BirthDate: TDateTime;
    function GetFullName: string;
  end;

بعد كتابة تعريف الإجرائية بالكامل، تأكد من أن مؤشر الكتابة على سطر هذا التعريف وباستخدام لوحة المفاتيح قم بإصدار الأمر Ctrl + Shift + C . سوف تقوم دلفي ببناء جسم هذه الأجرائية. ثم نضع فيها الكود اللازم لإعطائنا إسم الفرد بالكامل.

جسم الإجرائية أو ال function سيكون كالتالي:

{ TPerson }

function TPerson.GetFullName: string;
begin
  result := FirstName + ' ' + LastName;
end;

نقوم بتعديل إجرائية العرض لدينا كي نستفيد من الدالة الجديدة.

procedure TForm1.ShowPerson(P: TPerson);
begin
  Canvas.TextOut(10, 10, P.FirstName);
  Canvas.TextOut(10, 30, P.LastName);
  Canvas.TextOut(10, 50, DateToStr(P.BirthDate));

  Canvas.TextOut(10, 80, P.GetFullName);
end;

ماذا لدينا الآن

لدينا الآن صنفية class إسمها TPerson ، هذه الصنفية تحوي حقول لبيانات، كما تحوي على عمليات (بالأحرى عملية واحدة). العملية ممثلة في الدالة التي إسمها GetFullName . لاحظ إن جسم الدالة خارج جسم الصنفية ولكنها تتبعها، و الدليل على ذلك إن إسم الدالة يكون مسبوقا بإسم الصنفية كالتالي:

function TPerson.GetFullName: string;

الصنفية بوضعها الحالي تعتبر صندوقا مغلقا. لماذا، لأن حقول الصنفية و بياناتها، و كذلك إجرائياتها لايمكن الوصول إليها إلا من خلال كائن ينبعث منها. أي أننا لا نستطيع أن تستخدم حقولها أو نستدعي إجرائياتها مباشرة؛ بل يجب أولا أن نقوم بتعريف متغير من نفس نوع الصنفية، ثم نقوم بخلق هذا المتغير من خلال الأمر Create ليصبح كائنا object ، عندها فقط وعبر هذا الكائن نستطيع تلمس حقول الصنفية واستدعاء اجرائياتها.

وقفة تأملية

ماهي الصنفية Class و ماهو الكائن Object وما العلاقة بينهما.

الصنفية class يمكن اعتبارها قالب، هذا القالب مشكل بحيث يحوي العناصر الي صممت له مثل البيانات (المتغيرات و الخصائص) والعمليات (مثل الإجرائيات و الدوال و المنهاجيات).

المتغير من نوع الصنفية هو جسم مستخرج من هذا القالب بعد صبه فيه. بحيث يكون شكله شكل القالب، ولكنه جسم ميت لا حياة فيه. مثل المتغير Person قبل خلقه.

الكائن Object هو الجسم السابق المستخرج من القالب لكن بعد بث الحياة فيه وخلقه من خلال Create.

خواطر وتساؤلات

ماذا أيضا

إذا فهمت ما تم طرحه في هذه الجولة، أو إذا أصابتك ربكة أو دوخة بسيطة، أو إذا أحسست انك فهمت ولكن جزء من عقلك لايريد أن يفهم ولا يريد أن يرى الأمور بهذا المنظار الجديد؛ فتهانينا .. أنت على المسار الصحيح !!

الآن أقترح أن نتمرن قليلا على استخدام الصنفية، قم بإنشاء متغيرات أخرى من نفس نوع الصنفية TPerson، وسمّها مثلا: Person1 و Person2 ، أضف حقول أخرى على الصنفية، أضف إجرائيات ودوال أخرى، وحاول استدعائها. قم أيضا بإنشاء صنفيات أخرى، و طبق عليها اختباراتك.

نحن الآن كشفنا جزءا بسيطا من الغطاء حول مفاهيم المنحى للكائن. مفاهيم المنحى للكائن عميقة ومتشعبة، و يستحسن أن يتم الخوض فيها برفق وهدوء، فلا يتم التعمق فيها إلا بعد استيعاب ما تم فهمه وممارسته لأكثر من مرة.

في الجولة القادمة سوف نطور من الصنفية TPerson ليصبح لديها خصائص properties ومنهاجيات methods منظمة بطريقة أكثر احترافية.

الجولة الرابعة

في هذه الجولة سنغوص أكثر في مفاهيم المنحى للكائن، ونرقى بالصنفية TPerson إلى مستويات أعلى.

المنظورية

ننظر الآن إلى موضوع المنظورية visibility أو مجال الرؤية scope بالنسبة لعناصر الصنفية. أي ماهي العناصر داخل الصنفية التي يمكن للعالم الخارجي أن يراها و ينفذ إليها، و تلك التي تكون محجوبة عنه.

ماذا لو أضفنا داخل الصنفية كلمة private قبل المتغيرات مثل التالي:

  TPerson = class(TObject)
  private
    FirstName: string;
    LastName: string;
    BirthDate: TDateTime;
    function GetFullName: string;
  end;

ثم نجرب برنامجنا. سنلاحظ أن دلفي أعطتنا رسالة خطأ عند التعليمة Person.FirstName ، لماذا؟ لأن الكائن Person بعد هذا التغيير لا يستطيع أن يرى الخصائص والإجرائيات التي تم وسمها بكلمة private في الصنفية.

الوسم private تعني خاص أي خاص بالصنفية فقط، و بالتالي فإن أية تعريفات يتم سردها تحت قسم private لايمكن النفاذ إليها إلا فقط من قبل الاجرائيات التابعة للصنفية ذاتها.

نفس الأمر لو استخدمنا الوسم protected ومعناها محمي، التعريفات تحت هذا الوسم لايمكن النفاذ إليها أو رؤيتها إلا من قبل الصنفية ذاتها أو صنفية أخرى مشتقة منها (سنناقش الاشتقاق لاحقا).

  TPerson = class(TObject)
  protected
    FirstName: string;
    ---------

أما إذا وضعنا الوسم public وتعني عمومي أو عام، فإن كل ما هو تحت هذا القسم يكون مرئيا ومتاحا للعالم خارج الصنفية.

  TPerson = class(TObject)
  public
    FirstName: string;
    ------

وإذا لم نقم بتحديد منظورية عناصر الصنفية، فإن دلفي افتراضيا تجعلها عمومية ومتاحة.

الخصائص

سنقوم الآن بتحويل المتغيرات في الصنفية إلى خصائص properties. و لكن قبل أن تقوم بذلك، أقترح أن يتم إعادة ترتيب الصنفية وفق التالي:

  TPerson = class(TObject)
  private
    LastName: string;
    BirthDate: TDateTime;
  public
    FirstName: string;
    function GetFullName: string;
  end;

أي نجعل جميع المتغيرات بالصنفية تحت قسم private ما عدا المتغير FirstName يكون تحت قسم public.

أقترح أيضا أن يتم حفظ الملف uOOP للإحتياط.

نبدأ بتحويل المتغير FirstName إلى خاصية، وذلك بكتابة التعريف property قبلها كالتالي:

  public
    property FirstName: string;

الآن، نضع مؤشر الكتابة على سطر تعريف هذه الخاصية و نعطي الأمر Ctrl+Shift+C وسيقوم محرر دلفي آليا بتكملة التعريف وتوليد الكود اللازم. شكل الصنفية سيكون كالتالي:

  TPerson = class(TObject)
  private
    LastName: string;
    BirthDate: TDateTime;
    FFirstName: string;
    procedure SetFirstName(const Value: string);
  public
    property FirstName: string read FFirstName write SetFirstName;
    function GetFullName: string;
  end;
procedure TPerson.SetFirstName(const Value: string);
begin
  FFirstName := Value;
end;

لقد قام المحرر بتوليد تعليمات إضافية آليا والتي تمثلت في الآتي:

إذا وجدت كلامي السابق غامضا أو مبهما ، فلا تقلق، وسيتم توضيحه أكثر بمزيد من الأمثلة.

يمكن الآن تكرار العملية السابقة مع باقي المتغيرات، ننقل تعريف المتغيرات LastName و BirthDate إلى قسم public ، ثم نضع مؤشر الكثابة على سطر إحدى الخصائص وتنفيذ الأمر Ctrl+Shift+C ليقوم المحرر بتوليد التعليمات المتعلقة بهذه الخصائص.

شكل الصنفية سيكون كالتالي:

  TPerson = class(TObject)
  private
    FFirstName: string;
    FLastName: string;
    FBirthDate: TDateTime;

    procedure SetBirthDate(const Value: TDateTime);
    procedure SetFirstName(const Value: string);
    procedure SetLastName(const Value: string);
  public
    property FirstName: string read FFirstName write SetFirstName;
    property LastName: string read FLastName write SetLastName;
    property BirthDate: TDateTime read FBirthDate write SetBirthDate;
    
    function GetFullName: string;
  end;

وهذه مجموعة ال Setters التابعة لهذه الخصائص:

procedure TPerson.SetFirstName(const Value: string);
begin
  FFirstName := Value;
end;

procedure TPerson.SetLastName(const Value: string);
begin
  FLastName := Value;
end;

procedure TPerson.SetBirthDate(const Value: TDateTime);
begin
  FBirthDate := Value;
end;

نقوم بتجربة البرامج ، وأرجو أن تكون الأمور على مايرام.

إضافة خاصية جديدة

الآن نقوم بتحديد خاصية جديدة. الخاصية ستكون بإسم Age أي العمر، وغرضنا منها أن تخبرنا بعمر الفرد من واقع معلومة تاريخ الميلاد. نريد أيضا من الخاصية أن تكون للقراءة فقط، أي أنها تعطي عمر الفرد فقط ولا تقبل أن يتم تحديد قيمة هذا العمر من خارج الصنفية.

  public
    ......... 
    .........
    property Age: integer read GetAge;

لاحظ أنه في تعريف الخاصية حددنا read فقط و لم نحدد write .

إذن حساب العمر سيكون من الدالة التي أسميناها GetAge كما هو واضح أعلاه. يمكننا تعريف هذه الدالة وبناء جسمها يدويا، أو نترك محرر دلفي يقوم بهذه العملية.

تعريف الدالة GetAge سيكون تحت قسم private كالتالي:

  private
    ......... 
    .........
    function GetAge: integer;

أما جسم الدالة GetAge سيكون كالتالي:

function TPerson.GetAge: integer;
begin
  result := Round(YearSpan(Now, BirthDate));
end;

الدالة تقوم بإعطاء العمر بصورة تقريبية، باستخدام الدالة YearSpan . (يجب إضافة DateUtils إلى قسم uses).

لاحظ أن الدالة GetAge معرفة في قسم private وبالتالي فإنها لن تكون مرئية للعالم الخارجي.

الآن لنستخدم هذه الخاصية الجديدة في شاشة العرض لدينا.

procedure TForm1.ShowPerson(P: TPerson);
begin
  Canvas.TextOut(10, 10, P.FirstName);
  Canvas.TextOut(10, 30, P.LastName);
  Canvas.TextOut(10, 50, DateToStr(P.BirthDate));

  Canvas.TextOut(10, 80, P.GetFullName);
  Canvas.TextOut(10, 100, IntToStr(P.Age));
end;

لنرى الآن شكل شاشة برنامجنا

المزيد من التوضيح

عندما كانت FirstName مجرد متغير في الصنفية، يمكن لأي مستخدم للصنفية أن يقرأ قيمتها أو يكتب لها قيمة جديدة دون قيود. فيقول Caption := Person.FirstName أو يقول Person.FirstName := 'Ali'.

بعدما حولنا هذا المتغير إلى خاصية property، أصبح لدينا القدرة لأن نحدد قيودا تحكم عمليات القراءة والكتابة لهذه الخاصية. كيف ذلك؟ الخاصية تعطينا الخيارات التالية للتحكم:

أي مستخدم للصنفية عندما يريد معرفة قيمة الخاصية أي قرائتها فإن الصنفية تعطيه القيمة حسب التالي:

property FirstName: string read FFirstName;

الخاصية تعطيه القيمة الموجودة في المتغير FFirstName.
property FirstName: string read GetFirstName;

الخاصية تعطيه القيمة الناتجة عن دالة إسمها GetFirsName 
(لم نحددها في مثالنا السابق، ولكن في الخاصية Age حددنا دالة تعطي القيمة) 

أي مستخدم للصنفية عندما يريد أن يحدد قيمة الخاصية أي كتابتها فإن الصنفية تسمح بذلك حسب التالي:

property FirstName: string read FFirstName write FFirstName;

الخاصية تأخذ القيمة وتضعها مباشرة في المتغير FFirstName 
property FirstName: string read FFirstName write SetFirstName;

الخاصية تأخذ القيمة وتمررها أولا إلى الإجرائية SetFirstName 
التي ستقرر ماذا ستفعل بها.
property FirstName: string read FFirstName;

الخاصية لا تقبل أية قيمة لها، أي أنها للقراءة فقط read only ترفض أية قيمة تعطى لها.
property FirstName: string read GetFirstName write SetFirstName;

عند القراءة تعطي ناتج الدالة GetFirstName 
وعند الكتابة تأخذ القيمة وتمررها إلى الإجرائية SetFirstName.

إذن هذه هي خيارات تحديد عمليات القراءة و الكتابة للخاصية، أي كيفية قراءة قيمة الخاصية وكيفية تحديدها. عمليات القراءة و الكتابة للخاصية تسمى Getters و Setters .

عندما نطلب من محرر دلفي إكمال بناء كود الخاصية بواسطة الأمر Ctrl+Shift+C يقوم بتوصيف ال Getter على أنه متغير من نفس نوع الخاصية مثل FFirstName، ويقوم بتوصيف ال Setter على أنه إجرائية بمحدد من نفس النوع مثل SetFirsName. هذه هي الوضعية السائدة في أغلب الأحيان، إذا أردت وضعية أخرى يمكنك تغيير ذلك.

في ال Setter مثل إجرائية SetFirstName نرى أن الإجرائية توجد بها تعليمة واحدة و هي :

FFirstName := Value و هي التعليمة الوحيدة التي يضعها محرر دلفي، و التي تحتاجها الخاصية لمعرفة قيمتها.

ملاحظة

هل لاحظتم أخوتي أن المتغيرات التابعة للخصائص مثل FFirsName و FLastName تبدأ بحرف F ؟ طبعا نستطيع أن نسمي هذا المتغيرات بالطريقة التي نريدها، ولكن وكعرف عام أيضا في دلفي؛ تكون هذه المتغيرات مسبوقة بحرف F للدلالة على أنها Field أي حقل داخل الصنفية.

شيء في الحلق

يوجد شيء في حلقي يزعجني منذ بدئنا للجولة الثالثة. ولم أشأ أن أشير إليه حتى لا أشوش أو أربك مسار تفكيرنا وفهمنا للأمور، و لكن الآن و بعد أن استوفينا جولتنا الرابعة، أظن أن الوقت قد حان لأن أفصح عنه.

تذكرون سادتي أنه عندما قمنا بإنشاء أو خلق الكائن Person فعلنا ذلك من خلال التعليمة:

Person := TPerson.Create;

وكما تذكرون أيضا، فقد أشرنا إلى أن الكائن الذي ينشأ يجب أن يتم إتلافه بعد الانتهاء من استخدامه.

OK، كقاعدة ذهبية : عند خلق أي كائن يجب أن يغلّف الكود الخاص بالتعامل مع هذا الكائن ضمن حائط try…finally وذلك كالتالي:

  Person := TPerson.Create;

  try
    Person.FirstName := 'Ahmad';
    Person.LastName := 'Hamza';
    Person.BirthDate := EncodeDate(1980, 3, 15);

    ShowPerson(Person);

  finally
    Person.Free;
  end;

بهذا نضمن أنه إذا حدث أي خطأ عند استخدامنا للكائن Person فإن برنامجنا لن يقفز من مكان الخطأ إلى خارج الإجرائية؛ بل يستمر ويذهب إلى التعليمات تحت قسم finally ليقوم بإزالة هذا الكائن ويحرر الذاكرة منه. سواء حدث خطأ أم لم يحدث.

شيء من الفلسفة

بعد أن أخذنا و بطريقة برمجية فكرة عن مفاهيم المنحى للكائن، لنسرد هنا بعضا من المصطلحات الخاصة بهذا المجال. ولتعذرني أخي القارئ فسوف يكون في حديثنا بعض الفلسفة، فأرجو منك أن تخلع T-Shirt البرمجة و تلبس عباءة الفلسفة، و تحمل معي قليلا.

Entity

بمعنى كينونة، أو كيان أو وجود ويقصد به أي شيء في غير عالم البرمجة سواء كان هذا الشيء مادي أو معنوي. في مثالنا السابق كان الأستاذ "أحمد حمزة" هو إنسان بشحمه و لحمه يأكل و يتنفس و يمشي في الأسواق، مثله مثل غيره ممن هو إنسان، إذن كلمة إنسان تعبر عن مفهوم تجريدي يجمع كل من هو بني آدم ، فيمكن القول أن "أحمد حمزة" هو entity كينونة، وأن إنسان كينونة أيضا لكن بمفهوم تجريدي أعلى.

Class

بمعنى صنفية، وهي إطار تجريدي يضم الكينونات التي نرى أنها تتشابه في سماتها و تصرفاتها، الأخ "أحمد حمزة" رأيناه يشبه في شكله و تصرفاته أفرادا آخرين مثل "علي" و "إسماعيل"، عليه يمكن أن نضمهم تحت مسمى واحد و هو إنسان أو شخص، فأحمد إنسان وعلي إنسان وإسماعيل كذلك، هذا المُسمّى الموحد الذي يجمع بينهم نحدده أكثر برمجيا ونقول عنه صنفية class ونسمي هذه الصنفية TPerson، و نحدد في هذه الصنفية العناصر التي نرى أنها تجمع بين هؤلاء الكينونات أو التي تشكل مفهوم إنسان، في مثالنا السابق كانت رؤيتنا ضيقة، وحددنا فقط ثلاثة أو أربعة عناصر تجمع بين هؤلاء الأفراد أو يضمها مفهوم إنسان مثل FirstName و LastName ، ووضعناها داخل هذه الصنفية.

Object

وتعني كائن أو ماهية، و الكائن هنا هو وجود برمجي، فبعد ما رسمنا إطارا لصنفية TPerson ، نقوم باستحضار أو تجسيد كائن برمجي من نفس الفصيلة أو الصنفية فنقول Person := TPerson.Create ثم حددنا أن هذا الكائن البرمجي إسمه الأول “أحمد” وإسمه الثاني "حمزة". بحسب الخصائص أو العناصر التي أطرناها في الصنفية. الكائن Person الذي إسمه الأول "أحمد" وإسمه الثاني "حمزة" هو كائن برمجي، من فصيلة أو صنفية TPerson يعيش في عالمنا البرمجي أي برنامجنا، وأردنا به أن يمثل السيد "أحمد حمزة" الحقيقي الذي يعيش في أرض الواقع.

الجولة القادمة

الجولات القادمة ستكون في الجزء الثاني من هذه المقالة إن شاء الله، و فيها سنتوسع في الحديث عن الصنفيات، من ذلك كيفية إنشاء صنفية مشتقة من صنفية أخرى، وخيارات لإستخدام الخصائص والمناهج، كذلك كيفية حفظ واستراجاع البيانات الخاصة بالكائنات في برنامجنا.

أخذ و رد

يمكنكم إخوتي الكرام طرح أية استفسارات تخص ماورد في هذا المقالة لمناقشتها سويا قي قسم التعليقات أدناه.

إذا أحسستم أثناء قرائتكم لهذا المقال بوجود أي غموض أو لبس أو عدم وضوح كاف، أو إطالة زائدة، يرجى الإشارة إلى ذلك، و تحديد الموضوع أو الجزء الذي يعاني من أي عيب حتى يتم تعديله و تحسينه. أيضا إذا كان لديكم أي اقتراح أو ملاحظة ستسهم في زيادة توضيح المفاهيم التي طرحت في هذه المقالة فلا تترددون في طرحها.

أيضا إذا وجدتم أية أخطاء ضمن طرحنا لمفاهيم المنحى للكائن؛ فإني أرجوكم أن تقوموا بالتنبيه إليها حتى يتم تصحيحها فورا.

مرفق مع المقالة الكود البرمجي الذي استخدمناه في هذه المقالة مقسم حسب كل جولة.

الملفات المصدرية للمشروع

المنحى للكائن في صور

TPerson = Class

الصنفية TPerson عبارة عن قالب

var
  Person: TPerson
المتغير Person من نوع الصنفية TPerson جسم ميت
Person := TPerson.Create
Person صار كائنا حيا Object له مكان خاص به في الذاكرة
Person.FirstName := ‘xxxx’
الكائن Person بعد مزيد من التحديد لخصائصه
Person.Free
الكائن Person بعد إماتـته وعودته لمجرد متغير