यह फ्रैजाइल बेस क्लास समस्या से बचा जाता है । हर वर्ग निहित या स्पष्ट गारंटी और अपरिवर्तकों के एक सेट के साथ आता है। लिसकोव प्रतिस्थापन सिद्धांत यह कहता है कि उस वर्ग के सभी उपप्रकारों को भी ये सभी गारंटीएँ प्रदान करनी होंगी। हालांकि, अगर हम इसका उपयोग नहीं करते हैं तो इसका उल्लंघन करना वास्तव में आसान है final
। उदाहरण के लिए, चलो एक पासवर्ड चेकर है:
public class PasswordChecker {
public boolean passwordIsOk(String password) {
return password == "s3cret";
}
}
यदि हम उस वर्ग को ओवरराइड करने की अनुमति देते हैं, तो एक कार्यान्वयन सभी को लॉक कर सकता है, दूसरा सभी को एक्सेस दे सकता है:
public class OpenDoor extends PasswordChecker {
public boolean passwordIsOk(String password) {
return true;
}
}
यह आमतौर पर ठीक नहीं है, क्योंकि उपवर्गों में अब व्यवहार होता है जो मूल के लिए बहुत असंगत है। यदि हम वास्तव में कक्षा को अन्य व्यवहार के साथ विस्तारित करने का इरादा रखते हैं, तो जिम्मेदारी की एक श्रृंखला बेहतर होगी:
PasswordChecker passwordChecker =
new DefaultPasswordChecker(null);
// or:
PasswordChecker passwordChecker =
new OpenDoor(null);
// or:
PasswordChecker passwordChecker =
new DefaultPasswordChecker(
new OpenDoor(null)
);
public interface PasswordChecker {
boolean passwordIsOk(String password);
}
public final class DefaultPasswordChecker implements PasswordChecker {
private PasswordChecker next;
public DefaultPasswordChecker(PasswordChecker next) {
this.next = next;
}
@Override
public boolean passwordIsOk(String password) {
if ("s3cret".equals(password)) return true;
if (next != null) return next.passwordIsOk(password);
return false;
}
}
public final class OpenDoor implements PasswordChecker {
private PasswordChecker next;
public OpenDoor(PasswordChecker next) {
this.next = next;
}
@Override
public boolean passwordIsOk(String password) {
return true;
}
}
समस्या तब और अधिक स्पष्ट हो जाती है जब अधिक जटिल वर्ग अपने तरीकों को कहता है, और उन तरीकों को ओवरराइड किया जा सकता है। मैं कभी-कभी इसका सामना तब करता हूं जब डेटा संरचना को सुंदर रूप से प्रिंट करना या HTML लिखना। प्रत्येक विधि कुछ विजेट के लिए जिम्मेदार है।
public class Page {
...;
@Override
public String toString() {
PrintWriter out = ...;
out.print("<!DOCTYPE html>");
out.print("<html>");
out.print("<head>");
out.print("</head>");
out.print("<body>");
writeHeader(out);
writeMainContent(out);
writeMainFooter(out);
out.print("</body>");
out.print("</html>");
...
}
void writeMainContent(PrintWriter out) {
out.print("<div class='article'>");
out.print(htmlEscapedContent);
out.print("</div>");
}
...
}
अब मैं एक उपवर्ग बनाता हूं जो थोड़ा और स्टाइल जोड़ता है:
class SpiffyPage extends Page {
...;
@Override
void writeMainContent(PrintWriter out) {
out.print("<div class='row'>");
out.print("<div class='col-md-8'>");
super.writeMainContent(out);
out.print("</div>");
out.print("<div class='col-md-4'>");
out.print("<h4>About the Author</h4>");
out.print(htmlEscapedAuthorInfo);
out.print("</div>");
out.print("</div>");
}
}
अब एक पल के लिए नजरअंदाज करना कि यह HTML पेज बनाने के लिए बहुत अच्छा तरीका नहीं है, अगर मैं फिर से लेआउट बदलना चाहता हूं तो क्या होगा? मुझे एक SpiffyPage
उपवर्ग बनाना होगा जो किसी तरह उस सामग्री को लपेटता है। हम यहां जो देख सकते हैं वह टेम्पलेट पद्धति पैटर्न का एक आकस्मिक अनुप्रयोग है। टेम्पलेट विधियाँ आधार वर्ग में अच्छी तरह से परिभाषित विस्तार बिंदु हैं जिन्हें ओवरराइड करने का इरादा है।
और अगर बेस क्लास बदल जाए तो क्या होगा? यदि HTML सामग्री बहुत अधिक बदल जाती है, तो यह उपवर्गों द्वारा प्रदान किए गए लेआउट को तोड़ सकता है। इसलिए बाद में बेस क्लास को बदलना वास्तव में सुरक्षित नहीं है। यह स्पष्ट नहीं है कि यदि आपकी सभी कक्षाएं एक ही परियोजना में हैं, लेकिन बहुत ही ध्यान देने योग्य है यदि आधार वर्ग कुछ प्रकाशित सॉफ़्टवेयर का हिस्सा है जो अन्य लोग बनाते हैं।
यदि इस विस्तार की रणनीति का इरादा था, तो हम उपयोगकर्ता को इस तरह से स्वैप करने की अनुमति दे सकते थे कि प्रत्येक भाग कैसे उत्पन्न होता है। या तो, प्रत्येक ब्लॉक के लिए एक रणनीति हो सकती है जिसे बाहरी रूप से प्रदान किया जा सकता है। या, हम डेकोरेटर्स को घोंसला दे सकते हैं। यह उपरोक्त कोड के बराबर होगा, लेकिन कहीं अधिक स्पष्ट और कहीं अधिक लचीला:
Page page = ...;
page.decorateLayout(current -> new SpiffyPageDecorator(current));
print(page.toString());
public interface PageLayout {
void writePage(PrintWriter out, PageLayout top);
void writeMainContent(PrintWriter out, PageLayout top);
...
}
public final class Page {
private PageLayout layout = new DefaultPageLayout();
public void decorateLayout(Function<PageLayout, PageLayout> wrapper) {
layout = wrapper.apply(layout);
}
...
@Override public String toString() {
PrintWriter out = ...;
layout.writePage(out, layout);
...
}
}
public final class DefaultPageLayout implements PageLayout {
@Override public void writeLayout(PrintWriter out, PageLayout top) {
out.print("<!DOCTYPE html>");
out.print("<html>");
out.print("<head>");
out.print("</head>");
out.print("<body>");
top.writeHeader(out, top);
top.writeMainContent(out, top);
top.writeMainFooter(out, top);
out.print("</body>");
out.print("</html>");
}
@Override public void writeMainContent(PrintWriter out, PageLayout top) {
... /* as above*/
}
}
public final class SpiffyPageDecorator implements PageLayout {
private PageLayout inner;
public SpiffyPageDecorator(PageLayout inner) {
this.inner = inner;
}
@Override
void writePage(PrintWriter out, PageLayout top) {
inner.writePage(out, top);
}
@Override
void writeMainContent(PrintWriter out, PageLayout top) {
...
inner.writeMainContent(out, top);
...
}
}
(अतिरिक्त top
पैरामीटर यह सुनिश्चित करने के लिए आवश्यक है कि कॉल writeMainContent
डेकोरेटर श्रृंखला के शीर्ष से गुजरने के लिए है। यह खुले पुनरावृत्ति नामक उपवर्ग की एक विशेषता का अनुकरण करता है ।)
यदि हमारे पास कई सज्जाकार हैं, तो अब हम उन्हें अधिक स्वतंत्र रूप से मिला सकते हैं।
मौजूदा कार्यक्षमता को थोड़ा अनुकूलित करने की इच्छा से कहीं अधिक बार एक मौजूदा वर्ग के कुछ हिस्से का पुन: उपयोग करने की इच्छा है। मैंने एक ऐसा मामला देखा है जहाँ कोई ऐसा वर्ग चाहता था जहाँ आप वस्तुओं को जोड़ सकें और उन सभी पर पुनरावृति कर सकें। सही समाधान के लिए किया गया होगा:
final class Thingies implements Iterable<Thing> {
private ArrayList<Thing> thingList = new ArrayList<>();
@Override public Iterator<Thing> iterator() {
return thingList.iterator();
}
public void add(Thing thing) {
thingList.add(thing);
}
... // custom methods
}
इसके बजाय, उन्होंने एक उपवर्ग बनाया:
class Thingies extends ArrayList<Thing> {
... // custom methods
}
यह अचानक मतलब है कि पूरे इंटरफ़ेस हमारे इंटरफ़ेस ArrayList
का हिस्सा बन गया है। उपयोगकर्ता विशिष्ट सूचकांकों पर चीजें, या चीजें कर सकते हैं । इस तरह से इरादा था? ठीक। लेकिन अक्सर, हम सभी परिणामों के माध्यम से ध्यान से नहीं सोचते हैं।remove()
get()
इसलिए यह उचित है
extend
बिना सोचे समझे वर्ग कभी नहीं ।
- हमेशा अपनी कक्षाओं को इस तरह चिह्नित करें जैसे
final
कि आप किसी भी विधि को ओवरराइड करने का इरादा रखते हैं।
- जहाँ आप इकाई परीक्षण के लिए कार्यान्वयन को स्वैप करना चाहते हैं, वहां इंटरफेस बनाएँ।
ऐसे कई उदाहरण हैं जहां इस "नियम" को तोड़ा जाना है, लेकिन यह आमतौर पर आपको एक अच्छे, लचीले डिजाइन के लिए मार्गदर्शन करता है, और बेस क्लास में अनायास ही बदलाव (या उपवर्ग के अनपेक्षित उपयोगों के कारण बेस क्लास के उदाहरण के रूप में बग से बचा जाता है) )।
कुछ भाषाओं में सख्त प्रवर्तन तंत्र हैं:
- सभी विधियां डिफ़ॉल्ट रूप से अंतिम हैं और इसे स्पष्ट रूप से चिह्नित किया जाना है
virtual
- वे निजी विरासत प्रदान करते हैं जो इंटरफ़ेस को विरासत में नहीं देता है लेकिन केवल कार्यान्वयन है।
- उन्हें वर्चुअल के रूप में चिह्नित करने के लिए बेस क्लास विधियों की आवश्यकता होती है, और सभी ओवरराइड को भी चिह्नित करने की आवश्यकता होती है। यह उन समस्याओं से बचा जाता है जहां एक उपवर्ग ने एक नई पद्धति को परिभाषित किया था, लेकिन उसी हस्ताक्षर के साथ एक विधि को बाद में आधार वर्ग में जोड़ा गया था, लेकिन आभासी रूप से इसका उद्देश्य नहीं था।
final
? बहुत से लोग (मेरे सहित) पाते हैं कि यह हर गैर-अमूर्त वर्ग बनाने के लिए एक अच्छा डिज़ाइन हैfinal
।