Interface ها (واسط‌ ها) در زبان C#

09 فروردین 1398
درسنامه درس 16 از سری آموزش سی شارپ (C#)
csharp-interfaces

همانطور که در فصول گذشته مشاهده کردید کلاس‌ها و متدها را می‌توان در طی یک برنامه کنترل و محدودیت‌هایی روی آنها اعمال کرد که معمولا به مجموعه‌ی این عملیات Polymorphism گفته می‌شود. اما طی این آموزش‌ها به کلمه‌ی واسط یا Interface‌ زیاد اشاره کردیم. یکی از مهم‌ترین مباحث در زبان برنامه‌نویسی #C رابط‌ها یا واسطه‌ها هستن که در ساختار کدنویسی با نام Interface شناخته می‌شوند. در این فصل مفاهیمی که در این رابطه پوشش خواهیم داد به صورت زیر می‌باشد:

  • معرفی مفهوم واسط‌ها یا Interface ها
  • تعریف یک واسط یا Interface
  • نحوه‌ی استفاده از یک واسط
  • نحوه‌ی پیاده‌سازی ارث‌بری (Inheritance) در واسط‌ها (Interface)

واسط یا Interface چیست؟

واسط‌ها دقیقا مشابه کلاس‌ها بوده با این تفاوت که پیاده‌سازی نمی‌شوند. یعنی کد خاصی برای اجرای آنها ارائه نمی‌گردد. تنها نکته‌ای که وجود دارد: Interface ها شامل تعاریفی مانند events (رویدادها)، indexers (شاخص‌ها)، methods (متدها) و properties (ویژگی‌ها) هستند. علت اینکه واسط‌ها یا Interface کدنویسی نمی‌شود این است که آنها از کلاس‌ها یا ساختارها (struct) ارث‌بری می‌کنند.

حال که یک تعریف کلی از واسط‌ها ارائه دادیم، ممکن است این سوال برای شما پیش بیاید که interface ها چه کاربردی دارند؟ در پاسخ به این سوال باید بگوییم که interface ها قابلیت پیاده‌سازی چندین ویژگی از چندین interface مختلف را در یک کلاس یا struct در اختیار ما می‌گذارند. در ادامه بیشتر آشنا خواهید شد.

تعریف یک واسط یا interface

ساختار کلی تعریف یک واسط به صورت زیر است:

{access-modifier} interface {name}
{
    {members}
}

بنابراین در این ساختار داریم:

access-modifier:  جهت تعیین سطح دسترسی می‌باشد.

interface: به عنوان یک کلمه‌ی کلیدی در تعریف واسط بکارگرفته می‌شود.

name: به عنوان نام یک واسط شناخته می‌شود. برای تعریف یک واسط بهتر است از کلمه‌ی I در ابتدای آن استفاده کنید به عنوان مثال IName یا IMessage یا IDatabse

member: مجموعه‌ی متدها و ویژگی‌ها یا در حالت کلی اعضای واسط‌ها در این بخش قرار می‌گیرد. توجه داشته باشید که باید یک تعریف کلی برای هر عضو صورت پذیرد و سطح دسترسی اعضا در حالت عمومی (public) قرار گیرد.

برای تفهیم این موضوع یک مثال ساده می‌زنیم:

public interface INamed
{
    string Name { get; set; }

    void PrintName();
}

همانطور که در مثال فوق مشاهده می‌کنید، سطح دسترسی‌ای برای متد و خصوصیت مشخص نشده است. همچنین متد موجود در interface بدنه ندارد یا به عبارت دیگر فقط حاوی signature است. در ادامه قصد داریم از این واسط استفاده کنیم.

برای استفاده از یک واسط کافیست مانند حالتی که می‌خواهیم یک کلاس فرزند تعریف کرده تا یک سری خصوصیت و متد از کلاس والد به ارث ببرد، عمل کنیم بنابراین داریم:

public class Car : INamed
{
        
}

اما اگر این کد را به همینصورت قرار دهید با خطا مواجه خواهید شد زیرا interface تنها حاوی یک تعریف است و باید آن را در کلاسی که به ارث می‌برد تعریف کنیم:

public class Car : INamed
{
    public string Name { get; set; }
    public void PrintName()
    {
        Console.WriteLine(Name);
    }
}

اما برای استفاده از یک واسط چگونه باید عمل کرد؟ اگر یادتان باشد در محبث مربوط به ارث‌بری مطرح کردیم که اگر کلاس فرزند از کلاس وارث مشتق شود آنگاه تمام خصوصیات و ویژگی‌ها و متدهای موجود در کلاس والد را در اختیار دارد. این موضوع برای واسط‌ها نیز صادق است. در حقیقت می‌توان از واسط‌ها برای ایجاد شیء استفاده کرد:

INamed namedInstance = new Car();

اما نکته‌ی اصلی اینجاست که به هنگام استفاده از یک واسط تنها متدها و ویژگی‌هایی که در واسط تعریف‌اند در دسترس خواهند بود. یکی دیگر از قابلیت‌هایی که برای واسط مطرح می‌شود این است: در واسط‌ها امکان ارث‌بری چندگانه وجود دارد. یعنی می‌توان به صورت پیش‌فرض علاوه بر اینکه از یک کلاس یک سری خصوصیت و متد را به ارث برد، برخی دیگر را نیز از طریق واسط ها ارث‌بری کرد. یعنی می‌توان نام چند interface را روبه‌روی یک کلاس نوشت. به عنوان مثال یک واسط جدید با نام INotify ایجاد می‌کنیم:

public interface INotify
{
    void Notify();
}

بنابراین در کلاس Car که در فوق تعریف کرده‌ایم می‌توان از دو واسط به نامهای INamed و INotify‌ استفاده کرد:

public class Car : INamed, INotify
{
    public string Name { get; set; }
    public void PrintName()
    {
        Console.WriteLine(Name);
    }

    public void Notify()
    {
        Console.WriteLine("Notify me via Email!");
    }
}

تعریف یک Interface به صورت Explicit و Implicit

تمام پیاده‌سازی‌ای که در مثال قبل ملاحظه کردید به صورت Implicit بود، یعنی ابتدا Interface را تعریف کرده و سپس درون کلاسی که از آن به ارث می‌برد مجددا توابع و متدها و ویژگی‌های موجود در یک واسط را تعریف می‌کنیم. اما یک روش پیاده‌سازی دیگری به صورت Explicit وجود دارد که در آن ابتدا نام Interface موردنظر خود را آورده و سپس متد یا ویژگی موردنظر را می‌نویسم. در این حالت نیازی به تعیین سطح دسترسی برای متد‌ها نیست. بنابراین داریم:

public class Car : INamed, INotify
{
    public string Name { get; set; }
    public void PrintName()
    {
        Console.WriteLine(Name);
    }

    void INotify.Notify()
    {
        Console.WriteLine("Notify me via Email!");
    }
}

از مزیت‌های تعریف واسط‌ها به صورت Explicit این است که شما می‌توانید متدی با یک نام برای چندین Interface استفاده کنید. اما به هنگام استفاده از واسط‌های Explicit باید توجه کنید که تنها زمانی در دسترس هستند که از روی آنها شیء ساخته شود یعنی در کد زیر به متد Notify نمی‌توان دسترسی داشت:

Car carInstance = new Car();
carInstance.Notify();

و به هنگام اجرای برنامه با خطا مواجه خواهید شد بنابراین برای حل این مشکل باید به صورت زیر عمل کنید:

INotify notifyInstance = new Car();
notifyInstance.Notify();

اگر با دو اسم همنام برای یک متد بخواهیم Interface ای را مورد استفاده قرار دهیم باید مطابق فوق عمل کنیم. به مثل زیر توجه کنید:

public interface IEmailNotify
{
    void Notify();
}

public interface ISMSNotify
{
    void Notify();
}

سپس کلاس Car را به صورت زیر باز نویسی می‌‌کنیم:

public class Car : INamed, IEmailNotify, ISMSNotify
{
    public string Name { get; set; }
    public void PrintName()
    {
        Console.WriteLine(Name);
    }

    void IEmailNotify.Notify()
    {
        Console.WriteLine("Notify via Email!");
    }

    void ISMSNotify.Notify()
    {
        Console.WriteLine("Notify via SMS!");
    }
}

در نهایت به هنگام استفاده باید بر اساس نوع داده، شیء متد مربوطه فراخوانی شود:

var car = new Car();

ISMSNotify smsNotify = car;
smsNotify.Notify();
IEmailNotify emailNotify = car;
emailNotify.Notify();

مفاهیم مربوط به Interfaceها بسیار گسترده‌تر بوده که یکی از مهم‌ترین کاربردهای آنها حذف وابستگی بین کلاس‌ها یا پیاده‌سازی IoC مخفف Inversion of Control و DI یا Dependency Injection است. برای مطالعه‌ی دقیق‌تر مبحث حذف وابستگی مقاله زیر را مطالعه بفرمایید:

بسیار عالی به شما تبریک می‌گوییم که مفهومی بسیار ارزشمند چون واسط‌ها یا Interface ها را فراگرفتید. واسط‌ها کاربردهای بسیاری دارند که اکثر آنها در دو مقاله‌ی فوق ارائه شد. این سری از مجموعه‌ی آموزشی ادامه دارد. با ما همراه باشید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش سی شارپ (C#) توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما (1 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

رضا داودی
16 آبان 1399
با سلام میخواستم بابت توضیخات عالی سایتتون تشکر کنم... interface . Dependency Injection عالی بود.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.