我创建的单例有什么问题?

4
我创建了一个类,允许全局访问变量,同时只创建一次,实质上是单例模式。然而,它与任何“正确”实现单例模式的方式都不匹配。我认为这是因为它存在某些问题,但除了缺少延迟初始化之外,我看不出有什么问题。您有什么想法吗?
static class DefaultFields
{
    private static readonly string IniPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "defaultFields.ini");
    private static readonly IniConfigSource Ini = GetIni();               

    /// <summary>
    /// Creates a reference to the ini file on startup
    /// </summary>
    private static IniConfigSource GetIni()
    {
        // Create Ini File if it does not exist
        if (!File.Exists(IniPath))
        {
            using (FileStream stream = new FileStream(IniPath, FileMode.CreateNew))
            {
                var iniConfig = new IniConfigSource(stream);
                iniConfig.AddConfig("default");
                iniConfig.Save(IniPath);
            }
        }

        var source = new IniConfigSource(IniPath);
        return source;
    }

    public static IConfig Get()
    {
        return Ini.Configs["default"];
    }

    public static void Remove(string key)
    {
        Get().Remove(key);
        Ini.Save();
    }

    public static void Set(string key, string value)
    {
        Get().Set(key, value ?? "");
        Ini.Save();
    }
}

请参考以下链接:https://dev59.com/wnA65IYBdhLWcg3w-z1A - Numenor
8个回答

5

这个类不遵循通常的单例模式,因为它是静态的,只控制对静态变量的访问。

而单例通常是一个静态的类的单一实例,其中唯一的静态函数是用于创建和访问存储为普通非静态成员变量的单例。

这意味着该类可以很容易地被更改或多次实例化,但您的类不能。


我试图避免在几个不同的地方声明“IniConfigSource”,所以我只在一个类中声明了它,然后就只用那个类了。我的目标是能够只使用Default.Get()等,而不必一遍又一遍地定义这些变量。我理解静态类等通常的工作方式,只是对静态变量的工作方式感到困惑,并且认为我所做的必须是单例模式的一种类型。既然它不是单例模式,我也没有特别想要将其变成单例模式,我想现在这样就可以了,除了像“rwmnau”建议的添加一些锁定。 - John

4
您对单例模式的理解是正确的,它是一个提供全局访问的具有唯一实例的类。
它可能看起来像静态类,但通常以不同的方式实现。
此模式应该谨慎使用,因为一旦深入代码中,很难重构出单例。主要用于硬件限制或实现工厂的唯一访问点时。如果可能,应尽量避免使用它。
以下是实现示例:
public class A
{
    /// <summary>
    /// Unique instance to access to object A
    /// </summary>
    public static readonly A Singleton = new A();

    /// <summary>
    /// private constructor so it can only be created internally.
    /// </summary>
    private A()
    {
    }

    /// <summary>
    /// Instance method B does B..
    /// </summary>
    public void B()
    {
    }
}

这可以像下面这样使用:

A.Singleton.B()

希望能帮到你。


2
你的类中所有的方法都是静态的,因此你隐藏了单个实例对用户的可见性。使用单例模式,可以通过一个公共属性来暴露单个实例,通常这个属性被称为Instance(在其他语言中如Java可能会被称为getInstance或类似的方法)。

alt text

你的代码没有错 - 只是不符合单例模式。如果你想要实现一个单例,我建议阅读Jon Skeet的文章 C#中实现单例模式

2
我看到的最大问题是您在写入INI文件时没有进行任何SyncLock操作 - 多个线程同时尝试写入值可能会导致不可预测的结果,例如两个线程都进行了写入但只有一个得到持久化(或多个线程同时尝试写入文件,导致IO错误)。
我建议创建一个私有的“锁定”对象,并将对文件的写入包装在SyncLock中,以确保一次只有一个线程能够更改值(或者至少提交更改到INI文件)。

我同意,缺乏锁定很可能是一个问题。IConfig 实现在其 RemoveSet 方法中执行锁定是有可能的,但由于此代码不知道该实现是什么,因此我认为 DefaultFields 类应该自行执行锁定。 - Dr. Wily's Apprentice

1

我也对这个问题的答案感兴趣。 我认为,有大量使用延迟实例化的单例示例,但你必须根据情况询问自己是否真的需要。

虽然本文涉及Java,但概念仍应适用。 这提供了不同单例实现的许多示例。http://www.shaunabram.com/singleton-implementations/

我还看到过很多关于书籍“Effective Java”的引用,第71项 - 明智地使用延迟实例化。 基本上,除非你需要,否则不要这样做。


0

为什么Ini字段要使用readonly关键字?

但如果你想实现单例模式,代码会是这个样子:

static DefaultFields
{
    private readonly string IniPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "defaultFields.ini");
    private readonly IniConfigSource Ini = GetIni();               

    private static DefaultFields _default;

    public static DefaultFields Default 
    { 
        get { if(this._default == null){ this._default = new DefaultFields(); } return this._default; } 
    }

    private DefaultFields()
    {

    }

    /// <summary>
    /// Creates a reference to the ini file on startup
    /// </summary>
    private IniConfigSource GetIni()
    {
        // Create Ini File if it does not exist
        if (!File.Exists(IniPath))
        {
            using (FileStream stream = new FileStream(IniPath, FileMode.CreateNew))
            {
                var iniConfig = new IniConfigSource(stream);
                iniConfig.AddConfig("default");
                iniConfig.Save(IniPath);
            }
        }

        var source = new IniConfigSource(IniPath);
        return source;
    }

    public IConfig Get()
    {
        return Ini.Configs["default"];
    }

    public void Remove(string key)
    {
        Get().Remove(key);
        Ini.Save();
    }

    public void Set(string key, string value)
    {
        Get().Set(key, value ?? "");
        Ini.Save();
    }
}

0

这并不是真正的单例模式,而是一个静态类。

在许多方面,静态类与单例模式相似。但是,静态类无法实现接口,无法从基类继承功能,并且您无法携带对它们的引用。


0

对于单例类来说,惰性初始化非常重要。通过将类声明为静态类,您实现了一个静态类,而不是单例类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接