我可以用这种方式为所有派生的单例定义一个抽象类吗?
本文关键字:定义 一个 抽象类 单例 方式 派生 我可以 | 更新日期: 2023-09-27 18:13:46
这是我的抽象类,每次我想创建一个单例时必须派生它:
public abstract class Singleton<T> where T : Singleton<T>
{
private static readonly Lazy<T> _instance = new Lazy<T>(() =>
{
var constructor = typeof(T).GetConstructor(BindingFlags.NonPublic |
BindingFlags.Instance, null, new Type[0], null);
return (T)constructor.Invoke(null);
});
public static T Instance { get { return _instance.Value; } }
public Singleton() { }
}
因此,每次我需要遵循单例设计模式时,我可以这样写:
sealed class Server : Singleton<Server>
{
private Server() { }
...
}
这是完全正确的吗?如果不是,为什么?
编辑:- 在派生类示例中添加私有构造函数,并在抽象基类中调用。
- 重做类型参数初始化。
不,你不能,因为当你想使用new T()
时,你应该有一个公共构造函数,它不同于单例定义。因为在单例中,你应该有一个私有的构造函数,在这种情况下(公共的),每个人都可以创建你的对象的新实例,它不是一个单例
自实现单例是一种反模式。如果您只实现一个工厂,则无需继承并将类锁定到特定的表单中:
public class Server {} //Not coupled to any inheritance hierarchy.
public class Factory
{
private readonly Lazy<Server> _server = new Lazy<Server>(() => new Server());
public Server Server { get { return _server.Value; } }
}
然而,您实际上是将工厂用作服务定位器,而服务定位器也被认为是一种反模式,因为您可以轻松地使用DI将Server实例注入到您的消费类中。
public class MyServerConsumer
{
public MyServerConsumer(Server server)
{
//Do stuff.
}
}
温莎风格注册:
...
Component.For<Server>();
...
注意到这里从来没有提到过单例这个词吗?你仍然会得到"对象的单个实例",但你不必编写代码来维护这种关系,而且你的类从一开始就不会受到"单例"概念的约束和破坏
您正在使用的这种方法有一个明显的缺点。将类与Singleton类耦合。除了通常不是solid之外,您还失去了从您可能需要的类派生的能力(如果该类不是Singleton)。
最佳实践是你需要使用依赖注入和IoC容器。所有的IoC容器都允许您指定类是否为Singleton。
这样,类就完全忽略了它被实例化为Singleton的事实,并且很容易在不改变依赖关系的情况下即时更改它。
c#不能保证何时创建静态字段_instance
。这是因为c#标准简单地声明类(在IL中标记为BeforeFieldInit)可以在访问静态字段之前的任何时间初始化它们的静态字段。这意味着它们可以在第一次使用时初始化,也可以在之前的某个时间初始化,您无法确定何时初始化。
删除Lazy usage
我建议放弃Lazy
结构并引入一个静态变量来创建实例。这样您就可以利用c#标准的BeforeFieldInit
,但您将失去懒惰。虽然它在某种程度上是惰性的,但它不是在使用类型之前创建的。这很好,因为无论如何,大多数单例都是在引用时使用的。
public abstract class Singleton<T> where T : class, new()
{
private static readonly T _instance;
public static T Instance { get { return _instance; } }
public static Singleton()
{
_instance = new T();
}
}
公共部门问题
你现在的问题是new T()
结构迫使你有一个公共部门。这对单身人士来说不是很好。您可以使用通过反射调用的私有变量来解决这个问题。
这只是对pjvds回复的评论(我不能以常规方式评论,因为我没有足够的分数…)
比起通过反射使用私有构造函数,你可以拥有一个私有的init方法,并在静态singleton方法中的"new T()"之后调用它。