Singleton: How should it be used

后端 未结 24 1888
Happy的楠姐
Happy的楠姐 2020-11-22 04:57

Edit: From another question I provided an answer that has links to a lot of questions/answers about singletons: More info about singletons here:

So I have read th

相关标签:
24条回答
  • 2020-11-22 05:16

    Because a singleton only allows one instance to be created it effectively controls instance replication. for example you'd not need multiple instances of a lookup - a morse lookup map for example, thus wrapping it in a singleton class is apt. And just because you have a single instance of the class does not mean you are also limited on the number of references to that instance. You can queue calls(to avoid threading issues) to the instance and effect changes necessary. Yes, the general form of a singleton is a globally public one, you can certainly modify the design to create a more access restricted singleton. I haven't tired this before but I sure know it is possible. And to all those who commented saying the singleton pattern is utterly evil you should know this: yes it is evil if you do not use it properly or within it confines of effective functionality and predictable behavior: do not GENERALIZE.

    0 讨论(0)
  • 2020-11-22 05:18

    I think this is the most robust version for C#:

    using System;
    using System.Collections;
    using System.Threading;
    
    namespace DoFactory.GangOfFour.Singleton.RealWorld
    {
    
      // MainApp test application
    
      class MainApp
      {
        static void Main()
        {
          LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
          LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
          LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
          LoadBalancer b4 = LoadBalancer.GetLoadBalancer();
    
          // Same instance?
          if (b1 == b2 && b2 == b3 && b3 == b4)
          {
            Console.WriteLine("Same instance\n");
          }
    
          // All are the same instance -- use b1 arbitrarily
          // Load balance 15 server requests
          for (int i = 0; i < 15; i++)
          {
            Console.WriteLine(b1.Server);
          }
    
          // Wait for user
          Console.Read();    
        }
      }
    
      // "Singleton"
    
      class LoadBalancer
      {
        private static LoadBalancer instance;
        private ArrayList servers = new ArrayList();
    
        private Random random = new Random();
    
        // Lock synchronization object
        private static object syncLock = new object();
    
        // Constructor (protected)
        protected LoadBalancer()
        {
          // List of available servers
          servers.Add("ServerI");
          servers.Add("ServerII");
          servers.Add("ServerIII");
          servers.Add("ServerIV");
          servers.Add("ServerV");
        }
    
        public static LoadBalancer GetLoadBalancer()
        {
          // Support multithreaded applications through
          // 'Double checked locking' pattern which (once
          // the instance exists) avoids locking each
          // time the method is invoked
          if (instance == null)
          {
            lock (syncLock)
            {
              if (instance == null)
              {
                instance = new LoadBalancer();
              }
            }
          }
    
          return instance;
        }
    
        // Simple, but effective random load balancer
    
        public string Server
        {
          get
          {
            int r = random.Next(servers.Count);
            return servers[r].ToString();
          }
        }
      }
    }
    

    Here is the .NET-optimised version:

    using System;
    using System.Collections;
    
    namespace DoFactory.GangOfFour.Singleton.NETOptimized
    {
    
      // MainApp test application
    
      class MainApp
      {
    
        static void Main()
        {
          LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
          LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
          LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
          LoadBalancer b4 = LoadBalancer.GetLoadBalancer();
    
          // Confirm these are the same instance
          if (b1 == b2 && b2 == b3 && b3 == b4)
          {
            Console.WriteLine("Same instance\n");
          }
    
          // All are the same instance -- use b1 arbitrarily
          // Load balance 15 requests for a server
          for (int i = 0; i < 15; i++)
          {
            Console.WriteLine(b1.Server);
          }
    
          // Wait for user
          Console.Read();    
        }
      }
    
      // Singleton
    
      sealed class LoadBalancer
      {
        // Static members are lazily initialized.
        // .NET guarantees thread safety for static initialization
        private static readonly LoadBalancer instance =
          new LoadBalancer();
    
        private ArrayList servers = new ArrayList();
        private Random random = new Random();
    
        // Note: constructor is private.
        private LoadBalancer()
        {
          // List of available servers
          servers.Add("ServerI");
          servers.Add("ServerII");
          servers.Add("ServerIII");
          servers.Add("ServerIV");
          servers.Add("ServerV");
        }
    
        public static LoadBalancer GetLoadBalancer()
        {
          return instance;
        }
    
        // Simple, but effective load balancer
        public string Server
        {
          get
          {
            int r = random.Next(servers.Count);
            return servers[r].ToString();
          }
        }
      }
    }
    

    You can find this pattern at dotfactory.com.

    0 讨论(0)
  • 2020-11-22 05:21

    I find them useful when I have a class that encapsulates a lot of memory. For example in a recent game I've been working on I have an influence map class that contains a collection of very large arrays of contiguous memory. I want that all allocated at startup, all freed at shutdown and I definitely want only one copy of it. I also have to access it from many places. I find the singleton pattern to be very useful in this case.

    I'm sure there are other solutions but I find this one very useful and easy to implement.

    0 讨论(0)
  • 2020-11-22 05:22

    The problem with singletons is not their implementation. It is that they conflate two different concepts, neither of which is obviously desirable.

    1) Singletons provide a global access mechanism to an object. Although they might be marginally more threadsafe or marginally more reliable in languages without a well-defined initialization order, this usage is still the moral equivalent of a global variable. It's a global variable dressed up in some awkward syntax (foo::get_instance() instead of g_foo, say), but it serves the exact same purpose (a single object accessible across the entire program) and has the exact same drawbacks.

    2) Singletons prevent multiple instantiations of a class. It's rare, IME, that this kind of feature should be baked into a class. It's normally a much more contextual thing; a lot of the things that are regarded as one-and-only-one are really just happens-to-be-only-one. IMO a more appropriate solution is to just create only one instance--until you realize that you need more than one instance.

    0 讨论(0)
  • 2020-11-22 05:22

    The real downfall of Singletons is that they break inheritance. You can't derive a new class to give you extended functionality unless you have access to the code where the Singleton is referenced. So, beyond the fact the the Singleton will make your code tightly coupled (fixable by a Strategy Pattern ... aka Dependency Injection) it will also prevent you from closing off sections of the code from revision (shared libraries).

    So even the examples of loggers or thread pools are invalid and should be replaced by Strategies.

    0 讨论(0)
  • 2020-11-22 05:22

    The Meyers singleton pattern works well enough most of the time, and on the occasions it does it doesn't necessarily pay to look for anything better. As long as the constructor will never throw and there are no dependencies between singletons.

    A singleton is an implementation for a globally-accessible object (GAO from now on) although not all GAOs are singletons.

    Loggers themselves should not be singletons but the means to log should ideally be globally-accessible, to decouple where the log message is being generated from where or how it gets logged.

    Lazy-loading / lazy evaluation is a different concept and singleton usually implements that too. It comes with a lot of its own issues, in particular thread-safety and issues if it fails with exceptions such that what seemed like a good idea at the time turns out to be not so great after all. (A bit like COW implementation in strings).

    With that in mind, GOAs can be initialised like this:

    namespace {
    
    T1 * pt1 = NULL;
    T2 * pt2 = NULL;
    T3 * pt3 = NULL;
    T4 * pt4 = NULL;
    
    }
    
    int main( int argc, char* argv[])
    {
       T1 t1(args1);
       T2 t2(args2);
       T3 t3(args3);
       T4 t4(args4);
    
       pt1 = &t1;
       pt2 = &t2;
       pt3 = &t3;
       pt4 = &t4;
    
       dostuff();
    
    }
    
    T1& getT1()
    {
       return *pt1;
    }
    
    T2& getT2()
    {
       return *pt2;
    }
    
    T3& getT3()
    {
      return *pt3;
    }
    
    T4& getT4()
    {
      return *pt4;
    }
    

    It does not need to be done as crudely as that, and clearly in a loaded library that contains objects you probably want some other mechanism to manage their lifetime. (Put them in an object that you get when you load the library).

    As for when I use singletons? I used them for 2 things - A singleton table that indicates what libraries have been loaded with dlopen - A message handler that loggers can subscribe to and that you can send messages to. Required specifically for signal handlers.

    0 讨论(0)
提交回复
热议问题