من المترجم
هناك سببان وراء تعهدي بترجمة العديد من المواد على الإطار الذي تم تطويره منذ عشرين عامًا لبيئة البرمجة غير الشائعة:
1. قبل بضع سنوات ، بعد أن تعلمت الكثير من مسرات العمل مع Entity Framework باعتباره ORM لمنصة .Net ، بحثت عبثًا عن نظائرها الخاصة ببيئة Lazarus ، وبصفة عامة عن freepascal.
والمثير للدهشة ، أن ORMs جيدة مفقودة لها. كل ما تم العثور عليه آنذاك هو مشروع مفتوح المصدر يسمى
tiOPF ، تم تطويره في أواخر التسعينيات من أجل دلفي ، وتم نقله لاحقًا إلى freepascal. ومع ذلك ، فإن هذا الإطار يختلف اختلافًا جذريًا عن الشكل المعتاد للأجواء الكبيرة الكثيفة.
لا توجد طرق مرئية لتصميم الكائنات (في الكيان - النموذج أولاً) وتعيين الكائنات إلى الحقول في جداول قاعدة البيانات العلائقية (في الكيان - قاعدة البيانات أولاً) في tiOPF. يضع المطور نفسه هذه الحقيقة كواحد من أوجه القصور في المشروع ، ومع ذلك ، كميزة ، فهو يقدم توجيهاً كاملاً على وجه التحديد لنموذج أعمال الكائن ، إنه يستحق فقط رمز ثابت ...
لقد كانت لدي مشكلة على مستوى القرص الثابت المقترح. في ذلك الوقت ، لم أكن على دراية جيدة بهذه النماذج والأساليب التي استخدمها مطور الإطار بالكامل والمذكورة في الوثائق عدة مرات لكل فقرة (أنماط التصميم للزائر ، الرابط ، المراقب ، عدة مستويات من التجريد لاستقلال قواعد البيانات ، وما إلى ذلك. .). كان مشروعي الكبير الذي كان يعمل مع قاعدة البيانات في ذلك الوقت يركز بالكامل على المكونات المرئية للعازر وطريقة العمل مع قواعد البيانات التي توفرها البيئة البصرية ، ونتيجة لذلك ، أطنان من الكود نفسه: ثلاثة جداول في قاعدة البيانات نفسها مع نفس البنية والبيانات المتجانسة تقريبًا ، ثلاثة أشكال متطابقة للعرض ، وثلاثة أشكال مماثلة للتحرير ، وثلاثة أشكال متطابقة للتقارير ، وكل شيء آخر من الجزء العلوي من العنوان "كيف لا يتم تصميم البرامج".
بعد قراءة ما يكفي من الكتابات حول مبادئ التصميم الصحيح لقواعد البيانات وأنظمة المعلومات ، بما في ذلك دراسة القوالب ، بالإضافة إلى التعرف على إطار عمل الكيان ، قررت أن أقوم بعملية إعادة هيكلة كاملة لكل من قاعدة البيانات نفسها وتطبيقي. وإذا تعاملت مع المهمة الأولى تمامًا ، فعند تنفيذ المهمة الثانية ، كان هناك طريقان يسيران في اتجاهين مختلفين: إما الذهاب تمامًا إلى الدراسة .net و C # و Entity Framework ، أو العثور على ORM المناسب لنظام Lazarus المألوف. كان هناك أيضًا مسار ثالث غير واضح للدراجات - لكتابة ORM ليناسب احتياجاتك بنفسك ، لكن هذا ليس هو الهدف الآن.
لم يتم التعليق على الكود المصدري للإطار ، لكن المطورين أعدوا (على ما يبدو في الفترة الأولية للتطوير) قدرا معينا من الوثائق. كل هذا ، بطبيعة الحال ، يتحدث الإنجليزية ، وتُظهر التجربة أنه على الرغم من وفرة الكود والرسوم البيانية وعبارات برمجة القوالب ، فإن العديد من المبرمجين الناطقين بالروسية لا يزالون يتوجهون بشكل سيء في وثائق اللغة الإنجليزية. ليس دائمًا وليس لدى الجميع رغبة في تدريب القدرة على فهم النص التقني باللغة الإنجليزية دون الحاجة إلى أن يترجمه العقل إلى اللغة الروسية.
بالإضافة إلى ذلك ، فإن التدقيق اللغوي المتكرر للنص للترجمة يتيح لك رؤية ما فاتني عندما التقيت بالوثائق لأول مرة ، ولم أفهمها تمامًا أو غير صحيح. وهذا هو ، وهذا هو لنفسه فرصة لتعلم أفضل إطار قيد الدراسة.
2. في الوثائق ، يتخطى المؤلف عمداً أو لا يقصد بعض الكود ، ربما يكون واضحاً في رأيه. نظرًا لتقييد كتابتها ، تستخدم الوثائق آليات وكائنات عفا عليها الزمن كأمثلة ، تم حذفها أو لم تعد تستخدم في الإصدارات الجديدة من الإطار (لكن لم أقل أنها استمرت في التطور؟). أيضًا ، عندما كررت الأمثلة المطورة بنفسي ، وجدت بعض الأخطاء التي يجب إصلاحها. لذلك ، في الأماكن سمحت لنفسي ليس فقط بترجمة النص ، ولكن أيضًا لاستكماله أو مراجعته بحيث يظل ذا صلة ، وكانت الأمثلة ناجحة.
أريد أن أبدأ ترجمة المواد من مقال بيتر هنريكسون حول "الحوت" الأول الذي يقف عليه الإطار بأكمله - نموذج الزائر.
النص الأصلي نشر هنا .
قالب الزائر و tiOPF
الغرض من هذه المقالة هو تقديم قالب الزائر ، والذي يعد استخدامه أحد المفاهيم الرئيسية لإطار tiOPF (إطار عمل استمرارية كائن TechInsite). سننظر في المشكلة بالتفصيل ، بعد تحليل الحلول البديلة قبل استخدام الزائر. في عملية تطوير مفهوم الزائر الخاص بنا ، سنواجه تحديًا آخر: الحاجة إلى التكرار من خلال جميع الكائنات الموجودة في المجموعة. وسيتم أيضا دراسة هذه المسألة.
تتمثل المهمة الرئيسية في التوصل إلى طريقة معممة لتنفيذ مجموعة من الطرق ذات الصلة على بعض الكائنات في المجموعة. قد تختلف الطرق المنجزة اعتمادًا على الحالة الداخلية للكائنات. لا يمكننا تنفيذ الأساليب على الإطلاق ، ولكن يمكننا تنفيذ العديد من الطرق على نفس الكائنات.
المستوى اللازم من التدريب
يجب أن يكون القارئ معتادًا على كائن pascal ويتقن المبادئ الأساسية للبرمجة الموجهة للكائنات.
مثال مهمة العمل في هذه المقالة
على سبيل المثال ، سنقوم بتطوير دفتر عناوين يتيح لك إنشاء سجلات للأشخاص ومعلومات الاتصال الخاصة بهم. مع الزيادة في طرق الاتصال الممكنة بين الأشخاص ، يجب أن يسمح لك التطبيق بمرونة بإضافة مثل هذه الأساليب دون معالجة رمز مهمة (أتذكر أنه بمجرد الانتهاء من معالجة الرمز لإضافة رقم هاتف ، احتجت على الفور إلى معالجته مرة أخرى لإضافة البريد الإلكتروني). نحتاج إلى تقديم فئتين من العناوين: حقيقية ، مثل عنوان المنزل والبريد والعمل والإلكترونية: الهاتف الثابت والفاكس والجوال والبريد الإلكتروني والموقع الإلكتروني.
على مستوى العرض التقديمي ، يجب أن يبدو تطبيقنا مثل Explorer / Outlook ، أي ، من المفترض أن يستخدم المكونات القياسية مثل TreeView و ListView. يجب أن يعمل التطبيق بسرعة ولا يعطي انطباعًا عن برنامج الخادم العميل الضخم.
قد يبدو تطبيق ما يشبه هذا:

في قائمة سياق الشجرة ، يمكنك اختيار إضافة / إزالة جهة اتصال شخص أو شركة ، والنقر بزر الماوس الأيمن على قائمة بيانات جهة الاتصال لاستدعاء مربع حوار التحرير أو حذف البيانات أو إضافتها.
يمكن حفظ البيانات بأشكال مختلفة ، وفي المستقبل سننظر في كيفية استخدام هذا القالب لتنفيذ هذه الميزة.
قبل أن تبدأ
سنبدأ بمجموعة بسيطة من الكائنات - قائمة بالأشخاص الذين لديهم بدورهم خاصيتان - الاسم (الاسم) والعنوان (EmailAdrs). بادئ ذي بدء ، سيتم ملء القائمة بالبيانات في المنشئ ، وبعد ذلك سيتم تحميلها من ملف أو قاعدة بيانات. بالطبع ، هذا مثال مبسط للغاية ، لكنه يكفي لتنفيذ قالب الزائر بالكامل.
قم بإنشاء تطبيق جديد وإضافة فئتين من قسم واجهة الوحدة الرئيسية: TPersonList (الموروثة من TObjectList ويتطلب مكونًا إضافيًا في الوحدة النمطية contnrs) و TPerson (الموروثة من TObject):
TPersonList = class(TObjectList) public constructor Create; end; TPerson = class(TObject) private FEMailAdrs: string; FName: string; public property Name: string read FName write FName; property EMailAdrs: string read FEMailAdrs write FEMailAdrs; end;
في مُنشئ TPersonList ، نقوم بإنشاء ثلاثة كائنات TPerson ونضيفها إلى القائمة:
constructor TPersonList.Create; var lData: TPerson; begin inherited; lData := TPerson.Create; lData.Name := 'Malcolm Groves'; lData.EMailAdrs := 'malcolm@dontspamme.com';
أولاً ، سنتطرق إلى القائمة وننفذ عمليتين في كل عنصر من عناصر القائمة. العمليات متشابهة ، ولكنها ليست متشابهة: استدعاء ShowMessage بسيط لعرض محتويات خصائص الاسم و EmailAdrs لكائنات TPerson. أضف زرين إلى النموذج واسميهما شيئًا مثل هذا:

في النطاق المفضل لنموذجك ، يجب عليك أيضًا إضافة خاصية (أو مجرد حقل) FPersonList من النوع TPersonList (إذا تم الإعلان عن النوع أسفل النموذج ، إما تغيير الترتيب أو إصدار إعلان نوع أولي) ، واتصل بالمنشئ في معالج الأحداث onCreate:
FPersonList := TPersonList.Create;
لتحرير الذاكرة بشكل صحيح في معالج أحداث onClose للنموذج ، يجب إتلاف هذا الكائن:
FPersonList.Free.
الخطوة 1. التكرار الثابت
لإظهار أسماء من كائنات TPerson ، أضف التعليمات البرمجية التالية إلى معالج أحداث onClick للزر الأول:
procedure TForm1.Button1Click(Sender: TObject); var i: integer; begin for i := 0 to FPersonList.Count - 1 do ShowMessage(TPerson(FPersonList.Items[i]).Name); end;
بالنسبة للزر الثاني ، سيكون رمز المعالج كما يلي:
procedure TForm1.Button2Click(Sender: TObject); var i: integer; begin for i := 0 to FPersonList.Count - 1 do ShowMessage(TPerson(FPersonList.Items[i]).EMailAdrs); end;
فيما يلي المياه الضحلة لهذا الرمز:
- طريقتين التي تفعل الشيء نفسه تقريبا. كل الاختلاف هو فقط في اسم خاصية الكائن الذي تظهره ؛
- سيكون التكرار مملاً ، خاصةً عندما تضطر إلى كتابة حلقة مماثلة في مائة مكان في الكود ؛
- يلقي الصعب من TPerson محفوفة مواقف استثنائية. ماذا لو كان هناك مثيل لـ TAnimal في القائمة بدون خاصية عنوان؟ لا توجد آلية لإيقاف الخطأ والدفاع ضده في هذا الرمز.
دعنا نتعرف على كيفية تحسين الكود عن طريق إدخال التجريد: نقوم بتمرير كود التكرار إلى الفصل الأصل.
الخطوة 2. خلاصة التكرار
لذلك ، نريد نقل منطق التكرار إلى الفئة الأساسية. قائمة التكرار نفسها بسيطة للغاية:
for i := 0 to FList.Count - 1 do
يبدو أننا نخطط لاستخدام قالب
Iterator . من الكتاب الموجود في كتاب
أنماط تصميم Gang-of-Four ، من المعروف أن Iterator يمكن أن يكون خارجيًا وداخليًا. عند استخدام مكرر خارجي ، يتحكم العميل بشكل صريح في الاجتياز عن طريق استدعاء الطريقة التالية (على سبيل المثال ، يتم التحكم في تعداد عناصر TCollection بواسطة الأساليب الأولى ، التالية ، الأخيرة). سنستخدم المكرر الداخلي هنا ، لأنه من الأسهل تطبيق اجتياز الأشجار بمساعدته ، وهو هدفنا. سنضيف طريقة Iterate إلى فئة قائمتنا وسنمرر طريقة رد الاتصال بها ، والتي يجب إجراؤها على كل عنصر من عناصر القائمة. يتم الإعلان عن رد الاتصال في كائن pascal كنوع إجرائي ، سيكون لدينا ، على سبيل المثال ، TDoSomethingToAPerson.
لذلك ، نعلن عن نوع إجرائي TDoSomethingToAPerson ، والذي يأخذ معلمة واحدة من النوع TPerson. يتيح لك النوع الإجرائي استخدام الطريقة كمعلمة لطريقة أخرى ، أي تطبيق رد الاتصال. بهذه الطريقة ، سنقوم بإنشاء طريقتين ، واحدة منها ستظهر خاصية اسم الكائن ، والآخر - خاصية EmailAdrs ، وسيتم تمريرها هي نفسها كمعلمة إلى التكرار العام. أخيرًا ، يجب أن يكون قسم إعلان النوع كما يلي:
TPerson = class(TObject) private FEMailAdrs: string; FName: string; public property Name: string read FName write FName; property EMailAdrs: string read FEMailAdrs write FEMailAdrs; end; TDoSomethingToAPerson = procedure(const pData: TPerson) of object; TPersonList = class(TObjectList) public constructor Create; procedure DoSomething(pMethod: TDoSomethingToAPerson); end; DoSomething: procedure TPersonList.DoSomething(pMethod: TDoSomethingToAPerson); var i: integer; begin for i := 0 to Count - 1 do pMethod(TPerson(Items[i])); end;
الآن ، لتنفيذ الإجراءات اللازمة على عناصر القائمة ، نحتاج إلى القيام بأمرين. أولاً ، قم بتحديد العمليات اللازمة باستخدام الأساليب التي تحتوي على التوقيع المحدد بواسطة TDoSomethingToAPerson ، وثانياً ، قم باستدعاء مكالمات DoSomething مع المؤشرات التي تم تمريرها إلى هذه الطرق كمعلمة. في قسم وصف النموذج ، أضف إعلانين:
private FPersonList: TPersonList; procedure DoShowName(const pData: TPerson); procedure DoShowEmail(const pData: TPerson);
في تنفيذ هذه الأساليب ، نشير إلى:
procedure TForm1.DoShowName(const pData: TPerson); begin ShowMessage(pData.Name); end; procedure TForm1.DoShowEmail(const pData: TPerson); begin ShowMessage(pData.EMailAdrs); end;
يتم تغيير رمز معالجات الأزرار كما يلي:
procedure TForm1.Button1Click(Sender: TObject); begin FPersonList.DoSomething(@DoShowName); end; procedure TForm1.Button2Click(Sender: TObject); begin FPersonList.DoSomething(@DoShowEmail); end;
بالفعل أفضل. لدينا الآن ثلاثة مستويات من التجريد في الكود. التكرار العام هو طريقة لفئة تنفذ مجموعة من الكائنات. يتم وضع منطق الأعمال (حتى الآن مجرد إخراج رسالة لا تنتهي من خلال ShowMessage) بشكل منفصل. في مستوى العرض التقديمي (الواجهة الرسومية) ، يتم استدعاء منطق العمل في سطر واحد.
من السهل أن نتخيل كيف يمكن استبدال مكالمة ShowMessage برمز يحفظ بياناتنا من TPerson في قاعدة بيانات علائقية باستخدام استعلام SQL للكائن TQuery. على سبيل المثال ، مثل هذا:
procedure TForm1.SavePerson(const pData: TPerson); var lQuery: TQuery; begin lQuery := TQuery.Create(nil); try lQuery.SQL.Text := 'insert into people values (:Name, :EMailAdrs)'; lQuery.ParamByName('Name').AsString := pData.Name; lQuery.ParamByName('EMailAdrs').AsString := pData.EMailAdrs; lQuery.Datababase := gAppDatabase; lQuery.ExecSQL; finally lQuery.Free; end; end;
بالمناسبة ، يقدم هذا مشكلة جديدة في الحفاظ على اتصال بقاعدة البيانات. في طلبنا ، يتم إجراء الاتصال بقاعدة البيانات من خلال بعض كائنات gAppDatabase العالمية. ولكن أين سيكون موقعها وكيف تعمل؟ بالإضافة إلى ذلك ، نحن نعذّب في كل خطوة من خطوات التكرار لإنشاء كائنات TQuery ، وتكوين الاتصال ، وتنفيذ الاستعلام ، ولا تنس تحرير الذاكرة. سيكون من الأفضل التفاف هذا الرمز في فصل دراسي يتضمن غلافًا لإنشاء استعلامات SQL وتنفيذها ، بالإضافة إلى إعداد اتصال بقاعدة البيانات والمحافظة عليه.
الخطوة 3. تمرير كائن بدلاً من تمرير مؤشر إلى رد اتصال
تمرير الكائن إلى أسلوب التكرار للفئة الأساسية سيؤدي إلى حل مشكلة صيانة الحالة. سنقوم بإنشاء فئة الزوار التجريدية TPersonVisitor باستخدام طريقة تنفيذ واحدة ونمرر الكائن إلى هذه الطريقة كمعلمة. ويرد واجهة الزوار مجردة أدناه:
TPersonVisitor = class(TObject) public procedure Execute(pPerson: TPerson); virtual; abstract; end;
بعد ذلك ، أضف أسلوب Iterate إلى صف TPersonList لدينا:
TPersonList = class(TObjectList) public constructor Create; procedure Iterate(pVisitor: TPersonVisitor); end;
سيتم تنفيذ هذه الطريقة على النحو التالي:
procedure TPersonList.Iterate(pVisitor: TPersonVisitor); var i: integer; begin for i := 0 to Count - 1 do pVisitor.Execute(TPerson(Items[i])); end;
يتم تمرير كائن من الزائر الذي تم تطبيقه من فئة TPersonVisitor إلى طريقة Iterate ، وعند التكرار عبر عناصر القائمة لكل منهم ، يتم استدعاء الزائر المحدد (أسلوب التنفيذ) الخاص به مع مثيل TPerson كمعلمة.
دعنا ننشئ تطبيقين للزائر - TShowNameVisitor و TShowEmailVistor ، اللذين سينفذان العمل المطلوب. فيما يلي كيفية تجديد قسم واجهات الوحدة النمطية:
TShowNameVisitor = class(TPersonVisitor) public procedure Execute(pPerson: TPerson); override; end; TShowEmailVisitor = class(TPersonVisitor) public procedure Execute(pPerson: TPerson); override; end;
من أجل البساطة ، سيظل تنفيذ أساليب التنفيذ عليها عبارة عن سطر واحد - ShowMessage (pPerson.Name) و ShowMessage (pPerson.EMailAdrs).
وقم بتغيير الكود الخاص بنقرة زر المعالجات:
procedure TForm1.Button1Click(Sender: TObject); var lVis: TPersonVisitor; begin lVis := TShowNameVisitor.Create; try FPersonList.Iterate(lVis); finally lVis.Free; end; end; procedure TForm1.Button2Click(Sender: TObject); var lVis: TPersonVisitor; begin lVis := TShowEmailVisitor.Create; try FPersonList.Iterate(lVis); finally lVis.Free; end; end;
الآن ، وبعد حل مشكلة واحدة ، أنشأنا مشكلة أخرى لأنفسنا. يتم تضمين منطق التكرار في فصل منفصل ؛ يتم التفاف العمليات المنفذة أثناء التكرار في كائنات ، مما يسمح لنا بحفظ بعض المعلومات عن الحالة ، ولكن حجم الرمز زاد من سطر واحد (FPersonList.DoSomething (DoShowName) ؛ إلى تسعة أسطر لكل معالج زر. الآن سوف يساعدنا - هذا هو مدير الزوار ، الذي سيتولى إنشاء نسخهم وتحريرها. من المحتمل ، يمكننا توفير العديد من العمليات مع الكائنات التي يتعين القيام بها أثناء التكرار ، لهذا سيقوم مدير الزوار بتخزين قائمتهم وتصفحها في كل خطوة ، . Olnyaya فقط عمليات اختيار التالي سوف تظهر بوضوح الفوائد من هذا النهج، وسوف نستخدم زوار لحفظ البيانات في قاعدة بيانات علائقية كما يمكن أن يحمل بيانات بسيطة إنقاذ العملية من قبل ثلاث شركات مختلفة SQL: خلق، وحذف وUPDATE.
الخطوة 4. مزيد من التغليف للزائر
قبل الانتقال ، يجب علينا تغليف منطق عمل الزائر ، مع فصله عن منطق العمل الخاص بالتطبيق حتى لا يعود إليه. سوف يستغرق الأمر منا ثلاث خطوات للقيام بذلك: إنشاء فئات أساسية TVisited و TVisitor ، ثم الطبقات الأساسية لكائن الأعمال ومجموعة من كائنات الأعمال ، ثم ضبط فئاتنا الخاصة TPerson و TPersonList (أو TPeople) قليلاً حتى يصبحوا وريثين للقاعدة التي تم إنشاؤها الطبقات. بعبارات عامة ، سوف تتوافق بنية الفئات مع هذا المخطط:

يقوم كائن TVisitor بتنفيذ طريقتين: وظيفة AcceptVisitor وإجراء Execute ، حيث يتم تمرير كائن الكتابة TVisited. الكائن TVisited ، بدوره ، ينفذ طريقة Iterate مع معلمة من النوع TVisitor. بمعنى ، TVisited.Iterate يجب استدعاء الأسلوب Execute على كائن TVisitor المنقول ، وإرسال رابط إلى المثيل الخاص به كمعلمة ، وإذا كان المثيل عبارة عن مجموعة ، فسيتم استدعاء الأسلوب Execute لكل عنصر في المجموعة. تعتبر وظيفة AcceptVisitor ضرورية لأننا نعمل على تطوير نظام معمم. سيكون من الممكن الانتقال إلى الزائر ، الذي يعمل فقط مع أنواع TPerson ، كمثال لفئة TDog ، على سبيل المثال ، ويجب أن تكون هناك آلية لمنع الاستثناءات والوصول إلى الأخطاء بسبب عدم تطابق الكتابة. الفئة TVisited هي سليل فئة TPersistent ، حيث أننا سنحتاج في وقت لاحق إلى تنفيذ الوظائف المتعلقة باستخدام RTTI.
سيكون جزء واجهة الوحدة النمطية الآن كما يلي:
TVisited = class; TVisitor = class(TObject) protected function AcceptVisitor(pVisited: TVisited): boolean; virtual; abstract; public procedure Execute(pVisited: TVisited); virtual; abstract; end; TVisited = class(TPersistent) public procedure Iterate(pVisitor: TVisitor); virtual; end;
سيتم تنفيذ طرق الفصل التجريدي TVisitor من قبل الورثة ، فيما يلي التنفيذ العام لطريقة Iterate لـ TVisited:
procedure TVisited.Iterate(pVisitor: TVisitor); begin pVisitor.Execute(self); end;
في الوقت نفسه ، يتم الإعلان عن الطريقة الافتراضية لإمكانية تجاوزها في الورثة.
الخطوة 5. إنشاء كائن الأعمال المشتركة وجمع
يحتاج إطارنا إلى فئتين أساسيتين أخريين: لتحديد عنصر أعمال ومجموعة من هذه الكائنات. ندعو لهم TtiObject و TtiObjectList. واجهة الأول منهم:
TtiObject = class(TVisited) public constructor Create; virtual; end;
في وقت لاحق من عملية التطوير ، سنقوم بتعقيد هذه الفئة ، ولكن بالنسبة للمهمة الحالية ، لا يكفي سوى منشئ افتراضي واحد مع إمكانية تجاوزه في الورثة.
نحن نخطط لإنشاء فئة TtiObjectList من TVisited من أجل استخدام السلوك في الأساليب التي تم تنفيذها بالفعل من قبل سلف (هناك أيضًا أسباب أخرى لهذا الميراث التي سيتم مناقشتها في مكانها). بالإضافة إلى ذلك ، لا شيء يمنع استخدام
واجهات (واجهات) بدلاً من فئات مجردة.
سيكون جزء الواجهة من فئة TtiObjectList كما يلي:
TtiObjectList = class(TtiObject) private FList: TObjectList; public constructor Create; override; destructor Destroy; override; procedure Clear; procedure Iterate(pVisitor: TVisitor); override; procedure Add(pData: TObject); end;
كما ترون ، الحاوية نفسها مع عناصر الكائن موجودة في القسم المحمي ولن تكون متاحة للعملاء من هذه الفئة. الجزء الأكثر أهمية في الفصل هو تطبيق أسلوب Iterate المتجاوز. إذا كانت الطريقة في الفئة الأساسية تسمى ببساطة pVisitor.Execute (self) ، فالتطبيق هنا مرتبط بتعداد القائمة:
procedure TtiObjectList.Iterate(pVisitor: TVisitor); var i: integer; begin inherited Iterate(pVisitor); for i := 0 to FList.Count - 1 do (FList.Items[i] as TVisited).Iterate(pVisitor); end;
يأخذ تنفيذ أساليب الفصل الأخرى سطرًا واحدًا من التعليمات البرمجية دون مراعاة التعبيرات الموروثة التي يتم وضعها تلقائيًا:
Create: FList := TObjectList.Create; Destroy: FList.Free; Clear: if Assigned(FList) then FList.Clear; Add: if Assigned(FList) then FList.Add(pData);
هذا جزء مهم من النظام بأكمله. لدينا فئتان أساسيتان من منطق الأعمال: TtiObject و TtiObjectList. كلاهما له طريقة Iterate التي يتم فيها تمرير مثيل للفئة TVisited. يستدعي التكرار نفسه طريقة التنفيذ لفئة TVisitor ويمررها مرجعًا للكائن نفسه. تم تحديد هذه الدعوة مسبقًا في سلوك الفئة في المستوى الأعلى من الميراث. بالنسبة لفئة الحاوية ، يكون لكل كائن مخزّن في القائمة طريقة Iterate الخاصة به ، والتي تسمى بمعلمة type TVisitor ، أي أنه يضمن أن كل زائر محدد سيتجاوز كل الكائنات المخزنة في القائمة ، وكذلك القائمة نفسها ككائن حاوية.
الخطوة 6. إنشاء مدير زائر
لذا ، عد إلى المشكلة التي وضعناها نحن أنفسنا في الخطوة الثالثة. بما أننا لا نريد إنشاء نسخ من الزوار وتدميرها في كل مرة ، فسيكون تطوير المدير هو الحل. يجب أن تؤدي مهمتين رئيسيتين: إدارة قائمة الزوار (التي يتم تسجيلها على هذا النحو في قسم التهيئة للوحدات الفردية) وتشغيلها عند تلقي الأمر المناسب من العميل.
لتنفيذ المدير ، سنكمل وحدتنا بثلاثة فصول إضافية: TVisClassRef و TVisMapping و TtiVisitorManager.
TVisClassRef = class of TVisitor;
TVisClassRef هو نوع المرجع ويشير إلى اسم فئة معينة - سليل TVisitor. يكون معنى استخدام نوع المرجع كما يلي: عندما يتم استدعاء أسلوب التنفيذ الأساسي بالتوقيع
procedure Execute(const pData: TVisited; const pVisClass: TVisClassRef),
داخليًا ، يمكن أن تستخدم هذه الطريقة تعبيرًا مثل lVisitor: = pVisClass.Create لإنشاء مثيل لزائر معين ، دون معرفة نوعه أولاً. أي ، أي فئة - يمكن إنشاء سليل TVisitor ديناميكيًا داخل نفس طريقة التنفيذ عند تمرير اسم فئتها كمعلمة.
الفئة الثانية ، TVisMapping ، هي بنية بيانات بسيطة مع خاصيتين: إشارة إلى النوع TVisClassRef وخاصية سلسلة الأوامر. هناك حاجة إلى فصل لمقارنة العمليات التي يتم تنفيذها باسمهم (أمر ، على سبيل المثال ، "حفظ") وفئة الزائر التي تنفذها هذه الأوامر. أضف الكود الخاص به إلى المشروع:
TVisMapping = class(TObject) private FCommand: string; FVisitorClass: TVisClassRef; public property VisitorClass: TVisClassRef read FVisitorClass write FVisitorClass; property Command: string read FCommand write FCommand; end;
والفئة الأخيرة هي TtiVisitorManager. عندما نسجل الزائر باستخدام المدير ، يتم إنشاء مثيل لفئة TVisMapping ، والذي يتم إدخاله في قائمة المدير.
وبالتالي ، في المدير ، يتم إنشاء قائمة بالزوار مع مطابقة أوامر السلسلة ، وعند استلامها سيتم تنفيذها. تتم إضافة واجهة الفئة إلى الوحدة النمطية:
TtiVisitorManager = class(TObject) private FList: TObjectList; public constructor Create; destructor Destroy; override; procedure RegisterVisitor(const pCommand: string; pVisitorClass: TVisClassRef); procedure Execute(const pCommand: string; pData: TVisited); end;
طرقه الرئيسية هي RegisterVisitor و Execute. عادةً ما يسمى الأول في قسم التهيئة بالوحدة ، والذي يصف فئة الزائر ، ويبدو مثل هذا:
initialization gTIOPFManager.VisitorManager.RegisterVisitor('show', TShowNameVisitor); gTIOPFManager.VisitorManager.RegisterVisitor('show', TShowEMailAdrsVisitor);
سيكون رمز الطريقة نفسها كما يلي:
procedure TtiVisitorManager.RegisterVisitor(const pCommand: string; pVisitorClass: TVisClassRef); var lData: TVisMapping; begin lData := TVisMapping.Create; lData.Command := pCommand; lData.VisitorClass := pVisitorClass; FList.Add(lData); end;
ليس من الصعب ملاحظة أن هذا الرمز مشابه جدًا لتطبيق Pascal لقالب
المصنع .
تقبل طريقة التنفيذ المهمة الأخرى معلمتين: الأمر الذي سيتم من خلاله تحديد هوية الزائر أو مجموعته ، بالإضافة إلى كائن البيانات الذي سيتم استدعاء أسلوب Iterate الخاص به مع ارتباط بمثيل الزائر المطلوب. فيما يلي الشفرة الكاملة لأسلوب التنفيذ:
procedure TtiVisitorManager.Execute(const pCommand: string; pData: TVisited); var i: integer; lVisitor: TVisitor; begin for i := 0 to FList.Count - 1 do if SameText(pCommand, TVisMapping(FList.Items[i]).Command) then begin lVisitor := TVisMapping(FList.Items[i]).VisitorClass.Create; try pData.Iterate(lVisitor); finally lVisitor.Free; end; end; end;
وبالتالي ، لتشغيل اثنين من الزائرين المسجلين سابقًا مع فريق واحد ، نحتاج إلى سطر واحد فقط من التعليمات البرمجية:
gTIOPFManager.VisitorManager.Execute('show', FPeople);
بعد ذلك ، سنكمل مشروعنا بحيث يمكنك استدعاء أوامر مماثلة:
الخطوة 7. ضبط فئات منطق الأعمال
ستتيح لنا إضافة سلف فئتي TtiObject و TtiObjectList لعناصر أعمال TPerson و TPeople لدينا تغليف منطق التكرار في الفئة الأساسية وعدم لمسه بعد الآن ، بالإضافة إلى ذلك ، يصبح من الممكن نقل الكائنات مع البيانات إلى مدير الزوار.
سيبدو إعلان فئة الحاوية الجديد كما يلي:
TPeople = class(TtiObjectList);
في الواقع ، لا يتعين على طبقة TPeople تنفيذ أي شيء. من الناحية النظرية ، يمكننا الاستغناء عن إعلان TPeople على الإطلاق وتخزين الكائنات في مثيل لفئة TtiObjectList ، ولكن بما أننا نخطط لكتابة زوار يعالجون مثيلات TPeople فقط ، فنحن بحاجة إلى هذه الفئة. في وظيفة AcceptVisitor ، سيتم إجراء الاختبارات التالية:
Result := pVisited is TPeople.
بالنسبة لفئة TPerson ، نقوم بإضافة سلف TtiObject ، ونقل الخصائص الحالية إلى النطاق المنشور ، لأننا في المستقبل سنحتاج إلى العمل من خلال RTTI مع هذه الخصائص. سيؤدي هذا في وقت لاحق إلى حد كبير إلى تقليل التعليمات البرمجية المتضمنة في تعيين الكائنات والسجلات في قاعدة بيانات علائقية:
TPerson = class(TtiObject) private FEMailAdrs: string; FName: string; published property Name: string read FName write FName; property EMailAdrs: string read FEMailAdrs write FEMailAdrs; end;
الخطوة 8. إنشاء عرض النموذج الأولي
ملاحظة . في المقالة الأصلية ، اعتمد واجهة المستخدم الرسومية على مكونات أنشأها tiOPF لراحة العمل مع إطاره في دلفي. كانت هذه نظائرها لمكونات DB Aware ، والتي كانت عناصر تحكم قياسية مثل التسميات ، وحقول الإدخال ، وخانات الاختيار ، والقوائم ، وما إلى ذلك ، ولكنها ارتبطت ببعض خصائص كائنات tiObject بنفس الطريقة التي ارتبطت بها مكونات عرض البيانات مع الحقول في جداول قاعدة البيانات. بمرور الوقت ، قام مؤلف إطار العمل بتمييز الحزم مع هذه المكونات المرئية على أنها قديمة وغير مرغوب فيها للاستخدام.
في المقابل ، يقترح إنشاء علاقة بين المكونات المرئية وخصائص الفصل باستخدام نمط تصميم الوسيط. هذا القالب هو الثاني الأكثر أهمية في بنية الإطار بالكامل. إن وصف المؤلف للوسيط تمت تغطيته بمقال منفصل ، يمكن مقارنته في المجلد بهذا الدليل ، ولذا فإنني أقدم نسخته المبسطة هنا كواجهة مستخدم.أعد تسمية الزر 1 في نموذج المشروع إلى "show command" ، والزر 2 إما اتركه بدون معالج في الوقت الحالي ، أو قم بتسميته فورًا باسم "save command". رمي مكون المذكرة في النموذج ووضع جميع العناصر لذوقك.أضف فئة زائر ستقوم بتنفيذ أمر show:Interface - TShowVisitor = class(TVisitor) protected function AcceptVisitor(pVisited: TVisited): boolean; override; public procedure Execute(pVisited: TVisited); override; end;
والتنفيذ هو - function TShowVisitor.AcceptVisitor(pVisited: TVisited): boolean; begin Result := (pVisited is TPerson); end; procedure TShowVisitor.Execute(pVisited: TVisited); begin if not AcceptVisitor(pVisited) then exit; Form1.Memo1.Lines.Add(TPerson(pVisited).Name + ': ' + TPerson(pVisited).EMailAdrs); end;
يتحقق AcceptVisitor من أن الكائن الذي يتم نقله هو مثيل لـ TPerson ، لأنه يجب على الزائر تنفيذ الأمر بهذه الكائنات فقط. في حالة تطابق النوع ، يتم تنفيذ الأمر وإضافة سطر بخصائص الكائن إلى حقل النص.ستكون الإجراءات الداعمة لصحة الكود كما يلي. أضف خاصيتين إلى وصف النموذج نفسه في القسم الخاص: FPeople من النوع TPeople و VM من النوع TtiVisitorManager. في معالج أحداث إنشاء النماذج ، نحتاج إلى بدء هذه الخصائص ، وكذلك تسجيل الزائر باستخدام الأمر "show": FPeople := TPeople.Create; FillPeople; VM := TtiVisitorManager.Create; VM.RegisterVisitor('show',TShowVisitor);
FilPeople هو أيضًا إجراء إضافي يملأ قائمة بثلاثة كائنات ؛ يتم أخذ الكود من مُنشئ القائمة السابق. لا تنس أن تدمر كل الكائنات التي تم إنشاؤها. في هذه الحالة ، نكتب FPeople.Free و VM.Free في معالج إغلاق النموذج.والآن - bams! - معالج الزر الأول: Memo1.Clear; VM.Execute('show',FPeople);
توافق ، أكثر من ذلك بكثير متعة. ولا تقسم على تجزئة جميع الفئات في وحدة واحدة. في نهاية الدليل ، سنشعل هذه الأنقاض.الخطوة 9. الفئة الأساسية للزائر الذي يعمل مع الملفات النصية
في هذه المرحلة ، سنقوم بإنشاء الفئة الأساسية للزائر الذي يعرف كيفية التعامل مع الملفات النصية. هناك ثلاث طرق للعمل مع الملفات في كائن pascal: الإجراءات القديمة من وقت أول pascal (مثل AssignFile و ReadLn) ، والعمل من خلال التدفقات (TStringStream أو TFileStream) ، واستخدام كائن TStringList.إذا كانت الطريقة الأولى قديمة جدًا ، يكون البديلان الثاني والثالث بديلاً جيدًا يعتمد على OOP. في الوقت نفسه ، يوفر العمل مع التدفقات الإضافية مزايا مثل القدرة على ضغط البيانات وتشفيرها ، ولكن القراءة والكتابة سطراً إلى دفق ما هو نوع من التكرار في مثالنا. للبساطة ، سوف نختار TStringList ، والتي لديها طريقتين بسيطتين - LoadFromFile و SaveToFile. لكن تذكر أنه مع الملفات الكبيرة ، ستتباطأ هذه الطرق بشكل كبير ، وبالتالي سيكون الدفق هو الخيار الأمثل لها.واجهة الطبقة الأساسية TVisFile: TVisFile = class(TVisitor) protected FList: TStringList; FFileName: TFileName; public constructor Create; virtual; destructor Destroy; override; end;
وتنفيذ المنشئ والمدمر: constructor TVisFile.Create; begin inherited Create; FList := TStringList.Create; if FileExists(FFileName) then FList.LoadFromFile(FFileName); end; destructor TVisFile.Destroy; begin FList.SaveToFile(FFileName); FList.Free; inherited; end;
سيتم تعيين قيمة خاصية FFileName في مصممي أحفاد هذه الفئة الأساسية (فقط لا تستخدم القرص الصلب ، الذي سنرتبه هنا ، كنمط البرمجة الرئيسي بعد!). الرسم التوضيحي لفئات الزائر التي تعمل مع الملفات هو كما يلي:
وفقًا للرسم البياني أدناه ، نقوم بإنشاء اثنين من أحفاد الفئة الأساسية TVisFile: TVisTXTFile و TVisCSVFile. سيعمل الشخص مع ملفات * .csv التي يتم فيها فصل حقول البيانات برمز (فاصلة) ، والثاني - مع الملفات النصية التي تكون فيها حقول البيانات الفردية بطول ثابت في السطر. بالنسبة لهذه الفئات ، نعيد تعريف المنشئات فقط على النحو التالي: constructor TVisCSVFile.Create; begin FFileName := 'contacts.csv'; inherited Create; end; constructor TVisTXTFile.Create; begin FFileName := 'contacts.txt'; inherited Create; end.
الخطوة 10. إضافة زائر معالج الملفات النصية
سنضيف هنا زائرين محددين ، أحدهما سيقرأ ملفًا نصيًا ، والثاني سيكتب له. يجب على الزائر الذي يقوم بقراءة أن يتخطى أساليب الطبقة الأساسية AcceptVisitor وتنفيذها. يتحقق AcceptVisitor من تمرير كائن فئة TPeople إلى الزائر: Result := pVisited is TPeople;
تنفيذ التنفيذ كما يلي: procedure TVisTXtRead.Execute(pVisited: TVisited); var i: integer; lData: TPerson; begin if not AcceptVisitor(pVisited) then Exit;
يقوم الزائر أولاً بمسح قائمة كائن TPeople الذي تم تمريره إليه بواسطة المعلمة ، ثم يقرأ الأسطر من كائن TStringList الخاص به ، حيث يتم تحميل محتويات الملف ، ويقوم بإنشاء كائن TPerson على كل سطر ويضيفه إلى قائمة حاوية TPeople. للبساطة ، يتم فصل خصائص الاسم ورسائل البريد الإلكتروني في الملف النصي بمسافات.يقوم زائر السجل بتنفيذ العملية العكسية. يقوم مُنشئه (الذي تم تجاوزه) بمسح TStringList الداخلي (أي ، تنفيذ عملية FList.Clear ؛ إنه إلزامي بعد الموروثة) ، يتحقق AcceptVisitor من تمرير كائن فئة TPerson ، وهو ليس خطأ ، ولكنه يمثل اختلافًا مهمًا عن طريقة قراءة الزائر نفسها. قد يبدو من الأسهل تطبيق التسجيل بنفس الطريقة - مسح جميع الكائنات الحاوية ، وإضافتها إلى StringList ثم حفظها في ملف. كل هذا كان الأمر كذلك ، إذا كنا نتحدث بالفعل عن الكتابة النهائية للبيانات إلى ملف ، ومع ذلك نخطط لتعيين البيانات إلى قاعدة بيانات علائقية ، ينبغي تذكر ذلك. وفي هذه الحالة ، يجب علينا تنفيذ كود SQL فقط لتلك الكائنات التي تم تغييرها (تم إنشاؤها أو حذفها أو تحريرها). هذا هو السبب قبل أن يقوم الزائر بإجراء عملية على الكائن ،يجب عليه التحقق من المراسلات من نوعه: Result := pVisited is Tperson;
تضيف طريقة التنفيذ ببساطة إلى StringList الداخلية سلسلة منسقة بالقاعدة المحددة: أولاً ، محتويات خاصية اسم الكائن الذي تم تمريره ، مبطن بمسافات تصل إلى 20 حرفًا ، ثم محتويات خاصية emaiadrs: procedure TVisTXTSave.Execute(pVisited: TVisited); begin if not AcceptVisitor(pVisited) then exit; FList.Add(PadRight(TPerson(pVisited).Name,20)+PadRight(TPerson(pVisited).EMailAdrs,60)); end;
الخطوة 11. إضافة زائر معالج ملفات CSV
يتشابه زوار القراءة والكتابة في جميع زملائهم تقريبًا من فئات TXT ، باستثناء طريقة تنسيق السطر الأخير من الملف: في معيار CSV ، يتم فصل قيم الخاصية بفواصل. لقراءة السطور وتحليلها في خصائص ، نستخدم الدالة ExtractDelimited من الوحدة النمطية strutils ، ويتم إجراء الكتابة ببساطة عن طريق سَلسَلة الخطوط: procedure TVisCSVRead.Execute(pVisited: TVisited); var i: integer; lData: TPerson; begin if not AcceptVisitor(pVisited) then exit; TPeople(pVisited).Clear; for i := 0 to FList.Count - 1 do begin lData := TPerson.Create; lData.Name := ExtractDelimited(1, FList.Strings[i], [',']); lData.EMailAdrs := ExtractDelimited(2, FList.Strings[i], [',']); TPeople(pVisited).Add(lData); end; end; procedure TVisCSVSave.Execute(pVisited: TVisited); begin if not AcceptVisitor(pVisited) then exit; FList.Add(TPerson(pVisited).Name + ',' + TPerson(pVisited).EMailAdrs); end;
كل ما تبقى بالنسبة لنا هو تسجيل زوار جدد في المدير والتحقق من تشغيل التطبيق. في معالج إنشاء النماذج ، أضف التعليمات البرمجية التالية: VM.RegisterVisitor('readTXT', TVisTXTRead); VM.RegisterVisitor('saveTXT',TVisTXTSave); VM.RegisterVisitor('readCSV',TVisCSVRead); VM.RegisterVisitor('saveCSV',TVisCSVSave);
إرساء الأزرار الضرورية في النموذج وتعيين المعالجات المناسبة لهم:
procedure TForm1.ReadCSVbtnClick(Sender: TObject); begin VM.Execute('readCSV', FPeople); end; procedure TForm1.ReadTXTbtnClick(Sender: TObject); begin VM.Execute('readTXT', FPeople); end; procedure TForm1.SaveCSVbtnClick(Sender: TObject); begin VM.Execute('saveCSV', FPeople); end; procedure TForm1.SaveTXTbtnClick(Sender: TObject); begin VM.Execute('saveTXT', FPeople); end;
يتم تنفيذ تنسيقات ملفات إضافية لحفظ البيانات عن طريق إضافة الزوار المناسبين وتسجيلهم في المدير. وانتبه إلى ما يلي: لقد قمنا بتسمية الأوامر عمداً بطريقة مختلفة ، أي saveTXT و saveCSV. إذا كان كلا الزائرين يتطابقان مع أمر حفظ واحد ، فسيبدأ كلاهما في نفس الأمر ، تحقق من ذلك بنفسك.الخطوة 12. تنظيف الرمز النهائي
لمزيد من جمال ونقاء الشفرة ، وكذلك لإعداد مشروع لزيادة تطوير التفاعل مع DBMS ، سنقوم بتوزيع فصولنا في وحدات مختلفة وفقًا للمنطق والغرض منها. في النهاية ، ينبغي أن يكون لدينا الهيكل التالي للوحدات النمطية في مجلد المشروع ، والذي يسمح لنا بالاستغناء عن العلاقة الدائرية بينهما (عند تجميع نفسك ، ترتيب الوحدات اللازمة في أقسام الاستخدامات):وحدة
| وظيفة
| فصول
|
tivisitor.pas
| الفئات الأساسية لقالب الزوار والمدير
| TVisitor TVisited TVisMapping TtiVisitorManager
|
tiobject.pas
| فئات منطق الأعمال الأساسية
| TtiObject TtiObjectList
|
people_BOM.pas
| فئات منطق الأعمال المحددة
| TPerson TPeople
|
people_SRV.pas
| فصول ملموسة مسؤولة عن التفاعل
| TVisFile TVisTXTFile TVisCSVFile TVisCSVSave TVisCSVRead TVisTXTSave TVisTXTRead
|
الخاتمة
في هذه المقالة ، درسنا مشكلة التكرار على مجموعة أو قائمة الكائنات التي يمكن أن يكون لها أنواع مختلفة. استخدمنا قالب الزائر الذي اقترحته GoF لتنفيذ طريقتين مختلفتين لتعيين البيانات من كائنات إلى ملفات بتنسيقات مختلفة. في الوقت نفسه ، يمكن تنفيذ طرق مختلفة من قبل فريق واحد بسبب إنشاء مدير الزوار. في النهاية ، سوف تساعدنا الأمثلة البسيطة والتوضيحية التي تمت مناقشتها في المقالة على تطوير نظام مماثل لتعيين الكائنات إلى قاعدة بيانات علائقية.الأرشيف مع شفرة المصدر من الأمثلة - هنا