'अंतिम' कीवर्ड कभी क्यों उपयोगी होगा?


54

ऐसा लगता है कि जावा में उम्र के लिए वर्गों को नहीं-व्युत्पन्न घोषित करने की शक्ति थी, और अब सी ++ के पास भी है। हालाँकि, SOLID में Open / Close सिद्धांत के प्रकाश में, यह उपयोगी क्यों होगा? मेरे लिए, finalकीवर्ड ऐसा लगता है friend- यह कानूनी है, लेकिन यदि आप इसका उपयोग कर रहे हैं, तो संभवतः डिज़ाइन गलत है। कृपया कुछ उदाहरण प्रदान करें जहां एक गैर-व्युत्पन्न वर्ग एक महान वास्तुकला या डिजाइन पैटर्न का हिस्सा होगा।


43
यदि आपको लगता है कि गलत तरीके से डिजाइन किया गया है, तो आपको ऐसा क्यों लगता है final? बहुत से लोग (मेरे सहित) पाते हैं कि यह हर गैर-अमूर्त वर्ग बनाने के लिए एक अच्छा डिज़ाइन है final
चित्तीदार

20
वंशानुक्रम पर अनुकूल रचना और आपके पास हर गैर सार वर्ग हो सकता है final
एंडी

24
खुला / करीबी सिद्धांत 20 वीं शताब्दी से एक अर्थ में है, जब मंत्र कक्षाओं का एक पदानुक्रम बनाना था जो कि उन वर्गों से विरासत में मिला था जो बदले में अन्य वर्गों से विरासत में मिले थे। यह ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग सिखाने के लिए अच्छा था, लेकिन वास्तविक दुनिया की समस्याओं पर लागू होने पर यह पेचीदा, असंदिग्ध गंदगी पैदा करता है। एक्स्टेंसिबल होने के लिए क्लास डिजाइन करना कठिन है।
डेविड हैमेन

34
@DavidArno हास्यास्पद मत बनो। वंशानुक्रम वस्तु-उन्मुख प्रोग्रामिंग का साइन क्वालिफिकेशन नॉन है, और यह कहीं भी जटिल या गन्दा नहीं है जैसा कि कुछ अति-हठधर्मी व्यक्तियों को उपदेश देना पसंद है। यह किसी भी अन्य की तरह एक उपकरण है, और एक अच्छा प्रोग्रामर जानता है कि नौकरी के लिए सही उपकरण का उपयोग कैसे करें।
मेसन व्हीलर

48
एक उबरने वाले डेवलपर के रूप में, मुझे लगता है कि अंतिम रूप से महत्वपूर्ण वर्गों में हानिकारक व्यवहार को रोकने के लिए एक शानदार उपकरण के रूप में याद किया जाता है। मुझे यह भी लगता है कि विरासत को विभिन्न तरीकों से एक शक्तिशाली उपकरण कहा जाता है। यह लगभग वैसा ही है जैसे कि हांफने के अलग-अलग उपकरण में पेशेवरों और विपक्ष हैं और हम इंजीनियरों के रूप में उन कारकों को संतुलित करते हैं जैसे हम अपने सॉफ़्टवेयर का उत्पादन करते हैं!
corsiKa

जवाबों:


136

final आशय व्यक्त करता है । यह एक वर्ग, विधि या चर के उपयोगकर्ता को बताता है "यह तत्व बदलने वाला नहीं है, और यदि आप इसे बदलना चाहते हैं, तो आपने मौजूदा डिज़ाइन को नहीं समझा है।"

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

आप टिप्पणियों और आर्किटेक्चर दस्तावेज़ों के माध्यम से भी ऐसा कर सकते हैं, लेकिन कंपाइलर ऐसी चीज़ों को लागू करने के लिए हमेशा बेहतर है जो यह आशा कर सकते हैं कि भविष्य के उपयोगकर्ता दस्तावेज़ को पढ़ेंगे और उसका पालन करेंगे।


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

8
@ KilianFoth ठीक है, लेकिन ईमानदारी से, यह कैसे आपकी समस्या है कि आवेदन प्रोग्रामर क्या करते हैं?
coredump

20
@ मेरे समूह का उपयोग करने वाले लोग बुरी तरह से खराब सिस्टम बनाते हैं। खराब सिस्टम खराब प्रतिष्ठा का उत्पादन करते हैं। उपयोगकर्ता फेल रैंडम के राक्षसी अस्थिर ऐप से किलियन के महान कोड को भेद नहीं कर पाएंगे। परिणाम: मैं प्रोग्रामिंग क्रेडिट और ग्राहकों में खो जाता हूं। अपने कोड का दुरुपयोग करने के लिए कठिन बनाना डॉलर और सेंट का सवाल है।
किलन फथ

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

31
@JimmyB अंगूठे का नियम: अगर आपको लगता है कि आपको पता है कि आपकी रचना किस लिए इस्तेमाल की जाएगी, तो आप पहले से ही गलत हैं। बेल ने अनिवार्य रूप से मुजाक प्रणाली के रूप में टेलीफोन की कल्पना की। क्लेनेक्स का आविष्कार अधिक सुविधाजनक रूप से मेकअप हटाने के उद्देश्य से किया गया था। आईबीएम के अध्यक्ष थॉमस वाटसन ने एक बार कहा था "मुझे लगता है कि शायद पांच कंप्यूटरों के लिए एक विश्व बाजार है।" 2001 में पैट्रियट एक्ट की शुरुआत करने वाले प्रतिनिधि जिम सेंसेनब्रेनर यह कहते हुए रिकॉर्ड पर हैं कि यह विशेष रूप से एनएसए को उन चीजों को करने से रोकने के लिए था जो वे कर रहे हैं "पैट्रियट अधिनियम प्राधिकरण के साथ।" और इसी तरह ...
मैसन व्हीलर

59

यह फ्रैजाइल बेस क्लास समस्या से बचा जाता है । हर वर्ग निहित या स्पष्ट गारंटी और अपरिवर्तकों के एक सेट के साथ आता है। लिसकोव प्रतिस्थापन सिद्धांत यह कहता है कि उस वर्ग के सभी उपप्रकारों को भी ये सभी गारंटीएँ प्रदान करनी होंगी। हालांकि, अगर हम इसका उपयोग नहीं करते हैं तो इसका उल्लंघन करना वास्तव में आसान है 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
  • वे निजी विरासत प्रदान करते हैं जो इंटरफ़ेस को विरासत में नहीं देता है लेकिन केवल कार्यान्वयन है।
  • उन्हें वर्चुअल के रूप में चिह्नित करने के लिए बेस क्लास विधियों की आवश्यकता होती है, और सभी ओवरराइड को भी चिह्नित करने की आवश्यकता होती है। यह उन समस्याओं से बचा जाता है जहां एक उपवर्ग ने एक नई पद्धति को परिभाषित किया था, लेकिन उसी हस्ताक्षर के साथ एक विधि को बाद में आधार वर्ग में जोड़ा गया था, लेकिन आभासी रूप से इसका उद्देश्य नहीं था।

3
आप "नाजुक आधार वर्ग समस्या" का उल्लेख करने के लिए कम से कम +100 के लायक हैं। :)
डेविड अरनो

6
मैं यहां बनाए गए बिंदुओं से आश्वस्त नहीं हूं। हां, नाजुक आधार वर्ग एक समस्या है, लेकिन अंतिम कार्यान्वयन को बदलने के साथ सभी मुद्दों को हल नहीं करता है। आपका पहला उदाहरण खराब है क्योंकि आप मान रहे हैं कि आप पासवर्डचेकर के लिए सभी संभावित उपयोग के मामलों को जानते हैं ("सभी को लॉक करना या सभी को एक्सेस करने की अनुमति देना ... ठीक नहीं है" - कौन कहता है?)। आपका अंतिम "इसलिए उचित ..." सूची वास्तव में खराब है - आप मूल रूप से कुछ भी नहीं निकालने की सलाह दे रहे हैं और सब कुछ अंतिम रूप में चिह्नित कर रहे हैं - जो पूरी तरह से ओओपी, विरासत और कोड-पुन: उपयोग की उपयोगिता को दर्शाता है।
एडेलफस १२'१६ को १:00:००

4
आपका पहला उदाहरण नाजुक आधार वर्ग की समस्या का उदाहरण नहीं है। नाजुक आधार वर्ग की समस्या में, बेस क्लास में बदलाव एक उपवर्ग को तोड़ देता है। लेकिन उस उदाहरण में, आपका उपवर्ग उपवर्ग के अनुबंध का पालन नहीं करता है। ये दो अलग-अलग समस्याएं हैं। (इसके अतिरिक्त, यह वास्तव में उचित है कि कुछ परिस्थितियों में, आप एक पासवर्ड चेकर (विकास के लिए कह सकते हैं) को निष्क्रिय कर सकते हैं
विंस्टन एवर्ट

5
"यह नाजुक आधार वर्ग की समस्या से बचा जाता है" - उसी तरह कि खुद को मारने से भूख लगने से बचा जाता है।
user253751

9
@ इमिबिज़, इसके अधिक खाने से बचने के लिए भोजन की विषाक्तता से बचने की तरह है। ज़रूर, कभी नहीं खाने से समस्या होगी। लेकिन केवल उन जगहों पर भोजन करना जिन पर आप भरोसा करते हैं वे बहुत मायने रखते हैं।
विंस्टन एवर्ट

33

मुझे आश्चर्य है कि किसी ने अभी तक प्रभावी जावा, 2 संस्करण जोशुआ बलोच द्वारा उल्लेख नहीं किया है (जिसे हर जावा डेवलपर के लिए कम से कम पढ़ना चाहिए)। पुस्तक में आइटम 17 इस पर विस्तार से चर्चा करता है, और इसका शीर्षक है: " वंशानुक्रम के लिए डिज़ाइन और दस्तावेज़ या फिर इसे प्रतिबंधित करें "।

मैं पुस्तक में सभी अच्छी सलाह नहीं दोहराऊंगा, लेकिन ये विशेष अनुच्छेद प्रासंगिक लगते हैं:

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

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


21

finalउपयोगी कारणों में से एक यह है कि यह सुनिश्चित करता है कि आप एक वर्ग को इस तरह से उप-वर्ग नहीं कर सकते हैं जो मूल वर्ग के अनुबंध का उल्लंघन करेगा। इस तरह के उपवर्ग सॉलिड (सभी "एल" के अधिकांश) का उल्लंघन होगा और एक वर्ग बनाने से finalरोकता है।

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

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

फ़ाइनल कभी-कभी सरल गलतियों को रोकने में भी सहायक होता है, जैसे कि एक विधि के भीतर दो चीज़ों के लिए एक ही चर का पुनः उपयोग करना, आदि। स्काला में, आपको केवल वही उपयोग करने के लिए प्रोत्साहित किया जाता है, valजो जावा में अंतिम चर से मेल खाता है, और वास्तव में किसी का भी उपयोग नहीं करता है। varया गैर-अंतिम चर को संदेह की नजर से देखा जाता है।

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


6
अंत में, संकलक कर सकते हैं, कम से कम सिद्धांत में => मैंने व्यक्तिगत रूप से क्लैंग के भटकाव पास की समीक्षा की है, और मैं पुष्टि करता हूं कि यह व्यवहार में उपयोग किया जाता है।
मैथ्यू एम।

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

3
@JesseTG अगर एक बार में सभी कोड तक इसकी पहुँच है, तो शायद। हालांकि, अलग फ़ाइल संकलन के बारे में क्या?
मोनिका

3
@JesseTG विचलन या (मोनोमोर्फिक / पॉलीमोर्फिक) इनलाइन कैचिंग JIT कंपाइलरों में एक सामान्य तकनीक है, क्योंकि सिस्टम को पता है कि कौन सी कक्षाएं वर्तमान में भरी हुई हैं, और कोड को हटा सकते हैं यदि कोई ओवरराइडिंग विधियों की धारणा गलत नहीं है। हालाँकि, समय संकलक से आगे नहीं जा सकता। जब मैं उस कक्षा का उपयोग करने वाला एक जावा वर्ग और कोड संकलित करता हूं, तो मैं बाद में एक उपवर्ग का संकलन कर सकता हूं और उपभोग कोड में एक उदाहरण दे सकता हूं। सबसे आसान तरीका है कि क्लासपाथ के सामने एक और जार जोड़ें। कंपाइलर उस सब के बारे में नहीं जान सकता, क्योंकि यह रन टाइम पर होता है।
आमोन

5
@ बंद आप यह मान सकते हैं कि स्ट्रेंथ अपरिवर्तनीय हैं क्योंकि परावर्तन के माध्यम से स्ट्रिंग्स को उत्परिवर्तित किया जा सकता है SecurityManager। अधिकांश कार्यक्रम इसका उपयोग नहीं करते हैं, लेकिन वे किसी भी गैर-विश्वसनीय कोड को नहीं चलाते हैं। +++ आपको यह मानकर चलना होगा कि स्ट्रिंग अपरिवर्तनीय हैं अन्यथा आपको शून्य सुरक्षा और बोनस के रूप में मनमाने ढंग से बग का एक गुच्छा मिलता है। जावा स्ट्रिंग्स को संभालने वाला कोई भी प्रोग्रामर उत्पादक कोड में बदल सकता है।
माॅर्टिनस

7

दूसरा कारण प्रदर्शन है । पहला कारण यह है कि कुछ वर्गों के पास महत्वपूर्ण व्यवहार या राज्य हैं जिन्हें सिस्टम को काम करने की अनुमति देने के लिए बदलना नहीं चाहिए। उदाहरण के लिए, यदि मेरे पास एक क्लास "पासवर्ड-चेक" है और उस वर्ग का निर्माण करने के लिए मैंने सुरक्षा विशेषज्ञों की एक टीम को नियुक्त किया है और यह वर्ग अच्छी तरह से अध्ययन किए गए और परिभाषित प्रोल के साथ सैकड़ों एटीएम के साथ संचार करता है। विश्वविद्यालय से बाहर एक नए काम पर रखने वाले व्यक्ति को एक "TrustMePasswordCheck" वर्ग बनाने की अनुमति दें जो उपरोक्त वर्ग का विस्तार करता है, जो मेरे सिस्टम के लिए बहुत हानिकारक हो सकता है; उन विधियों को ओवरराइड नहीं किया जाना चाहिए, बस।



जेवीएम वर्गों को अंतिम रूप देने के लिए पर्याप्त स्मार्ट है यदि उनके पास कोई उपवर्ग नहीं है, भले ही वे अंतिम घोषित न हों।
user253751

नीचा दिखाया गया क्योंकि 'प्रदर्शन' का दावा कई बार भंग किया गया है।
पॉल स्मिथ

7

जब मुझे कक्षा की आवश्यकता होगी, तो मैं एक कक्षा लिखूँगा। अगर मुझे उपवर्गों की आवश्यकता नहीं है, तो मुझे उपवर्गों की परवाह नहीं है। मैं यह सुनिश्चित करता हूं कि मेरी कक्षा इरादा के अनुसार व्यवहार करती है, और जिन जगहों पर मैं कक्षा का उपयोग करता हूं, वे मान लेते हैं कि कक्षा इरादा के अनुसार व्यवहार करती है।

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

आपको लगता है कि "वस्तु उन्मुख नहीं है"? मुझे एक वर्ग बनाने के लिए भुगतान किया गया था जो वह करता है जो यह करना चाहिए था। किसी ने मुझे एक वर्ग बनाने के लिए भुगतान नहीं किया था जिसे उपवर्गित किया जा सकता था। यदि आपको मेरी कक्षा को पुन: प्रयोज्य बनाने के लिए भुगतान किया जाता है, तो आप इसका स्वागत करते हैं। "अंतिम" कीवर्ड को हटाकर प्रारंभ करें।

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

एडेल्फ़स: "यदि आप इसे उप-वर्ग करना चाहते हैं, तो स्रोत कोड लें, 'अंतिम' निकालें, और यह आपकी ज़िम्मेदारी है?" "अंतिम" "निष्पक्ष चेतावनी" के बराबर है।

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

M4ks: आप हमेशा सब कुछ निजी बनाते हैं जिसे बाहर से एक्सेस नहीं करना चाहिए। फिर, यदि आप उप-वर्ग करना चाहते हैं, तो आप स्रोत कोड लेते हैं, ज़रूरत पड़ने पर चीजों को "संरक्षित" में बदलते हैं, और जो आप करते हैं उसकी ज़िम्मेदारी लेते हैं। अगर आपको लगता है कि आपको उन चीजों तक पहुंचने की आवश्यकता है जो मैंने निजी चिह्नित की हैं, तो आप बेहतर जानते हैं कि आप क्या कर रहे हैं।

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


4
-1 इस उत्तर में उन सभी बातों का वर्णन किया गया है जो सॉफ्टवेयर डेवलपर्स के साथ गलत है। यदि कोई आपकी कक्षा को उपवर्ग द्वारा पुन: उपयोग करना चाहता है, तो उन्हें करने दें। यह आपकी ज़िम्मेदारी क्यों होगी कि वे इसका उपयोग (या दुरुपयोग) कैसे करते हैं? मूल रूप से, आप उपयोग कर रहे अंतिम वायुसेना के रूप में आप k, आप मेरी कक्षा का उपयोग नहीं कर रहे हैं। * "किसी ने मुझे एक वर्ग बनाने के लिए भुगतान नहीं किया था जिसे उपवर्गित किया जा सकता था" । क्या आप गंभीर हैं? यही कारण है कि सॉफ्टवेयर इंजीनियर कार्यरत हैं - ठोस, पुन: प्रयोज्य कोड बनाने के लिए।
एडेलफस

4
-1 बस सबकुछ निजी कर दें, इसलिए कोई भी
व्यक्ति उपश्रमण के

3
@adelphus जबकि इस उत्तर का शब्द कुंद है, कठोर पर सीमाबद्ध है, यह "गलत" दृष्टिकोण नहीं है। वास्तव में यह एक ही दृष्टिकोण है क्योंकि इस प्रश्न के अधिकांश उत्तर अभी तक केवल क्लिनिकल टोन के साथ हैं।
नेमसिसएक्स

+1 का उल्लेख करने के लिए कि आप 'अंतिम' निकाल सकते हैं। यह आपके कोड के सभी संभावित उपयोगों के बारे में दावा करने के लिए अभिमानी है। फिर भी यह स्पष्ट करने के लिए विनम्र है कि आप कुछ संभावित उपयोगों को बनाए नहीं रख सकते हैं, और उन उपयोगों के लिए एक कांटा बनाए रखने की आवश्यकता होगी।
शाम

4

आइए कल्पना करें कि एक प्लेटफॉर्म के लिए एसडीके निम्न वर्ग को जहाज करता है:

class HTTPRequest {
   void get(String url, String method = "GET");
   void post(String url) {
       get(url, "POST");
   }
}

एक आवेदन इस वर्ग को उपवर्गित करता है:

class MyHTTPRequest extends HTTPRequest {
    void get(String url, String method = "GET") {
        requestCounter++;
        super.get(url, method);
    }
}

सब ठीक है और ठीक है, लेकिन एसडीके पर काम करने वाला कोई व्यक्ति यह तय करता है कि एक विधि पारित getकरना मूर्खतापूर्ण है, और इंटरफ़ेस को पीछे की संगतता को लागू करना बेहतर बनाता है।

class HTTPRequest {
   @Deprecated
   void get(String url, String method) {
        request(url, method);
   }

   void get(String url) {
       request(url, "GET");
   }
   void post(String url) {
       request(url, "POST");
   }

   void request(String url, String method);
}

सब कुछ ठीक लगता है, जब तक कि ऊपर से आवेदन नए एसडीके के साथ पुन: कनेक्ट नहीं किया जाता है। अचानक, ओवरराइड प्राप्त विधि को अब और नहीं बुलाया जा रहा है, और अनुरोध को गिना नहीं जा रहा है।

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

फ़ाइनल आपकी कक्षा को किसी को भी उपवर्गित करने से रोकता है। इस तरह, कक्षा के अंदर के तरीकों को इस चिंता के बिना बदला जा सकता है कि कहीं न कहीं कोई व्यक्ति इस बात पर निर्भर करता है कि कौन सी विधि कॉल की गई है।


1

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

फाइनल का अर्थ है कि आप यह बदलने के लिए स्वतंत्र हैं कि आपकी कक्षा बिना किसी अनपेक्षित परिवर्तन के अन्य लोगों के कोड में रेंगने वाले व्यवहार में कैसे काम करती है जो आधार के रूप में आप पर निर्भर है।

एक उदाहरण के रूप में, मैं एक कक्षा लिखता हूं जिसका नाम है हॉबिटकेलर, जो महान है, क्योंकि सभी शौक ट्रिकी हैं और शायद मर जाना चाहिए। खरोंच है कि, वे सभी निश्चित रूप से मरने की जरूरत है।

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

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

इसलिए मेरे लिए एक कर्तव्यनिष्ठ डेवलपर होने के लिए, और मेरी कक्षा का उपयोग करके भविष्य के लिए सहज शौक मृत्यु सुनिश्चित करें, मुझे किसी भी वर्ग में होने वाले किसी भी बदलाव के साथ बहुत सावधान रहना होगा, जिसे बढ़ाया जा सकता है।

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


2
यदि आप लक्ष्यीकरण विधि बदलने जा रहे हैं तो आपने इसे कभी सार्वजनिक क्यों किया? और यदि आप एक वर्ग के व्यवहार को महत्वपूर्ण रूप से बदलते हैं, तो आपको अतिरंजित के बजाय एक और संस्करण की आवश्यकता हैfinal
M4ks

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

0

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

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

एक अन्य तरीका यह है कि अपनी कक्षा को सार्वजनिक एपीआई के साथ अमूर्त वर्ग (या इंटरफ़ेस) के रूप में लागू किया जाए। फिर आप एक पूरी तरह से नए कार्यान्वयन का उत्पादन कर सकते हैं जो उसी क्लाइंट को प्लग कर सकता है। यदि आपके नए इंटरफ़ेस को मूल रूप में समान व्यवहार की आवश्यकता है, तो आप या तो कर सकते हैं:

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

0

मेरे लिए यह डिजाइन की बात है।

मान लीजिए कि मेरे पास एक कार्यक्रम है जो कर्मचारियों के लिए वेतन की गणना करता है। यदि मेरे पास एक वर्ग है जो देश के आधार पर 2 तारीखों (प्रत्येक देश के लिए एक वर्ग) के बीच काम के दिनों की संख्या लौटाता है, तो मैं उस अंतिम को डालूंगा, और प्रत्येक उद्यम को उनके कैलेंडर के लिए एक निशुल्क दिन प्रदान करने के लिए एक विधि प्रदान करूंगा।

क्यों? सरल। मान लीजिए कि एक डेवलपर वर्किंगडेयस एम्सकंपनी में बेस क्लास वर्किंगयूएसए को इनहेरिट करना चाहता है और यह दर्शाता है कि उसके उद्यम को हड़ताल / रखरखाव / जो भी कारण 2 डी के लिए बंद हो जाएगा।

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

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

उपाय? कक्षा को अंतिम बनाएं और एक AddFreeDay (companyID mycompany, Date freeDay) विधि प्रदान करें। इस तरह से आपको यकीन है कि जब आप वर्किंगडेस्काउंट क्लास कहते हैं तो यह आपका मुख्य वर्ग होता है न कि उपवर्ग

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