यदि मेरा फ़ंक्शन जटिल है और बहुत सारे चर हैं, तो क्या मुझे एक वर्ग बनाना चाहिए?


40

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

दूसरे शब्दों में, मैं जावा जैसी भाषा में अनावश्यक कक्षाएं बनाने के लिए कम दोषी महसूस करता हूं, लेकिन मुझे ऐसा लगता है कि पायथन जैसे कम बॉयलरप्लेट-वाई भाषाओं में बेहतर तरीका हो सकता है।

मेरे कार्यक्रम को कई बार एक अपेक्षाकृत जटिल ऑपरेशन करने की आवश्यकता है। उस ऑपरेशन के लिए "बहीखाता" की बहुत आवश्यकता होती है, कुछ अस्थायी फ़ाइलों को बनाने और हटाने के लिए, आदि।

इसलिए इसे बहुत सारे अन्य "सबऑपरेशंस" को कॉल करने की आवश्यकता है - सब कुछ एक विशाल विधि में डालना बहुत अच्छा नहीं है, मॉड्यूलर, रीडिंग, आदि।

अब ये मेरे मन में आने वाले दृष्टिकोण हैं:

1. एक ऐसी कक्षा बनाएं जिसमें केवल एक सार्वजनिक विधि हो और आंतरिक उदाहरणों के लिए आवश्यक आंतरिक अवस्था को इसके उदाहरण चर में रखे।

यह कुछ इस तरह दिखेगा:

class Thing:

    def __init__(self, var1, var2):
        self.var1 = var1
        self.var2 = var2
        self.var3 = []

    def the_public_method(self, param1, param2):
        self.var4 = param1
        self.var5 = param2
        self.var6 = param1 + param2 * self.var1
        self.__suboperation1()
        self.__suboperation2()
        self.__suboperation3()


    def __suboperation1(self):
        # Do something with self.var1, self.var2, self.var6
        # Do something with the result and self.var3
        # self.var7 = something
        # ...
        self.__suboperation4()
        self.__suboperation5()
        # ...

    def suboperation2(self):
        # Uses self.var1 and self.var3

#    ...
#    etc.

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

# Make a thing object
thing = Thing(1,2)

# Call the only method you can call
thing.the_public_method(3,4)

# You don't need thing anymore

2. एक वर्ग के बिना कार्यों का एक गुच्छा बनाएं और उनके बीच विभिन्न आंतरिक रूप से आवश्यक चर पास करें (तर्क के रूप में)।

मैं इसके साथ जो समस्या देख रहा हूं वह यह है कि मुझे फंक्शन्स के बीच बहुत सारे वैरिएबल पास करने होंगे। इसके अलावा, फ़ंक्शन एक-दूसरे के साथ निकटता से संबंधित होंगे, लेकिन वे एक साथ समूहीकृत नहीं होंगे।

3. जैसे - लेकिन राज्य चर को पार करने के बजाय वैश्विक बनाते हैं।

यह बिल्कुल अच्छा नहीं होगा, क्योंकि मुझे अलग-अलग इनपुट के साथ एक से अधिक बार ऑपरेशन करना होगा।

क्या कोई चौथा, बेहतर, दृष्टिकोण है? यदि नहीं, तो इनमें से कौन सा दृष्टिकोण बेहतर होगा और क्यों? क्या मुझे कुछ याद आ रहा है?


9
"मैं इस दृष्टिकोण के साथ जो समस्या देख रहा हूं वह यह है कि इस वर्ग की स्थिति केवल आंतरिक रूप से समझ में आती है, और इसके उदाहरणों के साथ कुछ भी नहीं कर सकती है सिवाय उनकी एकमात्र सार्वजनिक पद्धति को कॉल करने के।" आपने इसे एक समस्या के रूप में व्यक्त किया है, लेकिन आप ऐसा क्यों सोचते हैं।
पैट्रिक मौपिन

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

शायद यह इस बारे में स्पष्ट है कि मैं क्या करने की कोशिश कर रहा हूं। जैसे, मुझे जावा में एनम के स्थान पर साधारण कक्षाओं का उपयोग करने के साथ कुछ भी गलत नहीं दिखाई देता है, लेकिन फिर भी ऐसी चीजें हैं जिनके लिए एनम अधिक स्वाभाविक हैं। तो यह प्रश्न वास्तव में है कि क्या मैं जो करने की कोशिश कर रहा हूं उसके लिए एक अधिक प्राकृतिक दृष्टिकोण है।
इनलोर


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

जवाबों:


47
  1. एक ऐसा वर्ग बनाएं, जिसमें एक ही सार्वजनिक विधि हो और आंतरिक उदाहरणों के लिए आवश्यक आंतरिक स्थिति को उसके उदाहरण चर में रखे।

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

विकल्प 1 सही ढंग से उपयोग किए गए एनकैप्सुलेशन का एक अच्छा उदाहरण है। आप चाहते हैं कि आंतरिक स्थिति बाहरी कोड से छिपी हो।

यदि इसका मतलब है कि आपकी कक्षा में केवल एक सार्वजनिक तरीका है, तो ऐसा ही हो। यह बनाए रखना इतना आसान होगा।

ओओपी में यदि आपके पास एक वर्ग है जो बिल्कुल 1 काम करता है, एक छोटी सार्वजनिक सतह है, और अपनी सभी आंतरिक स्थिति को छिपाए रखता है, तो आप हैं (जैसा कि चार्ली शीन कहेंगे) जीतें

  1. एक वर्ग के बिना कार्यों का एक गुच्छा बनाएं और उनके बीच विभिन्न आंतरिक रूप से आवश्यक चर पास करें (तर्क के रूप में)।

मैं इसके साथ जो समस्या देख रहा हूं वह यह है कि मुझे फंक्शन्स के बीच बहुत सारे वैरिएबल पास करने होंगे। इसके अलावा, फ़ंक्शन एक दूसरे के साथ निकटता से संबंधित होंगे, लेकिन एक साथ समूहीकृत नहीं होंगे।

विकल्प 2 कम सामंजस्य से ग्रस्त है । इससे रखरखाव अधिक कठिन हो जाएगा।

  1. 2 की तरह। लेकिन राज्य के चर को उन्हें पारित करने के बजाय वैश्विक बनाएं।

विकल्प 3, विकल्प 2 की तरह, कम सामंजस्य से ग्रस्त है, लेकिन बहुत अधिक गंभीर रूप से!

इतिहास से पता चला है कि वैश्विक चरों की सुविधा के द्वारा की जाने वाली क्रूर रखरखाव लागत से आगे निकल जाती है। यही कारण है कि आप पुराने फार्ट्स को सुनते हैं जैसे कि मैं हर समय एनकैप्सुलेशन के बारे में सोचता हूं।


जीतने का विकल्प # 1 है


6
# 1 हालांकि, एक बहुत बदसूरत एपीआई है। अगर मैंने इसके लिए एक वर्ग बनाने का फैसला किया, तो मैं शायद एक ही सार्वजनिक समारोह बनाऊंगा जो कक्षा को सौंपता है और पूरी कक्षा को निजी बनाता है। ThingDoer(var1, var2).do_it()बनाम do_thing(var1, var2)
user2357112

7
जबकि विकल्प 1 एक स्पष्ट विजेता है, मैं एक कदम आगे जाऊंगा। आंतरिक ऑब्जेक्ट राज्य का उपयोग करने के बजाय, स्थानीय चर और विधि मापदंडों का उपयोग करें। यह सार्वजनिक समारोह को प्रतिवादी बनाता है, जो अधिक सुरक्षित है।

4
एक अतिरिक्त चीज जो आप विकल्प 1 के विस्तार में कर सकते हैं: परिणामी वर्ग को संरक्षित करें (नाम के सामने एक अंडरस्कोर जोड़कर), और फिर def do_thing(var1, var2): return _ThingDoer(var1, var2).run()बाहरी एपीआई को थोड़ा सुंदर बनाने के लिए एक मॉड्यूल-स्तर फ़ंक्शन को परिभाषित करें ।
सोज़र्ड जॉब पोस्टमस

4
मैं 1 के लिए आपके तर्क का पालन नहीं करता। आंतरिक स्थिति पहले से ही छिपी हुई है। वर्ग जोड़ने से वह परिवर्तित नहीं होता है। इसलिए मैं यह नहीं देखता कि आप क्यों सलाह देते हैं (1)। वास्तव में वर्ग के अस्तित्व को उजागर करना आवश्यक नहीं है।
usr

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

23

मुझे लगता है कि # 1 वास्तव में एक बुरा विकल्प है।

चलो अपने कार्य पर विचार करें:

def the_public_method(self, param1, param2):
    self.var4 = param1
    self.var5 = param2 
    self.var6 = param1 + param2 * self.var1
    self.__suboperation1()
    self.__suboperation2()
    self.__suboperation3()

उप-धारा 1 डेटा के किस भाग का उपयोग करता है? क्या यह सबऑपरेशन 2 द्वारा उपयोग किए गए डेटा को इकट्ठा करता है? जब आप डेटा को स्वयं पर संग्रहीत करके पास करते हैं, तो मैं यह नहीं बता सकता कि कार्यक्षमता के टुकड़े कैसे संबंधित हैं। जब मैं स्वयं को देखता हूं, तो कुछ विशेषताएं निर्माता से होती हैं, कुछ कॉल से the_public_method, और कुछ यादृच्छिक रूप से अन्य स्थानों पर जोड़ दी जाती हैं। मेरी राय में, यह एक गड़बड़ है।

संख्या # 2 के बारे में क्या? ठीक है, पहले, चलो इसके साथ दूसरी समस्या को देखें:

इसके अलावा, फ़ंक्शन एक दूसरे के साथ निकटता से संबंधित होंगे, लेकिन एक साथ समूहीकृत नहीं होंगे।

वे एक साथ एक मॉड्यूल में होंगे, इसलिए वे पूरी तरह से एक साथ समूहीकृत होंगे।

मैं इसके साथ जो समस्या देख रहा हूं वह यह है कि मुझे फंक्शन्स के बीच बहुत सारे वैरिएबल पास करने होंगे।

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

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

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

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

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

def install_program():
    copied_files = []
    try:
        for filename in FILES_TO_COPY:
           temporary_file = create_temporary_file()
           copy(target_filename(filename), temporary_file)
           copied_files = [target_filename(filename), temporary_file)
           copy(source_filename(filename), target_filename(filename))
     except CancelledException:
        for source_file, temp_file in copied_files:
            copy(temp_file, source_file)
     else:
        for source_file, temp_file in copied_files:
            delete(temp_file)

अब उस तर्क को रजिस्ट्री सेटिंग्स, प्रोग्राम आइकन, आदि करने के लिए गुणा करें, और आपको काफी गड़बड़ मिल गई है।

मुझे लगता है कि आपका # 1 समाधान जैसा दिखता है:

class Installer:
    def install(self):
        try:
            self.copy_new_files()
        except CancellationError:
            self.restore_original_files()
        else:
            self.remove_temp_files()

यह समग्र एल्गोरिथ्म को स्पष्ट करता है, लेकिन यह अलग-अलग टुकड़ों को संवाद करने के तरीके को छुपाता है।

दृष्टिकोण # 2 कुछ इस तरह दिखता है:

def install_program():
    try:
       temp_files = copy_new_files()
    except CancellationError as error:
       restore_old_files(error.files_that_were_copied)
    else:
       remove_temp_files(temp_files)

अब यह स्पष्ट है कि डेटा के टुकड़े कैसे फंक्शन्स के बीच चलते हैं, लेकिन यह बहुत अजीब है।

तो इस फ़ंक्शन को कैसे लिखा जाना चाहिए?

def install_program():
    with FileTransactionLog() as file_transaction_log:
         copy_new_files(file_transaction_log)

FileTransactionLog ऑब्जेक्ट एक संदर्भ प्रबंधक है। जब copy_new_files किसी फ़ाइल की प्रतिलिपि करता है, तो वह FileTransactionLog के माध्यम से करता है जो अस्थायी प्रतिलिपि बनाने का काम करता है और उन फ़ाइलों का ट्रैक रखता है जिन्हें कॉपी किया गया है। अपवाद के मामले में यह मूल फाइलों को वापस कॉपी करता है, और सफलता के मामले में यह अस्थायी प्रतियां हटा देता है।

यह काम करता है क्योंकि हमने कार्य का अधिक प्राकृतिक अपघटन पाया है। पहले, हम तर्क को रद्द करने के तरीके के बारे में तर्क के साथ आवेदन को स्थापित करने के तरीके के बारे में बता रहे थे। अब लेनदेन लॉग अस्थायी फ़ाइलों और बहीखाता पद्धति के बारे में सभी विवरणों को संभालता है, और फ़ंक्शन बुनियादी एल्गोरिथ्म पर ध्यान केंद्रित कर सकता है।

मुझे संदेह है कि आपका मामला उसी नाव में है। आपको किसी तरह के ऑब्जेक्ट में बहीखाता तत्वों को निकालने की आवश्यकता है ताकि आपके जटिल कार्य को अधिक सरल और सुरुचिपूर्ण ढंग से व्यक्त किया जा सके।


9

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

def publicFunction(var1, var2, param1, param2)
    thing = Thing(var1, var2)
    thing.theMethod(param1, param2)

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


4

एक तरफ, सवाल किसी भी तरह भाषा अज्ञेय है; लेकिन दूसरी ओर, कार्यान्वयन भाषा और उसके प्रतिमानों पर निर्भर करता है। इस मामले में यह पायथन है, जो कई प्रतिमानों का समर्थन करता है।

अपने समाधान के अलावा, वहाँ भी एक संभावना, संचालन पूरा करना है राज्यविहीन एक अधिक कार्यात्मक तरह से है, जैसे

def outer(param1, param2):
    def inner1(param1, param2, param3):
        pass
    def inner2(param1, param2):
        pass
    return inner2(inner1(param1),param2,param3)

यह सब नीचे आता है

  • पठनीयता
  • संगति
  • रख-रखाव

लेकिन -अगर आपका कोडबेस OOP है, तो यह निरंतरता का उल्लंघन करता है अगर अचानक कुछ हिस्सों को (अधिक) कार्यात्मक शैली में लिखा जाता है।


4

क्यों डिजाइन जब मैं कोडिंग हो सकता है?

मैं उन उत्तरों के लिए एक विरोधाभासी दृश्य प्रस्तुत करता हूं जो मैंने पढ़े हैं। उस दृष्टिकोण से सभी उत्तर और स्पष्ट रूप से यहां तक ​​कि सवाल खुद को कोडिंग यांत्रिकी पर केंद्रित है। लेकिन यह एक डिजाइन मुद्दा है।

यदि मेरा फ़ंक्शन जटिल है और बहुत सारे चर हैं, तो क्या मुझे एक वर्ग बनाना चाहिए?

हाँ, क्योंकि यह आपके डिजाइन के लिए समझ में आता है। यह स्वयं के लिए एक वर्ग हो सकता है या किसी अन्य वर्ग का हिस्सा हो सकता है या यह व्यवहार कक्षाओं के बीच वितरित किया जा सकता है।


ऑब्जेक्ट ओरिएंटेड डिज़ाइन जटिलता के बारे में है

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

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

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


OO डिजाइन स्वाभाविक रूप से जटिलता का प्रबंधन करता है OOP स्वाभाविक रूप से अनावश्यक जटिलता का परिचय देता है।
माइल्स राउत

"अनावश्यक जटिलता" मुझे सहकर्मियों की अनमोल टिप्पणियों की याद दिलाती है जैसे "ओह, यह बहुत अधिक कक्षाएं हैं।" अनुभव मुझे बताता है कि रस निचोड़ के लायक है। यह सबसे अच्छा है मैं लगभग पूरे वर्गों को 1-3 लाइनों के साथ लंबी और हर वर्ग के साथ देख रहा हूं, यह किसी भी दिए गए तरीके का हिस्सा जटिलता है: एक एकल छोटा LOC दो संग्रह की तुलना करता है और डुप्लिकेट लौटाता है - बेशक इसके पीछे कोड है। जटिल कोड के लिए "बहुत सारे कोड" को भ्रमित न करें। किसी भी मामले में, कुछ भी मुफ्त नहीं है।
राडारबॉब

1
"सरल" कोड के बहुत सारे जो एक जटिल तरीके से बातचीत करते हैं, वे थोड़ी मात्रा में जटिल कोड से भी बदतर हैं।
माइल्स राउत

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

3

अपनी आवश्यकताओं की तुलना मानक पायथन लाइब्रेरी में मौजूद चीज़ों से क्यों न करें, और फिर देखें कि इसे कैसे लागू किया जाता है?

ध्यान दें, यदि आपको किसी वस्तु की आवश्यकता नहीं है, तो आप अभी भी कार्यों के भीतर कार्यों को परिभाषित कर सकते हैं। पायथन 3 के साथ nonlocalआपको अपने मूल कार्य में परिवर्तन करने की अनुमति देने की नई घोषणा है।

आप अभी भी अपने फ़ंक्शन के अंदर कुछ सरल निजी कक्षाओं के लिए उपयोगी हो सकते हैं ताकि अमूर्तता को लागू किया जा सके और संचालन को ख़त्म किया जा सके।


धन्यवाद। और क्या आप मानक पुस्तकालय में कुछ भी जानते हैं जो मुझे लगता है कि प्रश्न में क्या है?
इनलान

मुझे नेस्टेड फ़ंक्शंस का उपयोग करके कुछ उद्धृत करना मुश्किल होगा, वास्तव में मुझे nonlocalअपने वर्तमान में स्थापित अजगर लिबास में कहीं भी नहीं मिलता है । शायद आप दिल ले सकते हैं textwrap.pyजिसमें एक TextWrapperवर्ग है, लेकिन यह भी एक def wrap(text)फ़ंक्शन है जो बस एक TextWrapper उदाहरण बनाता है , इस .wrap()पर विधि को कॉल करता है और लौटता है। इसलिए क्लास का उपयोग करें, लेकिन कुछ सुविधा फ़ंक्शंस जोड़ें।
मयूह
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.