[language-agnostic] ماذا يعني "البرنامج إلى واجهة"؟



14 Answers

المثال المحدد الذي اعتدت أن أقدمه للطلاب هو أنهم يجب أن يكتبوا

List myList = new ArrayList(); // programming to the List interface

بدلا من

ArrayList myList = new ArrayList(); // this is bad

هذه تبدو نفسها بالضبط في برنامج قصير ، ولكن إذا ذهبت إلى استخدام myList 100 مرة في البرنامج الخاص بك ، يمكنك البدء في رؤية الفرق. يضمن الإعلان الأول أن تقوم فقط باستدعاء الطرق الموجودة على myList التي تم تعريفها بواسطة واجهة List (لذلك لا توجد أساليب محددة من ArrayList ). إذا كنت مبرمجًا على الواجهة بهذه الطريقة ، في وقت لاحق يمكنك أن تقرر أنك تحتاج بالفعل

List myList = new TreeList();

وعليك فقط تغيير شفرتك في تلك النقطة. أنت تعلم بالفعل أن بقية شفرتك لا تفعل أي شيء يمكن كسره عن طريق تغيير التطبيق لأنك مبرمج على الواجهة .

الفوائد أكثر وضوحا (على ما أظن) عندما كنت تتحدث عن معلمات الأسلوب وقيم العودة. خذ هذا على سبيل المثال:

public ArrayList doSomething(HashMap map);

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

public List doSomething(Map map);

لا يهم الآن نوع List التي ترجع إليها ، أو نوع Map يتم تمريرها كمعلمة. لن تجبر التغييرات التي تجريها داخل طريقة doSomething على تغيير رمز الاتصال.

Question

لقد رأيت هذا ذكر عدة مرات وأنا لست واضحا على ما يعنيه. متى ولماذا تفعل هذا؟

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

هل الأمر كذلك إذا كنت ستفعل:

IInterface classRef = new ObjectWhatever()

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

آسف إذا غاب عن النقطة تماما.




Interface is like contract where you want your implementation class to implement methods written in contract(Interface).Since java does not provide multiple inheritance,programming to interface is a good way to achieve purpose of multiple inheritance.If you have a class A that is already extending some other class B but you want that class A should also follow certain guidelines or implement certain contract then you can do so by programming to interface strategy.




short story:Postman is asked to go home by home and receive the covers contains (letters,documents,cheques,giftcard,application,loveletter) with address written on it to deliver.

Suppose there is no cover and ask post man to go home by home and receive all the things and deliver to other person the postman can get confuse,

so better wrap it with cover(in our story it is interface) then he will do his job fine.

Now postman job is to receive and deliver the covers only..(he dont bothered what is inside in the cover).

Create type of interface not actual type, but implement with actual type.

Create to interface means your components get Fits into the rest of code easily

I give you example.

you have AirPlane interface as below.

interface Airplane{
    parkPlane();
    servicePlane();
}

Suppose you have methods in your Controller class of Planes like

parkPlane(Airplane plane)

و

servicePlane(Airplane plane)

implemented in your program. It will not BREAK your code. I mean, it need not to change as long as it accepts arguments as AirPlane .

Because it will accept any Airplane despite of actual type, flyer , highflyr , fighter , etc.

Also, in a collection:

List<Airplane> plane; // Will take all your planes.

The following example will clear your understanding.

You have a fighter plane that implements it, so

public class Fighter implements Airplane {

    public void  parkPlane(){
        // Specific implementations for fighter plane to park
    }
    public void  servicePlane(){
        // Specific implementatoins for fighter plane to service.
    }
}

The same thing for HighFlyer and other clasess:

public class HighFlyer implements Airplane {

    public void  parkPlane(){
        // Specific implementations for HighFlyer plane to park
    }

    public void  servicePlane(){
        // specific implementatoins for HighFlyer plane to service.
    }
}

Now think your controller classes using AirPlane several times,

Suppose your Controller class is ControlPlane like below,

public Class ControlPlane{ 
 AirPlane plane;
 // so much method with AirPlane reference are used here...
}

here magic comes as

you may make your new AirPlane type instances as many as you want and you are not changing

code of ControlPlane class.

you can add instance..

JumboJetPlane // implementing AirPlane interface.
AirBus        // implementing AirPlane interface.

you may remove instances.. of previously created types too.




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

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

EDIT: يوجد أدناه رابط لمقالة حيث يناقش Erich Gamma اقتباسه ، "Program to an interface، not a implementation".

http://www.artima.com/lejava/articles/designprinciples.html




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




Program to an interface allows to change implementation of contract defined by interface seamlessly. It allows loose coupling between contract and specific implementations.

IInterface classRef = new ObjectWhatever()

You could use any class that implements IInterface? When would you need to do that?

Have a look at this SE question for good example.

Why should the interface for a Java class be preferred?

does using an Interface hit performance?

if so how much?

نعم فعلا. It will have slight performance overhead in sub-seconds. But if your application has requirement to change the implementation of interface dynamically, don't worry about performance impact.

how can you avoid it without having to maintain two bits of code?

Don't try to avoid multiple implementations of interface if your application need them. In absence of tight coupling of interface with one specific implementation, you may have to deploy the patch to change one implementation to other implementation.

One good use case: Implementation of Strategy pattern:

Real World Example of the Strategy Pattern




في ما يلي مثال بسيط لتوضيحه عند برمجة نظام حجز رحلة.

//This interface is very flexible and abstract
    addPassenger(Plane seat, Ticket ticket); 

//Boeing is implementation of Plane
    addPassenger(Boeing747 seat, EconomyTicket ticket); 
    addPassenger(Cessna, BusinessClass ticket);


    addPassenger(J15, E87687); 



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




In Java these concrete classes all implement the CharSequence interface:

CharBuffer, String, StringBuffer, StringBuilder

These concrete classes do not have a common parent class other than Object, so there is nothing that relates them, other than the fact they each have something to do with arrays of characters, representing such, or manipulating such. For instance, the characters of String cannot be changed once a String object is instantiated, whereas the characters of StringBuffer or StringBuilder can be edited.

Yet each one of these classes is capable of suitably implementing the CharSequence interface methods:

char charAt(int index)
int length()
CharSequence subSequence(int start, int end)
String toString()

In some cases Java class library classes that used to accept String have been revised to now accept the CharSequence interface. So if you have an instance of StringBuilder, instead of extracting a String object (which means instantiating a new object instance), can instead just pass the StringBuilder itself as it implements the CharSequence interface.

The Appendable interface that some classes implement has much the same kind of benefit for any situation where characters can be appended to an instance of the underlying concrete class object instance. All of these concrete classes implement the Appendable interface:

BufferedWriter, CharArrayWriter, CharBuffer, FileWriter, FilterWriter, LogStream, OutputStreamWriter, PipedWriter, PrintStream, PrintWriter, StringBuffer, StringBuilder, StringWriter, Writer




Also I see a lot of good and explanatory answers here, so I want to give my point of view here, including some extra information what I noticed when using this method.

Unit testing

For the last two years, I have written a hobby project and I did not write unit tests for it. After writing about 50K lines I found out it would be really necessary to write unit tests. I did not use interfaces (or very sparingly) ... and when I made my first unit test, I found out it was complicated. لماذا ا؟

Because I had to make a lot of class instances, used for input as class variables and/or parameters. So the tests look more like integration tests (having to make a complete 'framework' of classes since all was tied together).

Fear of interfaces So I decided to use interfaces. My fear was that I had to implement all functionality everywhere (in all used classes) multiple times. In some way this is true, however, by using inheritance it can be reduced a lot.

Combination of interfaces and inheritance I found out the combination is very good to be used. I give a very simple example.

public interface IPricable
{
    int Price { get; }
}

public interface ICar : IPricable

public abstract class Article
{
    public int Price { get { return ... } }
}

public class Car : Article, ICar
{
    // Price does not need to be defined here
}

This way copying code is not necessary, while still having the benefit of using a car as interface (ICar).




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




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

// if I want to add search capabilities to my application and support multiple search
// engines such as google, yahoo, live, etc.

interface ISearchProvider
{
    string Search(string keywords);
}

بعد ذلك يمكنني إنشاء GoogleSearchProvider و YahooSearchProvider و LiveSearchProvider وما إلى ذلك.

// if I want to support multiple downloads using different protocols
// HTTP, HTTPS, FTP, FTPS, etc.
interface IUrlDownload
{
    void Download(string url)
}

// how about an image loader for different kinds of images JPG, GIF, PNG, etc.
interface IImageLoader
{
    Bitmap LoadImage(string filename)
}

ثم إنشاء JpegImageLoader ، GifImageLoader ، PngImageLoader ، إلخ.

تعمل معظم الوظائف الإضافية ووظائف المكون الإضافي خارج الواجهات.

استخدام شائع آخر هو لنمط مستودع التخزين. قل أرغب في تحميل قائمة الرموز البريدية من مصادر مختلفة

interface IZipCodeRepository
{
    IList<ZipCode> GetZipCodes(string state);
}

ثم يمكنني إنشاء XMLZipCodeRepository ، SQLZipCodeRepository ، CSVZipCodeRepository ، إلخ. بالنسبة لتطبيقات الويب الخاصة بي ، غالباً ما أقوم بإنشاء مستودعات XML في وقت مبكر حتى أتمكن من الحصول على شيء وتشغيله قبل أن تكون قاعدة بيانات Sql جاهزة. بمجرد أن تكون قاعدة البيانات جاهزة ، اكتب SQLRepository لاستبدال إصدار XML. تبقى بقية التعليمة البرمجية الخاصة بي دون تغيير منذ تشغيل soley إيقاف الواجهات.

يمكن أن تقبل الطرق واجهات مثل:

PrintZipCodes(IZipCodeRepository zipCodeRepository, string state)
{
    foreach (ZipCode zipCode in zipCodeRepository.GetZipCodes(state))
    {
        Console.WriteLine(zipCode.ToString());
    }
}



أنا في وقت متأخر قادم على هذا السؤال ، ولكن أريد أن أذكر هنا أن سطر "البرنامج إلى واجهة ، وليس تنفيذ" كان بعض النقاش الجيد في كتاب أنماط (Goop of Four) تصميم GoF.

وذكر ، في ص. 18:

البرنامج إلى واجهة ، وليس التنفيذ

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

وفوق ذلك ، بدأت مع:

هناك فائدتان للتعامل مع الكائنات فقط من حيث الواجهة المعرّفة بواسطة فئات الملخصات:

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

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




البرمجة على واجهة لا علاقة لها على الإطلاق مع واجهات مجردة كما نرى في جافا أو .NET. انها ليست حتى مفهوم OOP.

ما يعنيه حقًا هو عدم الخوض في الدوام مع عناصر كائن أو بنية بيانات. استخدم واجهة Program Abstract أو API للتفاعل مع بياناتك. في Java أو C # يعني استخدام الخصائص والأساليب العامة بدلاً من الوصول إلى الحقل الخام. بالنسبة إلى C ، يعني استخدام الوظائف بدلاً من المؤشرات الأولية.

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




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

وهذا يجعلها مستقلة تمامًا عن أي فئة إضافية معينة - فهي لا تهتم بالصفوف. انها تهتم فقط أنها تلبي مواصفات الواجهة.

Interfaces are a way of defining points of extensibility like this. Code that talks to an interface is more loosely coupled - in fact it is not coupled at all to any other specific code. It can inter-operate with plugins written years later by people who have never met the original developer.

You could instead use a base class with virtual functions - all plugins would be derived from the base class. But this is much more limiting because a class can only have one base class, whereas it can implement any number of interfaces.




Related