تفاوت بین Interface و کلاس Abstract در چیست؟
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: چهار دقیقه

یکی از سوالات مصاحبه‌ای که اکثر مواقع پرسیده میشود، تفاوت Interface و  Abstract class می‌باشد؛ امیدوارم این مقاله برای شما مفید باشد.

Interface چیست ؟ 
به طور کلی  Interfaceها  یک قالب اجرائی برای کلاسها می‌باشند. بدین صورت که با تعریف مشخصات کلی متدها، بدون پیاده سازی آنها، کلاسهای مشتق شده را ملزم به پیاده سازی کامل آن متدها میکند. بنابراین فقط مشخصات متدها یک بار در Interface تعریف می‌شوند و هر جا که لازم باشد پس از ارث بری، متدهای آنها پیاده سازی می‌شوند. در کلیه نسخ دات نت، Interface‌ها با حرف I شروع میشوند و با این خصیصه از دیگر اجزاء، جدا و مشخص می‌شوند. تعریف آن بسیار شبیه کلاس‌ها میباشد؛ ولی با همان تفاوت که در بالا ذکر شد، یعنی متدهای آن‌ها فاقد کد است. اینترفیس‌ها سازنده و فیلد هم ندارند و نمی‌شود از روی آنها نمونه‌ای ایجاد کرد. 
 

مزایای  Interface ‌ها چیست ؟

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

 

Abstract Class چیست ؟

کلاس Abstract، یکی از ابزارهای مهم OOP می‌باشد که نمی‌توان از آنها نمونه‌ای ساخت. به عبارتی دیگر نمی‌توانیم متغیری از کلاس Abstract تعریف کنیم. یک کلاس Abstract  شبیه Interface میباشد ولی با دیدی وسیعتر. این کلاسها می‌تواند دارای متدهای Abstract باشند که شبیه Interface فقط اعلام میشوند و باید در کلاسهای مشتق شده بازنویسی شوند. البته میتوان در این کلاسها متدهایی داشت که Abstract نیستند و احتیاجی به پیاده سازی آنها در کلاسهای مشتق شده ندارند.

باید توجه داشت که تنها متدهایی از کلاس abstract الزام به پیاده سازی دارند که صریحا کلمه‌ی abstract در تعریف آن متد ذکر شده باشد.
در واقع همین متد‌ها هم الزامی به پیاده سازی ندارند. یعنی می‌شود در subclass هم به صورت abstract ذکر شوند. البته به شرطی که subclass هم به صورت abstract تعریف شده باشد.
در ضمن کلاس abstract میتواند متد‌های ساده یا غیر abstract هم داشته باشد. همانطور که میدانید متد‌های غیر abstract باید بدنه داشته باشند و نیازی به پیاده سازی ندارند.
پس کلاس abstract هم میتواند متدهایی داشته باشد که باید پیاده سازی شوند و هم متدهایی داشته باشد که لازم نباشد پیاده سازی شوند.

با توجه به تعاریف ذکر شده کلاس Abstract  حالتی بین کلاسهای معمولی و Interface‌ها میباشد و کلاسی میباشد که غیر قطعی و ناتمام است که باید در سطح فرزندانش تکمیل شود .

 

 مزایای کلاسهای  Abstract چیست ؟

یکی از مزیت‌های کلاس Abstract  فراهم نمودن کلاسی پایه برای دیگر کلاسهای مشتق شده است؛ با این توضیح که متدهای آن می‌توانند کد نویسی شده باشند یا خیر. از طرفی پیاده سازی تمام متدهای Abstract در کلاس مشتق شده اجباری نیست (برخلاف Interface).

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

  

 تفاوت بین کلاسهای  Abstract  و  Interface

1- یک کلاس معمولی تنها می‌تواند از یک کلاس Abstract ارث بری کند ولی همان کلاس میتواند از چندین Interface ارث ببرد.

2- یک Interface  فقط میتواند اعلان متدها و خصوصیتها را داشته باشد؛ اما یک کلاس Abstract  علاوه بر آنها میتوانید متدها و خصوصیتهایی با کدهای کامل داشته باشد.

3- عناصر موجود در کلاس Abstract میتوانند مانند یک کلاس معمولی دارای سطح دسترسی باشند؛ ولی Interface‌ها فاقد این امکان هستند.

4- وقتی شما متدی را به کلاس Abstract اضافه می‌کنید، به طور خودکار به همه زیر کلاسها اعمال می‌شود؛ اما در Interface اگر متدی اضافه کنید باید در تمام زیر کلاسها آن را اعمال کنید .

5- کلاس‌های Abstract مانند کلاسهای معمولی می‌توانند دارای فیلد و عناصر دیگری (مثل ثابت‌ها) باشند؛ در حالیکه یک Interface فاقد این امکان می‌باشد. همچنین کلاس abstract میتواند شامل سازنده باشد، اما اینترفیس نمیتواند.

6- Abstract  یکی از انواع کلاس است؛ ولی Interface کلاس نیست .

7- اینترفیس تنها میتواند از اینترفیس ارث بری کند اما کلاس abstract میتواند از اینترفیس، کلاس Abstract و یا سایر کلاس‌ها ارث بری کند. 

  

چه زمانی از  Interface ‌ها و یا کلاسهای  Abstract  استفاده کنیم؟

- با توجه به توضیحات ذکر شده  مواقعی که نیاز به وراثت چند گانه داریم، باید از Interface استفاده کنیم؛ به دلیل اینکه این امکان در کلاس‌های Abstract  وجود ندارد.

- زمانی که بخواهیم تمام متدهای معرفی شده در کلاس پایه به طور کامل در کلاس مشتق شده پیاده شوند باید از Interface استفاده کنیم.

- وقتی در پروژه‌های بزرگ با تغییرات زیادی مواجه هستیم، استفاده از کلاس Abstract  توصیه می‌شود؛ چون با تغییر آن به طور خودکار تغییرات در کلاسهای مشتق شده اعمال می‌شوند.

- با توجه به اینکه به غیر از اعلان متدها و خصوصیت‌ها امکان تعریف عناصر دیگری در Interfaceها وجود ندارد، در صورتیکه ملزم به استفاده  از این عناصر باشیم، استفاده از کلاسهای Abstract  ضروری می‌باشد.

- در صورتی که نخواهیم کلیه متد‌ها در کلاس‌های مشتق شده پیاده سازی شوند و تعدادی از آنها را در کلاس پدر کدنویسی کنیم، باید از کلاس Abstract استفاده کنیم.

- به طور کلی یک Interface چارچوب و قابلیتهای یک کلاس را مشخص میکند و یک قرارداد است؛ ولی کلاس Abstract نوع کلاس را معین می‌کند. این تفاوت کمک بسیاری برای تشخیص زمان استفاده از این دو را به برنامه نویسان میدهد.

  • #
    ‫۳ سال و ۷ ماه قبل، پنجشنبه ۲ بهمن ۱۳۹۹، ساعت ۱۴:۴۵
    به روز رسانی نکات مطرح شده‌ی در این مطلب، بر اساس امکانات C# 8.0 به بعد

    1- یک کلاس معمولی تنها می‌تواند از یک کلاس Abstract ارث بری کند ولی همان کلاس میتواند از چندین Interface ارث ببرد.
    وضعیت فعلی: هنوز هم برقرار است. اما چون اینبار اینترفیس‌ها هم می‌توانند به همراه کد باشند، می‌توان به multiple inheritance از طریق اینترفیس‌ها نیز رسید. هنگامیکه پیاده سازی‌های پیش‌فرض اینترفیس‌ها فراخوانی می‌شوند، فراخوان باید نوع اینترفیس متناظر را هم دقیقا مشخص کند. این ویژگی، مشکل معروفی را به نام «diamond problem»، که در ارث بری چندگانه‌ی از کلاس‌های پایه در زبان‌های دیگر وجود دارد، حل می‌کند: زمانیکه دو یا چند کلاس پایه، متد هم نامی را داشته باشند.
     
    2- یک Interface  فقط میتواند اعلان متدها و خصوصیتها را داشته باشد؛ اما یک کلاس Abstract  علاوه بر آنها میتوانید متدها و خصوصیتهایی با کدهای کامل داشته باشد.
    وضعیت فعلی: دیگر اینطور نیست. اینترفیس‌ها در C# 8.0 می‌توانند به همراه «default implementation» هم باشند.


    3- عناصر موجود در کلاس Abstract میتوانند مانند یک کلاس معمولی دارای سطح دسترسی باشند؛ ولی Interface‌ها فاقد این امکان هستند.
    وضعیت فعلی: دیگر اینطور نیست. از زمان C# 8.0 به بعد، اعضای یک اینترفیس هم می‌توانند دارای access modifiers باشند. حالت پیش‌فرض، public است. اما اکنون اینترفیس‌ها می‌توانند دارای اعضای private هم باشند که فقط داخل همان اینترفیس قابل دسترسی هستند. این مورد برای خواناتر کردن پیاده سازی‌های پیش‌فرض متدها در اینترفیس‌ها می‌تواند مفید باشد. در اینجا امکان تعریف اعضای protected نیز میسر است.


    4- وقتی شما متدی را به کلاس Abstract اضافه می‌کنید، به طور خودکار به همه زیر کلاسها اعمال می‌شود؛ اما در Interface اگر متدی اضافه کنید باید در تمام زیر کلاسها آن را اعمال کنید . 

    وضعیت فعلی: دیگر اینطور نیست؛ به علت امکان تعریف «default implementation» در اینترفیس‌ها.

    5- کلاس‌های Abstract مانند کلاسهای معمولی می‌توانند دارای فیلد و عناصر دیگری (مثل ثابت‌ها) باشند؛ در حالیکه یک Interface فاقد این امکان می‌باشد. همچنین کلاس abstract میتواند شامل سازنده باشد، اما اینترفیس نمیتواند.
    وضعیت فعلی: هنوز هم تا حدودی برقرار است. با تغییرات زبان #C، اکنون اینترفیس‌ها می‌توانند دارای فیلدها، سازنده‌ها و تخریب‌گرهای استاتیک هم باشند. یک فیلد استاتیک، فیلدی است که مقدار آن متعلق به یک وهله‌ی خاص نیست (هنوز هم نمی‌توان instance filed در اینجا داشت) و به اشتراک گذاشته می‌شود (دقیقا مانند فیلدهای استاتیک کلاس‌ها عمل می‌کند).
  • #
    ‫۱ سال و ۷ ماه قبل، چهارشنبه ۱۴ دی ۱۴۰۱، ساعت ۰۳:۵۳
    با سلام؛ چرا زمانیکه یک کلاس abstract از نوع جنریک، از یک اینترفیس ارث بری میکنه، الزامی به پیاده سازی متدهای اینترفیس وجود نداره و اگر بخواهیم پیاده سازی پیشفرضی رو از متدهای اینترفیس در کلاس abstract داشته باشیم، چگونه باید عمل کرد؟
    • #
      ‫۱ سال و ۷ ماه قبل، چهارشنبه ۱۴ دی ۱۴۰۱، ساعت ۱۴:۲۶
      اتفاقا اگر یک کلاس abstract از یک اینترفیس ارث‌بری کند، باید تمام متدهای آن اینترفیس را هم ذکر و هم پیاده سازی کند. اما با استفاده از واژه‌ی کلیدی abstract می‌توان قسمت پیاده سازی را به پیاده ساز کلاس abstract واگذار کرد:
      interface IFoo
      {
          void Bar();
      }
      
      abstract class Foo : IFoo
      {
          public abstract void Bar();
      }
      به شخصه در طی این چند سال، حتی یکبار چنین ترکیبی را ندیده‌ام. صرف وجود قابلیتی، دلیل بر استفاده‌ی از آن نیست. عموما یا از اینترفیس استفاده می‌کنند و یا از کلاس‌های abstract و نه از هر دو با هم. اگر هم از کلاس‌های abstract به‌ندرت استفاده شده و می‌شود، تنها و تنها، جائی بوده که برای مثال در فریم‌ورک MVC، نخواسته‌اند با تغییر ساختار کلاس پایه‌ی Controller، چند ده‌هزار برنامه نویس استفاده کننده‌ی از آن‌را با وادار کردن به پیاده سازی اجزای یک اینترفیس تغییر یافته، ناراحت کنند (چون در آن زمان فقط کلاس‌های abstract، امکان داشتن پیاده سازی پیش‌فرض را داشتند) که البته این محدودیت را هم بعدها با امکان تعریف «default implementation» در اینترفیس‌ها، رفع کردند. یعنی بیشتر در اینجا رسیدگی به وضعیت روحی و روانی استفاده کننده‌ها با بالابردن درجه‌ی سازگاری فریم‌ورک‌های جدید با نگارش‌های قدیمی آن‌ها مدنظر بوده و نه بازی با مفاهیم.
      • #
        ‫۱ سال و ۷ ماه قبل، شنبه ۱۷ دی ۱۴۰۱، ساعت ۱۳:۵۷
        بله درست می‌فرمائید .
        منتها پیاده سازی الگوی Repository فریم ورک Abp پر است از  ترکیب استفاده از Interface و Abstract !
        در نظر بگیرید اگر بخواهیم رویکردی داشته باشیم تا از این الگو در انواع Orm‌ها و یا انواع دیتابیس‌ها استفاده کنیم استفاده از این رویکرد درست نیست؟
        سناریوی زیر رو در نظر بگیرید :
        public interface IRepositoryBase<TEntity> : IReadOnlyRepositoryBase<TEntity>
            where TEntity : class, IEntity
        {
            Task<TEntity> InsertAsync([NotNull] TEntity entity);
            Task InsertManyAsync([NotNull] IEnumerable<TEntity> entities);
            Task<TEntity> UpdateAsync([NotNull] TEntity entity);
            Task UpdateManyAsync([NotNull] IEnumerable<TEntity> entities);
            // Todo InsertOrUpdate(TEntity entity);
            Task DeleteAsync([NotNull] TEntity entity);
            Task DeleteManyAsync([NotNull] IEnumerable<TEntity> entities);
        
        }
        
        public interface IRepositoryBase<TEntity, TKey> : IRepositoryBase<TEntity>, IReadOnlyRepositoryBase<TEntity, TKey>
            where TEntity : class, IEntity<TKey>
        {
            Task DeleteAsync(TKey id);  //TODO: Return true if deleted
            Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids);
        
        
        }

        مثلا در لایه MyFrameWork.Infratructure.EfCore  یه پیاده سازی پیش فرض از این اینترفیس‌ها در کلاس abstract EfCoreRepositoryBase داشته باشیم تا نخواهیم در کلاس‌های Repository بیزینس دامین مون هر سری یک سری متد‌ها رو پیاده سازی کنیم ؟ مثلا در IUserRepository

        • #
          ‫۱ سال و ۷ ماه قبل، شنبه ۱۷ دی ۱۴۰۱، ساعت ۱۴:۳۴
          نیازی به تکرار سابقه‌ی امکان داشتن تعاریف پیش‌فرض در کلاس‌های abstract و بعد از چندسال فراهم شدن مانند آن در اینترفیس‌ها، وجود همزمان طراحی‌های قدیمی و جدید و همچنین مفهوم، امکانات و کاربرد کلاس پایه‌ی Controller ای که به آن اشاره شد، نیست. همچنین مطالبی در مورد Overengineering و Generic repository‌ها هم مهم هستند. مطلب عنوان شده، دعوت به پرهیز از Overengineering  بود که اگر اینترفیس‌ها قراردادی را تعریف می‌کنند و کلاس‌های abstract هم می‌توانند مانند آن‌ها هم قراردادی را تعریف کرده و هم پیاده سازی‌های پیش‌فرضی را ارائه دهند که شبیه به تعریف قالب‌ها عمل می‌کنند، چه نیازی است تا در بالای یک کلاس abstract، یک interface را هم به عنوان قرار داد پایه تعریف کرد؛ درحالیکه این کلاس abstract باید تک تک اعضای این اینترفیس را مجددا به نحوی تکرار کند؛ یا باید آن‌ها را پیاده سازی کند (تکرار با پیاده سازی و به معنای تعریف یک قالب برای کلاسی که قرار است از آن مشتق شود) و یا باید توسط واژه‌ی کلیدی abstract، پیاده سازی آن‌‌ها را به کلاس مشتق شده‌ی از آن واگذار کند (تکرار با واگذاری و به معنای تعریف یک قرارداد، شبیه به تکرار اجباری اعضای همان اینترفیسی که از آن مشتق شده!).