Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

الواجهات الصريحة

جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 28 من 70.

عندما تقوم فئة (class) بتنفيذ واجهات (interfaces) متعددة تحتوي على أعضاء بنفس الاسم، فإنك تواجه تعارضاً في التسمية. يحل التنفيذ الصريح للواجهة (Explicit interface implementation) هذه المشكلة من خلال السماح لك بتقديم تنفيذات منفصلة لكل واجهة.

مع التنفيذ الصريح، تقوم بوضع اسم الواجهة كبادئة لاسم العضو وتحذف محدد الوصول:

public interface IAmericanPlug
{
    void Connect();
}

public interface IEuropeanPlug
{
    void Connect();
}

public class UniversalAdapter : IAmericanPlug, IEuropeanPlug
{
    void IAmericanPlug.Connect()
    {
        Console.WriteLine("Connected to 120V");
    }
    
    void IEuropeanPlug.Connect()
    {
        Console.WriteLine("Connected to 220V");
    }
}

يمكن الوصول إلى الأعضاء المنفذين بشكل صريح فقط من خلال متغير من نوع الواجهة (interface)، وليس من خلال الفئة (class) نفسها:

var adapter = new UniversalAdapter();
// adapter.Connect();  // لن يتم التجميع!

IAmericanPlug american = adapter;
american.Connect();  // متصل بـ 120 فولت

IEuropeanPlug european = adapter;
european.Connect();  // متصل بـ 220 فولت

تعد هذه التقنية مفيدة أيضاً عندما تريد إخفاء أعضاء الواجهة (interface members) من واجهة برمجة التطبيقات (API) العامة للفئة. فإذا كانت الطريقة (method) منطقية فقط عند التعامل مع الكائن كواجهة محددة، فإن التنفيذ الصريح (explicit implementation) يحافظ على نظافة مساحة سطح الفئة الخاصة بك مع الاستمرار في الوفاء بالعقد (contract).

challenge icon

التحدي

سهل

لنقم ببناء نظام منشئ تقارير يوضح كيف يسمح التنفيذ الصريح للواجهة (explicit interface implementation) لفئة واحدة بتقديم سلوكيات مختلفة اعتماداً على الواجهة المستخدمة. ستقوم بإنشاء مستند يمكن تنسيقه بشكل مختلف للعرض مقابل الطباعة.

ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:

  • IDisplayable.cs: قم بتعريف واجهة تسمى IDisplayable داخل مساحة الأسماء Reports. تمثل هذه الواجهة المحتوى الذي يمكن عرضه على الشاشة. يجب أن تصرح عن طريقة Render() التي تعيد سلسلة نصية (string).
  • IPrintable.cs: قم بتعريف واجهة تسمى IPrintable داخل مساحة الأسماء Reports. تمثل هذه الواجهة المحتوى الذي يمكن إرساله إلى الطابعة. يجب أن تصرح أيضاً عن طريقة Render() تعيد سلسلة نصية - لاحظ أن هذا يؤدي إلى تصادم في الأسماء مع IDisplayable!
  • Report.cs: قم بتعريف فئة Report في مساحة الأسماء Reports التي تنفذ كلاً من IDisplayable و IPrintable. يجب أن يحتوي التقرير على خاصية Title (string) وخاصية Content (string)، مع مشيد (constructor) يضبط كلتا القيمتين. استخدم التنفيذ الصريح للواجهة لتوفير سلوكيات Render() مختلفة:
    • عند الرندر للعرض: "[SCREEN] {Title}: {Content}"
    • عند الرندر للطباعة: "[PRINT] {Title} | {Content}"
  • Program.cs: في ملفك الرئيسي، قم بإنشاء Report باستخدام قيم الإدخال. نظراً لأنه لا يمكن الوصول إلى التنفيذات الصريحة إلا من خلال متغيرات الواجهة، قم بتخزين نفس التقرير في كل من متغير IDisplayable ومتغير IPrintable، ثم استدعِ Render() على كل منهما لإظهار كيف ينتج نفس الكائن مخرجات مختلفة بناءً على الواجهة التي تستخدمها.

ستتلقى مدخلين:

  • عنوان التقرير (Report title)
  • محتوى التقرير (Report content)

اطبع المخرجات بهذا التنسيق:

Display version:
{Render() via IDisplayable}
Print version:
{Render() via IPrintable}

على سبيل المثال، إذا كانت المدخلات هي Sales Report و Q4 revenue increased by 15%، يجب أن تكون المخرجات:

Display version:
[SCREEN] Sales Report: Q4 revenue increased by 15%
Print version:
[PRINT] Sales Report | Q4 revenue increased by 15%

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

ورقة مرجعية

عندما تقوم فئة (class) بتنفيذ واجهات (interfaces) متعددة تحتوي على أعضاء بنفس الاسم، يمكنك استخدام التنفيذ الصريح للواجهة (explicit interface implementation) لتوفير تنفيذات منفصلة لكل واجهة.

لتنفيذ عضو واجهة بشكل صريح، قم ببادئة اسم العضو باسم الواجهة واحذف معدل الوصول (access modifier):

public interface IAmericanPlug
{
    void Connect();
}

public interface IEuropeanPlug
{
    void Connect();
}

public class UniversalAdapter : IAmericanPlug, IEuropeanPlug
{
    void IAmericanPlug.Connect()
    {
        Console.WriteLine("Connected to 120V");
    }
    
    void IEuropeanPlug.Connect()
    {
        Console.WriteLine("Connected to 220V");
    }
}

لا يمكن الوصول إلى الأعضاء المنفذة بشكل صريح إلا من خلال متغير من نوع الواجهة، وليس من خلال الفئة نفسها:

var adapter = new UniversalAdapter();
// adapter.Connect();  // لن يتم التجميع!

IAmericanPlug american = adapter;
american.Connect();  // Connected to 120V

IEuropeanPlug european = adapter;
european.Connect();  // Connected to 220V

هذه التقنية مفيدة لـ:

  • حل تعارض الأسماء بين أعضاء الواجهة
  • إخفاء أعضاء الواجهة من واجهة برمجة التطبيقات العامة (public API) للفئة عندما لا يكون لها معنى إلا في سياق واجهة محددة

جرّب بنفسك

using System;
using Reports;

class Program
{
    public static void Main(string[] args)
    {
        // قراءة المدخلات
        string title = Console.ReadLine();
        string content = Console.ReadLine();

        // TODO: إنشاء كائن Report بالعنوان والمحتوى

        // TODO: تخزين التقرير في متغير IDisplayable

        // TODO: تخزين نفس التقرير في متغير IPrintable

        // TODO: طباعة نسخة العرض عن طريق استدعاء Render() على IDisplayable
        Console.WriteLine("Display version:");
        // اطبع نتيجة العرض هنا

        // TODO: طباعة نسخة الطباعة عن طريق استدعاء Render() على IPrintable
        Console.WriteLine("Print version:");
        // اطبع نتيجة العرض هنا
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming