我需要解释一下为什么我不使用静态方法/属性。例如,
String s=String.Empty;
这个属性(属于.Net框架)是否有误?应该是这样的吗?
String s= new EmptySting();
或者
IEmptyStringFactory factory=new EmptyStringFactory();
String s= factory.Create();
为什么每次想使用空字符串时都要创建一个新对象呢?基本上,空字符串是一个单例对象。
正如威尔所说,静态变量在测试方面确实可能会有问题,但这并不意味着您应该在所有地方都使用静态变量。
(个人而言,我更喜欢使用""而不是string.Empty
,但这是一个已经被讨论过的话题。)
我认为使用静态内容最糟糕的一点是可能导致类之间紧密耦合。例如在 System.Web.Abstractions 推出前使用 ASP.NET 时,这样会使所有类变得难以测试,可能会更容易引起系统范围内的错误。
你的三个不同示例的语义非常不同。我会尝试按照我的实践方式进行分解。
String s=String.Empty;
这是一个单例模式。当你希望确保某些东西只有一个实例时,可以使用它。在这种情况下,由于字符串是不可变的,只需要一个“空”字符串即可。不要滥用单例模式,因为它们很难测试。但是当它们有意义时,它们非常强大。
String s= new EmptySting();
这是您的标准构造函数。尽可能使用此构造函数。只有当单例模式的情况非常压倒性时,才需要重构为单例模式。在string.Empty
的情况下,使用单例非常合理,因为字符串的状态不能被引用类更改。
IEmptyStringFactory factory=new EmptyStringFactory();
String s= factory.Create();
StringFactory.CreateEmpty()
或 StringFactory.Create("foo")
)。然而,在字符串的情况下,构造足够简单,使用工厂就会强行解决没有问题的问题。String.Empty
来说,它更像是一个常量(类似于 Math.PI
或者 Math.E
),是为该类型定义的。创建一个子类来表示一个特定值通常是不好的。Int.TryParse
)。Math
全都是静态的,你无法这样做,而必须每次都进行转换。如果 Math
是基于实例的,你可以创建一个新的 Math
对象,该对象默认为你想要的弧度或角度,并且仍然可以具有典型行为的静态属性。Math mD = new Math(AngleMode.Degrees); // ooooh, use one with degrees instead
double x = mD.Sin(angleInDegrees);
double x = Math.Sin(angleInDegrees * Math.PI / 180);
一般来说,静态的目的是确保程序中只有一个静态“东西”的实例。
当您知道创建的“东西”在程序的生命周期内永远不会改变时,静态非常有用。例如,在您的示例中,System.String
定义了一个私有静态字段来存储空字符串,该字符串仅分配一次,并通过静态属性公开。
正如提到的那样,静态存在测试问题。例如,很难模拟静态类型,因为它们无法实例化或派生。还很难将模拟引入某些静态方法,因为它们使用的字段也必须是静态的。(您可以使用静态setter属性来解决此问题,但我个人尽量避免这样做,因为它通常会破坏封装性)。
大多数情况下,使用静态是可以的。您需要根据程序的复杂性决定何时进行使用静态和实例实体的权衡。
使用静态方法有以下缺点:
使用静态方法的原因
从面向场景的设计角度,选择静态方法还是实例方法的标准应该是:如果可以在不创建类的实例的情况下调用方法,则将其设置为静态方法。否则,请将其设置为实例方法。第一种选项使调用成为一次性过程,并避免 .ctor 调用。
这里还有另一个有用的标准,即责任是否在正确的位置上。例如,您有一个帐户类。假设您需要实现货币转换功能,例如从美元到欧元。您会将它作为 Account 类的成员吗?account.ConvertTo(Currency.Euro)? 还是您要创建一个封装该责任的不同类?CurrencyConverter.Convert(account, Currency.Euro)? 对我来说,后者更好,因为它在不同类中封装了责任,而在前者中,我会将货币转换知识分散到不同的账户中。