2010-02-17 20 views
20

में परिभाषा और कार्यान्वयन को अलग करना इंटरफ़ेस या सार कक्षाओं का उपयोग करके परिभाषा और कार्यान्वयन को अलग करने के लिए बेहतर तरीका क्या है?सार बनाम इंटरफेस - डेल्फी

मैं वास्तव में मिश्रण वस्तुओं को अन्य वस्तुओं के साथ मिश्रित संदर्भ पसंद नहीं करता हूं। मुझे कल्पना है कि बड़ी परियोजनाओं को बनाए रखने के दौरान यह एक दुःस्वप्न बन सकता है।

लेकिन कभी-कभी मुझे 2 या अधिक कक्षाओं/इंटरफेस से कक्षा प्राप्त करने की आवश्यकता होगी।

आपका अनुभव क्या है?

+1

+1, बहुत अच्छा सवाल। – mghie

+0

संबंधित प्रश्न: http://stackoverflow.com/questions/918380/abstract-classes-vs-interfaces-vs-mixins। डेल्फी के लिए विशिष्ट सवाल उचित लगता है। – mghie

+0

मुझे अंतर पता है, लेकिन वास्तव में मुझे लगता है कि कुछ याद आ रही है :) शायद दोनों का समझौता? एक नई घोषणा के बारे में क्या: टीबीएस = अमूर्त वर्ग ... अंत; एक इंटरफेस की तरह अधिनियम, लेकिन संदर्भ गिनती के बिना। –

उत्तर

22

यह समझने की कुंजी यह जानना है कि यह परिभाषा बनाम से अधिक है।कार्यान्वयन। यह वही संज्ञा का वर्णन करने के विभिन्न तरीके के बारे में बताया गया है:

  • कक्षा विरासत सवाल का जवाब: "इस वस्तु किस तरह का है"
  • इंटरफ़ेस कार्यान्वयन प्रश्न का उत्तर दें: "मैं इस ऑब्जेक्ट के साथ क्या कर सकता हूं?"

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

type 
    TMaterial = (mtPlastic, mtSteel, mtSilver); 

    TUtensil = class 
    public 
     function GetWeight : Integer; virtual; abstract; 
     procedure Wash; virtual; // Yes, it's self-cleaning 
    published 
     property Material : TMaterial read FMaterial write FMaterial; 
    end; 

यह सब किसी भी बर्तन करने के लिए डेटा और कार्यक्षमता आम का वर्णन करता है - क्या यह बना है, इसका वजन क्या है (जो ठोस प्रकार पर निर्भर करता है), लेकिन आप देखेंगे कि अमूर्त वर्ग वास्तव में कुछ भी नहीं करता है। TFork और TKnife वास्तव में अधिक सामान्य नहीं है कि आप बेस क्लास में डाल सकते हैं। आप तकनीकी रूप से CutTFork के साथ कर सकते हैं, लेकिन TSpoon एक खिंचाव हो सकता है, तो इस तथ्य को कैसे प्रतिबिंबित किया जाए कि केवल कुछ बर्तन कुछ चीजें कर सकते हैं?

ठीक है, हम पदानुक्रम का विस्तार शुरू कर सकते हैं, लेकिन यह गंदा हो जाता है:

type 
    TSharpUtensil = class 
    public 
     procedure Cut(food : TFood); virtual; abstract; 
    end; 

कि तेज लोगों का ख्याल रखता है, लेकिन अगर हम समूह के बजाय इस तरह से करना चाहते हैं?

type 
    TLiftingUtensil = class 
    public 
     procedure Lift(food : TFood); virtual; abstract; 
    end; 

TFork और TKnife दोनों TSharpUtensil के तहत फिट होगा, लेकिन TKnife चिकन का एक टुकड़ा ऊपर उठाने के लिए बहुत घटिया है। हम या तो इन पदानुक्रमों में से किसी एक को चुनने के लिए समाप्त होते हैं, या इस कार्यक्षमता को सामान्य TUtensil में फेंक देते हैं और कक्षाएं प्राप्त कर चुके हैं जो विधियों को लागू करने से इनकार करते हैं। डिजाइन के लिहाज से, यह एक ऐसी स्थिति है कि हम खुद में अटक गया। पता लगाना चाहते हैं नहीं है

बेशक

इस के साथ वास्तविक समस्या यह है कि हम विरासत का उपयोग कर रहे वर्णन करने के लिए क्या एक वस्तु करता है, न कि इसमें क्या है। पूर्व के लिए, हमारे पास इंटरफेस हैं। हम इस डिज़ाइन को एक बहुत साफ कर सकते हैं:

type 
    IPointy = interface 
     procedure Pierce(food : TFood); 
    end; 

    IScoop = interface 
     procedure Scoop(food : TFood); 
    end; 

अब हम सुलझा सकते हैं ठोस प्रकार क्या करना:

type 
    TFork = class(TUtensil, IPointy, IScoop) 
     ... 
    end; 

    TKnife = class(TUtensil, IPointy) 
     ... 
    end; 

    TSpoon = class(TUtensil, IScoop) 
     ... 
    end; 

    TSkewer = class(TStick, IPointy) 
     ... 
    end; 

    TShovel = class(TGardenTool, IScoop) 
     ... 
    end; 

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

किया जाए या नहीं आप अमूर्त वर्ग या एक या इंटरफेस नीचे की ओर वास्तव में आप इसके साथ क्या करने की जरूरत पर निर्भर करता है अधिक का उपयोग करने के लिए चुन:,

type 
    TDishwasher = class 
     procedure Wash(utensils : Array of TUtensil); 
    end; 

यह समझ में आता है क्योंकि केवल बर्तन में जाना डिशवॉशर, कम से कम हमारे सीमित रसोईघर में जिसमें व्यंजन या कप जैसी सुविधाएं शामिल नहीं हैं। TSkewer और TShovel शायद वहां नहीं जाते हैं, भले ही वे खाने की प्रक्रिया में तकनीकी रूप से भाग ले सकें।

दूसरी ओर:

type 
    THungryMan = class 
     procedure EatChicken(food : TFood; utensil : TUtensil); 
    end; 

यह इतना अच्छा नहीं हो सकता है। वह सिर्फ TKnife (अच्छी तरह से नहीं, आसानी से) के साथ नहीं खा सकता है। और TFork और TKnife दोनों की आवश्यकता नहीं है; क्या होगा यदि यह एक चिकन विंग है?

यह एक बहुत अधिक समझ में आता है:

type 
    THungryMan = class 
     procedure EatPudding(food : TFood; scoop : IScoop); 
    end; 

अब हम उसे दे सकते हैं या तो TFork, TSpoon, या TShovel, और वह खुश है, लेकिन नहीं TKnife, जो अभी भी एक बर्तन है, लेकिन ऐसा नहीं करता वास्तव में यहां मदद करें।

आप यह भी देखेंगे कि दूसरा संस्करण वर्ग पदानुक्रम में परिवर्तनों के प्रति कम संवेदनशील है। यदि हम से प्राप्त होने के लिए TFork को बदलने का निर्णय लेते हैं, तो हमारे आदमी अभी भी खुश हैं जब तक कि यह अभी भी IScoop लागू करता है।


मैं भी एक तरह से यहां संदर्भ गिनती मुद्दे पर भुला दिया है, और मैं @Deltics यह सबसे अच्छा ने कहा कि लगता है; सिर्फ इसलिए कि आपके पास AddRef है इसका मतलब यह नहीं है कि आपको उसी चीज़ को करने की आवश्यकता है जो TInterfacedObject करता है। इंटरफेस संदर्भ-गिनती एक आकस्मिक विशेषता है, यह उस समय के लिए उपयोगी उपकरण है जब आपको इसकी आवश्यकता होती है, लेकिन यदि आप कक्षा अर्थशास्त्र (और अक्सर आप हैं) के साथ इंटरफेस मिश्रण करने जा रहे हैं, तो यह हमेशा नहीं होता है संदर्भ-गणना सुविधा का उपयोग स्मृति प्रबंधन के रूप में करने के लिए समझ में आता है।

असल में, मैं कहता हूं कि अधिकतर समय में, आप शायद संदर्भ गणना अर्थशास्त्र संदर्भ नहीं चाहते हैं। हाँ, वहां, मैंने यह कहा। मुझे हमेशा लगा कि पूरी रेफ-गिनती चीज सिर्फ ओएलई स्वचालन और ऐसे (IDispatch) का समर्थन करने में मदद करने के लिए थी। जब तक आपके इंटरफ़ेस के स्वचालित विनाश को पाने का कोई अच्छा कारण न हो, तो बस इसके बारे में भूल जाएं, TInterfacedObject का उपयोग न करें। जब आपको आवश्यकता हो तो आप इसे हमेशा बदल सकते हैं - यह एक इंटरफेस का उपयोग करने का मुद्दा है! स्मृति/आजीवन प्रबंधन के परिप्रेक्ष्य से नहीं, एक उच्च स्तरीय डिज़ाइन बिंदु दृश्य से इंटरफेस के बारे में सोचें।


तो कहानी का नैतिक है:

  • जब आप समर्थन कुछ विशेष कार्यक्षमता को एक वस्तु की आवश्यकता होती है, एक इंटरफेस का उपयोग करने का प्रयास करें।

  • जब वस्तुओं एक ही परिवार की रहे हैं और आप शेयर आम सुविधाओं करने के लिए उन्हें चाहते हैं, एक आम आधार वर्ग से विरासत।

  • और यदि दोनों स्थितियां लागू होती हैं, तो दोनों का उपयोग करें!

3

डेल्फी में कार्यान्वयन से परिभाषा को अलग करने के तीन तरीके हैं।

  1. आपके पास प्रत्येक इकाई में एक अलगाव है जहां आप इंटरफ़ेस अनुभाग में प्रकाशन वर्ग रख सकते हैं और कार्यान्वयन अनुभाग में इसका कार्यान्वयन कर सकते हैं। कोड अभी भी एक ही इकाई में रहता है लेकिन कम से कम आपके कोड के "उपयोगकर्ता" को केवल इंटरफ़ेस को पढ़ने की आवश्यकता होती है, न कि कार्यान्वयन की झटके।

  2. अपनी कक्षा में वर्चुअल या गतिशील-सहयोगी घोषित कार्यों का उपयोग करते समय आप सबक्लास में ओवरराइड कर सकते हैं। अधिकांश वर्ग पुस्तकालयों का उपयोग करने का यही तरीका है। टीस्ट्रीम देखें और यह थंडलस्ट्रीम, टीफाइलस्ट्रीम इत्यादि जैसी व्युत्पन्न कक्षाएं

  3. आप केवल कक्षा व्युत्पन्न की तुलना में एक अलग पदानुक्रम की आवश्यकता होने पर इंटरफेस का उपयोग कर सकते हैं। इंटरफेस हमेशा IInterface से व्युत्पन्न होते हैं जिसे COM आधारित IU अज्ञात के लिए मॉडलिंग किया जाता है: आपको इसके साथ संदर्भित संदर्भ गणना और पूछताछ प्रकार की जानकारी मिलती है।

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

betweeen 2 और 3 का चयन कभी-कभी काफी व्यक्तिपरक होता है। मैं निम्नलिखित का उपयोग करता हूं:

  • यदि संभव हो, तो वर्चुअल और गतिशील का उपयोग करें और व्युत्पन्न कक्षाओं में उनको ओवरराइड करें।
  • इंटरफेस के साथ काम करते समय: एक बेस क्लास बनाएं जो एक चर के रूप में इंटरफेका उदाहरण के संदर्भ को स्वीकार करता है और जितना संभव हो सके अपने इंटरफेस को सरल रखें; प्रत्येक पहलू के लिए एक अलग इंटरका चर बनाने की कोशिश करें। कोई इंटरफ़ेस निर्दिष्ट नहीं होने पर स्थान पर डिफ़ॉल्ट कार्यान्वयन करने का प्रयास करें।
  • यदि उपर्युक्त बहुत सीमित है: TInterfacedObject-s का उपयोग शुरू करें और वास्तव में संभावित चक्रों और इसलिए स्मृति लीक के लिए देखें।
+0

मुझे यह लिखना पसंद है। बहुत सारी जानकारी। ध्यान दें कि एफपीसी में "3 के ​​लिए" मुद्दे के आसपास काम करने के लिए कॉर्बा इंटरफेस है, लेकिन यह वास्तव में कभी बाहर नहीं निकलता है। सरल इंटरफेस के लिए –

+0

+1। 15 इंटरफ़ेस विधियों को लागू करने के लिए मजबूर होना पड़ता है जब आपको उनमें से केवल एक का उपयोग करने की आवश्यकता नहीं होती है। –

8

मुझे शक है कि यह "बेहतर दृष्टिकोण" का सवाल है - वे बस विभिन्न उपयोग मामलों है।

  • आप एक वर्ग पदानुक्रम है, तो नहीं है, और आप एक निर्माण करने के लिए नहीं करना चाहते हैं, और यह भी मतलब नहीं है एक ही पदानुक्रम में असंबंधित वर्गों के लिए मजबूर करने - लेकिन आप करना चाहते हैं वैसे भी वर्ग के विशिष्ट नाम पता किए बिना कुछ वर्गों बराबर का इलाज ->

    इंटरफेस जाना (Javas तुलनीय या Iterateable उदाहरण के लिए के बारे में सोचना तरीका है, आप उन वर्गों से प्राप्त करने के लिए होता है, तो (बशर्ते वे कक्षाएं हों), वे पूरी तरह बेकार होंगे।

  • यदि आप में उचित कक्षा hiearchy है, तो आप इस पदानुक्रम के सभी वर्गों के लिए एक समान पहुंच बिंदु प्रदान करने के लिए अमूर्त कक्षाओं का उपयोग कर सकते हैं, लाभ के साथ कि आप डिफ़ॉल्ट व्यवहार को भी कार्यान्वित कर सकते हैं।
+0

+1। और यदि उपयोग के मामले ओवरलैप करते हैं तो निश्चित रूप से कक्षा वर्ग पदानुक्रम में वर्गों को इंटरफेस लागू करने के लिए पूरी तरह से उचित है। – mghie

+0

मैं (भी) कक्षाओं को टेस्ट करने योग्य बनाने के बारे में सोचता हूं। एक विधि या वर्ग केवल पारित वस्तु पर उचित कार्यों का आह्वान करने के लिए एक इंटरफेस/अमूर्त वर्ग का उपयोग करता है। फिर आप इकाई परीक्षण (निर्भरता उलटा सिद्धांत) के लिए विभिन्न कार्यान्वयन का उपयोग कर सकते हैं। –

+0

जितना अधिक आप टीडीडी करना शुरू करते हैं, उतना अधिक आप इंटरफेस का उपयोग करके खुद को पाते हैं। इंटरफेस आपको लेखन टेस्टकेस को सरल बनाने में सक्षम बनाता है। * विरासत पर रचना का अनुकूलन * समझ में आता है। हम पूरी निपुणता हासिल करने की कोशिश में विभिन्न चरणों से गुजर चुके हैं। मैं * सब कुछ एक इंटरफ़ेस * चरण बना रहा हूं :) निम्नलिखित दिलचस्प हो सकता है http://bsix12.com/shu-ha-ri/ –

5

आप संदर्भ गणना के बिना इंटरफेस रख सकते हैं। कंपाइलर सभी इंटरफेस के लिए AddRef और रिलीज को कॉल जोड़ता है लेकिन उन ऑब्जेक्ट्स का आजीवन प्रबंधन पहलू पूरी तरह से अज्ञात के कार्यान्वयन के लिए नीचे है।

यदि आप टीइंटरफेस्ड ऑब्जेक्ट से प्राप्त होते हैं तो ऑब्जेक्ट आजीवन वास्तव में संदर्भित किया जाएगा, लेकिन यदि आप अपनी कक्षा को टॉब्जेक्ट से प्राप्त करते हैं और वास्तव में संदर्भों की गणना किए बिना और अज्ञात को कार्यान्वित किए बिना "स्वयं" को मुक्त किए बिना IU अज्ञात को लागू करते हैं तो आपको एक प्राप्त होगा आधार वर्ग जो इंटरफेस का समर्थन करता है लेकिन सामान्य रूप से एक सामान्य रूप से प्रबंधित जीवनकाल सामान्य है।

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

यह ऐसा कुछ है जिसे मैंने अतीत में परिष्कृत और बड़ी परियोजनाओं में सफलतापूर्वक उपयोग किया है, यहां तक ​​कि इंटरफेस का समर्थन करने वाले रेफ गिनती और गैर-रेफरी गिनती वस्तुओं को भी मिलाकर।

+0

टीकंपोनेंट से प्राप्त करने से संदर्भ गणना भी अक्षम हो जाएगी (बस अगर आप आलसी हैं)। पाठ्यक्रम के अपने कार्यान्वयन की तुलना में नुकसान यह है कि यह कुछ ओवरहेड जोड़ता है। – dummzeuch

1

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

1

मैं कॉम इंटरफेस को कभी भी कभी भी इस्तेमाल नहीं करने के बिंदु से नापसंद करता हूं, सिवाय इसके कि किसी और ने एक उत्पादित किया हो। शायद यह COM और टाइप लाइब्रेरी सामान के अविश्वास से आया था। इंटरफेस का उपयोग करने के बजाय मैंने कॉलबैक प्लग-इन वाले कक्षाओं के रूप में इंटरफेस को "फिक" किया है। मुझे आश्चर्य है कि किसी और ने मेरे दर्द को महसूस किया है, और इंटरफेस के उपयोग से परहेज किया जैसे कि वे एक प्लेग थे?

मुझे पता है कि कुछ लोग इंटरफ़ेस को मेरी कमजोरी से बचने पर विचार करेंगे। लेकिन मुझे लगता है कि इंटरफेस का उपयोग कर सभी डेल्फी कोड में "कोड गंध" है।

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

+1

यदि आप कारणों पर विस्तारित हैं तो यह सहायक होगा। COM इंटरफेस के बारे में इतना दर्दनाक क्या था? – reinierpost

+0

कुछ डेवलपर्स भी कोड लाइनों का सपना देखते हैं जैसे ISmellVeryBadly = इंटरफ़ेस (...) :) – mjn

+0

हां, मैं भी COM के लिए एलर्जी हूं। हो सकता है कि यह डायरेक्टएक्स के संपर्क में हो जो मुझे बंद कर दे। –

संबंधित मुद्दे