क्या मुझे एक कंस्ट्रक्टर में एक ऑब्जेक्ट पास करना चाहिए, या क्लास में तत्काल करना चाहिए?


10

इन दो उदाहरणों पर विचार करें:

किसी कंस्ट्रक्टर को ऑब्जेक्ट पास करना

class ExampleA
{
  private $config;
  public function __construct($config)
  {
     $this->config = $config;
  }
}
$config = new Config;
$exampleA = new ExampleA($config);

तुरंत क्लास लगाना

class ExampleB
{
  private $config;
  public function __construct()
  {
     $this->config = new Config;
  }
}
$exampleA = new ExampleA();

संपत्ति के रूप में किसी वस्तु को जोड़ने का सही तरीका कौन सा है? मुझे कब एक का उपयोग करना चाहिए? क्या यूनिट परीक्षण मुझे प्रभावित करना चाहिए जो मुझे उपयोग करना चाहिए?


इस पर बेहतर हो सकता है codereview.stackexchange.com/questions ?
FrustratedWithFormsDesigner

9
@FrustratedWithFormsDesigner - यह कोड समीक्षा के लिए उपयुक्त नहीं है।
ChrisF

जवाबों:


14

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

दूसरी ओर, शायद आपके ExampleA लिए एक नई और साफ configवस्तु की आवश्यकता होती है , इसलिए ऐसे मामले हो सकते हैं जहां दूसरा उदाहरण अधिक उपयुक्त हो, जैसे कि ऐसे मामले जहां प्रत्येक उदाहरण में संभवतः एक अलग कॉन्फ़िगरेशन हो सकता है।


1
तो, ए यूनिट परीक्षण के लिए अच्छा है, बी अन्य परिस्थितियों के लिए अच्छा है ... दोनों क्यों नहीं हैं? मेरे पास अक्सर दो कंस्ट्रक्टर हैं, एक मैनुअल डि के लिए, एक सामान्य उपयोग के लिए (मैं डि के लिए यूनिटी का उपयोग करता हूं, जो डिस्ट्रिक्टर्स को अपनी डीआई आवश्यकताओं को पूरा करने के लिए उत्पन्न करेगा)।
एड जेम्स

3
@EdWoodcock यह वास्तव में इकाई परीक्षण बनाम अन्य परिस्थितियों के बारे में नहीं है। ऑब्जेक्ट को या तो बाहर से निर्भरता की आवश्यकता होती है या वह इसे अंदर पर प्रबंधित करता है। दोनों कभी भी नहीं।
मटकावेई

9

परीक्षणशीलता के बारे में मत भूलना!

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

इसलिए मैं पहले विकल्प के साथ जाऊंगा ExampleA


यही कारण है कि मैंने परीक्षण का उल्लेख किया - कि जोड़ने के लिए धन्यवाद :)
कैदी

5

यदि ऑब्जेक्ट पर निर्भरता के जीवनकाल के प्रबंधन की ज़िम्मेदारी है , तो कंस्ट्रक्टर में ऑब्जेक्ट बनाना ठीक है (और इसे विध्वंसक में डिस्पोज़ करना)। *

यदि ऑब्जेक्ट निर्भरता के जीवनकाल के प्रबंधन के लिए ज़िम्मेदार नहीं है, तो इसे कंस्ट्रक्टर में पास किया जाना चाहिए और बाहर से प्रबंधित किया जाना चाहिए (जैसे एक आईओसी कंटेनर द्वारा)।

इस मामले में मुझे नहीं लगता कि आपके क्लासए को $ कॉन्फ़िगर बनाने की ज़िम्मेदारी लेनी चाहिए, जब तक कि इसे निपटाने के लिए भी ज़िम्मेदार न हो, या यदि काग़ज़ के प्रत्येक उदाहरण के लिए कॉनफ़िगर करना अद्वितीय है।

* परीक्षण क्षमता में सहायता करने के लिए, निर्माता अपने निर्माता में निर्भरता के निर्माण के लिए एक कारखाना वर्ग / विधि का संदर्भ ले सकता है, जिससे सामंजस्य और परीक्षण क्षमता बढ़ सकती है।

//Object which manages the lifetime of its dependency (C#):
public class ClassA : IDisposable
{
    public Config Config { get; private set; }

    public ClassA()
    {
        this.Config = new Config(); // Tightly coupled to Config class...
    }

    public void Dispose()
    {
        this.Config.Dispose();
    }
}

// Object which does not manage its dependency:
public class ClassA
{
    public Config Config { get; set; }

    public ClassA(Config config) // dependency may be injected...
    {
        this.Config = config;
    }
}

// Object which manages its dependency in a testable way:
public class ClassA : IDisposable
{
    public Config Config { get; private set; }

    public ClassA(IConfigFactory configFactory) // dependency may be mocked...
    {
        this.Config = configFactory.BuildConfig();
    }

    public void Dispose()
    {
        this.Config.Dispose();
    }
}

2

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

आपके उदाहरण में, क्या होगा यदि आपका विन्यास वर्ग:

a) गैर-तुच्छ आवंटन है, जैसे कि पूल या फैक्ट्री विधि से आना।

ख) आवंटित करने में विफल हो सकता है। निर्माणकर्ता में इसे पास करने से बड़े करीने से इस मुद्दे से बचा जाता है।

c) वास्तव में विन्यास का एक उपवर्ग है।

कंस्ट्रक्टर में ऑब्जेक्ट को पास करने से सबसे अधिक लचीलापन मिलता है।


1

नीचे दिया गया उत्तर गलत है, लेकिन मैं इसे दूसरों से सीखने के लिए रखूंगा (नीचे देखें)

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

class ExampleA
{
  private $config;
  public function __construct()
  {
     $this->config = Config->getInstance();
  }
}
$exampleA = new ExampleA();

में ExampleB, दूसरे हाथ पर, आप हमेशा का एक अलग इंस्टेंस मिल जाएगा Configके प्रत्येक उदाहरण के लिए ExampleB

आपको किस संस्करण को वास्तव में लागू करना चाहिए यह इस बात पर निर्भर करता है कि आवेदन किस तरह से उदाहरणों को संभालेगा Config:

  • यदि प्रत्येक उदाहरण का ExampleXअलग उदाहरण होना चाहिए, तो Configसाथ जाएं ExampleB;
  • यदि प्रत्येक का उदाहरण ExampleX(और केवल एक) का साझा होगा Config, का उपयोग करें ExampleA with Config Singleton;
  • अगर उदाहरण के ExampleXविभिन्न उदाहरणों का उपयोग कर सकते हैं Config, के साथ छड़ी ExampleA

क्यों Configएक सिंगलटन में परिवर्तित करना गलत है:

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

  1. यदि उदाहरण ExampleAके Configलिए अपना स्वयं का संदर्भ प्राप्त होता है , तो कक्षाएं कसकर युग्मित हो जाएंगी। (जैसे कुछ उपवर्ग) के ExampleAएक अलग संस्करण का उपयोग करने के लिए एक उदाहरण होने का कोई तरीका नहीं होगा Config। यह भयानक है अगर आप ExampleAमॉक-अप उदाहरण का उपयोग करके परीक्षण करना चाहते हैं Configक्योंकि इसे प्रदान करने का कोई तरीका नहीं है ExampleA

  2. वहाँ का आधार एक होगा, और केवल एक, Configशायद का उदाहरण अब धारण करता है , लेकिन आप हमेशा यह सुनिश्चित नहीं कर सकते हैं कि भविष्य में भी यही होगा । यदि बाद में कुछ बिंदु पर यह पता चलता है कि Configइच्छा के कई उदाहरण वांछनीय होंगे, तो कोड को फिर से लिखे बिना इसे प्राप्त करने का कोई तरीका नहीं है।

  3. भले ही एक-और-केवल एक उदाहरण Configसभी अनंत काल के लिए सही है, लेकिन ऐसा हो सकता है कि आप कुछ उपवर्गों का उपयोग करने में सक्षम होना चाहते हैं Config(जबकि अभी भी केवल एक उदाहरण है)। लेकिन, चूंकि कोड सीधे उदाहरण के माध्यम से getInstance()प्राप्त करता है Config, जो कि एक staticविधि है, इसलिए उपवर्ग प्राप्त करने का कोई तरीका नहीं है। फिर से, कोड को फिर से लिखा जाना चाहिए।

  4. ExampleAउपयोग किए जाने वाले तथ्य को Configछिपाया जाएगा, कम से कम जब केवल एपीआई को देखा जाएगा ExampleA। यह बुरी बात हो सकती है या नहीं भी हो सकती है, लेकिन व्यक्तिगत रूप से मुझे लगता है कि यह एक नुकसान जैसा लगता है; उदाहरण के लिए, बनाए रखने के दौरान, यह पता लगाने का कोई सरल तरीका नहीं है Configकि हर दूसरे वर्ग के कार्यान्वयन को देखे बिना परिवर्तनों से कौन सी कक्षाएं प्रभावित होंगी ।

  5. यहां तक ​​कि अगर तथ्य यह है कि ExampleAएक सिंगलटन Config का उपयोग करता है अपने आप में एक समस्या नहीं है, यह अभी भी एक परीक्षण के दृष्टिकोण से एक समस्या बन सकता है। सिंगलटन ऑब्जेक्ट राज्य को ले जाएगा जो कि आवेदन की समाप्ति तक जारी रहेगा। इकाई परीक्षण चलाते समय यह एक समस्या हो सकती है क्योंकि आप चाहते हैं कि एक परीक्षण को दूसरे से अलग किया जाए (यानी कि एक परीक्षण निष्पादित होने पर दूसरे के परिणाम को प्रभावित नहीं करना चाहिए)। इसे ठीक करने के लिए, प्रत्येक परीक्षण रन (संभावित रूप से पूरे एप्लिकेशन को पुनरारंभ करने) के बीच सिंगलटन ऑब्जेक्ट को नष्ट कर दिया जाना चाहिए, जो समय लेने वाली (थकाऊ और कष्टप्रद का उल्लेख नहीं करने के लिए) हो सकता है।

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


4
मैं ऐसा करने की सिफारिश नहीं करूंगा .. इस तरह से आप युगल वर्ग को कसकर पकड़ेंगे ExampleAऔर Config- जो अच्छी बात नहीं है।
पॉल

@Paul: यह सच है। अच्छी पकड़, उस बारे में नहीं सोचा था।
गाब्लिन

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

4
मैं हमेशा आपके गलत करने के कारणों के लिए सिंग्लेटन्स का उपयोग करके फिर से सुझाव दूंगा।
रेयनोस

1

करने के लिए सबसे सरल बात ExampleAहै Config। आपको सबसे सरल काम करना चाहिए, जब तक कि कुछ अधिक जटिल करने के लिए एक सम्मोहक कारण न हो।

Decoupling के लिए एक कारण ExampleAऔर Configके परीक्षण क्षमता में सुधार होगा ExampleA। प्रत्यक्ष युग्मन के testability नीचा होगा ExampleAअगर Configतरीकों कि, धीमी गति से जटिल है, या तेजी से विकसित होते हैं। परीक्षण के लिए, एक विधि धीमी है अगर यह कुछ माइक्रोसेकंड से अधिक चलती है। यदि सभी तरीके Configसरल और तेज हैं, तो मैं सरल दृष्टिकोण और सीधे जोड़े ExampleAको ले जाऊंगा Config


1

आपका पहला उदाहरण डिपेंडेंसी इंजेक्शन पैटर्न का एक उदाहरण है। बाहरी निर्भरता वाले एक वर्ग को कंस्ट्रक्टर, सेटर, आदि द्वारा निर्भरता सौंपी जाती है।

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

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

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


0

यदि आपकी कक्षा $configबाहरी कक्षाओं को उजागर नहीं करती है , तो मैं इसे कंस्ट्रक्टर के अंदर बना दूँगा। इस तरह, आप अपनी आंतरिक स्थिति को निजी रख रहे हैं।

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


0

ExampleA को ठोस वर्ग विन्यास से अलग किया जाता है , जो कि अच्छा है , बशर्ते कि प्राप्त किया गया है यदि प्रकार का विन्यास नहीं है, लेकिन विन्यास का सार सुपरक्लास का प्रकार है।

ExampleB को ठोस वर्ग कॉन्फिगरेशन के लिए मजबूती से जोड़ा जाता है जो खराब है

ऑब्जेक्ट को इंस्टेंट करना कक्षाओं के बीच एक मजबूत युग्मन बनाता है। इसे फैक्ट्री क्लास में किया जाना चाहिए।

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