14

[编辑]

我最初的问题是“为什么要在静态和非静态之间做出决定?两者都做同样的......”

不幸的是,它被编辑为一个我真正想避免的 C# 特定问题。

所以,让我做一些补充:

当我说接口时,我指的不是 C#-keyword-interface,而是我理解的类似于 C++-interface 的东西:一组定义明确的函数来操作我的对象。当说弱化我的界面时,我的意思是我有不同的功能(静态/非静态)来做同样的事情。当有不同的功能来做同样的事情时,我的界面不再定义良好。

因此,正如管理员 Bob 所发布的,我可以实现一个 Validate() 函数

Document.Validate(myDocumentObject);    

但是也

myConcreteDocumentObject.Validate();

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

myConcreteDocument.Copy(toPath);

但是也

Document.Copy(myConcreteDocumentObject, toPath)

或者

Document.Copy(fromPath, toPath)

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

一般来说,我说的是静态方法而不是静态类(对不起,如果我忘了提及)。

但正如 Anton Gogolev 所说,我认为我的 Document 类不是一个很好的例子,也没有很好的设计,所以我想我必须看看 Single Responsibility Principle。

我还可以实现某种与我的 DocumentClass 一起运行的 ManagerClass:

例如:

myDocumentManagerObject.Copy(myConcreteDocumentObject, toPath);

或者

myDocumentManagerObject.Copy(myConcreteDocumentObject, toPath);

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

(我希望这不会指向关于 OOP 的宗教讨论;)。)

[/编辑]


旧版:

起初,这似乎是一个非常基本的问题,例如“何时使用静态方法,何时不使用”,但这是我不时遇到的问题(我很难描述真正的问题是什么;也许只是为了了解为什么(不)使用 1)或为什么(不)使用 2))。

(虽然我使用的是 C#-Syntax,但这不是 C# 限制的问题)

在 OOP 中,有两种处理对象的方法(其中包括):

1)如果我想让我的对象做某事,我只是告诉他这样做:

myConcreteObject.DoSomething();

这就像和一个对象说话一样。

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

ObjectClass.JustDoIt();

在某种程度上,我认为静态函数只是“感觉”更好。所以我倾向于经常使用静态方法(独立于具体实例——独立总是好事)。

因此,在设计课程时,我经常需要决定是采用方法 1) 还是方法 2):

想象一下,您有一个“文档”类,它应该代表应该保存到数据库中的文档:

一份文件

  • 由文件系统中的一个或多个图像文件组成(这些成为单个文档页面)
  • 有类似书目的东西 - 用户可以添加有关文档信息的字段 - 保存到额外的文件中
  • 并且应该有一些操作,如 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);

为什么不?也不错,手感很好。。。

那么,实施哪一个呢?两个都?或者将静态方法放在一种帮助类中?还是选择方法 1) 并坚持不削弱我的 Document-class 的接口?

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

Class.Function(aConcreteClassObject, parameters);

但也是非静态的:

aConcreteObject.DoSomething(parameters);

举一个真实的例子:

[编辑(从路径添加参数“对不起,我忘记了”)]

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

[/编辑]

但是也:

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

甚至(有点像 OOP-Overkill):

//----- 1) non static approach, too -------------------
fileObject.ToPath = @"C:\Test\file.txt";     // property of fileObject
fileObject.Copy();                           // copy to toPath

那么,为什么(不)使用 1)或为什么(不)使用 2)?

(我不会过多地关注 Document 类示例,因为它更像是一个关于良好类设计的一般问题。)

4

11 回答 11

16

开始了。

首先:

所以我倾向于经常使用静态方法(独立于具体实例——独立总是好事)。

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

就你Document而言,我不会去。您已经列出了Document类的所有职责,包括数据聚合、将自身保存到数据库以及页面操作和复制。

这是太多了。根据SRP,每个“模块”(这里的“模块”用作一个包罗万象的术语)应该只有一个改变的理由。你Document有很多责任,因此它有很多改变的理由。这不好。

考虑到这一点,我会将所有逻辑移至具有严格定义职责的其他类。我相信 Herb Sutter 或 Andrei Alexandrescu 引入了一个或多或少公认的移动标准,如下:可以通过其公共合同对对象执行的所有操作(思考方法)都应该移到外部有问题的对象。


于 2009-04-28T13:58:01.077 回答
9

不能使用静态方法来实现接口,也不能覆盖静态方法。所以使用静态方法意味着你根本不做 OOP。

想想您将如何仅使用静态方法来实现以下功能?

interface IDocument 
{
   void Print(IDevice targetDevice);
}

IDocument instance;

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

instance = new WordDocument();
instance.Print(printer);
于 2009-04-28T14:02:10.817 回答
8

吻。如果您不必调用构造函数,那就更好了。

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

  • 它不对传递给它的变量之外的变量进行操作。
  • 除了调用方法时,它不需要任何内存(不计算从函数返回的内容)

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

  • 某些实例(Java)中的静态方法无法被覆盖/子类化,因此它们更适合不需要更改实现的情况。
  • 有些人会争辩说,静态方法本质上是难以测试的。

我也会参考这个线程,以及一个简单的谷歌搜索,坦率地说,它提供了大量关于这个主题的讨论。

于 2009-04-28T13:52:27.313 回答
6

我的“规则”是:

  • 如果我不需要使用类中的属性,请将其设为静态。(换句话说,如果方法没有真正附加到类,只是为了逻辑关联,使用 static )
于 2009-04-28T13:54:10.053 回答
3

一般来说,如果你有这样的方法:

Document.Copy(myDocumentObject, toPath);

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

于 2009-04-28T13:54:42.177 回答
2

通常,在使用 OO 思维方式进行编程时,您将希望避免使用静态方法。在 OOP 中,想法是将一切都表示为对象,并赋予每个对象一组清晰的能力,以表示其核心抽象。静态方法“打破”了这种抽象。

您谈论具有复制方法的 Document 类的示例就是一个很好的示例。我认为正确的 OO 实现是第一种方式。也就是说,将复制作为这样的实例方法:

document1.copy(toPath)

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

Document.copy(fromPath, toPath)
于 2009-05-13T19:42:50.933 回答
1

如果你不得不问,不要使用静态。

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

  • 如果有问题的类可以存在多次,则它不是静态的。

  • 如果所讨论的方法对实例信息起作用,则它不是静态的。

  • 如果方法或类是关于元信息的,它是静态的。

有了这些指导方针,文件和文档很明显是多份的,复制是对实例的一种行为。该方法不应该是静态的。

于 2009-04-28T14:08:39.333 回答
1

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

何时使用静态的一个很好的例子是当你想要这样做的时候

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

这是非常可测试的,并且将方法与对象紧密耦合并不重要。使用静态方法的一个不好的地方是当它使用其他东西然后只返回一些东西时,一个例子是在验证对象的 Biz 层中,如果它通过验证,它将数据保存到数据库

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

   return docErrors; 
} 

这是一个真正的测试痛苦,因为每次你运行它,并且它通过验证你的数据库,你的 Biz 逻辑可能不会产生错误,但你的 DAL 层可能,所以不是只测试 Biz 层的功能您还必须测试 DAL 层,并且将对象、Biz 层和 Dal 紧密耦合在一起,这使得测试和维护非常困难。

于 2009-04-28T15:23:31.150 回答
0

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

于 2009-04-28T13:54:54.560 回答
0

与 altCongnito 相同,我将添加每个人都会使用的 fileObject.Copy,而不是对象 fileObject。静态用于与类有理想关系而不是函数依赖的函数。

于 2009-04-28T13:56:46.053 回答
0

如果您使用任何其他对象,那么您将默认使用实例级别的方法,以便您可以使用依赖注入配置这些依赖项。

例如,如果其中一个图像是 SVG 图像,那么您可能依赖于 XML 解析器,该解析器(至少在 Java 中)有许多实现,同样对于我想象的 SVG 渲染器和许多其他组成图像类型可能需要类似的安排随着对象状态的演变或必须在不同的使用场景中更改(例如测试、生产、不同项目重用您的代码)而演变。

闪烁的琥珀色警告灯表示您可能正在使用不属于框架默认库的类,因此您选择了第三方组件,如果使用静态,您不适合修改该决定。

一个有用的“红线”是,如果您触及另一个进程(数据库服务器、Web 服务等),那么我会在 100% 的情况下认为静态方法很糟糕,因为这会使单元测试更加困难。

于 2009-04-29T13:13:37.257 回答