"प्रोग्राम टू इंटरफेसेस, नॉट इम्प्लीमेंटेशन" का क्या मतलब है?


131

डिजाइन पैटर्न के बारे में पढ़ते समय इस वाक्यांश पर एक ठोकर लगती है।

लेकिन मुझे यह समझ में नहीं आता है, क्या कोई मेरे लिए यह समझा सकता है?


जवाबों:


148

इंटरफेस केवल अनुबंध या हस्ताक्षर हैं और वे कार्यान्वयन के बारे में कुछ भी नहीं जानते हैं।

इंटरफ़ेस के खिलाफ कोडिंग का मतलब है, क्लाइंट कोड में हमेशा एक इंटरफ़ेस ऑब्जेक्ट होता है जिसे किसी कारखाने द्वारा आपूर्ति की जाती है। कारखाने द्वारा लौटाया गया कोई भी उदाहरण टाइप इंटरफ़ेस का होगा जिसे किसी भी कारखाने के उम्मीदवार वर्ग ने लागू किया होगा। इस तरह से क्लाइंट प्रोग्राम कार्यान्वयन के बारे में चिंतित नहीं है और इंटरफ़ेस हस्ताक्षर निर्धारित करता है कि सभी ऑपरेशन क्या किए जा सकते हैं। इसका उपयोग रन-टाइम में प्रोग्राम के व्यवहार को बदलने के लिए किया जा सकता है। यह आपको रखरखाव के दृष्टिकोण से कहीं बेहतर कार्यक्रम लिखने में भी मदद करता है।

यहां आपके लिए एक बुनियादी उदाहरण है।

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

[STAThread]
static void Main()
{
    //This is your client code.
    ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
    speaker.Speak();
    Console.ReadLine();
}

public interface ISpeaker
{
    void Speak();
}

public class EnglishSpeaker : ISpeaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : ISpeaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak German.");
    }

    #endregion
}

public class SpanishSpeaker : ISpeaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    #endregion
}

वैकल्पिक शब्द

यह केवल एक मूल उदाहरण है और सिद्धांत का वास्तविक स्पष्टीकरण इस उत्तर के दायरे से परे है।

संपादित करें

मैंने ऊपर के उदाहरण को अद्यतन किया है और एक सार Speakerआधार वर्ग को जोड़ा है । इस अद्यतन में, मैंने सभी वक्ताओं के लिए "SayHello" में एक विशेषता जोड़ी। सभी वक्ता "हैलो वर्ल्ड" बोलते हैं। तो यह समान फ़ंक्शन के साथ एक सामान्य विशेषता है। वर्ग आरेख का संदर्भ लें और आप पाएंगे कि Speakerअमूर्त वर्ग ISpeakerइंटरफ़ेस लागू करता है और Speak()सार के रूप में चिह्नित करता है जिसका अर्थ है कि प्रत्येक अध्यक्ष कार्यान्वयन Speak()विधि को लागू करने के लिए जिम्मेदार है क्योंकि यह भिन्न होता Speakerहै Speaker। लेकिन सभी स्पीकर एकमत से "हैलो" कहते हैं। इसलिए सार वक्ता वर्ग में हम एक विधि को परिभाषित करते हैं जो "हैलो वर्ल्ड" कहती है और प्रत्येक Speakerकार्यान्वयन SayHello()विधि को प्राप्त करेगा ।

ऐसे मामले पर विचार करें जहां SpanishSpeakerनमस्ते नहीं कहा जा सकता है इसलिए उस स्थिति में आप SayHello()स्पेनिश स्पीकर के लिए विधि को ओवरराइड कर सकते हैं और उचित अपवाद उठा सकते हैं।

कृपया ध्यान दें कि, हमने इंटरफ़ेस ISpeaker में कोई परिवर्तन नहीं किया है। और क्लाइंट कोड और स्पीकरफैक्ट्री भी अप्रभावित अपरिवर्तित रहते हैं। और यह हम प्रोग्रामिंग-टू-इंटरफेस द्वारा प्राप्त करते हैं ।

और हम इस व्यवहार को प्राप्त कर सकते हैं बस एक आधार सार वर्ग अध्यक्ष और प्रत्येक कार्यान्वयन में कुछ मामूली संशोधन इस प्रकार मूल कार्यक्रम को अपरिवर्तित छोड़कर। यह किसी भी एप्लिकेशन की एक वांछित विशेषता है और यह आपके एप्लिकेशन को आसानी से बनाए रखने योग्य बनाता है।

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

class Program
{
    [STAThread]
    static void Main()
    {
        //This is your client code.
        ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
        speaker.Speak();
        Console.ReadLine();
    }
}

public interface ISpeaker
{
    void Speak();
}

public abstract class Speaker : ISpeaker
{

    #region ISpeaker Members

    public abstract void Speak();

    public virtual void SayHello()
    {
        Console.WriteLine("Hello world.");
    }

    #endregion
}

public class EnglishSpeaker : Speaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        this.SayHello();
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : Speaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        Console.WriteLine("I speak German.");
        this.SayHello();
    }

    #endregion
}

public class SpanishSpeaker : Speaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    public override void SayHello()
    {
        throw new ApplicationException("I cannot say Hello World.");
    }

    #endregion
}

वैकल्पिक शब्द


19
इंटरफ़ेस के लिए प्रोग्रामिंग केवल संदर्भ चर के प्रकार के बारे में नहीं है । इसका मतलब यह भी है कि आप अपने कार्यान्वयन के बारे में किसी भी अंतर्निहित धारणा का उपयोग नहीं करते हैं। उदाहरण के लिए यदि आप एक Listप्रकार के रूप में उपयोग करते हैं , तो आपका अभी भी यह मान सकता है कि बार-बार कॉल करने से यादृच्छिक पहुंच तेज है get(i)
जोकिम सॉयर

16
कारखानों इंटरफेस के लिए प्रोग्रामिंग के लिए रूढ़िवादी हैं, लेकिन मुझे लगता है कि यह स्पष्टीकरण ऐसा लगता है जैसे कि वे इसका हिस्सा हैं।
टी।

@ टून: आपसे सहमत हूँ। मैं प्रोग्रामिंग-टू-इंटरफ़ेस के लिए एक बहुत ही बुनियादी और सरल उदाहरण प्रदान करना चाहता था। मैं कुछ पक्षियों और जानवरों की कक्षाओं पर IFlyable इंटरफ़ेस लागू करके प्रश्नकर्ता को भ्रमित नहीं करना चाहता था।
यह। __curious_geek

@यह। यदि मैं इसके बजाय एक अमूर्त वर्ग या एक मुखौटा पैटर्न का उपयोग करता हूं, तो क्या इसे अभी भी "प्रोग्राम टू ए इंटरफ़ेस" कहा जाएगा? या क्या मुझे स्पष्ट रूप से एक इंटरफ़ेस का उपयोग करना है और इसे एक वर्ग पर लागू करना है?
never_had_a_name 19

1
छवियों को बनाने के लिए आप किस uml टूल का उपयोग कर रहे थे?
एडम एरॉल्ड

29

एक वस्तु और उसके ग्राहकों के बीच एक अनुबंध के रूप में एक इंटरफ़ेस के बारे में सोचो। वह इंटरफ़ेस उन चीजों को निर्दिष्ट करता है जो एक वस्तु कर सकती है, और उन चीजों तक पहुंचने के लिए हस्ताक्षर।

कार्यान्वयन वास्तविक व्यवहार हैं। उदाहरण के लिए कहो कि आपके पास एक विधि है सॉर्ट ()। आप QuickSort या MergeSort को लागू कर सकते हैं। जब तक इंटरफ़ेस नहीं बदलता क्लाइंट कोड कॉलिंग सॉर्ट के लिए कोई फर्क नहीं पड़ता।

जावा एपीआई और .NET फ्रेमवर्क जैसी लाइब्रेरीज़ इंटरफेस का भारी उपयोग करती हैं क्योंकि लाखों प्रोग्रामर प्रदान की गई वस्तुओं का उपयोग करते हैं। इन पुस्तकालयों के रचनाकारों को बहुत सावधान रहना होगा कि वे इन पुस्तकालयों में कक्षाओं के लिए इंटरफ़ेस को न बदलें क्योंकि इससे पुस्तकालय का उपयोग करने वाले सभी प्रोग्रामर प्रभावित होंगे। दूसरी ओर वे कार्यान्वयन को जितना चाहें बदल सकते हैं।

यदि, एक प्रोग्रामर के रूप में, आप कार्यान्वयन के खिलाफ कोड करते हैं तो जैसे ही यह बदलता है आपका कोड काम करना बंद कर देता है। तो इंटरफ़ेस के लाभों के बारे में इस तरह से सोचें:

  1. यह उन चीजों को छुपाता है जिन्हें आपको उपयोग करने के लिए ऑब्जेक्ट को सरल बनाने की आवश्यकता नहीं है।
  2. यह इस बात का अनुबंध प्रदान करता है कि वस्तु कैसे व्यवहार करेगी ताकि आप उस पर निर्भर रह सकें

इसका मतलब है कि आपको इस बात से अवगत होने की आवश्यकता है कि आप क्या करने के लिए वस्तु को अनुबंधित कर रहे हैं: उदाहरण में, आप केवल एक प्रकार के लिए अनुबंध कर रहे हैं, जरूरी नहीं कि एक स्थिर प्रकार।
पेंग्वेट

इसलिए लाइब्रेरी डॉक्यूमेंटेशन में कार्यान्वयन का उल्लेख नहीं किया गया है, वे केवल शामिल वर्ग इंटरफेस के विवरण हैं।
जो इडॉन

17

इसका मतलब है कि आपको अपने कोड को लिखने की कोशिश करनी चाहिए ताकि यह सीधे कार्यान्वयन के बजाय एक अमूर्त (अमूर्त वर्ग या इंटरफ़ेस) का उपयोग करे।

सामान्य रूप से कार्यान्वयन को आपके कोड में निर्माता या एक विधि कॉल के माध्यम से इंजेक्ट किया जाता है। तो, आपका कोड इंटरफ़ेस या अमूर्त वर्ग के बारे में जानता है और इस अनुबंध पर परिभाषित कुछ भी कह सकता है। एक वास्तविक वस्तु के रूप में (इंटरफ़ेस / सार वर्ग का कार्यान्वयन) का उपयोग किया जाता है, कॉल ऑब्जेक्ट पर काम कर रहे हैं।

यह सिद्धांतों Liskov Substitution Principleका एल (एलएसपी) का एक सबसेट है SOLID

.NET में एक उदाहरण के साथ कोड होगा IListबजाय Listया Dictionaryतो आप किसी भी वर्ग इस्तेमाल कर सकते हैं, कि औजार IListदूसरे के स्थान पर अपने कोड में:

// myList can be _any_ object that implements IList
public int GetListCount(IList myList)
{
    // Do anything that IList supports
    return myList.Count();
}

बेस क्लास लाइब्रेरी (बीसीएल) का एक और उदाहरण ProviderBaseअमूर्त वर्ग है - यह कुछ बुनियादी ढाँचा प्रदान करता है, और महत्वपूर्ण रूप से इसका अर्थ है कि यदि आप इसके खिलाफ कोड करते हैं, तो सभी प्रदाता कार्यान्वयनों का उपयोग किया जा सकता है।


लेकिन एक ग्राहक एक इंटरफेस के साथ कैसे बातचीत कर सकता है और अपने खाली तरीकों का उपयोग कर सकता है?
never_had_a_name

1
ग्राहक इंटरफ़ेस के साथ बातचीत नहीं करता है, लेकिन इंटरफ़ेस के माध्यम से: ऑब्जेक्ट अन्य ऑब्जेक्ट्स के साथ तरीकों (संदेशों) के माध्यम से बातचीत करते हैं और एक इंटरफ़ेस भाषा का एक प्रकार है - जब आप जानते हैं कि कुछ निश्चित वस्तु (व्यक्ति) लागू होती है (बोलती है) अंग्रेजी (IList) ), आप उस वस्तु के बारे में अधिक जानने के लिए किसी भी आवश्यकता का उपयोग कर सकते हैं (कि वह भी एक इतालवी है), क्योंकि यह उस संदर्भ में आवश्यक नहीं है (यदि आप मदद मांगना चाहते हैं तो आपको यह जानने की आवश्यकता नहीं है कि वह इतालवी भी बोलता है। अगर आप अंग्रेजी समझते हैं)।
गेब्रियल Gabričerbák

Btw। IMHO Liskov प्रतिस्थापन सिद्धांत वंशानुक्रम के अर्थ के बारे में है और इसका इंटरफेस के साथ कोई लेना-देना नहीं है, जो विरासत के बिना भाषाओं में भी पाया जा सकता है (Google से जाएं)।
गेब्रियल Gabričerbák

5

यदि आप दहन-कार युग में एक कार क्लास लिखने के लिए थे, तो इस वर्ग के एक भाग के रूप में आप ऑयलचेंज () को लागू करने का एक शानदार मौका है। लेकिन, जब इलेक्ट्रिक कारों को पेश किया जाता है, तो आप परेशानी में पड़ जाएंगे क्योंकि इन कारों के लिए कोई तेल-परिवर्तन शामिल नहीं है, और कोई कार्यान्वयन नहीं है।

समस्या का हल कार क्लास में एक परफ़ॉर्मेंस () इंटरफ़ेस और उपयुक्त कार्यान्वयन के अंदर विवरणों को छिपाना है। प्रत्येक कार का प्रकार परफॉर्मेंस () के लिए अपना स्वयं का कार्यान्वयन प्रदान करेगा। एक कार के मालिक के रूप में आप सभी के साथ सौदा करना है प्रदर्शन () और जब एक चेंज है तो आदत डालने के बारे में चिंता न करें।

class MaintenanceSpecialist {
    public:
        virtual int performMaintenance() = 0;
};

class CombustionEnginedMaintenance : public MaintenanceSpecialist {
    int performMaintenance() { 
        printf("combustionEnginedMaintenance: We specialize in maintenance of Combustion engines \n");
        return 0;
    }
};

class ElectricMaintenance : public MaintenanceSpecialist {
    int performMaintenance() {
        printf("electricMaintenance: We specialize in maintenance of Electric Cars \n");
        return 0;
    }
};

class Car {
    public:
        MaintenanceSpecialist *mSpecialist;
        virtual int maintenance() {
            printf("Just wash the car \n");
            return 0;
        };
};

class GasolineCar : public Car {
    public: 
        GasolineCar() {
        mSpecialist = new CombustionEnginedMaintenance();
        }
        int maintenance() {
        mSpecialist->performMaintenance();
        return 0;
        }
};

class ElectricCar : public Car {
    public: 
        ElectricCar() {
             mSpecialist = new ElectricMaintenance();
        }

        int maintenance(){
            mSpecialist->performMaintenance();
            return 0;
        }
};

int _tmain(int argc, _TCHAR* argv[]) {

    Car *myCar; 

    myCar = new GasolineCar();
    myCar->maintenance(); /* I dont know what is involved in maintenance. But, I do know the maintenance has to be performed */


    myCar = new ElectricCar(); 
    myCar->maintenance(); 

    return 0;
}

अतिरिक्त स्पष्टीकरण: आप एक कार के मालिक हैं जो कई कारों का मालिक है। आप उस सेवा को बाहर निकालते हैं जिसे आप आउटसोर्स करना चाहते हैं। हमारे मामले में हम सभी कारों के रखरखाव के काम को आउटसोर्स करना चाहते हैं।

  1. आप उस अनुबंध (इंटरफ़ेस) की पहचान करते हैं जो आपकी सभी कारों और सेवा प्रदाताओं के लिए अच्छा है।
  2. सेवा प्रदाता सेवा प्रदान करने के लिए एक तंत्र के साथ बाहर आते हैं।
  3. आप सेवा प्रदाता के साथ कार के प्रकार को संबद्ध करने के बारे में चिंता नहीं करना चाहते हैं। आप केवल तब निर्दिष्ट करते हैं जब आप रखरखाव को शेड्यूल करना चाहते हैं और इसे लागू करना चाहते हैं। उपयुक्त सेवा कंपनी को रखरखाव कार्य में कूदना चाहिए और प्रदर्शन करना चाहिए।

    वैकल्पिक दृष्टिकोण।

  4. आप काम की पहचान करते हैं (एक नया इंटरफ़ेस इंटरफ़ेस हो सकता है) जो आपकी सभी कारों के लिए अच्छा है।
  5. आप सेवा प्रदान करने के लिए एक तंत्र के साथ बाहर आते हैं। मूल रूप से आप कार्यान्वयन प्रदान करने जा रहे हैं।
  6. आप काम का आह्वान करते हैं और खुद करते हैं। यहां आप उचित रखरखाव का काम करने जा रहे हैं।

    2 दृष्टिकोण के नकारात्मक पक्ष क्या है? आप रखरखाव करने का सबसे अच्छा तरीका खोजने में विशेषज्ञ नहीं हो सकते हैं। आपका काम कार चलाना और उसका आनंद लेना है। इसे बनाए रखने के व्यवसाय में नहीं।

    क्या यह पहली दृष्टिकोण के नकारात्मक पक्ष है? एक कंपनी आदि खोजने का ओवरहेड है जब तक आप किराये की कार कंपनी नहीं हैं, यह प्रयास के लायक नहीं हो सकता है।


4

यह कथन युग्मन के बारे में है। ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग का उपयोग करने का एक संभावित कारण पुन: उपयोग है। इसलिए उदाहरण के लिए आप अपने एल्गोरिथ्म को दो सहयोगी वस्तुओं ए और बी के बीच विभाजित कर सकते हैं। यह बाद में एक और एल्गोरिथ्म के निर्माण के लिए उपयोगी हो सकता है, जो एक या दो अन्य वस्तुओं का पुन: उपयोग कर सकता है। हालाँकि, जब वे ऑब्जेक्ट संचार करते हैं (संदेश भेजते हैं - कॉल के तरीके), वे एक-दूसरे के बीच निर्भरता पैदा करते हैं। लेकिन अगर आप एक के बिना दूसरे का उपयोग करना चाहते हैं, तो आपको यह निर्दिष्ट करने की आवश्यकता है कि ऑब्जेक्ट ए के लिए कुछ अन्य ऑब्जेक्ट सी क्या करना चाहिए यदि हम बी की जगह लेते हैं। उन विवरणों को इंटरफेस कहा जाता है। यह ऑब्जेक्ट ए को इंटरफ़ेस पर निर्भर विभिन्न ऑब्जेक्ट के साथ बदलाव के बिना संवाद करने की अनुमति देता है। आपके द्वारा उल्लिखित कथन में कहा गया है कि यदि आप एल्गोरिथ्म के कुछ भाग (या आमतौर पर एक कार्यक्रम) का पुन: उपयोग करने की योजना बनाते हैं, तो आपको इंटरफेस बनाना चाहिए और उन पर भरोसा करना चाहिए,


2

जैसा कि दूसरों ने कहा है, इसका मतलब है कि आपके कॉलिंग कोड को केवल एक अमूर्त माता-पिता के बारे में पता होना चाहिए, न कि वास्तविक कार्यान्वयन वर्ग जो काम करेगा।

यह समझने में क्या मदद करता है कि आपको हमेशा इंटरफ़ेस के लिए कार्यक्रम क्यों करना चाहिए। कई कारण हैं, लेकिन समझाने में सबसे आसान दो हैं

1) परीक्षण।

मान लीजिए कि मेरा पूरा डेटाबेस कोड एक कक्षा में है। यदि मेरे कार्यक्रम को ठोस वर्ग के बारे में पता है, तो मैं केवल अपने कोड को वास्तव में उस वर्ग के खिलाफ चलाकर परीक्षण कर सकता हूं। मैं उपयोग कर रहा हूँ -> का मतलब है "वार्ता"।

वर्करक्लास -> डेलक्लास हालांकि, आइए मिश्रण में एक इंटरफ़ेस जोड़ें।

वर्करक्लास -> IDAL -> DALClass।

इसलिए DALClass IDAL इंटरफ़ेस को लागू करता है, और कार्यकर्ता वर्ग केवल इसके माध्यम से कॉल करता है।

अब अगर हम कोड के लिए परीक्षण लिखना चाहते हैं, तो हम इसके बजाय एक साधारण वर्ग बना सकते हैं जो सिर्फ डेटाबेस की तरह काम करता है।

वर्करक्लास -> IDAL -> IFakeDAL।

२) पुनः प्रयोग

ऊपर दिए गए उदाहरण के बाद, मान लें कि हम SQL सर्वर (जो कि हमारे कंक्रीट डलास का उपयोग करता है) से मोनोगॉब के लिए जाना चाहते हैं। यह प्रमुख काम होगा, लेकिन अगर हम एक इंटरफ़ेस के लिए क्रमादेशित नहीं हैं। उस मामले में हम सिर्फ नया DB वर्ग लिखते हैं, और परिवर्तन (कारखाने के माध्यम से)

वर्करक्लास -> IDAL -> DALClass

सेवा

वर्करक्लास -> IDAL -> MongoDBClass


1

इंटरफेस क्षमताओं का वर्णन करते हैं। अनिवार्य कोड लिखते समय, विशिष्ट प्रकारों या कक्षाओं के बजाय आपके द्वारा उपयोग की जाने वाली क्षमताओं के बारे में बात करें।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.