طريقة التمديد
في البرمجة الكائنية التوجه ، تُعرَّف طريقة الإضافة بأنها طريقة تُضاف إلى كائن بعد تجميع الكائن الأصلي . غالبًا ما يكون الكائن المُعدَّل فئةً أو نموذجًا أوليًا أو نوعًا. تُعدّ طرق الإضافة من خصائص بعض لغات البرمجة الكائنية التوجه. لا يوجد فرق نحوي بين استدعاء طريقة إضافة واستدعاء طريقة مُصرَّح عنها في تعريف النوع. [ 1 ]
مع ذلك، لا تُطبّق جميع اللغات أساليب الإضافة بنفس القدر من الأمان. على سبيل المثال، لا تُجري لغات مثل C# وJava (عبر Manifold أو Lombok أو Fluent ) وKotlin أي تعديل على الفئة المُوسّعة، لأن ذلك قد يُخلّ بتسلسل الفئات ويتداخل مع استدعاء الأساليب الافتراضية. بدلاً من ذلك، تُطبّق هذه اللغات أساليب الإضافة بشكل ثابت وتستخدم الاستدعاء الثابت لاستدعائها.
الدعم في لغات البرمجة
تُعدّ أساليب الإضافة من ميزات العديد من اللغات، بما في ذلك C# ، وJava عبر Manifold أو Lombok أو Fluent ، و Gosu ، و JavaScript ، و Oxygene ، وRuby ، وSmalltalk ، وKotlin ، وDart ، و Visual Basic.NET ، و Xojo . في اللغات الديناميكية مثل Python ، لا حاجة لمفهوم أسلوب الإضافة، إذ يُمكن توسيع الفئات (باستثناء الفئات المُدمجة) دون أي صيغة خاصة (وهو أسلوب يُعرف باسم " تعديل الكود "، ويُستخدم في مكتبات مثل gevent ).
في VB.NET و Oxygene، يتم التعرف عليها من خلال وجود الكلمة extensionالمفتاحية أو السمة " ". أما في Xojo، Extendsفتُستخدم الكلمة المفتاحية " " مع الدوال العامة.
في لغة C#، تُنفَّذ هذه الدوال كطرق ثابتة في فئات ثابتة، حيث يكون الوسيط الأول من فئة موسعة ويسبقه thisالكلمة المفتاحية " ". وقد اقتُرحت طرق الإضافة لإدراجها في لغة C++ ، واستُشهد بطرق الإضافة على نمط C# كمصدر إلهام. [ 2 ]
في لغة جافا، تُضاف الدوال الإضافية عبر مكتبة Manifold ، وهي ملف JAR يُضاف إلى مسار فئات المشروع. على غرار لغة C#، تُعرّف دالة جافا الإضافية كدالة ثابتة (static) في فئة @Extension، حيث يكون للوسيط الأول نفس نوع الفئة المُوسّعة، ويُشار إليها بالتعليق التوضيحي @This. بدلاً من ذلك، تسمح إضافة Fluent باستدعاء أي دالة ثابتة كدالة إضافية دون استخدام التعليقات التوضيحية، طالما تطابق توقيع الدالة.
في لغة Smalltalk، يُمكن لأي كود إضافة دالة إلى أي فئة في أي وقت، وذلك بإرسال رسالة إنشاء دالة (مثل `<data-name> methodsFor:`) إلى الفئة التي يرغب المستخدم في توسيعها. يُسمى قسم الدوال في Smalltalk عادةً باسم الحزمة التي توفر التوسيع، ويُحاط بعلامات النجمة (*). على سبيل المثال، عندما يقوم كود تطبيق Etoys بتوسيع فئات في المكتبة الأساسية، تُوضع الدوال المُضافة في *etoys*قسم الدوال.
في لغة روبي، كما هو الحال في لغة سمول توك، لا توجد ميزة لغوية خاصة بالتوسيع، إذ تسمح روبي بإعادة فتح الأصناف في أي وقت باستخدام الكلمة classالمفتاحية لإضافة طرق جديدة. غالبًا ما يصف مجتمع روبي طريقة التوسيع بأنها نوع من التعديل المباشر . توجد أيضًا ميزة أحدث لإضافة توسيعات آمنة/محلية للكائنات، تُسمى " التحسينات" ، ولكنها أقل استخدامًا.
في لغة Rust ، لا توجد دوال إضافية، ولكن بالنسبة للأنواع التي يُعرّفها المستخدم، يُمكن إضافة دوال جديدة باستخدام implكتل برمجية في أي وقت impl Trait. يُمنع وضع implكتل برمجية على أنواع لا يملكها المستخدم، أو إذا لم تكن السمة أو أحد الأنواع مُعرّفة في الحزمة الحالية (وتُسمى "قاعدة اليتيم"). [ 3 ] يُستخدم هذا لمنع حزمتين من تطبيق السمة نفسها على النوع نفسه بطريقة غير متوافقة.
في لغة Swift، extensionتشير الكلمة المفتاحية إلى بنية شبيهة بالفئة تسمح بإضافة طرق، ومنشئات، وحقول إلى فئة موجودة، بما في ذلك القدرة على تطبيق واجهة/بروتوكول جديد على الفئة الموجودة. [ 4 ]
طرق التوسيع كميزة تمكينية
إلى جانب أساليب الإضافة التي تسمح بتوسيع التعليمات البرمجية المكتوبة من قِبل الآخرين كما هو موضح أدناه، تُتيح أساليب الإضافة أنماطًا مفيدة بحد ذاتها أيضًا. والسبب الرئيسي وراء تقديم أساليب الإضافة هو استعلام اللغة المتكامل (LINQ). يدعم المُصرّف أساليب الإضافة، مما يسمح بتكامل LINQ بشكل كامل مع التعليمات البرمجية القديمة تمامًا كما هو الحال مع التعليمات البرمجية الجديدة، بالإضافة إلى دعم صيغة الاستعلام التي تقتصر حاليًا على لغات Microsoft .NET الرئيسية .
باستخدام النظام ؛Console.WriteLine ( new [ ] { Math.PI , Math.E } .Where ( d = > d > 3 ) .Select ( d = > Math.Sin ( d / 2 ) ) . Sum ( ) ) ; // الناتج : // 1مركزية السلوك المشترك
ومع ذلك، تسمح أساليب التمديد بتنفيذ الميزات مرة واحدة بطرق تمكن من إعادة الاستخدام دون الحاجة إلى الوراثة أو تكلفة استدعاء الأساليب الافتراضية ، أو تتطلب من منفذي الواجهة تنفيذ وظائف بسيطة أو معقدة بشكل مؤسف.
ومن السيناريوهات المفيدة بشكل خاص هو إذا كانت الميزة تعمل على واجهة لا يوجد لها تطبيق ملموس أو لم يقدم مؤلف مكتبة الفئات تطبيقًا مفيدًا، على سبيل المثال كما هو الحال غالبًا في المكتبات التي توفر للمطورين بنية إضافية أو وظائف مماثلة.
لنفترض أن الكود التالي هو الكود الوحيد الموجود في مكتبة الفئات. مع ذلك، سيتمكن كل من يُنفذ واجهة ILogger من كتابة سلسلة نصية مُنسقة، بمجرد تضمين عبارة using MyCoolLogger ، دون الحاجة إلى تنفيذها مرة واحدة أو إلى إنشاء فئة فرعية من مكتبة فئات تُقدم تطبيقًا لواجهة ILogger.
namespace Wikipedia.Util.Logging ;باستخدام النظام ؛public interface ILogger { void Write ( string text ); }public static class LoggerExtensions { public static void Write ( this ILogger logger , string format , params object [] args ) { if ( logger != null ) { logger . Write ( String . Format ( format , args )); } } }استخدم كـ :
باستخدام MyProject.Util.Logging ؛MyLoggerImplementation logger = new ( ); logger.Write ( " {0}: {1}" , " kiddo sais" , "Mam mam mam mam ..." ); logger.Write ( " {0}: {1}" , " kiddo sais" , "Ma ma ma ma... " ); logger.Write ( "{0}: {1}" , " kiddo sais" , "Mama mama mama mama " ); logger.Write ( "{0}: {1}" , " kiddo sais" , "Mamma mamma mamma ... " ); logger.Write ( " {0}: {1}" , " kiddo sais" , "Elisabeth Lizzy Liz..." ); logger.Write ( " {0}: {1}" , "mamma sais" , "WHAT?!?!!!" ); logger . اكتب ( "{0}: {1}" , "kiddo sais" , "hi." );تحسين التوصيل غير المحكم
تتيح أساليب الإضافة لمستخدمي مكتبات الفئات تجنب تعريف أي وسيط أو متغير أو أي عنصر آخر بنوع مُستمد من تلك المكتبة. ويمكن تنفيذ إنشاء وتحويل الأنواع المستخدمة في مكتبة الفئات كأساليب إضافة. بعد تنفيذ عمليات التحويل والإنشاء بعناية، يصبح الانتقال من مكتبة فئات إلى أخرى سهلاً بمجرد تغيير عبارة `using` التي تُتيح أساليب الإضافة للمُصرّف للربط بها.
واجهات برمجة التطبيقات السلسة
تُستخدم أساليب الإضافة بشكل خاص في تنفيذ ما يُسمى بالواجهات السلسة. ومن الأمثلة على ذلك واجهة برمجة تطبيقات تكوين Entity Framework من مايكروسوفت ، والتي تسمح، على سبيل المثال، بكتابة كود يُشبه اللغة الإنجليزية العادية قدر الإمكان.
يمكن للمرء أن يجادل بأن هذا ممكن بنفس القدر بدون أساليب التوسيع، ولكن سيجد المرء أنه من الناحية العملية، توفر أساليب التوسيع تجربة أفضل لأنه يتم وضع قيود أقل على التسلسل الهرمي للفئة لجعله يعمل - ويقرأ - كما هو مطلوب.
يستخدم المثال التالي إطار عمل Entity Framework ويُهيئ فئة TodoList ليتم تخزينها في جدول قاعدة البيانات Lists، ويُعرّف مفتاحًا أساسيًا ومفتاحًا خارجيًا . ويمكن فهم الكود تقريبًا على النحو التالي: "تحتوي قائمة TodoList على المفتاح TodoListID، واسم مجموعة الكيانات الخاصة بها هو Lists، وتحتوي على العديد من عناصر TodoItem، كل منها يحتوي على قائمة TodoList مطلوبة".
namespace Wikipedia.Examples ;public class TodoItemContext : DbContext { public DbSet <TodoItem> TodoItems { get ; set ; } public DbSet <TodoList> TodoLists { get ; set ; }protected override void OnModelCreating ( DbModelBuilder modelBuilder ) { base . OnModelCreating ( modelBuilder ); modelBuilder . Entity < TodoList > () . HasKey ( e => e . TodoListId ) . HasEntitySetName ( "Lists" ) . HasMany ( e => e . Todos ) . WithRequired ( e => e . TodoList ); } }إنتاجية
لنأخذ على سبيل المثال واجهة IEnumerable، مع ملاحظة بساطتها - إذ تحتوي على دالة واحدة تُعدّ أساسًا لـ LINQ. توجد العديد من تطبيقات هذه الواجهة في Microsoft .NET. كان من المرهق إلزام كل تطبيق من هذه التطبيقات بتنفيذ سلسلة الدوال المُعرّفة في مساحة اسم System.Linq للعمل على IEnumerable. كان هذا سيُجبر جهات أخرى غير Microsoft على استخدام IEnumerable لتنفيذ تلك الدوال أيضًا، وهو أمر غير فعّال نظرًا للاستخدام الواسع لهذه الواجهة. من خلال تنفيذ دالة واحدة فقط من هذه الواجهة، يُمكن استخدام LINQ بشكل فوري تقريبًا. في معظم الحالات، يتم تفويض دالة GetEnumerator الخاصة بـ IEnumerable إلى تطبيق GetEnumerator الخاص بمجموعة أو قائمة أو مصفوفة خاصة.
namespace Wikipedia.Examples ;باستخدام System ؛ باستخدام System.Collections.Generic ؛public class BankAccount : IEnumerable <decimal> { private List < Tuple < DateTime , decimal >> credits ; // يفترض أن جميعها سالبة private List < Tuple < DateTime , decimal >> debits ; // يفترض أن جميعها موجبةpublic IEnumerator <decimal> GetEnumerator ( ) { IEnumerator <decimal> query = from dc in debits.Union ( credits ) orderby dc.Item1 // التاريخ select dc.Item2 ; // المبلغ foreach ( decimal amount in query ) { yield return amount ; } } } // بافتراض وجود نسخة من BankAccount باسم ba واستخدام System.Linq على الملف الحالي، // يمكن الآن كتابة ba.Sum() للحصول على رصيد الحساب، وba.Reverse() لعرض أحدث المعاملات أولاً، // وba.Average() للحصول على متوسط المبلغ لكل معاملة، إلخ - دون الحاجة إلى كتابة أي عامل حسابيأداء
ومع ذلك، يمكن إضافة تطبيقات إضافية لميزة توفرها طريقة الامتداد لتحسين الأداء، أو للتعامل مع تطبيقات الواجهة المختلفة، مثل تزويد المترجم بتطبيق خاص IEnumerableبالمصفوفات (في System.SZArrayHelper)، والذي سيختاره تلقائيًا لاستدعاءات طريقة الامتداد على المراجع المكتوبة بنوع المصفوفة، لأن وسيطتها ستكون أكثر تحديدًا ( this T[] value) من طريقة الامتداد التي تحمل نفس الاسم والتي تعمل على مثيلات الواجهة IEnumerable( this IEnumerable value).
تخفيف الحاجة إلى فئة أساسية مشتركة
في الأصناف العامة، تسمح أساليب الإضافة بتنفيذ سلوك متاح لجميع نسخ النوع العام دون اشتراط اشتقاقها من صنف أساسي مشترك، ودون تقييد معلمات النوع بفرع وراثي محدد. وهذا مفيد لأن الحالات التي ينطبق عليها هذا الشرط تتطلب صنفًا أساسيًا غير عام لتنفيذ الميزة المشتركة، مما يستلزم بدوره أن يقوم الصنف الفرعي العام بإجراء تحويلات بين الأنواع ( boxing و/أو casting) كلما كان النوع المستخدم أحد معلمات النوع.
الاستخدام المحافظ
فيما يتعلق بتفضيل استخدام الدوال الإضافية على غيرها من الوسائل لتحقيق إعادة الاستخدام والتصميم الكائني التوجه الأمثل، قد تُسبب هذه الدوال الإضافية ازدحامًا في ميزات الإكمال التلقائي لمحررات الأكواد، مثل IntelliSense في Visual Studio، لذا يُفضل وضعها في مساحة اسم خاصة بها لتمكين المطور من استيرادها بشكل انتقائي. أو بدلاً من ذلك، يُفضل تعريفها على نوع محدد بما يكفي لظهورها في IntelliSense فقط عند الضرورة القصوى. وبالنظر إلى ما سبق، قد يصعب على المطور العثور عليها إذا توقعها ولكنه لم يجدها في IntelliSense بسبب عدم وجود عبارة using، إذ قد لا يكون المطور قد ربط الدالة بالفئة التي تُعرّفها، أو حتى بمساحة الاسم التي تنتمي إليها، بل بالنوع الذي تُوسّعه ومساحة الاسم التي ينتمي إليها هذا النوع.
المشكلة
في البرمجة، قد تنشأ حالات تستدعي إضافة وظائف جديدة إلى فئة موجودة، كإضافة دالة جديدة مثلاً. عادةً ما يقوم المبرمج بتعديل شفرة المصدر للفئة ، لكن هذا يُجبره على إعادة تجميع جميع الملفات التنفيذية مع هذه التغييرات، ويتطلب منه القدرة على تعديل الفئة نفسها، وهو أمر غير ممكن دائمًا، كما هو الحال عند استخدام فئات من مكتبة خارجية . يتم تجاوز هذه المشكلة عادةً بإحدى ثلاث طرق، جميعها محدودة وغير بديهية إلى حد ما:
- قم بتوريث الفئة ثم قم بتنفيذ الوظائف في طريقة مثيل في الفئة المشتقة.
- قم بتنفيذ الوظيفة في دالة ثابتة مضافة إلى فئة مساعدة .
- استخدم التجميع بدلاً من الوراثة .
حلول C# الحالية
الخيار الأول أسهل من حيث المبدأ، ولكنه محدود لأن العديد من الفئات تقيّد وراثة بعض الأعضاء أو تمنعها تمامًا. يشمل ذلك الفئة المغلقة وأنواع البيانات الأولية المختلفة في لغة C# مثل int و float و string . أما الخيار الثاني فلا يشترك في هذه القيود، ولكنه قد يكون أقل سهولة في الاستخدام لأنه يتطلب الإشارة إلى فئة منفصلة بدلًا من استخدام توابع الفئة المعنية مباشرةً.
على سبيل المثال، لنفترض الحاجة إلى توسيع فئة السلسلة النصية بإضافة دالة عكسية جديدة تُرجع سلسلة نصية بأحرفها مرتبةً بترتيب عكسي. ولأن فئة السلسلة النصية نوع مغلق، تُضاف هذه الدالة عادةً إلى فئة أدوات جديدة بطريقة مشابهة لما يلي:
string x = "some string value" ; string y = Utility . Reverse ( x );مع ذلك، قد يصبح التنقل في هذا الأمر أكثر صعوبة مع ازدياد مكتبة الدوال والفئات المساعدة، خاصةً للمبتدئين. كما أن الموقع أقل وضوحًا، لأنه على عكس معظم دوال السلاسل النصية، لن يكون هذا التابع جزءًا من فئة السلاسل النصية، بل سيكون ضمن فئة مختلفة. لذا، سيكون من الأفضل استخدام الصيغة التالية:
string x = "some string value" ; string y = x . Reverse ();حلول VB.NET الحالية
في معظم النواحي، يُشابه حل VB.NET حل C# المذكور أعلاه. إلا أن VB.NET يتميز بإمكانية تمرير العناصر إلى الامتداد بالمرجع (بينما يسمح C# بذلك بالقيمة فقط). وهذا يسمح بما يلي:
Dim x As String = "some string value" x . Reverse ()بما أن لغة Visual Basic تسمح بتمرير الكائن المصدر بالمرجع، فمن الممكن إجراء تغييرات عليه مباشرةً، دون الحاجة إلى إنشاء متغير آخر. كما أنها أكثر سهولة في الاستخدام لأنها تعمل بطريقة متسقة مع أساليب الفئات الموجودة.
طريقة التمديد
مع ذلك، فإن ميزة اللغة الجديدة المتمثلة في أساليب الإضافة في C# 3.0 تُتيح كتابة الكود الأخير. يتطلب هذا الأسلوب فئة ثابتة وأسلوبًا ثابتًا، كما يلي.
namespace Wikipedia.Examples ;باستخدام النظام ؛public static class Utility { public static string Reverse ( this string input ) { char [] chars = input . ToCharArray (); Array . Reverse ( chars ); return new String ( chars ); } }في التعريف، يُشير المُعدِّل 'this' قبل الوسيط الأول إلى أنه دالة امتداد (في هذه الحالة، إلى النوع 'string'). عند الاستدعاء، لا يتم تمرير الوسيط الأول لأنه معروف مسبقًا باسم الكائن المُستدعي (الكائن الذي يسبق النقطة).
يتمثل الفرق الرئيسي بين استدعاء الدوال الإضافية واستدعاء الدوال المساعدة الثابتة في أن الدوال الثابتة تُستدعى باستخدام صيغة البادئة ، بينما تُستدعى الدوال الإضافية باستخدام صيغة الوسط . وتؤدي الصيغة الأخيرة إلى كود أكثر وضوحًا عند استخدام نتيجة عملية ما في عملية أخرى.
- باستخدام الأساليب الثابتة
HelperClass.Operation2 ( HelperClass.Operation1 ( x , arg1 ) , arg2 )
- مع طرق التمديد
x . العملية 1 ( الوسيط 1 ). العملية 2 ( الوسيط 2 )
تعارضات التسمية في أساليب الامتداد وأساليب النسخ
في لغة C# 3.0، يمكن أن يوجد لكل فئة دالة مثيل ودالة امتداد بنفس التوقيع. في هذه الحالة، تُفضّل دالة المثيل على دالة الامتداد. لا يُصدر المُصرّف ولا بيئة التطوير المتكاملة Microsoft Visual Studio أي تحذير بشأن تعارض التسمية. لنفترض فئة C# التالية، حيث GetAlphabet()يتم استدعاء الدالة على مثيل من هذه الفئة:
namespace Wikipedia.Examples ;باستخدام النظام ؛class AlphabetMaker { // عند تنفيذ هذه الطريقة، ستُخفي التنفيذ الموجود في فئة ExtensionMethods. public void GetAlphabet () { Console . WriteLine ( "abc" ); } }static class ExtensionMethods { // سيتم استدعاء هذا فقط في حالة عدم وجود دالة مثيل بنفس التوقيع. public static void GetAlphabet ( this AlphabetMaker am ) { Console . WriteLine ( "ABC" ); } }نتيجة استدعاء الدالة GetAlphabet()على نسخة من الكائن AlphabetMakerإذا كانت طريقة الامتداد فقط موجودة:
ABC
النتيجة في حال وجود كل من طريقة الكائن وطريقة الامتداد:
أبجد
انظر أيضاً
- UFCS ، وهي طريقة لاستخدام الدوال الحرة كطرق توسيع متوفرة في لغة البرمجة D
- فئات الأنواع
- الأنواع المجهولة
- تعبيرات لامدا
- أشجار التعبير
- تعديل وقت التشغيل
- الكتابة البطة
مراجع
- ↑ "أساليب الإضافة" . مايكروسوفت . تم الاسترجاع في 23-11-2008 .
- ↑ كو، جوناثان؛ أور، روجر. "أساليب الإضافة للغة C++" (ملف PDF) . open-std.org . WG21 . تم الاطلاع عليه بتاريخ 4 فبراير 2025 .
- ↑ مرجع لغة Rust (25 فبراير 2025). "التطبيقات" . doc.rust-lang.org . مرجع لغة Rust.
- ↑ "الإضافات - لغة برمجة سويفت (سويفت 5.7)" . docs.swift.org . تم الاطلاع عليه بتاريخ 12-06-2022 .
روابط خارجية
- مجموعة مفتوحة المصدر من مكتبات أساليب الإضافة للغة C# . مؤرشفة الآن على Codeplex
- طريقة الإضافة في لغة C#
- طريقة التمديد
- أساليب الإضافة في لغة C# . مجموعة.
- extensionmethod.net قاعدة بيانات ضخمة تحتوي على دوال إضافية بلغات C# وVisual Basic وF# وJavascript
- شرح ومثال برمجي
- تعريف وظائفك الخاصة في jQuery
- صيغة استدعاء الدالة الموحدة
- أساليب الإضافة في لغة C#
- أساليب الإضافة في جافا مع مانيفولد
- أساليب الإضافة في جافا باستخدام لومبوك
- أساليب الإضافة في جافا باستخدام Fluent
- وظائف الإضافة في لغة كوتلين
- طريقة (برمجة الحاسوب)
