एकता में, मैं सिंगलटन पैटर्न को सही तरीके से कैसे लागू करूं?


36

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

क्या इसके लिए कोई सही, या बल्कि पसंदीदा तरीका है?

मैंने जिन दो मुख्य उदाहरणों का सामना किया है वे हैं:

प्रथम

public class GameManager
{
    private static GameManager _instance;

    public static GameManager Instance
    {
        get
        {
            if(_instance == null)
            {
                _instance = GameObject.FindObjectOfType<GameManager>();
            }

            return _instance;
        }
    }

    void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }
}

दूसरा

public class GameManager
{
    private static GameManager _instance;

    public static GameManager Instance
    {
        get
        {
            if(_instance == null)
            {
                instance = new GameObject("Game Manager");
                instance.AddComponent<GameManager>();
            }

            return _instance;
        }
    }

    void Awake()
    {
        _instance = this;
    }
}

मुख्य अंतर मैं दोनों के बीच देख सकता हूं:

पहला दृष्टिकोण गेम ऑब्जेक्ट स्टैक को नेविगेट करने का प्रयास करेगा, GameManagerजिसका एक उदाहरण खोजने के लिए भले ही यह केवल (या केवल होना चाहिए) एक बार ऐसा लगता है कि यह बहुत ही अनपेक्षित हो सकता है क्योंकि विकास के दौरान आकार में दृश्य बढ़ते हैं।

इसके अलावा, पहला दृष्टिकोण ऑब्जेक्ट को हटा दिया जाता है जब एप्लिकेशन दृश्य बदलता है, जो यह सुनिश्चित करता है कि ऑब्जेक्ट दृश्यों के बीच कायम है। दूसरा दृष्टिकोण इसका पालन नहीं करता है।

दूसरा दृष्टिकोण अजीब लगता है क्योंकि इस मामले में जहां सूचक में आवृत्ति शून्य है, यह एक नया गेमऑबजेक्ट बनाएगा और इसके लिए एक गेममैंगर घटक असाइन करेगा। हालाँकि, यह पहली बार नहीं चल सकता है यह GameManager घटक पहले से ही दृश्य में एक वस्तु से जुड़ा हुआ है, इसलिए यह मुझे कुछ भ्रम पैदा कर रहा है।

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


क्या कहा जाता है GameManager करने वाला है? क्या यह एक गेम ऑबजेक्ट होना चाहिए?
बंमज़ैक

1
यह वास्तव में सवाल नहीं है कि क्या करना GameManagerचाहिए, बल्कि यह सुनिश्चित करने के लिए कि वस्तु का केवल एक उदाहरण और इसे लागू करने का सबसे अच्छा तरीका क्या है।
कप्तानरेडमफ

इस ट्यूटोरियल ने बहुत बारीकी से समझाया, सिंगलटन को कैसे लागू किया जाए unitygeek.com/unity_c_singleton , मुझे आशा है कि यह उपयोगी है
राहुल ललित

जवाबों:


30

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

public class SomeClass : MonoBehaviour {
    private static SomeClass _instance;

    public static SomeClass Instance { get { return _instance; } }


    private void Awake()
    {
        if (_instance != null && _instance != this)
        {
            Destroy(this.gameObject);
        } else {
            _instance = this;
        }
    }
}

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


2
OnDestroy() { if (this == _instance) { _instance = null; } }यदि आप प्रत्येक दृश्य में एक अलग उदाहरण रखना चाहते हैं , तो आप भी चाहें ।
डिट्रीच एप्प

नष्ट () के बजाय GameObject आईएनजी आप एक त्रुटि उठाना चाहिए।

2
संभवतः। आप इसे लॉग इन करना चाह सकते हैं, लेकिन मुझे नहीं लगता कि आपको कोई त्रुटि उठानी चाहिए, जब तक कि आप कुछ विशेष करने की कोशिश नहीं कर रहे हैं। ऐसे कई उदाहरण हैं जिनकी मैं कल्पना कर सकता हूं कि एक त्रुटि को बढ़ाने से वास्तव में अधिक समस्या पैदा होगी तो यह ठीक हो जाएगी।
पियर्सनआर्टफोटो 21

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

मुझे पता है कि मैं देर से आ रहा हूं, लेकिन सिर्फ यह बताना चाहता हूं कि इस उत्तर के सिंगलटन में संपादक पुनः लोड होने से नहीं बचता है, क्योंकि स्थैतिक Instanceसंपत्ति का सफाया हो जाता है। एक उदाहरण जो नीचे दिए गए किसी एक उत्तर में नहीं मिलता है। , या wiki.unity3d.com/index.php/Singleton (जो हालांकि पुराना हो सकता है, लेकिन इसके साथ मेरे प्रयोग से काम करने लगता है)
जैकब अर्नोल्ड

24

यहाँ एक त्वरित सारांश है:

                 Create object   Removes scene   Global    Keep across
               if not in scene?   duplicates?    access?   Scene loads?

Method 1              No              No           Yes        Yes

Method 2              Yes             No           Yes        No

PearsonArtPhoto       No              Yes          Yes        No
Method 3

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

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

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


1
नोट: यह संभव होना चाहिए कि सभी चार तरीकों को बनाने के लिए सभी तीन तरीकों को मिलाएं जिसमें सभी चार विशेषताएं हों।
18

3
इस उत्तर का जोर यह नहीं है कि "आपको एक सिंगलटन कार्यान्वयन की तलाश करनी चाहिए जो यह सब करता है" बल्कि "आपको यह पहचानना चाहिए कि आप वास्तव में इस सिंगलटन से कौन सी सुविधाएँ चाहते हैं, और एक कार्यान्वयन चुनें जो उन सुविधाओं को वितरित करता है - भले ही वह कार्यान्वयन हो सभी "पर एक सिंगलटन नहीं
DMGregory

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

17

Singletonएकता के लिए एक सामान्य पैटर्न का सबसे अच्छा कार्यान्वयन मुझे पता है (निश्चित रूप से) मेरा अपना है।

यह सब कुछ कर सकता है , और यह बहुत करीने से और कुशलता से करता है :

Create object        Removes scene        Global access?               Keep across
if not in scene?     duplicates?                                       Scene loads?

     Yes                  Yes                  Yes                     Yes (optional)

अन्य लाभ:

  • यह धागा सुरक्षित है
  • यह सिंगलटन इंस्टेंस प्राप्त करने (बनाने) से संबंधित बग्स से बचता है जब अनुप्रयोग यह सुनिश्चित करके छोड़ देता है कि सिंग्लेटन्स के बाद नहीं बनाया जा सकता है OnApplicationQuit()। (और यह एकल वैश्विक ध्वज के साथ ऐसा करता है, प्रत्येक सिंगलटन प्रकार के बजाय उनका अपना है)
  • यह Unity 2017 के मोनो अपडेट (C # 6 के लगभग बराबर) का उपयोग करता है। (लेकिन यह आसानी से प्राचीन संस्करण के लिए अनुकूलित किया जा सकता है)
  • यह कुछ मुफ्त कैंडी के साथ आता है !

और क्योंकि साझा करना देखभाल है , यहाँ यह है:

public abstract class Singleton<T> : Singleton where T : MonoBehaviour
{
    #region  Fields
    [CanBeNull]
    private static T _instance;

    [NotNull]
    // ReSharper disable once StaticMemberInGenericType
    private static readonly object Lock = new object();

    [SerializeField]
    private bool _persistent = true;
    #endregion

    #region  Properties
    [NotNull]
    public static T Instance
    {
        get
        {
            if (Quitting)
            {
                Debug.LogWarning($"[{nameof(Singleton)}<{typeof(T)}>] Instance will not be returned because the application is quitting.");
                // ReSharper disable once AssignNullToNotNullAttribute
                return null;
            }
            lock (Lock)
            {
                if (_instance != null)
                    return _instance;
                var instances = FindObjectsOfType<T>();
                var count = instances.Length;
                if (count > 0)
                {
                    if (count == 1)
                        return _instance = instances[0];
                    Debug.LogWarning($"[{nameof(Singleton)}<{typeof(T)}>] There should never be more than one {nameof(Singleton)} of type {typeof(T)} in the scene, but {count} were found. The first instance found will be used, and all others will be destroyed.");
                    for (var i = 1; i < instances.Length; i++)
                        Destroy(instances[i]);
                    return _instance = instances[0];
                }

                Debug.Log($"[{nameof(Singleton)}<{typeof(T)}>] An instance is needed in the scene and no existing instances were found, so a new instance will be created.");
                return _instance = new GameObject($"({nameof(Singleton)}){typeof(T)}")
                           .AddComponent<T>();
            }
        }
    }
    #endregion

    #region  Methods
    private void Awake()
    {
        if (_persistent)
            DontDestroyOnLoad(gameObject);
        OnAwake();
    }

    protected virtual void OnAwake() { }
    #endregion
}

public abstract class Singleton : MonoBehaviour
{
    #region  Properties
    public static bool Quitting { get; private set; }
    #endregion

    #region  Methods
    private void OnApplicationQuit()
    {
        Quitting = true;
    }
    #endregion
}
//Free candy!

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

1
@netpoetica सरल। एकता बिल्डरों का समर्थन नहीं करती है। इसीलिए आपको किसी भी वर्ग में इस्तेमाल किए जा रहे कंस्ट्रक्टर्स विरासत में नहीं मिलते हैं MonoBehaviour, और मेरा मानना ​​है कि किसी भी वर्ग का इस्तेमाल सीधे तौर पर यूनिटी द्वारा किया जाता है।
XenoRo

मुझे यकीन नहीं है कि मैं इसका उपयोग कैसे करूं। क्या इसका मतलब सिर्फ कक्षा के माता-पिता से सवाल करना है? घोषित करने के बाद SampleSingletonClass : Singleton, SampleSingletonClass.Instanceवापस आता है SampleSingletonClass does not contain a definition for Instance
बेन आई।

@BenI। आपको जेनेरिक Singleton<>क्लास का उपयोग करना होगा । इसीलिए जेनेरिक बेस Singletonक्लास का बच्चा है ।
XenoRo

ओह बेशक! यह काफी स्पष्ट है। मुझे यकीन नहीं है कि मैंने ऐसा क्यों नहीं देखा। = /
बेन आई।

6

मैं बस यह जोड़ना चाहूंगा कि DontDestroyOnLoadयदि आप चाहते हैं कि आपकी सिंगलटन पूरे दृश्यों में बनी रहे तो कॉल करना उपयोगी हो सकता है ।

public class Singleton : MonoBehaviour
{ 
    private static Singleton _instance;

    public static Singleton Instance 
    { 
        get { return _instance; } 
    } 

    private void Awake() 
    { 
        if (_instance != null && _instance != this) 
        { 
            Destroy(this.gameObject);
            return;
        }

        _instance = this;
        DontDestroyOnLoad(this.gameObject);
    } 
}

यह बहुत आसान है। मैं इस सटीक प्रश्न को पूछने के लिए @ PearsonArtPhoto की प्रतिक्रिया पर एक टिप्पणी पोस्ट करने वाला था:]
CaptainRedmuff

5

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

public class Singleton{
    private Singleton(){
        //Class initialization goes here.
    }

    public void someSingletonMethod(){
        //Some method that acts on the Singleton.
    }

    private static Singleton _instance;
    public static Singleton Instance 
    { 
        get { 
            if (_instance == null)
                _instance = new Singleton();
            return _instance; 
        }
    } 
}

public class SingletonController: MonoBehaviour{
   //Create a local reference so that the editor can read it.
   public Singleton instance;
   void Awake(){
       instance = Singleton.Instance;
   }
   //You can reference the singleton instance directly, but it might be better to just reflect its methods in the controller.
   public void someMethod(){
       instance.someSingletonMethod();
   }
} 

यह बहुत अच्छा है!
CaptainRedmuff

1
मुझे इस पद्धति को समझने में परेशानी हो रही है, क्या आप इस विषय पर थोड़ा और विस्तार कर सकते हैं। धन्यवाद।
हेक्स

3

यहाँ नीचे एक सिंगलटन अमूर्त वर्ग का मेरा कार्यान्वयन है। यहां बताया गया है कि यह 4 मानदंडों के खिलाफ कैसे खड़ी होती है

             Create object   Removes scene   Global    Keep across
           if not in scene?   duplicates?    access?   Scene loads?

             No (but why         Yes           Yes        Yes
             should it?)

कुछ अन्य तरीकों की तुलना में इसके कुछ और फायदे हैं:

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

    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    
    public abstract class Singleton<T> : MonoBehaviour where T : Singleton<T>
    {
        #region  Variables
        protected static bool Quitting { get; private set; }
    
        private static readonly object Lock = new object();
        private static Dictionary<System.Type, Singleton<T>> _instances;
    
        public static T Instance
        {
            get
            {
                if (Quitting)
                {
                    return null;
                }
                lock (Lock)
                {
                    if (_instances == null)
                        _instances = new Dictionary<System.Type, Singleton<T>>();
    
                    if (_instances.ContainsKey(typeof(T)))
                        return (T)_instances[typeof(T)];
                    else
                        return null;
                }
            }
        }
    
        #endregion
    
        #region  Methods
        private void OnEnable()
        {
            if (!Quitting)
            {
                bool iAmSingleton = false;
    
                lock (Lock)
                {
                    if (_instances == null)
                        _instances = new Dictionary<System.Type, Singleton<T>>();
    
                    if (_instances.ContainsKey(this.GetType()))
                        Destroy(this.gameObject);
                    else
                    {
                        iAmSingleton = true;
    
                        _instances.Add(this.GetType(), this);
    
                        DontDestroyOnLoad(gameObject);
                    }
                }
    
                if(iAmSingleton)
                    OnEnableCallback();
            }
        }
    
        private void OnApplicationQuit()
        {
            Quitting = true;
    
            OnApplicationQuitCallback();
        }
    
        protected abstract void OnApplicationQuitCallback();
    
        protected abstract void OnEnableCallback();
        #endregion
    }

एक मूर्खतापूर्ण सवाल हो सकता है, लेकिन तुम क्यों किया OnApplicationQuitCallbackऔर OnEnableCallbackके रूप में abstractके बजाय सिर्फ खाली की virtualतरीकों? कम से कम मेरे मामले में मेरे पास कोई तर्क नहीं है / तर्क को सक्षम करने और खाली ओवरराइड को गंदा महसूस करने के लिए। लेकिन मुझे कुछ याद आ रहा है।
याकूब अर्नोल्ड

@JakubArnold मैंने थोड़ी देर में इस पर ध्यान नहीं दिया, लेकिन पहली नज़र में ऐसा लगता है कि आप सही हैं, आभासी तरीकों से बेहतर होगा
बर्ट्रेंड

@JakubArnold वास्तव में मुझे लगता है कि मैं अपनी सोच को पीछे से याद करता हूं: मैं उन लोगों को जागरूक करना चाहता था जो इसे एक घटक के रूप में इस्तेमाल करते थे जो इसे इस्तेमाल कर सकते थे : OnApplicationQuitCallbackऔर OnEnableCallbackइसे आभासी तरीकों के रूप में रखने से यह कम स्पष्ट होता है। शायद थोड़ा अजीब है लेकिन जहाँ तक मुझे याद है कि वह मेरी तर्कसंगत थी।
बर्ट्रेंड

2

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


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

2

मैं अपना कार्यान्वयन भविष्य की पीढ़ियों के लिए भी करूंगा।

void Awake()
    {
        if (instance == null)
            instance = this;
        else if (instance != this)
            Destroy(gameObject.GetComponent(instance.GetType()));
        DontDestroyOnLoad(gameObject);
    }

मेरे लिए यह पंक्ति Destroy(gameObject.GetComponent(instance.GetType()));बहुत महत्वपूर्ण है क्योंकि एक बार जब मैंने एक दृश्य में एक अन्य गेमऑबजेक्ट पर एक सिंगलटन स्क्रिप्ट को छोड़ दिया था और पूरे गेम ऑब्जेक्ट को हटा दिया जा रहा था। यह केवल घटक को नष्ट कर देगा यदि यह पहले से मौजूद है।


1

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

इसलिए आपको सिंगलटन कोड लिखने की आवश्यकता नहीं है। बस इस Singleton.cs बेस क्लास को डाउनलोड करें , इसे अपनी परियोजना में जोड़ें, और इसका विस्तार करने वाला अपना सिंगलटन बनाएं:

public class MySingleton : Singleton<MySingleton> {
  protected MySingleton () {} // Protect the constructor!

  public string globalVar;

  void Awake () {
      Debug.Log("Awoke Singleton Instance: " + gameObject.GetInstanceID());
  }
}

अब आपका MySingleton वर्ग एक सिंगलटन है, और आप इसे Instance कह सकते हैं:

MySingleton.Instance.globalVar = "A";
Debug.Log ("globalVar: " + MySingleton.Instance.globalVar);

यहाँ एक पूरा ट्यूटोरियल है: http://www.bivis.com.br/2016/05/04/unity-reusable-singleton-tutorial/

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