静态类和单例模式之间存在什么真正的(即实际的)区别?
两者都可以在没有实例化的情况下调用,都只提供一个“实例”,而且它们都不是线程安全的。还有其他区别吗?
getInstance()
方法(尽管在大多数情况下可能是 it doesn't matter )。
singleton
对象,其中 static
方法只是函数,是一个非 OO 实体。
是什么让您说单例或静态方法不是线程安全的?通常两者都应该实现为线程安全的。
单例和一堆静态方法之间的最大区别在于单例可以实现接口(或从有用的基类派生,尽管根据我的经验这不太常见),因此您可以传递单例,就好像它“只是另一个“ 执行。
真正的答案是 Jon Skeet,on another forum here。
单例允许访问单个创建的实例——该实例(或者更确切地说,对该实例的引用)可以作为参数传递给其他方法,并被视为普通对象。静态类只允许静态方法。
单例对象存储在堆中,而静态对象存储在堆栈中。我们可以克隆(如果设计者没有禁止的话)单例对象,但我们不能克隆静态类对象。单例类遵循 OOP(面向对象原则),静态类不遵循。我们可以使用 Singleton 类实现接口,但类的静态方法(或例如 C# 静态类)不能。
单例模式与静态类相比有几个优点。首先,单例可以扩展类和实现接口,而静态类不能(它可以扩展类,但不继承它们的实例成员)。单例可以延迟或异步初始化,而静态类通常在首次加载时进行初始化,从而导致潜在的类加载器问题。然而,最重要的优点是可以多态地处理单例,而不会强迫用户假设只有一个实例。
static
类不适用于任何需要状态的东西。将一堆函数放在一起很有用,即 Math
(或项目中的 Utils
)。所以类名只是给了我们一个线索,我们可以在哪里找到函数,仅此而已。
Singleton
是我最喜欢的模式,我用它来一次性管理一些事情。它比 static
类更灵活,并且可以维护它的状态。它可以实现接口,从其他类继承并允许继承。
我在 static
和 singleton
之间选择的规则:
如果有一堆函数应该放在一起,那么 static
是选择。其他任何需要对某些资源进行单一访问的东西都可以作为 singleton
来实现。
State
是对象的不同属性的组合,这些属性通常会随着时间而变化。你可以谷歌正式定义。
静态类:-
您不能创建静态类的实例。加载包含该类的程序或命名空间时,由 .NET Framework 公共语言运行时 (CLR) 自动加载。我们不能将静态类传递给方法。我们不能将静态类继承到 C# 中的另一个静态类。具有所有静态方法的类。更好的性能(静态方法在编译时绑定)
单身人士:-
您可以创建对象的一个实例并重复使用它。 Singleton 实例在用户请求时首次创建。您可以创建单例类的对象并将其传递给方法。单例类没有说任何继承限制。我们可以处理单例类的对象,但不能处理静态类的对象。方法可以被覆盖。需要时可以延迟加载(总是加载静态类)。我们可以实现接口(静态类不能实现接口)。
静态类是只有静态方法的类,更好的词是“函数”。静态类中体现的设计风格是纯程序化的。
另一方面,Singleton 是一种特定于 OO 设计的模式。它是一个对象的实例(具有其中固有的所有可能性,例如多态性),具有一个创建过程,可确保该特定角色在其整个生命周期内只有一个实例。
在单例模式中,您可以将单例创建为派生类型的实例,而使用静态类则无法做到这一点。
快速示例:
if( useD3D )
IRenderer::instance = new D3DRenderer
else
IRenderer::instance = new OpenGLRenderer
在 Jon Skeet's Answer 上展开
单例和一堆静态方法之间的最大区别在于,单例可以实现接口(或派生自有用的基类,尽管这在 IME 中不太常见),因此您可以传递单例,就好像它是“只是另一个”实现一样。
在对类进行单元测试时,单例更容易使用。无论您在何处将单例作为参数(构造函数、设置器或方法)传递,您都可以替换为单例的模拟或存根版本。
MySingleton mockOfMySingleton = mock(MySingleton.class)
的示例。
new ClazzToTest(mockSingleton);
静态类
具有所有静态方法的类。
更好的性能(静态方法在编译时绑定)
不能覆盖方法,但可以使用方法隐藏。 (Java 中隐藏的方法是什么?甚至 JavaDoc 的解释都令人困惑) public class Animal { public static void foo() { System.out.println("Animal"); } } public class Cat extends Animal { public static void foo() { // 隐藏 Animal.foo() System.out.println("Cat"); } }
辛格尔顿
只能实例化一次的对象。
方法可以被覆盖(为什么 Java 不允许覆盖静态方法?)
比静态方法更容易模拟
更善于维持状态
总之,我只会使用静态类来保存 util 方法,而将 Singleton 用于其他所有内容。
编辑
静态类也是延迟加载的。谢谢@jmoreno(静态类初始化什么时候发生?)
静态类的方法隐藏。谢谢@MaxPeng。
Animal animal = new Cat();
那么 animal.foo();
会发生什么?
单例的另一个优点是它可以很容易地序列化,如果您需要将其状态保存到磁盘或将其远程发送到某个地方,这可能是必要的。
我不是一个伟大的 OO 理论家,但据我所知,我认为与 Singleton 相比,静态类唯一缺乏的 OO 特性是多态性。但是如果你不需要它,你当然可以使用静态类进行继承(不确定接口实现)以及数据和函数封装。
Morendil 的评论,“体现在静态类中的设计风格纯粹是程序性的”我可能是错的,但我不同意。在静态方法中,您可以访问静态成员,这与访问其单个实例成员的单例方法完全相同。
编辑:我现在实际上在想,另一个区别是静态类在程序开始时被实例化*并存在于程序的整个生命周期中,而单例在某些时候被显式实例化并且也可以被销毁。
* 或者它可能在第一次使用时被实例化,这取决于语言,我认为。
为了说明 Jon 的观点,如果 Logger 是静态类,则无法完成下面所示的操作。类 SomeClass
期望将 ILogger
实现的实例传递到其构造函数中。
单例类对于依赖注入的可能很重要。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
var someClass = new SomeClass(Logger.GetLogger());
}
}
public class SomeClass
{
public SomeClass(ILogger MyLogger)
{
}
}
public class Logger : ILogger
{
private static Logger _logger;
private Logger() { }
public static Logger GetLogger()
{
if (_logger==null)
{
_logger = new Logger();
}
return _logger;
}
public void Log()
{
}
}
public interface ILogger
{
void Log();
}
}
单例被实例化。只是只创建了一个实例,因此 Singleton 中的单个实例。
另一方面,静态类不能被实例化。
好吧,单例只是一个被实例化的普通类,但只从客户端代码间接地实例化一次。静态类未实例化。据我所知静态方法(静态类必须有静态方法)比非静态更快。
编辑:FxCop 性能规则描述:“不访问实例数据或调用实例方法的方法可以标记为静态(在 VB 中共享)。这样做之后,编译器将向这些成员发出非虚拟调用站点,这将阻止在运行时检查确保当前对象指针不为空的每个调用。这可以为性能敏感代码带来可衡量的性能提升。在某些情况下,无法访问当前对象实例代表正确性问题。我实际上不知道这是否也适用于静态类中的静态方法。
主要区别在于:
单例有一个实例/对象,而静态类是一堆静态方法
单例可以通过接口进行扩展,而静态类则不能。
Singleton可以被继承,它支持SOLID原则中的开/关原则,另一方面静态类不能被继承,我们需要自己进行更改。
单例对象可以传递给方法,而静态类因为它没有实例,所以不能作为参数传递
与静态类的区别
JDK有单例和静态的例子,一方面java.lang.Math
是一个带有静态方法的final类,另一方面java.lang.Runtime
是一个单例类。
单例的优点
如果您需要维护状态而不是单例模式比静态类更好的选择,因为在静态类中维护状态会导致错误,尤其是在并发环境中,这可能会导致竞争条件,而没有足够的同步并行修改多个线程。
如果单例类是重对象,则可以延迟加载,但静态类没有这样的优势,并且总是急切地加载。
使用单例,您可以使用继承和多态来扩展基类、实现接口并提供不同的实现。
由于 Java 中的静态方法不能被覆盖,它们会导致不灵活。另一方面,您可以通过扩展来覆盖单例类中定义的方法。
静态类的缺点
为单例编写单元测试比静态类更容易,因为您可以在需要单例时传递模拟对象。
静态类的优点
静态类提供比单例更好的性能,因为静态方法是在编译时绑定的。
单例模式有多种实现方式,各有优缺点。
急切加载单例
双重检查锁定单例
按需初始化持有者成语
基于枚举的单例
每一个的详细描述都太冗长了,所以我只是放了一篇好文章的链接 - All you want to know about Singleton
从测试的角度来看,单例是更好的方法。与静态类不同,单例可以实现接口,您可以使用模拟实例并注入它们。
在下面的示例中,我将说明这一点。假设您有一个 isGoodPrice() 方法,它使用方法 getPrice() 并且您将 getPrice() 实现为单例中的方法。
提供 getPrice 功能的单例:
public class SupportedVersionSingelton {
private static ICalculator instance = null;
private SupportedVersionSingelton(){
}
public static ICalculator getInstance(){
if(instance == null){
instance = new SupportedVersionSingelton();
}
return instance;
}
@Override
public int getPrice() {
// calculate price logic here
return 0;
}
}
使用 getPrice:
public class Advisor {
public boolean isGoodDeal(){
boolean isGoodDeal = false;
ICalculator supportedVersion = SupportedVersionSingelton.getInstance();
int price = supportedVersion.getPrice();
// logic to determine if price is a good deal.
if(price < 5){
isGoodDeal = true;
}
return isGoodDeal;
}
}
In case you would like to test the method isGoodPrice , with mocking the getPrice() method you could do it by:
Make your singleton implement an interface and inject it.
public interface ICalculator {
int getPrice();
}
最终单例实现:
public class SupportedVersionSingelton implements ICalculator {
private static ICalculator instance = null;
private SupportedVersionSingelton(){
}
public static ICalculator getInstance(){
if(instance == null){
instance = new SupportedVersionSingelton();
}
return instance;
}
@Override
public int getPrice() {
return 0;
}
// for testing purpose
public static void setInstance(ICalculator mockObject){
if(instance != null ){
instance = mockObject;
}
测试类:
public class TestCalculation {
class SupportedVersionDouble implements ICalculator{
@Override
public int getPrice() {
return 1;
}
}
@Before
public void setUp() throws Exception {
ICalculator supportedVersionDouble = new SupportedVersionDouble();
SupportedVersionSingelton.setInstance(supportedVersionDouble);
}
@Test
public void test() {
Advisor advidor = new Advisor();
boolean isGoodDeal = advidor.isGoodDeal();
Assert.assertEquals(isGoodDeal, true);
}
}
如果我们采用静态方法来实现 getPrice() 的替代方案,则很难模拟 getPrice()。您可以使用 power mock 来模拟静态,但并非所有产品都可以使用它。
我同意这个定义:
“单个”一词意味着整个应用程序生命周期中的单个对象,因此范围在应用程序级别。静态没有任何对象指针,因此范围在应用程序域级别。此外,两者都应该实现为线程安全的。
您可以找到有关以下方面的有趣其他差异:Singleton Pattern Versus Static Class
一个显着的区别是 Singleton 附带的不同实例化。
对于静态类,它由 CLR 创建,我们无法控制它。使用单例,对象在第一个尝试访问的实例上被实例化。
延迟加载接口的支持,以便可以提供单独的实现返回派生类型的能力(作为延迟加载和接口实现的组合)
在许多情况下,这两者没有实际区别,特别是如果单例实例从不更改或更改非常缓慢,例如保持配置。
我想说最大的区别是单例仍然是普通的 Java Bean,而不是专门的仅静态 Java 类。正因为如此,单例在更多情况下被接受;它实际上是默认 Spring Framework 的实例化策略。消费者可能知道也可能不知道它是一个被传递的单例,它只是把它当作一个普通的 Java bean。如果需求发生变化,而单例需要变成原型,就像我们在 Spring 中经常看到的那样,它可以完全无缝地完成,而无需对消费者进行一行代码更改。
前面有人提到静态类应该是纯过程的,例如 java.lang.Math。在我看来,这样的类永远不应该被传递,它们不应该持有静态 final 作为属性以外的任何东西。对于其他一切,请使用单例,因为它更灵活且更易于维护。
我们有我们的数据库框架,可以连接到后端。为了避免跨多个用户的脏读,我们使用单例模式来确保我们在任何时间点都有可用的单个实例。
在 C# 中,静态类不能实现接口。当单个实例类需要为业务合同或 IoC 目的实现接口时,这就是我使用单例模式而不使用静态类的地方
Singleton 提供了一种在无状态场景中维护状态的方法
希望对你有帮助。。
在我写的一篇文章中,我描述了我对为什么单例比静态类好得多的观点:
静态类实际上不是规范类——它是一个包含函数和变量的命名空间 使用静态类不是一个好习惯,因为它违反了面向对象的编程原则 静态类不能作为其他参数传递静态类不适合“懒惰”初始化 静态类的初始化和使用总是很难跟踪 实现线程管理很困难
Singleton 类在应用程序生命周期中提供一个对象(只有一个实例)如 java.lang.Runtime 而静态类只提供静态方法如 java.lang.Math
Java 中的静态方法不能被覆盖,但是 Singleton 类中定义的方法可以通过扩展来覆盖。
单例类能够继承和多态来扩展基类,实现接口并能够提供不同的实现。而静态不是。
例如:java.lang.Runtime
,是 Java 中的 Singleton 类,调用 getRuntime()
方法返回与当前 Java 应用程序关联的运行时对象,但确保每个 JVM 仅一个实例。
一个。序列化 - 静态成员属于该类,因此不能被序列化。
湾。尽管我们已经将构造函数设为私有,静态成员变量仍然会被携带到子类中。
C。我们不能进行延迟初始化,因为所有内容都只会在类加载时加载。
从客户端的角度来看,静态行为对客户端是已知的,但单例行为可以对客户端隐藏。客户可能永远不会知道只有一个实例,他一次又一次地玩弄。
我阅读了以下内容,并认为这也很有意义:
照顾业务 请记住,最重要的 OO 规则之一是对象对自己负责。这意味着关于类生命周期的问题应该在类中处理,而不是委托给静态等语言结构。
来自《面向对象的思维过程》第 4 版一书。
我们可以创建单例类的对象并将其传递给方法。单例类没有任何继承限制。我们不能处理静态类的对象,但可以单例类。
Java 中的静态类只有静态方法。它是功能的容器。它是基于过程编程设计创建的。
单例类是面向对象设计中的一种模式。 Singleton 类在 JVM 中只有一个对象实例。这种模式以这样一种方式实现,即在 JVM 中始终只存在该类的一个实例。
Foo
,并且您有一个将Foo
作为参数的方法。通过这种设置,调用者可以选择使用单例作为实现——或者他们可以使用不同的实现。该方法与单例解耦。将其与类只有静态方法的情况进行比较——每段想要调用这些方法的代码都与类紧密耦合,因为它需要指定哪个类包含静态方法。