क्या बहुत सी स्थैतिक विधियों का उपयोग करना बुरी बात है?


97

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

मैंने विभिन्न अनुशंसाओं (स्टैकऑवरफ़्लो पर सहित) को स्थिर विधियों का उपयोग करने के लिए नहीं पढ़ा है, लेकिन मैं अभी भी यह समझने में विफल हूं कि ऊपर अंगूठे के नियम के साथ क्या गलत है।

यह एक उचित दृष्टिकोण है या नहीं?

जवाबों:


153

दो प्रकार के सामान्य स्थिर तरीके हैं:

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

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


यह ठीक वैसी ही समस्या थी जिसका मुझे हल करना था - सिंगलटन ऑब्जेक्ट्स का उपयोग, या बल्कि दुरुपयोग।
ओवरस्लैक किया

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

1
"शुद्ध फ़ंक्शन" और "अशुद्ध फ़ंक्शन" शब्द कार्यात्मक प्रोग्रामिंग में दिए गए नाम हैं जिन्हें आप "सुरक्षित" और "असुरक्षित" स्टैटिक्स कहते हैं।
ओमनीमाइक

19

किसी भी आंतरिक स्थिति के बिना एक वस्तु एक संदिग्ध चीज है।

आम तौर पर, ऑब्जेक्ट राज्य और व्यवहार को घेरते हैं। एक वस्तु जो केवल व्यवहार को एनकैप्सुलेट करती है वह विषम है। कभी-कभी यह लाइटवेट या फ्लाईवेट का उदाहरण है ।

दूसरी बार, यह एक वस्तु भाषा में किया गया प्रक्रियात्मक डिज़ाइन है।


6
मैं सुनता हूं कि आप क्या कह रहे हैं, लेकिन एक मैथ ऑब्जेक्ट की तरह कुछ भी व्यवहार पर कुछ भी कैसे कर सकता है?
जोनोव

10
उसने सिर्फ शक किया, गलत नहीं, और वह बिल्कुल सही है।
बिल के

2
@ जोंव: मठ एक बहुत ही विशेष मामला है जहां कई स्टेटलेस कार्य होते हैं। बेशक, यदि आप जावा में फंक्शनल प्रोग्रामिंग कर रहे हैं, तो आपके पास कई स्टेटलेस फ़ंक्शन होंगे।
एल।

14

यह वास्तव में केवल जॉन मिलिकिन के महान जवाब का अनुसरण है।


यद्यपि यह स्टेटलेस तरीके (जो बहुत अधिक कार्य करता है) स्थैतिक बनाने के लिए सुरक्षित हो सकता है, यह कभी-कभी युग्मन को जन्म दे सकता है जिसे संशोधित करना मुश्किल है। विचार करें कि आपके पास एक स्थिर विधि है:

public class StaticClassVersionOne {
    public static void doSomeFunkyThing(int arg);
}

जिसे आप कहते हैं:

StaticClassVersionOne.doSomeFunkyThing(42);

जो सभी अच्छी तरह से और अच्छा है, और बहुत सुविधाजनक है, जब तक कि आप एक ऐसे मामले में नहीं आते हैं जहां आपको स्थैतिक विधि के व्यवहार को संशोधित करना पड़ता है, और पाते हैं कि आप कसकर बाध्य हैं StaticClassVersionOne। संभवतः आप कोड को संशोधित कर सकते हैं और यह ठीक होगा, लेकिन अगर पुराने व्यवहार पर निर्भर अन्य कॉलर्स थे, तो उन्हें विधि के शरीर में हिसाब करना होगा। कुछ मामलों में, अगर शरीर इन सभी व्यवहारों को संतुलित करने की कोशिश करता है, तो शरीर बहुत बदसूरत या अचूक हो सकता है। यदि आप विधियों को विभाजित करते हैं, तो आपको इसे लेने के लिए कई स्थानों पर कोड को संशोधित करना पड़ सकता है, या नई कक्षाओं में कॉल कर सकते हैं।

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

यह एक संभावित स्थिति हो सकती है या नहीं, लेकिन मुझे लगता है कि यह विचार करने लायक है।


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

6

अन्य विकल्प उन्हें मूल वस्तु पर गैर-स्थिर तरीकों के रूप में जोड़ना है:

अर्थात, बदलना:

public class BarUtil {
    public static Foo transform(Bar toFoo) { ... }
}

में

public class Bar {
    ...
    public Foo transform() { ...}
}

हालाँकि कई स्थितियों में यह संभव नहीं है (उदाहरण के लिए, XSD / WSDL / etc से नियमित वर्ग कोड पीढ़ी), या यह कक्षा को बहुत लंबा कर देगा, और परिवर्तन के तरीके अक्सर जटिल वस्तुओं के लिए एक वास्तविक दर्द हो सकते हैं और आप उन्हें बस चाहते हैं उनके अपने अलग वर्ग में। तो हाँ, मैं उपयोगिता वर्गों में स्थिर तरीके हैं।


5

जब तक वे सही स्थानों पर उपयोग किए जाते हैं तब तक स्थिर कक्षाएं ठीक होती हैं।

अर्थात्: वे विधियां जो 'लीफ' विधियां हैं (वे राज्य को संशोधित नहीं करती हैं, वे केवल किसी तरह इनपुट को बदल देती हैं)। इसके अच्छे उदाहरण Path.Combine जैसी चीजें हैं। इस प्रकार की चीजें उपयोगी होती हैं और टेरियर सिंटैक्स के लिए बनाती हैं।

मेरे पास स्टैटिक्स की समस्याएं हैं:

सबसे पहले, यदि आपके पास स्थिर कक्षाएं हैं, तो निर्भरताएं छिपी हुई हैं। निम्नलिखित को धयान मे रखते हुए:

public static class ResourceLoader
{
    public static void Init(string _rootPath) { ... etc. }
    public static void GetResource(string _resourceName)  { ... etc. }
    public static void Quit() { ... etc. }
}

public static class TextureManager
{
    private static Dictionary<string, Texture> m_textures;

    public static Init(IEnumerable<GraphicsFormat> _formats) 
    {
        m_textures = new Dictionary<string, Texture>();

        foreach(var graphicsFormat in _formats)
        {
              // do something to create loading classes for all 
              // supported formats or some other contrived example!
        }
    }

    public static Texture GetTexture(string _path) 
    {
        if(m_textures.ContainsKey(_path))
            return m_textures[_path];

        // How do we know that ResourceLoader is valid at this point?
        var texture = ResourceLoader.LoadResource(_path);
        m_textures.Add(_path, texture);
        return texture; 
    }

    public static Quit() { ... cleanup code }       
}

TextureManager को देखते हुए, आप यह नहीं बता सकते हैं कि एक निर्माणकर्ता को देखकर क्या आरंभिक कदम उठाए जाने चाहिए। सही क्रम में इसकी निर्भरता और प्रारंभिक चीजों को खोजने के लिए आपको कक्षा में पहुंचना चाहिए। इस स्थिति में, इसे चलाने से पहले रिसोर्सलॉडर को इनिशियलाइज़ करना होगा। अब इस निर्भरता को दुःस्वप्न के पैमाने पर और आप शायद अनुमान लगा सकते हैं कि क्या होगा। कल्पना करें कि कोड को बनाए रखने की कोशिश कर रहा है, जहां कोई प्रारंभिक क्रम का स्पष्ट आदेश नहीं है। उदाहरणों के साथ निर्भरता इंजेक्शन के साथ इसके विपरीत - उस मामले में कोड भी संकलित नहीं करेगा यदि निर्भरताएं पूरी नहीं हुई हैं!

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

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

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

यहाँ समस्याओं पर एक अच्छा लेख है: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/


4

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

उस ने कहा, यदि आपके पास आंतरिक डेटा का कोई उपयोग नहीं है, तो वे उपयोग करने के लिए ठीक हैं और निष्पादित करने के लिए थोड़ा तेज़ हैं। सुनिश्चित करें कि आप उनमें वैश्विक डेटा को नहीं छू रहे हैं।

  • कुछ भाषाओं में वर्ग-स्तरीय चर भी होते हैं, जो डेटा और स्थैतिक विधियों के इनकैप्सुलेशन के लिए अनुमति देते हैं।

4

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

आपके मामले में जहां आप ए को बी में बदल रहे हैं, कहते हैं कि हम जो कर रहे हैं वह पाठ को बदलने के लिए है

"hello" =>(transform)=> "<b>Hello!</b>"

तब एक स्थिर विधि समझ में आती है।

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

class Interface{
    method toHtml(){
        return transformed string (e.g. "<b>Hello!</b>")
    }

    method toConsole(){
        return transformed string (e.g. "printf Hello!")
    }
}


class Object implements Interface {
    mystring = "hello"

    //the implementations of the interface would yield the necessary 
    //functionality, and it is reusable across the board since it 
    //is an interface so... you can make it specific to the object

   method toHtml()
   method toConsole()
}

संपादित करें: स्थैतिक विधियों के महान उपयोग का एक अच्छा उदाहरण Asp.Net MVC या रूबी में HTML सहायक विधियाँ हैं। वे HTML तत्व बनाते हैं जो किसी वस्तु के व्यवहार से बंधे नहीं हैं, और इसलिए स्थिर हैं।

संपादित 2: संरचित प्रोग्रामिंग के लिए कार्यात्मक प्रोग्रामिंग को बदल दिया (किसी कारण से मैं भ्रमित हो गया), इसे इंगित करने के लिए टॉर्स्टन को सहारा देता है।


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

3

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

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


3

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


2

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

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

और स्थिर तरीकों का कोई समान लाभ नहीं है।

तो हाँ, वे बुरे हैं।


1

जब तक आंतरिक स्थिति खेलने में नहीं आती, यह ठीक है। ध्यान दें कि आमतौर पर स्थिर तरीकों से थ्रेड-सुरक्षित होने की उम्मीद की जाती है, इसलिए यदि आप सहायक डेटा संरचनाओं का उपयोग करते हैं, तो उन्हें थ्रेड-सुरक्षित तरीके से उपयोग करें।


1

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


1

यदि यह एक उपयोगिता विधि है, तो इसे स्थिर बनाना अच्छा है। इस सिद्धांत पर अमरूद और अपाचे कॉमन्स बनाए गए हैं।

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

इसलिए मेरे ऐप लॉजिक में मेरे पास आमतौर पर छोटे स्टेटिक यूटिलिटी जैसे मेथड कॉल होते हैं। अर्थात

static cutNotNull(String s, int length){
  return s == null ? null : s.substring(0, length);
}

लाभों में से एक यह है कि मैं ऐसे तरीकों का परीक्षण नहीं करता हूं :-)


1

वैसे, सिल्वर बुलेट का कोई कोर्स नहीं है। थोड़ा उपयोगिताओं / सहायकों के लिए स्थैतिक कक्षाएं ठीक हैं। लेकिन व्यापार तर्क प्रोग्रामिंग के लिए स्थैतिक तरीकों का उपयोग करना निश्चित रूप से बुराई है। निम्नलिखित कोड पर विचार करें

   public class BusinessService
   {

        public Guid CreateItem(Item newItem, Guid userID, Guid ownerID)
        {
            var newItemId = itemsRepository.Create(createItem, userID, ownerID);
            **var searchItem = ItemsProcessor.SplitItem(newItem);**
            searchRepository.Add(searchItem);
            return newItemId;
        }
    }

आप एक स्थैतिक विधि को कॉल करते हैं, जिससे ItemsProcessor.SplitItem(newItem);यह बदबू आ रही है

  • आपके पास स्पष्ट निर्भरता घोषित नहीं है और यदि आप कोड में खुदाई नहीं करते हैं, तो आप अपने वर्ग और स्थिर विधि कंटेनर के बीच युग्मन की अनदेखी कर सकते हैं
  • आप BusinessServiceइसे अलग-थलग नहीं कर सकते ItemsProcessor(अधिकांश परीक्षण उपकरण स्थैतिक कक्षाओं का मजाक नहीं उड़ाते हैं) और यह इकाई परीक्षण को असंभव बनाता है। कोई यूनिट टेस्ट == कम गुणवत्ता

0

स्टेटिक तरीके आमतौर पर स्टेटलेस कोड के लिए भी एक बुरा विकल्प होते हैं। इसके बजाय इन तरीकों के साथ एक सिंगलटन क्लास बनाएं जो एक बार इंस्टेंट हो जाए और उन कक्षाओं में इंजेक्ट किया जाए जो विधियों का उपयोग करना चाहते हैं। ऐसी कक्षाओं को मॉक और टेस्ट करना आसान होता है। वे कहीं अधिक वस्तु उन्मुख हैं। जरूरत पड़ने पर आप उन्हें प्रॉक्सी से लपेट सकते हैं। स्टैटिक्स OO को बहुत कठिन बनाते हैं और मुझे लगभग सभी मामलों में उनके उपयोग का कोई कारण नहीं दिखता है। 100% नहीं बल्कि लगभग सभी।

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