जब भी मैं एक ऐसी विधि देखता हूं जहां व्यवहार अपने पैरामीटर के प्रकार पर स्विच करता है, तो मैं तुरंत पहले विचार करता हूं कि क्या वह विधि वास्तव में विधि पैरामीटर पर है। उदाहरण के लिए, इसके बजाय एक विधि की तरह:
public void sort(List values) {
if (values instanceof LinkedList) {
// do efficient linked list sort
} else { // ArrayList
// do efficient array list sort
}
}
मैं यह करूँगा:
values.sort();
// ...
class ArrayList {
public void sort() {
// do efficient array list sort
}
}
class LinkedList {
public void sort() {
// do efficient linked list sort
}
}
हम व्यवहार को उस स्थान पर ले जाते हैं जो जानता है कि इसका उपयोग कब करना है। हम एक वास्तविक अमूर्त बनाते हैं जहाँ आपको प्रकार या कार्यान्वयन के विवरण को जानने की आवश्यकता नहीं है। आपकी स्थिति के लिए, इस विधि को मूल वर्ग (जिसे मैं कॉल करूंगा O
) से टाइप करने A
और इसे ओवरराइड करने के लिए स्थानांतरित करने के लिए अधिक समझदारी हो सकती है B
। यदि विधि को doIt
किसी वस्तु पर बुलाया जाता है , तो अलग-अलग व्यवहार के साथ स्थानांतरित doIt
करें A
और ओवरराइड करें B
। यदि doIt
मूल रूप से कहा जाता है, जहां से डेटा बिट्स हैं , या यदि विधि का उपयोग पर्याप्त स्थानों पर किया जाता है, तो आप मूल विधि और प्रतिनिधि को छोड़ सकते हैं:
class O {
int x;
int y;
public void doIt(A a) {
a.doIt(this.x, this.y);
}
}
हम हालांकि थोड़ा गहरा गोता लगा सकते हैं। आइए इसके बजाय एक बूलियन पैरामीटर का उपयोग करने के सुझाव को देखें और देखें कि हम आपके सहकर्मी के सोचने के तरीके के बारे में क्या सीख सकते हैं। उनका प्रस्ताव है:
public void doIt(A a, boolean isTypeB) {
if (isTypeB) {
// do B stuff
} else {
// do A stuff
}
}
यह instanceof
मेरे पहले उदाहरण में उपयोग किए गए एक बहुत बड़ा लग रहा है, सिवाय इसके कि हम उस चेक को बाहरी कर रहे हैं। इसका मतलब है कि हमें इसे दो में से एक तरीके से कॉल करना होगा:
o.doIt(a, a instanceof B);
या:
o.doIt(a, true); //or false
पहले तरीके से, कॉल पॉइंट का कोई पता नहीं है कि A
इसका क्या प्रकार है। इसलिए, क्या हमें पूरे रास्ते में बूलियंस को पास करना चाहिए? क्या यह वास्तव में एक पैटर्न है जो हम सभी कोड बेस पर चाहते हैं? यदि कोई तीसरा प्रकार है, तो हमें क्या करना चाहिए? यदि इस प्रकार विधि को कहा जाता है, तो हमें इसे प्रकार में ले जाना चाहिए और सिस्टम को हमारे लिए बहुरूपिक रूप से कार्यान्वयन का चयन करने देना चाहिए।
दूसरा तरीका में, हम पहले से ही पता होना चाहिए के प्रकार के a
कॉल बिंदु पर। आमतौर पर इसका मतलब है कि हम या तो वहाँ उदाहरण बना रहे हैं, या पैरामीटर के रूप में उस प्रकार का उदाहरण ले रहे हैं। O
उस पर एक विधि बनाना B
यहाँ काम करेगा। कंपाइलर को पता होगा कि किस विधि को चुनना है। जब हम इस तरह के बदलावों से गुजर रहे होते हैं, तो नकल गलत अमूर्तता पैदा करने से बेहतर है , कम से कम जब तक हम यह पता नहीं लगाते हैं कि हम वास्तव में कहां जा रहे हैं। बेशक, मैं सुझाव दे रहा हूं कि हम वास्तव में कोई फर्क नहीं पड़ता कि हमने इस बिंदु पर क्या बदल दिया है।
हमें A
और के बीच के संबंधों को और करीब से देखने की जरूरत है B
। आमतौर पर, हमें बताया जाता है कि हमें विरासत पर रचना का पक्ष लेना चाहिए । यह हर मामले में सच नहीं है, लेकिन यह मामलों की एक आश्चर्य की बात संख्या में सच है एक बार हम में खुदाई। B
से विरासत में मिली A
है, जिसका अर्थ हमें विश्वास है कि, B
एक है A
। B
बस ऐसे ही इस्तेमाल किया जाना चाहिए A
, सिवाय इसके कि यह थोड़ा अलग तरीके से काम करता है। लेकिन वे अंतर क्या हैं? क्या हम मतभेदों को अधिक ठोस नाम दे सकते हैं? क्या यह B
एक नहीं है A
, लेकिन वास्तव A
में ऐसा X
है A'
या हो सकता है B'
? अगर हमने ऐसा किया तो हमारा कोड कैसा दिखेगा?
यदि हमने पहले बताए अनुसार विधि को आगे बढ़ाया है A
, तो हम एक उदाहरण के रूप X
में इंजेक्ट कर सकते हैं A
, और उस विधि को X
निम्न पर सौंप सकते हैं :
class A {
X x;
A(X x) {
this.x = x;
}
public void doIt(int x, int y) {
x.doIt(x, y);
}
}
हम लागू कर सकते हैं A'
और B'
, और छुटकारा पा सकते हैं B
। हमने एक अवधारणा को एक नाम देकर कोड में सुधार किया है जो संभवतः अधिक अंतर्निहित हो सकता है, और खुद को संकलन समय के बजाय उस व्यवहार को रनटाइम पर सेट करने की अनुमति देता है। A
वास्तव में कम अमूर्त भी बन गया है। एक विस्तारित विरासत संबंध के बजाय, यह एक प्रत्यायोजित वस्तु पर कॉलिंग विधि है। वह वस्तु अमूर्त है, लेकिन कार्यान्वयन में अंतर पर अधिक केंद्रित है।
हालांकि देखने के लिए एक आखिरी चीज है। आइए अपने सहकर्मी के प्रस्ताव पर वापस जाएं। यदि सभी कॉल साइटों पर हम स्पष्ट रूप से जानते हैं कि A
हमारे पास किस प्रकार का है, तो हमें कॉल करना चाहिए जैसे:
B b = new B();
o.doIt(b, true);
हमने पहले यह मान लिया था कि कंपोजिंग में ऐसा A
है जो या X
तो है । लेकिन शायद यह धारणा भी सही नहीं है। क्या यह एकमात्र ऐसी जगह है जहां इस और मामलों के बीच अंतर है? अगर ऐसा है, तो शायद हम थोड़ा अलग तरीका अपना सकते हैं। हमारे पास अभी भी ऐसा है जो या तो है , लेकिन यह संबंधित नहीं है । केवल इसके बारे में परवाह है, तो चलो केवल इसे पास करें :A'
B'
A
B
X
A'
B'
A
O.doIt
O.doIt
class O {
int x;
int y;
public void doIt(A a, X x) {
x.doIt(a, x, y);
}
}
अब हमारी कॉल साइट इस तरह दिखती है:
A a = new A();
o.doIt(a, new B'());
एक बार फिर B
गायब हो जाता है, और अमूर्त अधिक ध्यान केंद्रित में चला जाता है X
। हालांकि, यह समय A
कम जानकर भी सरल है। यह और भी कम सार है।
कोड आधार में दोहराव को कम करना महत्वपूर्ण है, लेकिन हमें इस बात पर विचार करना चाहिए कि पहली बार में दोहराव क्यों होता है। दोहराव गहरे अमूर्तता का संकेत हो सकता है जो बाहर निकलने की कोशिश कर रहे हैं।