2010-11-30 7 views
5

मान लीजिए मैं एक अंतरफलक है कि कुछ संभावित आपरेशन का समर्थन करता है है:वैकल्पिक घटकों के साथ एक इंटरफेस के लिए एक अच्छा डिजाइन क्या है?

interface Frobnicator { 
    int doFoo(double v); 
    int doBar(); 
} 

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

मुझे इसे संभालने के कुछ तरीके दिखाई देते हैं। एक, जो जावा एपीआई में ली गई सामान्य रणनीति प्रतीत होता है, ऊपर दिखाए गए इंटरफ़ेस के पास है और असमर्थित विधियां UnsupportedOperationException बढ़ाती हैं। हालांकि, यह असफल रहा है, असफल होने के कारण - क्लाइंट कोड यह नहीं बता सकता कि doFoodoFoo पर कॉल करने का प्रयास करने तक काम करेगा या नहीं।

इसे supportsFoo() और supportsBar() विधियों के साथ बढ़ाया जा सकता है, जो सही do विधि कार्य करता है तो सही वापस लौटने के लिए परिभाषित किया गया है।

एक और रणनीति doFoo और doBar विधियों को FooFrobnicator और BarFrobnicator विधियों में क्रमशः कारक करना है। यदि ऑपरेशन असमर्थित है तो ये विधियां null लौटाएंगी। instanceof चेकों क्या करने वाले से ग्राहक कोड रखने के लिए, मैं एक Frobnicator इंटरफेस इस प्रकार निर्धारित करें:

interface Frobnicator { 
    /* Get a foo frobnicator, returning null if not possible */ 
    FooFrobnicator getFooFrobnicator(); 
    /* Get a bar frobnicator, returning null if not possible */ 
    BarFrobnicator getBarFrobnicator(); 
} 

interface FooFrobnicator { 
    int doFoo(double v); 
} 

interface BarFrobnicator { 
    int doBar(); 
} 

वैकल्पिक रूप से, FooFrobnicator और BarFrobnicatorFrobnicator का विस्तार कर सकता है, और get* तरीकों संभवतः as* नाम दिया जा।

इसके साथ एक मुद्दा नामकरण है: Frobnicator वास्तव में एक फ्रोबनिनेटर नहीं है, यह फ्रोबनिकेटर प्राप्त करने का एक तरीका है (जब तक कि मैं as* नामकरण का उपयोग नहीं करता)। यह भी एक ताकत कमजोर हो जाता है। नामकरण और जटिल हो सकता है, क्योंकि Frobnicator को FrobnicatorEngine सेवा से पुनर्प्राप्त किया जाएगा।

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

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

+5

वास्तव में एक तीसरा विकल्प है: 'फ्रोबनिनेटर फूफोबनिनेटर, बारफोबनिनेटर' को बढ़ाता है। एक घटक जो केवल 'FooFrobnicator' है, केवल 'doFoo() 'का समर्थन करेगा, जबकि' BarFrobnicator 'केवल' doBar() 'का समर्थन करेगा। इस के साथ क्या समस्या है? – Riduidel

+0

@ रिडुइडल फिर 'फ्रोबिनेटर' प्रदान करने वाली एक कक्षा है, लेकिन केवल 'डूफू()' का समर्थन करने के लिए 'बारफोबनाइनेटर' को भी लागू करना चाहिए, जो मैंने प्रस्तुत किए गए पहले इंटरफेस में समस्या को कम कर दिया है। मैंने कुछ इंटरफेस को सीधे इस्तेमाल करने की अनुमति देने के लिए प्रकारों को कैसे ले जाना है, इस बारे में कुछ सोचा है, लेकिन यह रन-टाइम कॉन्फ़िगरेशन को एक दुःस्वप्न बनाता है। –

उत्तर

3

मैं तुम्हें एक समस्या है लगता है कि अगर आपको लगता है कि वर्ग FooImpl लागू करता है इंटरफ़ेस फू कह रहे हैं, लेकिन यह वास्तव में फू की सभी को लागू नहीं करता है। इंटरफ़ेस एक अनुबंध माना जाता है जो कार्यान्वयन वर्ग (न्यूनतम पर) लागू करने के तरीकों को दर्शाता है।

अगर कुछ फू ऑब्जेक्ट प्राप्त करने के लिए FooFactory को कॉल कर रहा है, तो ऑब्जेक्ट फ़ूज़ करने के लिए सक्षम होना चाहिए।

तो, आप वहां से कहां जाते हैं? मैं सुझाव दूंगा कि आपके रचना विचार एक अच्छा है, विरासत का उपयोग थोड़ा कम है।

संरचना ठीक उसी तरह काम करेगी जैसा आपके पास getFooFrobnicator() और getBarFrobnicator() विधियों के साथ है। यदि आपको यह पसंद नहीं है कि आपका Frobnicator वास्तव में frobnicate नहीं है, तो इसे FrobnicatorHolder पर कॉल करें।

विरासत में आपके Frobnicator होंगे जिसमें सभी फ्रोबनाइनेटर्स के पास केवल विधियां होंगी, और उप-इंटरफेस में अतिरिक्त विधियां होंगी।

public interface Frobnicator{ 
    public void frobnicate(); 
} 

public interface FooFrobnicator{ 
    public void doFoo(); 
} 

public interface BarFrobnicator{ 
    public void doBar(); 
} 

तब आप if (frobnicator instanceof FooFrobnicator) { ((FooFrobnicator) frobnicator).doFoo() } का उपयोग कर सकते हैं और जीवन चल रहा है।

विरासत पर रचना का पक्ष लें। आप खुश रहेंगे

+0

अब तक, रचना तब तक जीत रही है जब तक मुझे सिस्टम के माध्यम से वास्तविक इंटरफेस को धक्का देने का कोई अच्छा तरीका न मिल जाए। –

+0

मैं अंततः संरचना दृष्टिकोण के साथ गया, जिसमें 'getobooFrobnicator' और' getBarFrobnicator' विधियों के साथ 'FrobnicatorEngine' है (प्रत्येक उस विशेष इंजन द्वारा समर्थित नहीं होने पर प्रत्येक वापसी शून्य)। आप सही हैं, जूता-सींग विरासत का प्रयास करते हैं जहां संरचना अधिक उपयुक्त है अनावश्यक और अनुचित दर्द है। रचना-आधारित एपीआई बहुत साफ है। मैं अभी भी वास्तविक प्रकारों को धक्का देने के लिए एक रास्ता खोजने का प्रयास कर सकता हूं, इसलिए चीजों को फूफ्रोबनाइनेटर आदि मिलते हैं, इंजनों की तुलना में rater, लेकिन यह भविष्य का काम है। –

1

UnsupportedOperationExceptions फेंकने के बजाय खाली गैर-कार्यान्वयन विधियों को जोड़ने के बारे में कैसे? इस तरह, असमर्थित विधि को कॉल करने से कोई दुष्प्रभाव नहीं होगा, और रन-टाइम त्रुटि नहीं होगी।

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

interface Frobnicator {} 

interface FooFrobnicator extends Frobnicator { 
    void doFoo(); 
} 

interface BarFrobnicator extends Frobnicator { 
    void doBar(); 
} 

संपादित करें:

कर इसे अपने तरीकों के एक बूलियन वापसी प्रकार जोड़ने के लिए, और यह सुनिश्चित करें कि वे केवल झूठे वापसी करता है, तो विधि समर्थित नहीं है बनाने के लिए किया जाएगा का एक और तरीका। इसलिए:

interface Frobnicator 
{ 
    boolean doFoo(); 
    boolean doBar(); 
} 

class FooFrobnicator implements Frobnicator 
{ 
    public boolean doFoo() { code, code, code; return true; } 
    public boolean doBar() { return false; } 
} 
+0

विधियां थोड़ा अधिक जटिल हैं; नो-ऑप्स काम नहीं करेगा। मैंने इसे प्रतिबिंबित करने के लिए प्रश्न अपडेट किया है। साथ ही, यदि ऑब्जेक्ट 'doFoo' का समर्थन नहीं करता है, तो क्लाइंट' doFoo' के लिए तैयार करने के लिए काम करने से बचने में सक्षम हो सकता है जो काम नहीं करेगा। मैंने इंटरफ़ेस समाधान के बारे में भी सोचा है; प्रश्न में वर्णित उन्नत दूसरी विधि अनिवार्य रूप से इस का परिशोधन है (क्लाइंट कोड की आवश्यकता को 'उदाहरण' चेक और कैस्ट करने की आवश्यकता से बचने के लिए)। –

+0

मूल रूप से मेरे उत्तर के समान, इसलिए मैंने इसे हटा दिया है। +1 –

+0

@ माइकल ई - आप क्लाइंट को 'exampleof' करने की आवश्यकता से बचते हैं, लेकिन उन्हें अभी भी' शून्य 'के लिए लौटाए गए इंटरफेस की जांच करनी है। एक और वर्बोज़ का आविष्कार क्यों करें, कुछ ऐसा करने का कस्टम तरीका जो भाषा पहले से ही समर्थन करता है? –

4

मुझे इसे संभालने के कुछ तरीके दिखाई देते हैं। एक, जो जावा एपीआई में ली गई सामान्य रणनीति प्रतीत होता है, ऊपर दिखाए गए इंटरफ़ेस के पास है और असमर्थित विधियां असमर्थितऑपरेशन अपवाद को बढ़ाती हैं। हालांकि, यह असफल रहा है, असफल होने के कारण - क्लाइंट कोड यह नहीं बता सकता कि क्या DoFoo काम करेगा जब तक कि वह doFoo को कॉल करने का प्रयास नहीं करता है।

जैसा कि आपने कहा था, सामान्य रणनीति template method डिज़ाइन पैटर्न का उपयोग करना है। एक उत्कृष्ट उदाहरण HttpServlet है।

यहां बताया गया है कि आप इसे कैसे प्राप्त कर सकते हैं।

public interface Frobnicator { 
    int doFoo(double v); 
    int doBar(); 
} 

public abstract class BaseFrobnicator implements Frobnicator { 
    public int doFoo(double v) { 
     throw new UnsupportedOperationException(); 
    } 
    public int doBar() { 
     throw new UnsupportedOperationException(); 
    } 
} 

/** 
* This concrete frobnicator only supports the {@link #doBar()} method. 
*/ 
public class ConcreteFrobnicator extends BaseFrobnicator { 
    public int doBar() { 
     return 42; 
    } 
} 

ग्राहक सिर्फ डॉक्स पढ़ सकते हैं और तदनुसार UnsupportedOperationException को संभालने के लिए है। चूंकि यह RuntimeException है, यह "प्रोग्रामर त्रुटि" के लिए एक आदर्श मामला है। सच है, यह असफल नहीं है (यानी संकलन नहीं), लेकिन डेवलपर के रूप में आपको यही भुगतान मिलता है। बस इसे रोकें या पकड़ें और इसे संभालें।

+0

आपको भुगतान मिलता है! – willcodejavaforfood

+0

असफल समय पर चेक-फास्ट एक ही चीज़ नहीं है जैसा चेक किया गया है। विफलता से क्लाइंट कोड समय से पहले पूछता है "क्या यह काम करेगा?" इसलिए यह कोशिश नहीं करेगा (जिसमें महंगा सेट-अप शामिल हो सकता है) यदि यह काम नहीं करेगा। साथ ही, 'असमर्थितऑपरेशन अपवाद' लिस्कोव प्रतिस्थापन की भावना का उल्लंघन है (कुछ मायनों में, प्रश्न में मेरा पूरा सेटअप भी है, लेकिन मैं इसे हल करने के लिए कम से कम भयानक तरीका ढूंढ रहा हूं)। –

+0

असफल होने के नाते, मेरा मतलब यह नहीं था कि इसका मतलब यह है कि इसका मतलब है। मैं बस सामान्य रूप से टेम्पलेट विधि पैटर्न का जिक्र कर रहा था। लिस्कोव के रूप में, इस मामले में उन चिंताओं की आवश्यकता है, संभावित दृष्टिकोण नहीं। वैसे, 'HttpServlet'' doOptions() 'विधि प्रदान करता है जो प्रतिबिंबित द्वारा समर्थित विधियों की जांच करता है। आप इसे भी विचार करना चाहेंगे। एक पूरी तरह से अलग विकल्प है कि पूरी चीज को फिर से डिजाइन करने के लिए केवल एक विधि है जो तर्क के रूप में एक और इंटरफ़ेस लेता है ताकि ग्राहक स्वयं एल्गोरिदम की आपूर्ति कर सके, रणनीति पैटर्न पर नज़र डालें। – BalusC

2

सबसे स्पष्ट समाधान केवल दो इंटरफेस FooFrobnicator और BarFrobnicator को परिभाषित करना है। यह ठीक होना चाहिए क्योंकि कक्षा कई इंटरफेस लागू कर सकती है।

यदि आवश्यक हो तो आप एक सुपर (मार्कर) इंटरफ़ेस प्रदान कर सकते हैं।

+1

+1 यदि आप यह कह रहे हैं, Frobnicator इंटरफ़ेस को संतुष्ट करने के लिए, किसी ऑब्जेक्ट को foo या bar करना चाहिए, या शायद दोनों - यह एक खराब इंटरफ़ेस है। यह बुरा डिजाइन है। आपके पास फूर्स और बैरर्स हैं, और आपकी कुछ वस्तुएं दोनों हैं - क्लास डिज़ाइन को प्रतिबिंबित करने दें। –

0

सरल समाधान supports*() तरीकों,

जोड़ने के लिए अगर आपको लगता है कि अच्छा शैली नहीं है, तो बस Frobnicator के बारे में भूल है (यह आप बहुत कम कर देता है के रूप में यह एक अंतरफलक आप पर भरोसा कर सकते नहीं है) और कोड सीधे FooFrobnicator और BarFrobnicator के खिलाफ, और यदि वे एक ही ऑब्जेक्ट द्वारा कार्यान्वित किए जाते हैं, तो ठीक है। यह आपको क्लाइंट कोड में एक विशिष्ट इंटरफ़ेस की आवश्यकता होने की संभावना भी देता है:

interface FooFrobnicator { 
    doFoo(); 
} 

interface BarFrobnicator { 
    doBar(); 
} 

public class Client { 
    ... 
    FooFrobnicator fooFrobnicator; 
    public void setFooFrobnicator(FooFrobnicator fooFrobnicator) { 
     this.fooFrobnicator = fooFrobnicator; 
    } 

    BarFrobnicator barFrobnicator; 
    public void setBarFrobnicator(BarFrobnicator barFrobnicator) { 
     this.barFrobnicator = barFrobnicator; 
    } 
    ... 
    public void doSomething() { 
     ... 
     if (fooFrobnicator != null) { ... } 
     ... 
     if (barFrobnicator != null) { ... } 
    } 
} 
... 
public class FrobnicatorImpl implements FooFrobnicator, BarFrobnicator { ... } 
... 
public void doSomething() { 
    ... 
    FrobnicatorImpl impl = new FrobnicatorImpl(); 
    Client client = new Client();   
    client.setFooFrobnicator(impl); 
    client.setBarFrobnicator(impl); 
    ... 
} 
-1

IMHO, आप इस तरह से बहुत जटिल बना रहे हैं। इंटरफ़ेस से दूर करो। आमतौर पर उपयोग की जाने वाली विधियों के साथ एक बेस क्लास बनाएं (यदि आप नहीं जानते कि उन्हें कैसे कार्यान्वित किया जाए तो वे बेस क्लास में कुछ भी नहीं कर सकते हैं)। फिर बेस क्लास & को जिस भी तरीके की आवश्यकता है उसे लागू/ओवरराइड करें।

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