यदि लिस्कोव प्रतिस्थापन सिद्धांत का उल्लंघन किया जाता है तो क्या गलत हो सकता है?


27

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


6
यदि आप एलएसपी का पालन नहीं करते हैं तो क्या गलत हो सकता है? सबसे खराब स्थिति: आप समन कोड-थुलु को समाप्त करते हैं! ;)
FrustratedWithFormsDesigner

1
उस मूल प्रश्न के लेखक के रूप में, मुझे यह जोड़ना होगा कि यह काफी अकादमिक प्रश्न था। हालांकि उल्लंघन के कारण कोड में त्रुटियां हो सकती हैं, मेरे पास कभी भी एक गंभीर बग या रखरखाव का मुद्दा नहीं था जिसे मैं एलएसपी के उल्लंघन में डाल सकता हूं।
पॉल टी डेविस

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

@PaulTDavies परिणाम की गंभीरता पर निर्भर करता है कि क्या उपयोगकर्ता (प्रोग्रामर जो लाइब्रेरी का उपयोग करते हैं) को पुस्तकालय के कार्यान्वयन का विस्तृत ज्ञान है (अर्थात पुस्तकालय के कोड तक पहुंच और परिचित है।) आखिरकार उपयोगकर्ता दर्जनों सशर्त जांच करेंगे या रैपर बनाएंगे। गैर-एलएसपी (वर्ग-विशिष्ट व्यवहार) के लिए पुस्तकालय के आसपास। सबसे खराब स्थिति तब होगी जब पुस्तकालय एक बंद स्रोत वाणिज्यिक उत्पाद होगा।
रोंगोंग

@ एंडर्स और रवांग, कृपया उन समस्याओं का एक उत्तर के साथ वर्णन करें। स्वीकृत उत्तर बहुत ज्यादा पॉल डेविस का समर्थन करता है जिसमें परिणाम मामूली लगते हैं (एक अपवाद) जो कि आपके पास एक अच्छा संकलक, स्थिर विश्लेषक, या न्यूनतम इकाई परीक्षण होने पर जल्दी से ध्यान दिया और सुधार किया जाएगा।
user949300

जवाबों:


31

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

अब जब टास्क पर क्लोज़ () कॉलिंग की जाती है, तो एक मौका है कि कॉल विफल हो जाएगी अगर यह शुरू की गई स्थिति के साथ एक प्रोजेक्टटैस्क है, जब यह बेस टास्क नहीं होता है।

सोचो अगर तुम करोगे:

public void ProcessTaskAndClose(Task taskToProcess)
{
    taskToProcess.Execute();
    taskToProcess.DateProcessed = DateTime.Now;
    taskToProcess.Close();
}

इस विधि में, कभी-कभी .Close () कॉल ऊपर उड़ जाएगी, इसलिए अब एक व्युत्पन्न प्रकार के ठोस कार्यान्वयन के आधार पर आपको यह तरीका बदलना होगा कि यह विधि व्यवहार करती है कि यह विधि कैसे लिखी जाएगी यदि टास्क में कोई उपप्रकार नहीं है जो हो सकता है इस विधि को सौंप दिया।

लिस्कोव प्रतिस्थापन उल्लंघनों के कारण, आपके प्रकार का उपयोग करने वाले कोड को उन्हें अलग-अलग तरीके से व्यवहार करने के लिए व्युत्पन्न प्रकारों के आंतरिक कामकाज का स्पष्ट ज्ञान होना चाहिए। यह कसकर जोड़े कोड और बस आम तौर पर कार्यान्वयन को लगातार उपयोग करने के लिए कठिन बना देता है।


इसका मतलब यह है कि एक बच्चे के वर्ग के अपने सार्वजनिक तरीके नहीं हो सकते हैं जो मूल वर्ग में घोषित नहीं हैं?
सांगो

@Songo: जरूरी नहीं: यह हो सकता है, लेकिन उन तरीकों एक बेस पॉइंटर (या संदर्भ या चर या जो भी भाषा आप इसे कॉल का उपयोग करते हैं) से "अगम्य" हैं और आपको ऑब्जेक्ट चलाने के लिए टाइप करने के लिए कुछ रन-टाइम प्रकार की जानकारी चाहिए। इससे पहले कि आप उन कार्यों को कॉल कर सकें। लेकिन यह एक ऐसा मामला है जो दृढ़ता से भाषाओं के वाक्य रचना और शब्दार्थ से संबंधित है।
एमिलियो गारवागलिया

2
नहीं। यह तब होता है जब एक बाल वर्ग को संदर्भित किया जाता है जैसे कि यह एक प्रकार का मूल वर्ग होता है, इस मामले में जो सदस्य मूल वर्ग में घोषित नहीं किए जाते हैं वे अप्राप्य हैं।
Chewy Gumball

1
@ फिल यिप; यह तंग युग्मन की परिभाषा है: एक चीज को बदलने से अन्य चीजों में परिवर्तन होता है। एक शिथिल युग्मित वर्ग का यह कार्यान्वयन हो सकता है कि आपको इसके बाहर कोड बदलने की आवश्यकता के बिना इसे बदल दिया जाए। यही कारण है कि अनुबंध अच्छे हैं, वे आपको मार्गदर्शन करते हैं कि कैसे अपने ऑब्जेक्ट के उपभोक्ताओं में बदलाव की आवश्यकता नहीं है: अनुबंध को पूरा करें और उपभोक्ताओं को कोई संशोधन की आवश्यकता नहीं होगी, इस प्रकार ढीले युग्मन प्राप्त होते हैं। जब आपके उपभोक्ताओं को आपके अनुबंध के बजाय आपके कार्यान्वयन के लिए कोड करना होगा तो यह तंग युग्मन है, और एलएसपी का उल्लंघन करते समय आवश्यक है।
जिमी हॉफ

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

13

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

विकिपीडिया राज्यों में एलएसपी

  • उपसर्गों को उपप्रकार में मजबूत नहीं किया जा सकता है।
  • उपसंहार को उपप्रकार में कमजोर नहीं किया जा सकता है।
  • सुपरपाइप के हमलावर को उपप्रकार में संरक्षित किया जाना चाहिए।

इनमें से किसी को भी पकड़ में नहीं आना चाहिए, कॉलर को ऐसा परिणाम मिल सकता है जिसकी उसे उम्मीद नहीं है।


1
क्या आप इसे प्रदर्शित करने के लिए कोई ठोस उदाहरण सोच सकते हैं?
मार्क बूथ

1
@MarkBooth-दीर्घवृत्त / वर्ग-आयत समस्या इसे प्रदर्शित करने के लिए उपयोगी हो सकती है; विकिपीडिया लेख शुरू करने के लिए एक अच्छी जगह है: en.wikipedia.org/wiki/Circle-ellipse_problem
Ed Hastings

7

साक्षात्कार के सवालों के जवाब से एक क्लासिक मामले पर विचार करें: आपने एलीप से सर्किल निकाला है। क्यूं कर? क्योंकि एक चक्र IS-AN दीर्घवृत्त है, निश्चित रूप से!

सिवाय ... दीर्घवृत्त के दो कार्य हैं:

Ellipse.set_alpha_radius(d)
Ellipse.set_beta_radius(d)

स्पष्ट रूप से, इन्हें सर्कल के लिए फिर से परिभाषित किया जाना चाहिए, क्योंकि एक सर्कल में एक समान त्रिज्या है। आपके पास दो संभावनाएँ हैं:

  1. Set_alpha_radius या set_beta_radius कॉल करने के बाद, दोनों एक ही राशि पर सेट होते हैं।
  2. Set_alpha_radius या set_beta_radius को कॉल करने के बाद, ऑब्जेक्ट अब एक सर्कल नहीं है।

अधिकांश OO भाषाएँ दूसरे का समर्थन नहीं करती हैं, और एक अच्छे कारण के लिए: यह जानकर आश्चर्य होगा कि आपका Circle अब Circle नहीं है। तो पहला विकल्प सबसे अच्छा है। लेकिन निम्नलिखित कार्य पर विचार करें:

some_function(Ellipse byref e)

कल्पना करें कि कुछ_फंक्शन e.set_alpha_radius कहता है। लेकिन क्योंकि ई वास्तव में एक सर्कल था, इसलिए आश्चर्यजनक रूप से इसका बीटा त्रिज्या भी निर्धारित है।

और यहाँ प्रतिस्थापन सिद्धांत निहित है: एक उपवर्ग एक सुपरक्लास के लिए स्थानापन्न होना चाहिए। अन्यथा आश्चर्यजनक चीजें होती हैं।


1
मुझे लगता है कि यदि आप उत्परिवर्तित वस्तुओं का उपयोग करते हैं तो आप मुसीबत में पड़ सकते हैं। एक वृत्त भी एक दीर्घवृत्त है। लेकिन यदि आप एक दीर्घवृत्त को प्रतिस्थापित करते हैं जो एक अन्य दीर्घवृत्त के साथ एक चक्र भी है (जो कि आप एक सेटर विधि का उपयोग करके कर रहे हैं) तो इस बात की कोई गारंटी नहीं है कि नया दीर्घवृत्त भी एक चक्र होगा (मंडल एक उचित उपसमूह हैं)।
जियोर्जियो

2
एक विशुद्ध रूप से कार्यात्मक दुनिया में (अपरिवर्तनीय वस्तुओं के साथ), विधि set_alpha_radius (d) वापसी प्रकार दीर्घवृत्त (दोनों दीर्घवृत्त और वृत्त वर्ग में) होगा।
जियोर्जियो

@ जियोर्जियो हां, मुझे यह उल्लेख करना चाहिए कि यह समस्या केवल उत्परिवर्तनीय वस्तुओं के साथ होती है।
काज़ ड्रैगन

@KazDragon: जब हम जानते हैं कि कोई वस्तु एक वृत्त के साथ एक दीर्घवृत्त को स्थानापन्न करेगी, तो एक दीर्घवृत्त एक वृत्त नहीं है? यदि कोई ऐसा करता है, तो वे उन संस्थाओं की सही समझ नहीं रखते हैं जो वे मॉडल बनाने की कोशिश कर रहे हैं। लेकिन इस प्रतिस्थापन की अनुमति देकर, क्या हम उस अंतर्निहित प्रणाली की ढीली समझ को प्रोत्साहित नहीं कर रहे हैं जिसे हम अपने सॉफ़्टवेयर में मॉडल करने की कोशिश कर रहे हैं, और इस तरह से खराब सॉफ़्टवेयर बना रहे हैं?
मैवरिक

@ मेरा मानना ​​है कि आपने उस रिश्ते को पढ़ा है जिसे मैंने पीछे बताया है। प्रस्तावित है-एक रिश्ता दूसरा तरीका है: एक चक्र एक दीर्घवृत्त है। विशेष रूप से, एक वृत्त एक दीर्घवृत्त होता है जहां अल्फा और बीटा रेडी समान होते हैं। और इसलिए, उम्मीद की जा सकती है कि पैरामीटर के रूप में दीर्घवृत्त की अपेक्षा करने वाला कोई भी फ़ंक्शन समान रूप से एक सर्कल ले सकता है। गणना_रिए (एलीप) पर विचार करें। उस के लिए एक चक्र पारित करने से एक ही परिणाम होगा। लेकिन समस्या यह है कि एलीप के उत्परिवर्तन कार्यों का व्यवहार सर्किल में उन लोगों के लिए प्रतिस्थापित करने योग्य नहीं है।
काज ड्रैगन

6

आम आदमी के शब्दों में:

आपके कोड में भयानक / बहुत सारे क्लॉस होंगे

उन में से हर एक मामले / स्विच क्लॉस को समय-समय पर नए मामलों को जोड़ने की आवश्यकता होगी, जिसका अर्थ है कि कोड आधार उतना स्केलेबल और रखरखाव योग्य नहीं है जितना कि होना चाहिए।

LSP कोड को हार्डवेयर की तरह काम करने की अनुमति देता है:

आपको अपने iPod को संशोधित करने की आवश्यकता नहीं है क्योंकि आपने बाहरी वक्ताओं की एक नई जोड़ी खरीदी है, क्योंकि पुराने और नए दोनों बाहरी स्पीकर समान इंटरफ़ेस का सम्मान करते हैं, वे आइपॉड के बिना वांछित कार्यक्षमता खोए बिना विनिमेय हैं।


2
-1: सभी बुरे उत्तर के आसपास
थॉमस ईडिंग

3
@ थोमस मैं असहमत हूं। यह एक अच्छा सादृश्य है। वह उम्मीदों को नहीं तोड़ने की बात करता है, जो कि एलएसपी के बारे में है। (हालांकि मामले / स्विच के बारे में हिस्सा थोड़ा कमजोर है, मैं सहमत हूँ)
एंड्रेस एफ।

2
और फिर Apple ने कनेक्टर्स को बदलकर LSP को तोड़ दिया। इस जवाब पर रहता है।
मैगस

मुझे नहीं मिलता कि एलएसपी के साथ स्विच स्टेटमेंट का क्या करना है। यदि आप typeof(someObject)यह तय करने की बात कर रहे हैं कि आपको "क्या करने की अनुमति है", तो यकीन है, लेकिन यह पूरी तरह से एक और विरोधी पैटर्न है।
सारा

स्विच स्टेटमेंट की मात्रा में भारी कमी एलएसपी का वांछनीय पक्ष प्रभाव है। जैसा कि ऑब्जेक्ट किसी अन्य ऑब्जेक्ट के लिए खड़े हो सकते हैं जो समान इंटरफ़ेस का विस्तार करते हैं, विशेष मामलों की देखभाल की आवश्यकता नहीं है।
ट्यूलेंस कोरडोवा

1

जावा के अंडरमैन के साथ एक वास्तविक जीवन उदाहरण देने के लिए

यह विरासत में से AbstractUndoableEditजिसका अनुबंध निर्दिष्ट करता है यह 2 राज्यों है कि (पूर्ववत और पुनः किया) और एकल कॉल के साथ उन दोनों के बीच जा सकते हैं undo()औरredo()

हालांकि, अंडरमॉन्जर में अधिक राज्य हैं और एक पूर्व बफर की तरह काम करता है (प्रत्येक कॉल undoकुछ पूर्ववत करने के लिए है, लेकिन सभी संपादन नहीं करता है, पोस्टकॉन्डिशन को कमजोर करना)

यह काल्पनिक स्थिति की ओर ले जाता है जहाँ आप end()कॉल करने से पहले एक CompoundEdit में एक UndoManager जोड़ते हैं, फिर उस CompoundEdit undo()पर कॉल करें, यह आपके संपादन को आंशिक रूप से पूर्ववत करने से पहले प्रत्येक संपादन पर कॉल करने के लिए ले जाएगा

मैंने इससे UndoManagerबचने के लिए अपना रोल किया (मुझे शायद इसका नाम बदलकर रखना चाहिए UndoBuffer)


1

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

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


0

आप इसे एक मॉडलिंग दृष्टिकोण से भी देख सकते हैं। जब आप कहते हैं कि कक्षा Aका एक उदाहरण कक्षा का एक उदाहरण भी है, तो आप कहते Bहैं कि " कक्षा की आवृत्ति के अवलोकन योग्य व्यवहार को Aभी कक्षा की आवृत्ति के अवलोकन योग्य व्यवहार के रूप में वर्गीकृत किया जा सकता है B" (यह केवल तभी संभव है जब कक्षा Bकी तुलना में कम विशिष्ट हो वर्ग A।)

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

जैसे एक टैग के साथ एक बॉक्स बनाना: "इस बॉक्स में केवल नीली गेंदें होती हैं", और फिर उसमें लाल गेंद फेंकना। यदि यह गलत जानकारी दिखाता है तो ऐसे टैग का क्या उपयोग है?


0

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

मेरे पास है Class A, जो इससे प्राप्त होता है Class BClass Aऔर Class Bगुणों का एक समूह साझा करें जो Class Aअपने स्वयं के कार्यान्वयन के साथ ओवरराइड करता है। Class Aप्रॉपर्टी सेट करना या प्राप्त करना एक ही प्रॉपर्टी को सेट करने या प्राप्त करने का एक अलग प्रभाव है Class B

public Class A
{
    public virtual string Name
    {
        get; set;
    }
}

Class B : A
{
    public override string Name
    {
        get
        {
            return TranslateName(base.Name);
        }
        set
        {
            base.Name = value;
            FunctionWithSideEffects();
        }
    }
}

इस तथ्य को एक तरफ रखते हुए कि यह .NET में अनुवाद करने के लिए पूरी तरह से भयानक तरीका है, इस कोड के साथ कई अन्य मुद्दे हैं।

इस मामले Nameमें एक सूचकांक और एक प्रवाह नियंत्रण चर के रूप में कई स्थानों पर उपयोग किया जाता है। उपरोक्त वर्गों को उनके कच्चे और व्युत्पन्न दोनों रूपों में पूरे कोडबेस में रखा गया है। इस मामले में लिस्कोव प्रतिस्थापन सिद्धांत का उल्लंघन करने का मतलब है कि मुझे प्रत्येक एकल कॉल के संदर्भ को जानने की जरूरत है जो बेस क्लास लेते हैं।

कोड दोनों की वस्तुओं का उपयोग करता है Class Aऔर Class Bइसलिए, मैं बस Class Aलोगों को उपयोग करने के लिए मजबूर करने के लिए सार नहीं बना सकता Class B

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

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

मुझे इसे ठीक करने के लिए क्या करना होगा एक NameTranslatedसंपत्ति बनाना है , जो संपत्ति का Class Bसंस्करण होगा Nameऔर बहुत, बहुत सावधानी से Nameमेरी नई NameTranslatedसंपत्ति का उपयोग करने के लिए व्युत्पन्न संपत्ति के हर संदर्भ को बदल दें । हालाँकि, इन संदर्भों में से एक भी गलत हो जाने से संपूर्ण एप्लिकेशन को उड़ा दिया जा सकता है।

यह देखते हुए कि कोडबेस के आसपास इकाई परीक्षण नहीं हैं, यह सबसे खतरनाक परिदृश्य होने के करीब है जो एक डेवलपर सामना कर सकता है। यदि मैंने उल्लंघन को नहीं बदला है तो मुझे मानसिक ऊर्जा का भारी मात्रा में खर्च करना होगा, इस बात का ध्यान रखें कि प्रत्येक विधि में किस प्रकार की वस्तु का संचालन किया जा रहा है और यदि मैं उल्लंघन को ठीक करता हूं तो मैं पूरे उत्पाद को निष्क्रिय समय पर विस्फोट कर सकता हूं।


क्या होगा यदि व्युत्पन्न वर्ग के भीतर आप विरासत में मिली संपत्ति को एक अलग तरह की चीज के साथ मिलाते हैं, जिसका नाम एक ही था [उदा। एक नेस्टेड वर्ग] और नए पहचानकर्ता बनाए BaseNameऔर TranslatedNameवर्ग-ए शैली Nameऔर वर्ग-बी दोनों का उपयोग करें ? तब किसी Nameप्रकार के चर पर पहुंचने की किसी भी कोशिश को Bएक कंपाइलर त्रुटि के साथ अस्वीकार कर दिया जाएगा, ताकि आप यह सुनिश्चित कर सकें कि सभी संदर्भ अन्य रूपों में से एक में परिवर्तित हो गए।
सुपरकैट

मैं अब उस जगह पर काम नहीं करता। इसे ठीक करना बहुत अजीब होता। :-)
स्टीफन

-4

यदि आप एलएसपी के उल्लंघन की समस्या को महसूस करना चाहते हैं, तो सोचें कि क्या हो अगर आपके पास केवल .dll /। बेस क्लास (कोई स्रोत कोड) नहीं है और आपको नए व्युत्पन्न वर्ग का निर्माण करना होगा। आप इस कार्य को कभी पूरा नहीं कर सकते।


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