oop مشاريع واجهة مقابل الطبقة الأساسية




مشاريع الفيجوال بيسك جاهزة (20)

متى ينبغي علي استخدام واجهة ومتى يجب عليّ استخدام فئة أساسية؟

هل يجب أن تكون واجهة دائمًا إذا كنت لا أرغب في تحديد تطبيق أساسي لهذه الأساليب؟

إذا كان لدي فئتي كلب وقط. لماذا أرغب في تنفيذ IPet بدلاً من PetBase؟ أستطيع أن أفهم وجود واجهات ل ISheds أو إيباركس (IMakesNoise؟) ، لأنه يمكن وضعها على الحيوانات الأليفة على أساس الحيوانات الأليفة ، ولكن أنا لا أفهم ما لاستخدامه لحيوان عام.

https://code.i-harness.com


واجهات

  • يعرف العقد بين وحدتين. لا يمكن أن يكون أي تنفيذ.
  • تسمح لك معظم اللغات بتطبيق واجهات متعددة
  • تعديل واجهة هو تغيير كسر. جميع التطبيقات تحتاج إلى إعادة تعديل / تعديل.
  • جميع الأعضاء هم العامة. يجب أن تنفذ التنفيذ جميع الأعضاء.
  • واجهات مساعدة في فصل. يمكنك استخدام أطر زائفة للتخلي عن أي شيء وراء واجهة
  • تشير الواجهات عادةً إلى نوع من السلوك
  • يتم فصل تطبيقات الواجهة / عزلها عن بعضها البعض

الطبقات الأساسية

  • يسمح لك بإضافة بعض التطبيقات الافتراضية التي تحصل عليها مجانًا عن طريق الاشتقاق
  • باستثناء C ++ ، يمكنك أن تشتق من فئة واحدة فقط. حتى لو كان من طبقات متعددة ، فعادة ما تكون فكرة سيئة.
  • تغيير الفئة الأساسية سهل نسبيًا. الاشتقاقات لا تحتاج إلى القيام بأي شيء خاص
  • يمكن للفئات الأساسية أن تقوم بتعريف الوظائف المحمية والعامة التي يمكن الوصول إليها عن طريق الاشتقاقات
  • لا يمكن سخرية الطبقات الأساسية بسهولة مثل واجهات
  • تشير الفئات الأساسية عادة إلى التسلسل الهرمي للنوع (IS A)
  • قد تعتمد اشتقاقات الفئة على بعض السلوك الأساسي (لديها معرفة معقدة بتنفيذ الوالدين). يمكن أن تكون الأمور فوضوية إذا قمت بإجراء تغيير في التنفيذ الأساسي لرجل واحد وكسر الآخرين.

أحد الاختلافات المهمة هو أنه يمكنك فقط ترميز فئة أساسية واحدة ، ولكن يمكنك تنفيذ العديد من الواجهات. لذا فأنت تريد فقط استخدام فئة أساسية إذا كنت متأكدًا تمامًا من أنك لن تحتاج أيضًا إلى أن ترث فئة أساسية مختلفة. بالإضافة إلى ذلك ، إذا وجدت أن واجهتك كبيرة للغاية ، فيجب عليك البدء في تقسيمها إلى بعض الأجزاء المنطقية التي تحدد الوظائف المستقلة ، بما أنه لا توجد قاعدة بأن فصلك لا يستطيع تنفيذها بالكامل (أو أنه بإمكانك تعريف مختلف واجهة فقط يرث لهم جميعا لتجميعها).


التعليقات السابقة حول استخدام الفصول التجريدية للتنفيذ المشترك هي بالتأكيد على العلامة. إحدى الفوائد التي لم أرها حتى الآن هي أن استخدام الواجهات يجعل من الأسهل بكثير تنفيذ الكائنات الوهمية بغرض اختبار الوحدة. إن تحديد IPet و PetBase كما وصفه Jason Cohen يمكّنك من تسخين شروط بيانات مختلفة بسهولة ، دون تحميل قاعدة بيانات فعلية (حتى تقرر أن الوقت قد حان لاختبار الشيء الحقيقي).


النمط الحديث هو تعريف IPet و PetBase.

وتتمثل ميزة الواجهة في أن الشفرة الأخرى يمكنها استخدامها دون أي روابط على الإطلاق إلى تعليمات برمجية أخرى قابلة للتنفيذ. تماما "نظيفة". كما يمكن أن تكون واجهات مختلطة.

لكن الفصول الأساسية مفيدة للتطبيقات البسيطة والمرافق العامة. لذلك ، قم بتوفير فئة أساسية مجردة لتوفير الوقت والرمز.


تم شرح الحالة الخاصة "فئات أساسية عبر واجهات" بشكل جيد في إرشادات الترميز Submain .NET:

الفصول الأساسية مقابل واجهات نوع الواجهة هو وصف جزئي لقيمة ، يمكن أن يدعمها العديد من أنواع الكائنات. استخدم الفئات الأساسية بدلاً من واجهات كلما أمكن ذلك. من منظور الإصدارات ، تكون الفئات أكثر مرونة من الواجهات. مع فصل دراسي ، يمكنك شحن الإصدار 1.0 ، ثم في الإصدار 2.0 إضافة طريقة جديدة للفئة. وطالما لم تكن الطريقة مجردة ، فإن أي فئات مشتقة موجودة تستمر في العمل دون تغيير.

نظرًا لأن الواجهات لا تدعم توريث التنفيذ ، لا ينطبق النمط الذي ينطبق على الفصول على الواجهات. يكافئ إضافة أسلوب إلى واجهة إضافة أسلوب مجردة إلى فئة أساسية؛ سيتم فصل أي فئة تقوم بتنفيذ الواجهة لأن الفئة لا يقوم بتطبيق الأسلوب الجديد. واجهات مناسبة في الحالات التالية:

  1. عدة فئات غير ذات صلة تريد دعم البروتوكول.
  2. لدى هذه الفئات بالفعل فئات أساسية (على سبيل المثال ، بعض عناصر تحكم واجهة المستخدم (UI) ، وبعضها خدمات ويب XML).
  3. التجميع غير مناسب أو عملي. في جميع الحالات الأخرى ، يعد الميراث الطبقي نموذجًا أفضل.

تمثل الواجهات والفئات الأساسية شكلين مختلفين من العلاقات.

تمثل الوراثة (الفئات الأساسية) علاقة "is-a". على سبيل المثال ، كلب أو قطة "is-a" محبوبة. هذه العلاقة تمثل دائما الهدف (واحد) للفئة (بالتزامن مع "مبدأ المسؤولية الواحدة" ).

على الجانب الآخر ، تمثل الواجهات ميزات إضافية للفئة. كنت أسميها علاقة "هي" ، كما هو الحال في " Foo يمكن التخلص منها" ، وبالتالي واجهة IDisposable في C #.


خوان،

أود أن أفكر في واجهات كطريقة لتوصيف فئة. قد تكون فئة سلالة معينة من الكلاب ، على سبيل المثال ، YorkshireTerrier ، منحدرة من فئة الكلاب الأم ، ولكنها أيضا تنفذ IFurry و IStubby و IYippieDog. لذا ، يحدد الفصل ما هو الفصل الدراسي ولكن الواجهة تخبرنا بأشياء حول هذا الموضوع.

ميزة هذا هو أنه يسمح لي ، على سبيل المثال ، جمع كل IYippieDog ورميها في مجموعتي المحيط. حتى الآن يمكنني الوصول عبر مجموعة معينة من الكائنات والعثور على تلك التي تفي بالمعايير التي أبحث عنها دون فحص الصف عن كثب.

أجد أن واجهات يجب أن تحدد حقا مجموعة فرعية من السلوك العام للفصل. إذا حددت جميع السلوكيات العامة لجميع الطبقات التي تنفذ ، فإنها عادة لا تحتاج إلى الوجود. لا يقولون لي أي شيء مفيد.

على الرغم من أن هذا الفكر يتعارض مع الفكرة القائلة بأن كل صف يجب أن يكون له واجهة ويجب عليك أن ترمز إلى الواجهة. هذا أمر جيد ، ولكن ينتهي بك الأمر مع وجود الكثير من واجهات إلى فصول واحدة ، مما يجعل الأمور مربكة. أتفهم أن الفكرة هي أنها لا تكلف فعلًا أي شيء ، ويمكنك الآن تبديل الأشياء من الداخل والخارج بسهولة. ومع ذلك ، أجد أنني نادرا ما أفعل ذلك. في معظم الأوقات ، أقوم فقط بتعديل الطبقة الموجودة في المكان ولديها نفس المشكلات التي كنت أفعلها دائمًا إذا كان من الضروري تغيير الواجهة العامة لهذا الفصل ، إلا أنه يتعين عليّ الآن تغييرها في مكانين.

لذلك إذا كنت تفكر مثلي فإنك بالتأكيد ستقول أن Cat and Dog قابلان للعيش. إنه توصيف يتطابق مع كليهما.

قطعة أخرى من هذا على الرغم من أنه ينبغي أن يكون لديهم نفس الطبقة الأساسية؟ السؤال هو أنهم بحاجة إلى أن يعاملوا على نطاق واسع على أنه الشيء نفسه. بالتأكيد كلاهما حيوانات ، ولكن هل هذا يناسب كيف سنستخدمها معًا.

قل أرغب في جمع كل دروس الحيوانات ووضعها في حاوية Ark.

أم أنهم بحاجة إلى أن يكونوا ثديين؟ ربما نحن بحاجة إلى نوع من مصنع حلب الحيوانات عبر؟

هل يحتاجون حتى إلى الارتباط معًا على الإطلاق؟ هل يكفي أن تعرف فقط أنها قابلة للفتح؟

غالبًا ما أشعر بالرغبة في استنباط تسلسل هرمي كامل للفصل الدراسي عندما أحتاج حقاً إلى فصل واحد. أفعل ذلك تحسبا يوما ما قد أحتاج إليها وعادة ما أفعل أبدا. حتى عندما أقوم بذلك ، عادة ما أجد أنه يجب علي فعل الكثير لإصلاحه. ذلك لأن الطبقة الأولى التي أخلقها ليست الكلب ، أنا لست محظوظة ، بل هي خلد الماء. الآن يستند بلدي التسلسل الهرمي الفصل بأكمله على الحالة الغريبة ولدي الكثير من رمز تهدر.

قد تجد أيضًا في مرحلة ما أن كل القطط غير قابلة للعيش (مثل تلك الشعرية). يمكنك الآن نقل هذه الواجهة إلى جميع فئات المشتقات التي تناسبك. سوف تجد أن تغيير أقل كسر أن جميع القطط المفاجئ لم تعد مشتقة من PettableBase.


شرح جيد في هذا المقال في جاوا العالمية

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

ليس من غير المألوف أن يكون لدي فصل تنفيذ واجهات أو أكثر.

دروس الملخص استخدمها كأساس لشيء آخر.

ما يلي هو مقتطف من المادة المذكورة أعلاه المادة JavaWorld.com ، المؤلف توني Sintes ، 04/20/01

واجهة مقابل الطبقة المجردة

اختيار واجهات وفئات مجردة ليست إما / أو اقتراح. إذا كنت بحاجة إلى تغيير تصميمك ، فاجعله واجهة. ومع ذلك ، قد يكون لديك فئات مجردة توفر بعض السلوك الافتراضي. فصول الملخص هي مرشحة ممتازة داخل أطر التطبيق.

تتيح لك الفئات المجردة تعريف بعض السلوكيات ؛ إنهم يجبرون أنصافك الفرعية على توفير الآخرين. على سبيل المثال ، إذا كان لديك إطار عمل للتطبيق ، فقد توفر فئة الملخص خدمة افتراضية مثل معالجة الأحداث والرسائل. تسمح هذه الخدمات للتطبيق الخاص بك بتوصيل إلى إطار التطبيق الخاص بك. ومع ذلك ، هناك بعض الوظائف الخاصة بالتطبيق التي يمكن أن يؤديها التطبيق الخاص بك فقط. قد تتضمن هذه الوظيفة مهام بدء التشغيل وإيقاف التشغيل ، والتي غالباً ما تعتمد على التطبيق. لذلك بدلاً من محاولة تعريف ذلك السلوك بحد ذاته ، يمكن للفئة الأساسية التجريدية أن تعلن عن طرق الإغلاق وبدء التشغيل المجردة. تعرف الطبقة الأساسية أنها بحاجة إلى هذه الأساليب ، ولكن الطبقة المجردة تسمح لصفك بالاعتراف بأنه لا يعرف كيفية تنفيذ تلك الإجراءات ؛ يعلم فقط أنه يجب بدء الإجراءات. عندما يحين وقت بدء التشغيل ، يمكن للفئة المجردة استدعاء طريقة بدء التشغيل. عندما تستدعي الطبقة الأساسية هذه الطريقة ، تقوم Java باستدعاء الطريقة المحددة بواسطة الفئة الفرعية.

ينسى العديد من المطورين أن الطبقة التي تحدد طريقة مجردة يمكن أن تستدعي هذه الطريقة أيضًا. تعتبر الدروس المجردة طريقة ممتازة لإنشاء التسلسل الهرمي المخطط للإرث. كما أنها خيار جيد للفصول الدراسية غير الورقية في التسلسلات الهرمية للفئات.

الطبقة مقابل واجهة

يقول البعض أنه يجب عليك تعريف جميع الطبقات من حيث الواجهات ، ولكن أعتقد أن التوصية تبدو متطرفة قليلاً. أستخدم الواجهات عندما أرى أن شيئًا ما في تصميمي سيتغير كثيرًا.

على سبيل المثال ، يتيح لك نمط الإستراتيجية تبديل خوارزميات وعمليات جديدة إلى البرنامج دون تغيير الكائنات التي تستخدمها. قد يعرف مشغل الوسائط كيفية تشغيل الأقراص المضغوطة ، وملفات MP3 ، وملفات wav. بالطبع ، لا تريد ترميز خوارزميات التشغيل هذه في المشغل ؛ سيجعل من الصعب إضافة تنسيق جديد مثل AVI. وعلاوة على ذلك ، سيتم تناثر التعليمات البرمجية الخاصة بك مع بيانات حالة غير مجدية. ولإضافة إهانة للإصابة ، ستحتاج إلى تحديث بيانات الحالة هذه في كل مرة تضيف فيها خوارزمية جديدة. الكل في الكل ، هذه ليست طريقة وجوه المنحى للبرنامج.

باستخدام نمط الإستراتيجية ، يمكنك ببساطة تغليف الخوارزمية خلف كائن. إذا قمت بذلك ، يمكنك توفير مكونات إضافية لوسائط جديدة في أي وقت. دعونا نسمي فئة البرنامج المساعد MediaStrategy. سيكون لهذا الكائن طريقة واحدة: playStream (Stream s). لذلك لإضافة خوارزمية جديدة ، فإننا ببساطة نوسع فئة الخوارزميات. الآن ، عندما يصادف البرنامج نوع الوسائط الجديد ، فإنه يفوض ببساطة تشغيل البث إلى إستراتيجيتنا الإعلامية. بالطبع ، ستحتاج إلى بعض السباكة لتفعيل استراتيجيات الخوارزمية التي ستحتاجها بشكل صحيح.

هذا مكان ممتاز لاستخدام واجهة. لقد استخدمنا نمط الإستراتيجية ، والذي يشير بوضوح إلى مكان في التصميم يتغير. وبالتالي ، يجب عليك تحديد الاستراتيجية كواجهة. يجب عليك عمومًا تفضيل الواجهات على الوراثة عندما تريد أن يكون لكائن ما نوعًا معينًا ؛ في هذه الحالة ، MediaStrategy. الاعتماد على الميراث لهوية النوع أمر خطير ؛ فإنه يؤمن لك في التسلسل الهرمي معين. لا تسمح Java بالوراثة المتعددة ، لذا لا يمكنك تمديد شيء يمنحك تطبيقًا مفيدًا أو مزيدًا من الهوية.


عندما بدأت في التعلم عن البرمجة الشيئية ، قمت بخطأ سهل وربما شائع باستخدام الميراث لتبادل السلوكيات المشتركة - حتى عندما لم يكن هذا السلوك ضروريًا لطبيعة الكائن.

لمزيد من البناء على مثال يستخدم كثيرا في هذا السؤال بالذات ، هناك الكثير من الأشياء التي يمكن صغرها - الصديقات ، والسيارات ، والبطانيات الضبابية ... - لذلك ربما أكون قد أتيت إلى فئة من التلاميذ الذين قدموا هذا السلوك المشترك ، ومختلف الفصول التي ترث منه.

ومع ذلك ، فإن الحيوانات الأليفة ليست جزءًا من طبيعة أي من هذه الكائنات. هناك مفاهيم أكثر أهمية بشكل كبير والتي تعتبر ضرورية لطبيعة هذه الشخصيات - صديقة الشخص ، والسيارة هي مركبة برية ، والقط هو حيوان ثديي ...

يجب تعيين السلوكيات أولاً للواجهات (بما في ذلك الواجهة الافتراضية للفئة) ، والترقية إلى فئة أساسية فقط إذا كانت (أ) مشتركة لمجموعة كبيرة من الفئات التي تكون مجموعات فرعية لفئة أكبر - بنفس المعنى الذي "القطة" و "الشخص" هي مجموعات فرعية من "الثدييات".

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

شيء آخر أود إضافته هو أنه إذا كانت الصفوف مجرد تجريدية - مع عدم وجود أعضاء أو أساليب غير مجردة وغير موروثة يتعرض لها الطفل أو الوالد أو العميل - فلماذا هو صنف؟ يمكن استبداله ، في بعض الحالات عن طريق واجهة وفي حالات أخرى من قبل Null.


لا تستخدم صفًا أساسيًا إلا إذا كنت تعرف معنى ذلك ، وأنه ينطبق في هذه الحالة. إذا كان ينطبق ، استخدمه ، وإلا ، استخدم الواجهات. لكن لاحظ الإجابة عن واجهات صغيرة.

الإرث العام مفرط في OOD ويعبر عنه أكثر بكثير مما يدركه معظم المطورين أو هم على استعداد للعيش فيه. انظر Liskov Substitutablity المبدأ

باختصار ، إذا كانت A "هي a" B ، فإن A لا تتطلب أكثر من B ولا تقدم أقل من B ، لكل طريقة تكشفها.


لنأخذ مثالك لفئة كلب وقط ، ودعنا نوضح استخدام C #:

كل من الكلاب والقطط هي حيوانات ، على وجه التحديد ، ثدييات رباعية الأضلاع (الحيوانات هي عامة جدا). دعنا نفترض أن لديك طبقة مجردة Mammal ، لكليهما:

public abstract class Mammal

من المحتمل أن تشتمل هذه الفئة الأساسية على طرق افتراضية مثل:

  • تغذية
  • زميل

وكلها سلوك له نفس التنفيذ تقريبا بين النوعين. لتحديد هذا سيكون لديك:

public class Dog : Mammal
public class Cat : Mammal

الآن لنفترض أن هناك ثدييات أخرى ، والتي سنراها عادة في حديقة الحيوانات:

public class Giraffe : Mammal
public class Rhinoceros : Mammal
public class Hippopotamus : Mammal

سيظل هذا صالحًا لأنه في صميم وظيفة Feed() و Mate() ستظل هي نفسها.

ومع ذلك ، فإن الزرافات ووحيد القرن وأفراس النهر ليست الحيوانات التي يمكنك إخراج الحيوانات منها. هذا هو المكان الذي ستكون فيه الواجهة مفيدة:

public interface IPettable
{
    IList<Trick> Tricks{get; set;}
    void Bathe();
    void Train(Trick t);
}

تنفيذ العقد أعلاه لن يكون هو نفسه بين القط والكلب. وضع تطبيقاتها في طبقة مجردة في الوراثة سيكون فكرة سيئة.

يجب أن تبدو تعريفات الكلب وقط ك:

public class Dog : Mammal, IPettable
public class Cat : Mammal, IPettable

من الناحية النظرية ، يمكنك تجاوزهم من طبقة أساسية أعلى ، ولكن في الأساس تسمح لك الواجهة بأن تضيف فقط الأشياء التي تحتاجها إلى الفصل دون الحاجة إلى الميراث.

نتيجة لذلك ، لأنه يمكنك عادة أن ترث فقط من فئة تجريدية واحدة (في معظم لغات OO المكتوبة بشكل ثابت والتي تتضمن ... استثناءات C ++) ولكن تكون قادرة على تنفيذ واجهات متعددة ، فهي تسمح لك ببناء الأشياء بشكل صارم كما هو مطلوب .


هذا أمر جميل. NET ، ولكن كتاب إرشادات تصميم الإطار يجادل بأن الطبقات العامة تعطي مرونة أكثر في إطار متطور. بمجرد شحن واجهة ، لا تحصل على فرصة لتغييرها دون كسر التعليمات البرمجية التي تستخدم هذه الواجهة. مع فصل دراسي ، يمكنك تعديله وليس كسر الشفرة التي ترتبط به. طالما قمت بإجراء التعديلات الصحيحة ، والتي تتضمن إضافة وظائف جديدة ، ستتمكن من توسيع وتنمية التعليمات البرمجية الخاصة بك.

يقول Krzysztof Cwalina في الصفحة 81:

على مدار الإصدارات الثلاثة من. NET Framework ، لقد تحدثت عن هذا المبدأ التوجيهي مع عدد غير قليل من المطورين على فريقنا. كثيرون منهم ، بما في ذلك أولئك الذين اختلفوا في البداية مع الإرشادات ، قالوا إنهم يندمون على شحن بعض API كواجهة. لم أسمع عن حالة واحدة شعر فيها أحدهم بالأسف لشحنه للطبقة.

أن يقال هناك بالتأكيد مكان للواجهات. كدليل إرشادي عام ، يوفر دائمًا تطبيقًا أساسيًا أساسيًا لواجهة إذا لم يكن لأي شيء آخر كمثال على طريقة لتنفيذ الواجهة. في أفضل الأحوال ، ستوفر الطبقة الأساسية الكثير من العمل.


يجب أن تكون واجهات صغيرة. حقا صغيرة. إذا كنت تحطم الكائنات الخاصة بك ، فمن المحتمل أن تحتوي واجهاتك على عدد قليل من الطرق والخصائص المحددة فقط.

الطبقات المجردة هي اختصارات. هل هناك أشياء تشترك فيها جميع مشتقات PetBase في أن يتم ترميزها مرة واحدة وأن يتم تنفيذها؟ إذا كانت الإجابة بنعم ، فقد حان الوقت لفئة مجردة.

الطبقات المجردة هي أيضا الحد. في حين أنها تعطيك اختصارًا رائعًا لإنتاج الكائنات التابعة ، إلا أنه لا يمكن لأي كائن محدد سوى تطبيق فئة تجريدية واحدة. في كثير من الأحيان ، أجد هذا الحد من الطبقات الملخص ، وهذا هو السبب في أنني استخدم الكثير من الواجهات.

قد تحتوي الفصول الدراسية Abstract على عدة واجهات. قد تقوم طبقة تجريبية PetBase الخاصة بك بتنفيذ IPet (الحيوانات الأليفة لديها أصحاب) و IDigestion (يأكلون الحيوانات الأليفة ، أو على الأقل يجب عليهم). ومع ذلك ، فإن PetBase ربما لن ينفذ IMammal ، حيث أن الحيوانات الأليفة ليست كلها حيوانات ثديية وليست كل الحيوانات الثديية هي الحيوانات الأليفة. يمكنك إضافة MammalPetBase التي تمتد PetBase وإضافة IMammal. FishBase يمكن أن يكون PetBase وإضافة IFish. سيكون لدى IFish ISwim و IUnderwaterBreather كواجهات.

نعم ، مثالى مفرط في التعقيد على سبيل المثال البسيط ، ولكن هذا جزء من الشيء العظيم حول كيفية عمل الطبقات والدروس التجريدية معًا.


Prefer interfaces over abstract classes

Rationale, the main points to consider [two already mentioned here] are :

  • Interfaces are more flexible, because a class can implement multiple interfaces. Since Java does not have multiple inheritance, using abstract classes prevents your users from using any other class hierarchy. In general, prefer interfaces when there are no default implementations or state. Java collections offer good examples of this (Map, Set, etc.).
  • Abstract classes have the advantage of allowing better forward compatibility. Once clients use an interface, you cannot change it; if they use an abstract class, you can still add behavior without breaking existing code. If compatibility is a concern, consider using abstract classes.
  • Even if you do have default implementations or internal state, consider offering an interface and an abstract implementation of it . This will assist clients, but still allow them greater freedom if desired [1].
    Of course, the subject has been discussed at length elsewhere [2,3].

[1] It adds more code, of course, but if brevity is your primary concern, you probably should have avoided Java in the first place!

[2] Joshua Bloch, Effective Java, items 16-18.

[3] http://www.codeproject.com/KB/ar ...


@Joel: Some languages (eg, C++) allow multiple-inheritance.


An inheritor of a base class should have an "is a" relationship. Interface represents An "implements a" relationship. So only use a base class when your inheritors will maintain the is a relationship.


Conceptually, an Interface is used to formally and semi-formally define a set of methods that an object will provide. Formally means a set of method names and signatures, semi-formally means human readable documentation associated with those methods. Interfaces are only descriptions of an API (after all, API stands for Application Programmer Interface ), they can't contain any implementation, and it's not possible to use or run an Interface. They only make explicit the contract of how you should interact with an object.

Classes provide an implementation, they can declare that they implement zero, one or more Interfaces. If a Class is intended to be inherited, the convention is to prefix the Class name with "Base".

There is a distinction between a Base Class and an Abstract Base Classes (ABC). ABCs mix interface and implementation together. Abstract outside of computer programming means "summary", that is "Abstract == Interface". An Abstract Base Class can then describe both an interface, as well as an empty, partial or complete implementation that is intended to be inherited.

Opinions on when to use Interfaces versus Abstract Base Classes versus just Classes is going to vary wildly based on both what you are developing, and which language you are developing in. Interfaces are often associated only with statically typed languages such as Java or C#, but dynamically typed languages can also have Interfaces and Abstract Base Classes. In Python for example, the distinction is made clear between a Class, which declares that it implements an Interface, and an object, which is an instance of a Class, and is said to provide that Interface. It's possible in a dynamic language that two objects that are both instances of the same Class, can declare that they provide completely different interfaces. In Python this is only possible for object attributes, while methods are shared state between all objects of a Class. However in Ruby, objects can have per-instance methods, so it's possible that the Interface between two objects of the same Class can vary as much as the programmer desires (however, Ruby doesn't have any explicit way of declaring Interfaces).

In dynamic languages the Interface to an object is often implicitly assumed, either by introspecting an object and asking it what methods it provides (Look Before You Leap) or preferably by simply attempting to use the desired Interface on an object and catching exceptions if the object doesn't provide that Interface (Easier to Ask Forgiveness than Permission). This can lead to "false positives" where two Interfaces have the same method name but are semantically different, however the trade-off is that your code is more flexible since you don't need to over specify up-front to anticipate all possible uses of your code.


I usually don't implement either until I need one. I favor interfaces over abstract classes because that gives a little more flexibility. If there's common behavior in some of the inheriting classes I move that up and make an abstract base class. I don't see the need for both, since they essentially server the same purpose, and having both is a bad code smell (imho) that the solution has been over-engineered.


It depends on your requirements. If IPet is simple enough, I would prefer to implement that. Otherwise, if PetBase implements a ton of functionality you don't want to duplicate, then have at it.

The downside to implementing a base class is the requirement to override (or new ) existing methods. This makes them virtual methods which means you have to be careful about how you use the object instance.

Lastly, the single inheritance of .NET kills me. A naive example: Say you're making a user control, so you inherit UserControl . But, now you're locked out of also inheriting PetBase . This forces you to reorganize, such as to make a PetBase class member, instead.


Regarding C#, in some senses interfaces and abstract classes can be interchangeable. However, the differences are: i) interfaces cannot implement code; ii) because of this, interfaces cannot call further up the stack to subclass; and iii) only can abstract class may be inherited on a class, whereas multiple interfaces may be implemented on a class.





static-typing