使用Singleton避免使用静态变量

本文关键字:静态 变量 Singleton 使用 | 更新日期: 2023-09-27 18:13:26

我的一个同事告诉我,我永远不应该使用静态变量,因为如果你在一个地方改变它们,它们就会在所有地方改变。他告诉我,我应该使用Singleton而不是静态变量。我知道Singleton是为了将一个类的实例数量限制为一个。Singleton如何帮助我处理静态变量?

使用Singleton避免使用静态变量

让我们一次解决一个问题:

我的一个同事告诉我,我永远不应该使用静态变量,因为如果你在一个地方改变了它们,它们就会在其他地方改变。

很明显,你的同事指的是静态变量的基本特征:静态变量只有一个实例。无论创建多少个类的实例,对静态变量的任何访问都是对相同的变量的访问。每个实例没有单独的变量。

他告诉我应该使用Singleton而不是静态变量。

这不是一个好的全局建议。静态变量和单例并不相互竞争,也不是真正的相互替代。单例是一个类的实例,以一种只能创建一个实例的方式进行管理。类似地,静态变量与类的一个(静态)实例绑定在一起,但不仅可以为类实例分配,还可以为任何数据类型(如标量)分配。实际上,要有效地使用单例模式,必须将其存储在静态变量中。

不能"使用单例"而不是静态变量。

另一方面,也许他的意思略有不同:也许他试图说,你的静态类不应该有许多不同的静态变量、方法、属性和字段(合起来就是成员),就像它们是一个类一样,你应该让这些字段变成非静态的,然后将包装类公开为一个单例实例。您仍然需要一个私有的静态字段和一个方法或属性(或者可能只是使用一个get-only属性)来公开单例。

我知道Singleton是为了将一个类的实例数量限制为一个。Singleton如何帮助我处理静态变量?

静态类的变量和单例类的相似之处在于它们都是一次实例化(前者由编译器强制,后者由实现强制)。在类内部使用单例而不是静态变量的原因是,单例需要是类的真正实例,而不是简单地由静态类的收集的静态成员组成。然后将该单例分配给一个静态变量,以便所有调用者都可以获得同一实例的副本。如上所述,可以将静态类的所有不同静态成员转换为新非静态类的实例成员,并将其公开为单例。

我还想提一下,到目前为止给出的其他答案都有线程安全问题。下面是一些管理singleton的正确模式。

下面,您可以看到Singleton类的实例,它具有实例(或非静态)成员,通过静态初始化或在静态构造函数中创建,并将其赋值给变量_singleton。我们使用这种模式来确保只实例化一次。然后,静态方法Instance提供对后备字段变量的只读访问,该变量包含我们的一个且只有一个Singleton实例。

public class Singleton {
   // static members
   private static readonly Singleton _singleton = new Singleton();
   public static Singleton Instance => _singleton
   // instance members
   private Singleton() { } // private so no one else can accidentally create an instance
   public string Gorp { get; set; }
}

或者,完全相同的东西,但有一个显式的静态构造函数:

public class Singleton {
   // static members
   private static readonly Singleton _singleton; // instead of here, you can...
   static Singleton() {
      _singleton = new Singleton(); // do it here
   }
   public static Singleton Instance => _singleton;
   // instance members
   private Singleton() { } // private so no one else can accidentally create an instance
   public string Gorp { get; set; }
}

您还可以使用没有显式支持字段的默认属性(如下所示),或者在静态构造函数中分配仅get的属性(未显示)。

public class Singleton {
   // static members
   public static Singleton Instance { get; } = new Singleton();
   // instance members
   private Singleton() { } // private so no one else can accidentally create an instance
   public string Gorp { get; set; }
}

由于静态构造函数保证只运行一次,无论是隐式的还是显式的,因此没有线程安全问题。注意Singleton类的任何访问都可以触发静态初始化,甚至是反射类型的访问。

可以把类的静态成员看作是一个独立的(虽然是连接的)类:

  1. 实例(非静态)成员的功能与普通类类似。除非你对它们执行new Class(),否则它们不会活。每次执行new时,都会得到一个新的实例。实例成员可以访问所有静态成员,包括私有成员(在同一个类中)。

  2. 静态成员就像一个单独的、特殊的类实例的成员,你不能使用new显式地创建它。在这个类中,只能访问或设置静态成员。有一个隐式或显式的静态构造函数,. net在第一次访问时运行它(就像类实例一样,只是你不显式地创建它,它是在需要时创建的)。类的静态成员可以被任何其他类在实例内或实例外随时访问,但要尊重internalprivate等访问修饰符。

EDIT @ErikE的回应是正确的方法。

为了线程安全,该字段应该这样初始化:

private static readonly Singleton instance = new Singleton();

使用单例的一种方法(取自http://msdn.microsoft.com/en-us/library/ff650316.aspx)

using System;
public class Singleton
{
   private static Singleton instance;
   private Singleton() {}
   public static Singleton Instance
   {
      get 
      {
         if (instance == null)
         {
            instance = new Singleton();
         }
         return instance;
      }
   }
   /// non-static members
   public string Foo { get; set; }        
   
}

,

var foo = Singleton.Instance.Foo;
Singleton.Instance.Foo = "Potential thread collision here.";

注意实例成员一个静态字段。你不能在不使用静态变量的情况下实现单例,而且(我似乎记得——已经有一段时间了)这个实例将在所有请求中共享。因此,它本质上不是线程安全的。

相反,可以考虑将这些值放在数据库或其他对线程更友好的持久存储中,并创建一个与数据库的这部分接口的类,以提供透明的访问。

public static class Foo
{
  public static string Bar
  {
    get { /// retrieve Bar from the db }
    set { /// update Bar in the db }
  }
}

static修饰符的全部意义在于确保被这样修改的对象无论在何处使用都是相同的,因为它不需要实例化。这个名字最初是作为一个静态变量而出现的,它在内存中有一个固定的位置,所有引用它的项都会引用相同的内存位置。

现在您可能希望在类中使用静态字段,在这种情况下,它在类实例化(构造)之前就存在。在某些情况下,您可能需要这样做。

单例是另一回事。它是一个通过使用私有构造函数和static属性被限制为单个实例化的类。所以在这方面,你仍然不能通过使用单例来避免静态。

回答上述问题:

创建一个没有静态字段的单例是非常愚蠢的(但也是可能的)。

要做到这一点,你需要使用别人的静态字段,如AppDomain.GetData或(在ASP.Net中)HttpContext.Application .

只是回答你的问题(我希望):而不是使用包含静态成员的静态类,如Class1,你可以实现像Class2一样的Singleton模式(请不要在此时开始讨论延迟初始化):

public static class Class1
{
    public static void DoSomething ()
    {
    }
}
public static class Class2
{
    private Class2() {
    }
    private Class2 instance;
    public Class2 GetInstance(){
        if (instance == null)
            instance = new Class2();
        return instance;
    }
    public void DoSomething ()
    {
    }
}

可以使用Class2.GetInstance().DoSomething()代替调用Class1.DoSomething()

编辑:正如你所看到的,Class2内部仍然有一个(私有)静态字段保存它的实例。

Edit2回复user966638的评论:我的理解是否正确,你有这样的代码:

public class Foo {
    private static Bar bar;
}

你的同事建议用这个代替它?

public class Foo {
    private BarSingleton bar;
}

如果你想要有不同的Foo实例,其中每个实例的bar属性可以设置为空,例如,这可能是这种情况。但是我不确定他的意思,他所说的用例到底是什么。

单例变量和静态变量都给你一个类的实例。为什么你应该选择单例而不是静态

  1. 使用Singleton,你可以自己管理实例的生命周期,这是你想要的
  2. 使用Singleton,您可以更好地控制实例的初始化。这在初始化一个类的实例是一件复杂的事情时很有用。
  3. 使静态变量线程安全是一项挑战,使用单例,这项任务变得非常容易

希望能有所帮助