تعدد الأشكال (Polymorphism) في C#
تعدد الأشكال (Polymorphism) أحد المبادئ الأساسية في البرمجة الموجهة للكائنات (OOP)... في لغة C# يُمكن تحقيق تعدد الأشكال Polymorphism من خلال عدة آليات مثل الوراثة (Inheritance) ، الواجهات (Interfaces) ، و التحميل الزائد (Overloading)... فكيف يتم ذلك ..؟!!
في هذا المقال، سنناقش تعدد الأشكال Polymorphism بشكل تفصيلي ونوضح كيف يمكن تطبيقه في لغة C# . سنتناول العلاقة بين الوراثة (Inheritance) و تعدد الأشكال (Polymorphism)، و دور الواجهات (Interfaces) في تحقيق تعدد الأشكال polymorphism، وكيفية استخدام التحميل الزائد (Overloading) لتطبيق تعدد الأشكال polymorphism، ويشمل المقال على أمثلة عملية تُظهرقدرة تعدد الأشكال (Polymorphism) في تصميم برامج مرنة وقابلة للتطوير…
في هذا المقال نتعرف على:
- ما هو تعدد الأشكال Polymorphism في البرمجة؟
- تطبيق مبدأ تعدد الأشكال (Polymorphism) في لغة C#.
- الوراثة (Inheritance) و تعدد الأشكال (Polymorphism) في C#.
- التحميل الزائد (Overloading) و تعدد الأشكال (Polymorphism) في C#.
- الواجهات (Interfaces) و تعدد الأشكال (Polymorphism) في C#.
ما هو تعدد الأشكال Polymorphism في البرمجة؟
تعني كلمة تعدد الأشكال Polymorphism توفرعدة أشكال عدة لغرض واحد. مثلاً الهواتف الذكية أو الشخصية تعدد أشكالها بحسب نوع الجهاز ولكن جميعها هواتف تؤدي نفس المهام. أما في البرمجة فإن تعدد الأشكال (Polymorphism) يعني إجراء نفس الوظيفة بنفس الاسم ولكن بطريقة تنفيذ مختلفة في كل مرة. بمعنى أدق إن تعدد الأشكال Polymorphism في البرمجة الموجهة للكائنات يشير إلى قدرة واجهة واحدة على أن تُستخدم للإشارة إلى تنفيذات متعددة لسلوك معين.
يمكن القول أن تعدد الأشكال Polymorphism يُشير إلى قدرة كائنات مختلفة على الاستجابة بنفس الطريقة لنفس العملية، مع اختلاف التنفيذ بناءً على نوع الكائن. بعبارة أخرى، يُتيح تعدد الأشكال كتابة كود عام يمكن تطبيقه على أنواع متعددة من الكائنات دون الحاجة إلى تعديل الكود الأساسي.
تعدد الأشكال Polymorphism كلمة يونانية تعني "- many-shaped متعدد الأشكال ". وقد تم استعارة مبدأ تعدد الأشكال Polymorphism من مبدأ في علم الأحياء حيث يمكن للكائن الحي أو النوع أن يكون له أشكال أو مراحل مختلفة عديدة. ويعني تعدد الأشكال (Polymorphism) في البرمجة الموجهة للكائنات OOP توفير واجهة واحدة للكيانات من أنواع مختلفة من التنفيذ. ويتوفر نوعان من تعدد الأشكال Polymorphism:
1. تعدد الأشكال الديناميكي (Dynamic Polymorphism):
ويعرف أيضًا باسم تعدد أشكال وقت التشغيل (Run-Time Polymorphism)، يتحقق تعدد الأشكال الديناميكي (Dynamic Polymorphism)، أثناء التنفيذ (قت التشغيل)، ما قد يعطي تأثير سلبياً على الأداء. ومن الأمثلة عليه مفهوم اعادة التعريف Overriding، حيث توفر الفئة الفرعية تنفيذًا محددًا للدالة يتم توفيرها بالفعل بواسطة فئتها الأصلية. (المزيد في مقال الوارثة في C#).
2.تعدد الأشكال الثابت (Static Polymorphism):
ويعرف أيضًا باسم تعدد أشكال وقت التجميع (Compile-Time Polymorphism)، يتحقق تعدد الأشكال الثابت (Static Polymorphism) أثناء تجميع الكود (وقت التجميع). ومن الأمثلة عليه التحميل الزائد للدوال (Methods Overloading) إحدى ميزات البرمجة الموجهة للكائنات OOP حيث يمكن أن يحتوي الـ Class على دوال متعددة بنفس الاسم ولكن بمعلمات (parameters) مختلفة. لزيادة تحميل الدوال يجب علينا تغيير عدد أو نوع المعلمات، أو كليهما.
وسواء كان تعدد الأشكال Polymorphism من النوع الثابث أو الديناميكي ، فإن تعدد الأشكال يتيح لنا التركيز على ما يفعله الكائن (object) بدلاً من نوعه، مما يسهل تطوير وصيانة التطبيقات البرمجية. فمبدأ تعدد الأشكال (Polymorphism) في البرمجة بمثابة حجر الزاوية الذي يعزز من مرونة وفعالية الكود. من خلال السماح للكائنات المختلفة بمشاركة نفس الواجهات، يمكن للمبرمجين تصميم أنظمة أكثر تنظيماً وقابلية للتوسع.
تطبيق مبدأ تعدد الأشكال (Polymorphism) في لغة C#
لغة C# اللغات الداعمة وبقوة لنهج البرمجة الموجهة للكائنات OOP، وباعتباره تعدد الأشكال (Polymorphism) الركيزة الثالثة للبرمجة الموجهة للكائنات (OOP)، بعد التغليف (Encapsulation) و الوراثة (Inheritance). تحقق لغة C# تعدد الأشكال من خلال عدد من المبادئ والمفاهيم مثل الوراثة (Inheritance) و الواجهات (interfaces) . فيما يلي نظرة على أهمها مثل :
- الوراثة (Inheritance) و تعدد الأشكال (Polymorphism) في C#.
- التحميل الزائد (Overloading) و تعدد الأشكال (Polymorphism) في C#.
- الواجهات (Interfaces) و تعدد الأشكال (Polymorphism) في C#.
لنرى كيف يتم تحقق هذه المبادئ والمفاهيم تعدد الأشكال polymorphism…
الوراثة (Inheritance) و تعدد الأشكال (Polymorphism) في C#
الوراثة أحد مبادئ البرمجة الموجهة للكائنات، الذي سبق وتكلمنا عنه في مقال الوراثة في لغة سي شارب، و تحقق الوراثة Inheritance مبدأ تعدد الأشكال polymorphism من خلال السماح بتوفير أشكال تنفيذ مختلفة للدالة في الفئات المشتقة (derived-classes)عنها في الفئة الأساسية، فمثلاً:
- مفهوم إعادة التعريف (Overriding): بواسطة العمل مع مفهوم إعادة التعريف Overriding في الوراثة (Inheritance) يمكن توفير تنفيذ جديد للدالة في الفئات المشتقة (derived-classes)، بحيث يجب أن يشمل بيان اعلان الدالة القابلة لإعادة التعريف في الـ base على الكلمات الأساسية "virtual " أو "abstract " أو "override".مما يعني هذه الدالة قابلة لإعادة التعريف (Overriding) من قبل الـ derived-classes. وبهذا يحقق مفهوم عادة التعريف Overriding تعدد الأشكال polymorphism من توفر تنفيذ مختلف للغرض واحد.
- مفهوم التجريد (Abstraction) : باستخدام التجريد (Abstraction) مع الوراثة (Inheritance) تجبر الفئات المشتقة (derived-classes) على توفير تنفيذ للدوال المجردة الموجودة في الـ Base-class مما يتح توفر تنفيذاً مختلف للدالة وهو ما يحقق تعدد الأشكال polymorphism.
- استخدام الكلمة الأساسية "new" : بواسطة استخدام الكلمة الأساسية "new" مع الوراثة (Inheritance) يمكن إخفاء العنصر الأساسي في الـ Base-class ، وتوفير نسخة جديد من العنصر في الفئات المشتقة (derived-classes). مما يتيح توفر تنفيذ مختلف للدالة وهو ما يحقق مبدأ تعدد الأشكال.
يقدم مقال الوراثة (Inheritance) في C# شرح مع الأمثلة التطبيقية على طُرق استخدام إعادة التعرف (Overriding) و التجريد (Abstraction)و الكلمة الأساسية "new" في الوراثة. كما يمكنكم الاطلاع على المزيد من الأمثلة عن إعادة التعريف Overriding و الوراثة (Inheritance) هنا.
التحميل الزائد (Overloading) و تعدد الأشكال (Polymorphism) في C#
التحميل الزائد Overloading من المفاهيم المدعومة في لغة سي شارب ويمكن أن يتوفر بصورتين هما:
التحميل الزائد للدوال Method-Overloading
ويعني التحميل الزائد للدوال Method-Overloading توفر عدة إصدارات من نفس الدالة زفي نفس النطاق لكن بمعلمات (parameters) مختلفة. بمعنى آخر، يمكنك تعريف دالة واحدة ضمن نطاق معين ولكن بإمكانيات مختلفة (مثل عدد المعلمات parameters أو أنواعها او ترتيبها يكون مختلف).
سبق وان شرحنا الـ Method-Overloading في مقال التعامل مع الدوال Methods في C# ، وفي البرمجة الموجهة للكائنات OOP، يمكن استخدام الـ Method-Overloading مع تعدد الـ Constructor كما أوضحنا في مقال نظرة أعمق على Class و Object.
المثال التالي يوضح استخدام الـ Method-Overloading على دوال الـ Class :
...........
class Printdata
{
public void print (int x){
Console.WriteLine("print an integer number: "+ x);
}
public void print (double x){
Console.WriteLine("print a float number: " + x);
}
public void print (string x){
Console.WriteLine("print a string: "+ x);
}
}
...........
المثال بالكامل متوفر هنا .
التحميل الزائد المعاملات Operator-Overloading
سي شارب يمكن ايضاً استخدام الـ Overloading مع المعاملات (Operators)، وفي هذه الحالة يسمى بـ Operator-Overloading حيث يمكن إعادة تعريف معظم المعاملات المضمنة المتوفرة في C#.
والـ Operators هي وظائف (دوال) بأسماء خاصة يتم الإعلان عنها بواسطة ستخدم الكلمة الأساسية" operator" متبوعة برمز المشغل الذي يتم تعريفه. وعلى غرار أي وظيفة أخرى، يحتوي الـ operator على نوع إرجاع وقائمة معلمات. وبيان الإعلان عن operator يجب أن يفي بالقواعد التالية:
- يتضمن كل من معدل الوصول العام (public) وان يكون عنصر الثابت (static).
- يحتوي المشغل الأحادي على parameter إدخال واحد، ويحتوي المشغل الثنائي على 2 parameters إدخال.
يوضح المثال التالي طريقة عمل الـ Overloading مع معامل حسابي (Arithmetic operator)، في المثال قمنا بإنشاء Rectangle Class الذي يحتوي حقول للطول وعرض المستطيل مع دالة لحساب مساحة المستطيل بالإضافة الى انشاء معامل operator يقوم بإرجاع Object من Rectangle Class بناء على بيانات كائنين من Rectangle Class، والذي يكون كتالي:
...........
//Create the operator of rectangle class to to add two Rectangle objects
public static Rectangle operator+ (Rectangle a, Rectangle b){
Rectangle rect_x = new Rectangle();
rect_x.Length = a.Length + b.Length;
rect_x.Width = a.Width + b.Width;
return rect_x;
}
...........
وتكون طريقة استخدام الـ operator كتالي .
...........
public static void Main(string[] args)
{
Rectangle rectangle1 = new Rectangle();
Rectangle rectangle2 = new Rectangle();
Rectangle rectangle3 = new Rectangle();
//Dimensions and area of the first rectangle
rectangle1.Length = 2.3;
rectangle1.Width = 5.1;
Console.WriteLine("Area of rectangle1 is: "+ rectangle1.Area());
//Dimensions and area of the second rectangle
rectangle2.Length = 3.2;
rectangle2.Width = 6.4;
Console.WriteLine("Area of rectangle2 is: "+ rectangle2.Area());
//Dimensions and area of the third rectangle
rectangle3 = rectangle1 + rectangle2;
Console.WriteLine("Area of rectangle3 is: "+ rectangle3.Area());
}
...........
المثال بالكامل متوفر هنا.
كما نلاحظ من المثال يمكن لأي أن Class أن يوفر تنفيذ مخصص لعملية في حالة كون أحد المتعاملين أو كليهما من هذا Class. ولكن ليست كل المعاملات قابلة للتحميل الزائد (Overloading) فيما يلي توضيح للمعاملات القابلة للتحميل الزائد (Overloading) من تلك التي لا يمكن تحميلها بشكل زائد.
نذكر تتضمن المقالات التالية شرح للعمليات الحسابية و المعاملات المتوفرة في لغة سي شارب:
- مقال التعامل مع لغة C# يتضمن شرح العمليات الحسابية Operations في C#.
- مقال الجملة الشرطية if...else في C# يتضمن شرح معاملات المقارنة (Comparison Operators) و المعاملات المنطقية (Logical Operators).
- مقال عبارة الاختيار Switch Statement في C# يتضمن شرح معاملات التعيين المركبة (Compound Assignment Operators) و معاملات الزيادة والنقصان (Increment and Decrement Operators).
أن مفهوم التحميل الزائد Overloading سواء مع للدوال Method-Overloading أو المعاملات Operator-Overloading يحقق مبدأ Polymorphism من خلال توفير واجهة واحدة لعدة أشكال من التنفيذ.
الواجهات (Interfaces) و تعدد الأشكال (Polymorphism) في C#
الواجهات (Interfaces) هي واحدة من الأدوات الأساسية في لغة C# المستخدمة في البرمجة الموجهة للكائنات (OOP). تُمثل الواجهة عقدًا (Contract) يُحدد مجموعة من الأساليب (Methods)، الخصائص (Properties)، الأحداث (Events)، والفهارس (Indexers) التي يجب أن تقوم أي فئة (Class) أو بنية (Struct) تُنفذ تلك الواجهة بتوفير تنفيذ لها.
ويمكن تعريف الواجهات (interfaces) بأنها تركيب يشابه الفئات (classes) في التكوين حيث يمكن أن تحتوي الواجهة (interface) على دوال وخصائص كعناصر لها. لكن الواجهات (interfaces) تحتوي فقط على إعلان العناصر. اما التنفيذ عاصر الواجهة يتم بواسطة الـ class الذي ينفذ الواجهة (interface) ضمناً أو بشكل مباشر.
خصائص الواجهات (interfaces) في C#
تتميز الواجهات (interfaces) في لغة سي شارب بما يلي:
- تحديد السلوك وليس التنفيذ: تحدد الواجهات ما يجب على الفئة (class) فعله وليس كيفية القيام بذلك. فالواجهة تتضمن فقط اعلان عن العناصر (Method , Property ....الخ) دون تقديم أي تنفيذ. يجب على ا فئة (Class) أو بنية (Struct) التي تُنفذ الواجهة تقديم التنفيذ الكامل للأعضاء.
- تعددية وراثة الواجهات (Multiple Inheritance): في سي شارب، من الممكن تحقق الوراثة المتعددة (Multiple Inheritance) بمساعدة الواجهات ولكن ليس مع الـ Classes . حيث ترث الـ Classes من interfaces متعددة، مما يسمح بدمج وظائف متعددة في Class واحد.
- لا تحتوي على بيانات: الواجهات لا يمكن أن تحتوي على متغيرات لأنها تمثل تنفيذًا معينًا للبيانات، بل تحتوي فقط على اعلانات عناصر.
- لا تتضمن العناصر الخاصة (private-members) : لا يمكن الواجهات interfaces أن تحتوي على أعضاء خاصة. فبشكل افتراضي، تكون جميع عناصر الواجهة عامة ومجردة.
- تستخدم الكلمة الأساسية interface: يتم تعريف الواجهات باستخدام الكلمة الأساسية interface.
- الإضافات الحديثة: بدءًا من C# 8.0، يمكن أن تحتوي الواجهات على أساليب افتراضية (Default Methods) مع تنفيذ.
في المثال التالي نرى كيف تستخدم الواجهات Interfaces في البرمجة الموجهة للكائنات OOP لتنفيذ الوراثة المتعددة (Multiple Inheritance) وتحقق مبدأ تعدد الأشكال polymorphism.
في البداية قمنا بإنشاء واجهتين لحساب مساحة ومحيط الأشكال كتالي:
...........
interface Circumference {double circumference();}
interface Area { double area();}
...........
في الـ Class الخاص بالمربع (Square) قمنا باستخدام الواجهات Interfaces الخاصة بـالمساحة والمحيط…
...........
class Square:Circumference,Area {
public double length;
public Square (double l)
{ length = l; }
public double circumference()
{ return 2 * length; }
public double area()
{ return length * length;}
public void display()
{
Console.WriteLine("Square area :{0}\nSquare circumference :{1}",area(), circumference());
}
}
...........
يمكن الواجهات Interfaces ايضاً أن تعمل مع الوارثة في المثال ورث الـ class المستطيل (Rectangle) من الـ class الشكل (Shape)..
...........
class Rectangle:Shape,Circumference,Area {
public double length;
public double Width;
public Rectangle (double l, double w){
length = l;
Width = w;
}
public double circumference()
{ return 2 * (length * Width); }
public double area()
{ return length * Width;}
public override void display()
{
Console.WriteLine("Rectangle area :{0}\nRectangle circumference :{1}",area(), circumference());
}
...........
المثال بالكامل متوفرهنا.
من المثال السابق نرى أن العمل مع الواجهات يتشابه مع عم الفئات المجردة Abstract class التي تعرفنا عليها في مقال الوارثة في سي شارب، لكن ثمة فروق بين طريقة عمل الواجهات والفئات المجردة أهمها مايلي :
الميزة | الواجهة (Interface) | الفئة المجردة (Abstract Class) |
---|---|---|
الغرض | (الوظائف)تحديد عقد للسلوكيات. | تحديد هيكل عام مع إمكانية تقديم تنفيذ جزئي. |
البيانات | لا يمكن أن تحتوي على حقول بيانات. | يمكن أن تحتوي على حقول بيانات . |
الوراثة المتعددة | يمكن لفئة أن ترث من عدة واجهات. | يمكن لفئة أن ترث من فئة مجردة واحدة فقط. |
التنفيذ الافتراضي | بدءًا من C# 8.0 يمكن أن تحتوي على أساليب افتراضية مع تنفيذ. | يمكن أن تحتوي على أساليب عادية مع تنفيذ. |
أهمية الواجهات Interfaces في البرمجة
تتعدد أهمية الواجهات (Interfaces) في البرمجة ومنها مايلي:
- إجبار الـ Classes على تنفيذ سلوك معين: تُستخدم الواجهات لتحديد مجموعة من القواعد التي يجب أن تتبعها الفئات (classes) التي تنفذها.
- تحقيق تعدد الأشكال (Polymorphism): من خلال الواجهات (interfaces) يمكن التعامل مع كائنات مختلفة (من فئات مختلفة) بطريقة موحدة.
- إعادة استخدام الكود: تُسهل الواجهات تصميم الأنظمة البرمجية الكبيرة، حيث يمكن دمج السلوكيات (الوظائف) من واجهات مختلفة في فئة (class) واحدة.
- تصميم مرن وقابل للتوسع: باستخدام الواجهات (interfaces)، يمكن إضافة سلوكيات جديدة (وظائف جديدة) بسهولة دون تعديل الكود الموجود.
تعتبر الواجهات (interfaces) وسيلة قوية لتحقيق تعدد الأشكال Polymorphism ، كما أنها تُستخدم في تصميم الأنظمة البرمجية الكبيرة لجعل الكود أكثر مرونة وقابلية للتوسع والصيانة.
إن تعدد الأشكال (Polymorphism) من مبادئ البرمجة الموجهة للكائنات (OOP) الأساسية ، الذي يُتيح توفر أسلوب تصميم يُسهم في كتابة كود أكثر وضوحًا و تنظيمًا و قابلية للتوسع. فمن خلال تعدد الأشكال polymorphism يمكن تصميم برامج تعمل على أنواع متعددة من الكائنات باستخدام واجهات موحدة، مما يُسهل عملية الصيانة والتطوير.و سواء كنت تستخدمه مع الوراثة (Inheritance) أو التحميل الزائد (Overloading) أو الواجهات (interfaces) فإن فهم الـ polymorphism وتطبيقه بشكل صحيح يُعد مهارة أساسية لأي مبرمج يسعى لتطوير برمجيات قوية.