如何决定使用C#静态方法还是非静态方法?

14

[编辑]

我的原始问题是“为什么要在静态和非静态之间做出决定?两者都可以做同样的事情...”

不幸的是,它被编辑成了一个特定于C#的问题,而我真正想避免这种情况。

因此,让我做一些补充:

当我说接口时,我并不是指C#关键字interface,而是我的理解类似于C++接口:一组定义良好的函数来操作我的对象。当我说削弱我的接口时,我的意思是我有不同的函数(静态/非静态)来执行相同的操作。当有不同的函数来执行相同的操作时,我的接口就不再定义良好了。

因此,正如Janitor Bob所发帖子的那样,我可以实现一个Validate()函数。

Document.Validate(myDocumentObject);    

但同时也

myConcreteDocumentObject.Validate();

为了回到我的Copy()示例,可以这样实现Copy()

myConcreteDocument.Copy(toPath);

但同时也

Document.Copy(myConcreteDocumentObject, toPath)
或者
Document.Copy(fromPath, toPath)

当我想到包含所有属于我的文档的文件的文件夹时(在这种情况下,我不依赖于具体的实例 - 但我依赖于其他事物 :))。

一般来说,我谈论的是静态方法而不是静态类(如果我忘记提到,抱歉)。

但正如Anton Gogolev所说,我认为我的Document类不是一个好的例子,也没有设计得很好,所以我认为我需要看一下单一职责原则。

我还可以实现某种操作我的DocumentClass的ManagerClass:

例如:

myDocumentManagerObject.Copy(myConcreteDocumentObject, toPath);
或者
myDocumentManagerObject.Copy(myConcreteDocumentObject, toPath);

在一开始,这似乎是一个非常基础的问题,例如"何时使用静态方法,何时不使用",但这是我偶尔会遇到的问题(我很难描述实际问题是什么;也许只是想知道为什么(不)使用1)或者为什么(不)使用2))。

(虽然我正在使用C#语法,但这不是一个受限于C#的问题。)

在面向对象编程中,有两种方法(除其他方法外)可以处理对象:

1)如果我希望我的对象执行某项任务,我只需告诉它执行即可:

但是,如果我参考方法1),我倾向于创建能够自己执行任务的对象,而不是由其他对象(DocumentManager)执行操作,针对我的DocumentObject执行某些操作。

(我希望这不会引起关于OOP的宗教性讨论;)

[/编辑]


myConcreteObject.DoSomething();

这就像是在与一个对象交谈。

2) 或者如果你喜欢静态方法:

ObjectClass.JustDoIt();

在某种程度上,我认为静态函数会更"顺畅"。因此,我经常使用静态方法(独立于具体实例 - 独立性总是好的)。因此,在设计一个类时,我经常需要决定采取1)或2)的方法:

假设您有一个表示应保存到数据库中的文档的类“Document”:

一个文档

  • 由来自文件系统的一个或多个图像文件组成(这些文件成为单个文档页面)
  • 具有类似于参考文献的东西 - 用户可以添加有关文档的信息的字段 - 这些信息将保存到额外的文件中
  • 并且应该有一些操作,例如Copy(),AddPage(),RemovePage()等。

现在我面临着几种创建这个类的方式:

//----- 1) non static approach/talking to objects -----
Document newDocument = new Document();

// Copy document to x (another database, for example)
newDocument.Copy(toPath);

我喜欢这个:我告诉文档将自己复制到数据库x中,对象自己完成了复制过程。很不错。

//----- 2) static approach ----------------------------
Document.Copy(myDocumentObject, toPath);

为什么不呢?很不错,感觉非常方便...

那么,要实现哪个呢?都实现吗?还是把静态方法放到某种辅助类中?或者选择第一种方法并坚持使用它以不削弱我的Document类的接口?

当考虑两种方法时,我得出结论(理论上)可以将任何函数实现为静态函数:

Class.Function(aConcreteClassObject, parameters);

但也不是静态的:

aConcreteObject.DoSomething(parameters);

举个现实世界的例子:

[编辑(添加了参数fromPath“抱歉,我忘记了”)]

//----- 2) static approach ----------------------------
File.Copy(fromPath, toPath);    // .Net-Framework-like

但也:

[/EDIT]

//----- 1) non static approach ------------------------
ExampeFileClass fileObject = new ExampleFileClass();
fileObject.Copy(toPath);

或者甚至(有点面向对象过度):

//----- 1) non static approach, too -------------------
fileObject.ToPath = @"C:\Test\file.txt";     // property of fileObject
fileObject.Copy();                           // copy to toPath
所以,为什么要(不)使用1),或者为什么要(不)使用2)?(我不会过多关注Document类示例,因为它更多是有关良好类设计的一般问题。)

1
File.Copy(toPath)- 类似于.Net Framework?请记住,File是一个特定的类,适用于文件。尝试查找System.Data,例如,并检查存在的所有不同IDbConnection实现(OdbcConnection,SqlConnection,OracleConnection等)。 那是“.Net Framework like”,而您无法使用静态方法获得该效果。 - vgru
11个回答

16

我们开始吧。

首先:

因此,我经常使用静态方法(独立于具体实例 - 独立性总是一件好事)。

相反,使用静态方法会使您非常依赖具体实例。

就您的Document而言,我不会选择任何一种方式。您列出了Document类的所有职责,包括数据聚合,将自身保存到数据库以及页面操作和复制。

这太多了。根据SRP,每个“模块”(此处“模块”用作通用术语)应该只有一个更改原因。您的Document有很多职责,因此它有大量的更改原因。这不好。

考虑到这一点,我会将所有逻辑移动到具有严格定义的职责的其他类中。我认为,Herb Sutter或Andrei Alexandrescu引入了一个更或多或少被接受的标准,即:通过其公共契约可以使用对象执行的所有操作(考虑方法)都应移至相关对象之外。



2
你是说一个类不应该有公共方法吗? - Svish
但是当他移动它们时,这些方法应该是静态的吗? - cgp
1
绝对不行。我的意思是,如果某个操作只能通过该类的公共契约来实现,那么这个操作就不应该成为该类的一部分。 - Anton Gogolev
@altCognito 这取决于情况。我不太喜欢使用静态方法,因为它们会使代码耦合度过高。 - Anton Gogolev
假设我有一个多边形类Polygon,并且想要计算它的各种(很多!)属性(质心、到另一个多边形的距离等)。这些方法应该放在哪里?如果我为每种类型的计算都有一个不同的类,那么通过IntelliSense进行发现就会变得非常困难,是吗? - Joel in Gö

9

您不能使用静态方法来实现接口,也不能覆盖静态方法。因此,使用静态方法意味着您并没有进行面向对象编程。

想一想,如果只使用静态方法,您将如何实现以下功能?

interface IDocument 
{
   void Print(IDevice targetDevice);
}

IDocument instance;

instance = new PdfDocument();
instance.Print(printer);

instance = new WordDocument();
instance.Print(printer);

9

KISS。如果不需要调用构造函数,那就更好了。

此外,静态方法应该能告诉你一些关于函数操作的信息:

  • 它不会操作传递给它以外的变量。
  • 除了当方法被调用时(不包括从函数返回的内容),它不需要任何其他内存。

还有一些重要的事情需要注意:

  • 在某些情况下(Java 中),静态方法无法被覆盖/子类化,因此它们更适合于实现不需要更改的情况。
  • 有人认为静态方法本质上很难测试

我还想参考这个帖子, 以及一个简单的谷歌搜索,其实这个主题已经有很多讨论了。


你的第二个要点很好,因为依赖关系会利用内存,当然,你几乎总是有某种依赖关系。因此,你的第二个要点实际上意味着没有有趣的方法将是静态的,这感觉很正确。我不同意构造函数会增加复杂性,它只是增加了一行代码,但对象实例有助于抽象化,减少了复杂性,因此符合KISS原则。 - Simon Gibbs
这是两倍的代码量。它引入了使用实例,这导致编程中最常见的失败:取消引用空对象。如果每次需要执行xpath查询、创建哈希(某些语言)或仅仅复制文件时都必须创建一个实例,我会感到非常不舒服。 - cgp
静态方法有其适用的场合,但我同意Anton的观点,根据所提出的内容,这可能不是其中之一。 - cgp
嗯,就方法而言,这些并不是非常有趣的。 - Simon Gibbs

6

我的“规则”是:

  • 如果我不需要使用类中的属性,则将其设置为静态的。(换句话说,如果该方法与类没有真正的关联,只是为了逻辑关联而存在,请使用静态)

考虑虚方法和接口实现。但这实际上是由代码分析(“性能”)提出的建议。 - Stefan Steinegger
我需要更加精确:这仅适用于私有成员! - Stefan Steinegger
Stefan,我想你没有理解我的意思。它与altCognito相同,只是不同的词。 - Sergio

3

一般来说,如果您有以下这样的方法:

Document.Copy(myDocumentObject, toPath);

我认为最好使用非静态方法,因为第一个参数是Document,这表明它实际上是对文档的操作。


2
通常,使用面向对象的思维方式进行编程时,您将希望避免使用静态方法。在面向对象编程中,想法是将所有内容表示为对象,并为每个对象提供清晰的一组功能,以表示其核心抽象。静态方法“破坏”了这种抽象。
您提到的具有复制方法的Document类的示例是一个典型的例子。我认为正确的面向对象实现是第一种方法。也就是说,像这样将复制作为实例方法:
document1.copy(toPath)

将自我复制的能力作为文档核心抽象的一部分是有道理的。这样,发送复制消息的客户端代码只需要指定要复制到哪里,因为可以理解文档会跟踪其内部位置。没有必要在其他地方复制该信息,这是您提出的第三个选项的主要问题,如下所示:

Document.copy(fromPath, toPath)

1

静态方法非常有用,我喜欢扩展方法,但它们会强制耦合,如果使用不当可能会使测试成为一场噩梦!

使用静态方法的好例子是在进行验证时。

public static errors Validate(Document myDoc)
{
..some validation code
}

这是非常可测试的,紧密耦合方法与对象并不重要。使用静态方法的一个糟糕场景是它除了返回值之外还做其他事情,例如在业务层验证对象并将数据保存到数据库中。

public static errors ValidateAndSave(Document myDoc)
{
    errors docErrors = Validate(myDoc);
    if(docErrors.count==0)
    {
         docErrors = SaveToDB(myDoc);
    }

   return docErrors; 
} 

这是一个真正的痛点,因为每次运行并通过验证后,您将数据传递到数据库,您的业务逻辑可能不会生成错误,但是您的DAL层可能会。因此,您不仅需要测试Biz层的功能,还需要测试DAL层,而且您紧密地耦合了对象、Biz层和Dal层,使得这非常难以测试和维护。


1

如果你必须问,就不要使用静态。

实际的经验法则是(有很多真正的技术原因,但我发现这有助于解释概念):

  • 如果所讨论的类可以存在多次,则它不是静态的。

  • 如果所讨论的方法针对实例信息进行操作,则它不是静态的。

  • 如果所讨论的方法或类涉及元信息,则它是静态的。

根据这些准则,文件和文档是多个的,复制是针对实例的操作。该方法不应为静态的。


0
一般来说,就对象而言,“复制”自己通常意味着将自己的数据克隆到一个新对象中。这里描述的“复制”是文件系统代表您执行的操作,而不是对象。因此,我会将其作为静态方法而不是文档实例的方法。

0

和altCoginto一样,我会添加fileObject.Copy,每个人都会使用它,而不仅仅是fileObject对象。静态函数与类具有理想的关系,并且不依赖于它的功能依赖。


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