oop فيجوال - واجهة مقابل الطبقة الأساسية




للصف جاهزة (25)

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

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

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


Answers

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

لذا بدلاً من IPet أو PetBase ، قد ينتهي بك Pet مع الذي IFurBehavior المعلمة. يتم تعيين المعلمة IFurBehavior بواسطة أسلوب CreateDog () PetFactory. هذه المعلمة التي تسمى الأسلوب shed ().

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

أوصي بهذا النمط حتى في لغات متعددة الإرث.


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


خوان،

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

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

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

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

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

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

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

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

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

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

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


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.


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

ذكرت عصابة أربعة في كتابهم "لأن الميراث يفضح فئة فرعية لتفاصيل تنفيذ الوالدين ، وغالبا ما يقال أن" الميراث يكسر التغليف ". انا اؤمن ان هذا صحيح.


Use Interfaces to enforce a contract ACROSS families of unrelated classes. For example, you might have common access methods for classes that represent collections, but contain radically different data ie one class might represent a result set from a query, while the other might represent the images in a gallery. Also, you can implement multiple interfaces, thus allowing you to blend (and signify) the capabilities of the class.

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


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

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

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


واجهات

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

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

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

لنأخذ مثالك لفئة كلب وقط ، ودعنا نوضح استخدام 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 ++) ولكن تكون قادرة على تنفيذ واجهات متعددة ، فهي تسمح لك ببناء الأشياء بشكل صارم كما هو مطلوب .


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.


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


حسنًا ، قال جوش بلوخ نفسه في برنامج Java 2d الفعال :

تفضل واجهات أكثر من الطبقات المجردة

بعض النقاط الرئيسية:

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

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

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

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

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

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

ملاحظة: شراء الكتاب. إنه كثير أكثر تفصيلاً.


Source : http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/

C# is a wonderful language that has matured and evolved over the last 14 years. This is great for us developers because a mature language provides us with a plethora of language features that are at our disposal.

However, with much power becomes much responsibility. Some of these features can be misused, or sometimes it is hard to understand why you would choose to use one feature over another. Over the years, a feature that I have seen many developers struggle with is when to choose to use an interface or to choose to use an abstract class. Both have there advantages and disadvantages and the correct time and place to use each. But how to we decide???

Both provide for reuse of common functionality between types. The most obvious difference right away is that interfaces provide no implementation for their functionality whereas abstract classes allow you to implement some “base” or “default” behavior and then have the ability to “override” this default behavior with the classes derived types if necessary.

This is all well and good and provides for great reuse of code and adheres to the DRY (Don't Repeat Yourself) principle of software development. Abstract classes are great to use when you have an “is a” relationship.

For example: A golden retriever “is a” type of dog. So is a poodle. They both can bark, as all dogs can. However, you might want to state that the poodle park is significantly different than the “default” dog bark. Therefor, it could make sense for you to implement something as follows:

public abstract class Dog
{
      public virtual void Bark()
      {
        Console.WriteLine("Base Class implementation of Bark");
      }
}

public class GoldenRetriever : Dog
{
   // the Bark method is inherited from the Dog class
}

public class Poodle : Dog
{
  // here we are overriding the base functionality of Bark with our new implementation
  // specific to the Poodle class
  public override void Bark()
  {
     Console.WriteLine("Poodle's implementation of Bark");
  }
}

// Add a list of dogs to a collection and call the bark method.

void Main()
{
    var poodle = new Poodle();
    var goldenRetriever = new GoldenRetriever();

    var dogs = new List<Dog>();
    dogs.Add(poodle);
    dogs.Add(goldenRetriever);

    foreach (var dog in dogs)
    {
       dog.Bark();
    }
}

// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark

// 

As you can see, this would be a great way to keep your code DRY and allow for the base class implementation be called when any of the types can just rely on the default Bark instead of a special case implementation. The classes like GoldenRetriever, Boxer, Lab could all could inherit the “default” (bass class) Bark at no charge just because they implement the Dog abstract class.

But I'm sure you already knew that.

You are here because you want to understand why you might want to choose an interface over an abstract class or vice versa. Well one reason you may want to choose an interface over an abstract class is when you don't have or want to prevent a default implementation. This is usually because the types that are implementing the interface not related in an “is a” relationship. Actually, they don't have to be related at all except for the fact that each type “is able” or has “the ablity” to do something or have something.

Now what the heck does that mean? Well, for example: A human is not a duck…and a duck is not a human. Pretty obvious. However, both a duck and a human have “the ability” to swim (given that the human passed his swimming lessons in 1st grade :) ). Also, since a duck is not a human or vice versa, this is not an “is a” realationship, but instead an “is able” relationship and we can use an interface to illustrate that:

// Create ISwimable interface
public interface ISwimable
{
      public void Swim();
}

// Have Human implement ISwimable Interface
public class Human : ISwimable

     public void Swim()
     {
        //Human's implementation of Swim
        Console.WriteLine("I'm a human swimming!");
     }

// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
     public void Swim()
     {
          // Duck's implementation of Swim
          Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
     }
}

//Now they can both be used in places where you just need an object that has the ability "to swim"

public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
     somethingThatCanSwim.Swim();
}

public void Main()
{
      var human = new Human();
      var duck = new Duck();

      var listOfThingsThatCanSwim = new List<ISwimable>();

      listOfThingsThatCanSwim.Add(duck);
      listOfThingsThatCanSwim.Add(human);

      foreach (var something in listOfThingsThatCanSwim)
      {
           ShowHowYouSwim(something);
      }
}

 // So at runtime the correct implementation of something.Swim() will be called
 // Output:
 // Quack! Quack! I'm a Duck swimming!
 // I'm a human swimming!

Using interfaces like the code above will allow you to pass an object into a method that “is able” to do something. The code doesn't care how it does it…All it knows is that it can call the Swim method on that object and that object will know which behavior take at run-time based on its type.

Once again, this helps your code stay DRY so that you would not have to write multiple methods that are calling the object to preform the same core function (ShowHowHumanSwims(human), ShowHowDuckSwims(duck), etc.)

Using an interface here allows the calling methods to not have to worry about what type is which or how the behavior is implemented. It just knows that given the interface, each object will have to have implemented the Swim method so it is safe to call it in its own code and allow the behavior of the Swim method be handled within its own class.

ملخص:

So my main rule of thumb is use an abstract class when you want to implement a “default” functionality for a class hierarchy or/and the classes or types you are working with share a “is a” relationship (ex. poodle “is a” type of dog).

On the other hand use an interface when you do not have an “is a” relationship but have types that share “the ability” to do something or have something (ex. Duck “is not” a human. However, duck and human share “the ability” to swim).

Another difference to note between abstract classes and interfaces is that a class can implement one to many interfaces but a class can only inherit from ONE abstract class (or any class for that matter). Yes, you can nest classes and have an inheritance hierarchy (which many programs do and should have) but you cannot inherit two classes in one derived class definition (this rule applies to C#. In some other languages you are able to do this, usually only because of the lack of interfaces in these languages).

Also remember when using interfaces to adhere to the Interface Segregation Principle (ISP). ISP states that no client should be forced to depend on methods it does not use. For this reason interfaces should be focused on specific tasks and are usually very small (ex. IDisposable, IComparable ).

Another tip is if you are developing small, concise bits of functionality, use interfaces. If you are designing large functional units, use an abstract class.

Hope this clears things up for some people!

Also if you can think of any better examples or want to point something out, please do so in the comments below!


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.


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

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

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

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

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

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


Another option to keep in mind is using the "has-a" relationship, aka "is implemented in terms of" or "composition." Sometimes this is a cleaner, more flexible way to structure things than using "is-a" inheritance.

It may not make as much sense logically to say that Dog and Cat both "have" a Pet, but it avoids common multiple inheritance pitfalls:

public class Pet
{
    void Bathe();
    void Train(Trick t);
}

public class Dog
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

public class Cat
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

Yes, this example shows that there is a lot of code duplication and lack of elegance involved in doing things this way. But one should also appreciate that this helps to keep Dog and Cat decoupled from the Pet class (in that Dog and Cat do not have access to the private members of Pet), and it leaves room for Dog and Cat to inherit from something else--possibly the Mammal class.

Composition is preferable when no private access is required and you don't need to refer to Dog and Cat using generic Pet references/pointers. Interfaces give you that generic reference capability and can help cut down on the verbosity of your code, but they can also obfuscate things when they are poorly organized. Inheritance is useful when you need private member access, and in using it you are committing yourself to highly coupling your Dog and Cat classes to your Pet class, which is a steep cost to pay.

Between inheritance, composition, and interfaces there is no one way that is always right, and it helps to consider how all three options can be used in harmony. Of the three, inheritance is typically the option that should be used the least often.


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

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

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

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

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


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.


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

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

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

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

لديّ قاعدة تقريبية

الوظيفة: من المحتمل أن تكون مختلفة في جميع الأجزاء: الواجهة.

البيانات ، والوظائف ، الأجزاء ستكون في الغالب متماثلة ، أجزاء مختلفة: الطبقة المجردة.

البيانات والوظائف ، تعمل في الواقع ، إذا مددت فقط مع تغييرات طفيفة: الطبقة العادية (الملموسة)

البيانات والوظائف ، لا توجد تغييرات مخطط لها: الطبقة العادية (الاسمنتية) مع المعدل النهائي.

البيانات ، وربما وظيفة: للقراءة فقط: أعضاء التعداد.

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


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


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 ...


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





oop interface language-agnostic base-class static-typing