如何在C#静态和非静态方法之间做出决定?

时间:2009-04-28 13:48:59

标签: c# oop class-design

[编辑]

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

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

所以,让我做一些补充:

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

因此,正如看门人Bob所发布的那样,我可以实现一个Validate() - 函数

Document.Validate(myDocumentObject);    

但也

myConcreteDocumentObject.Validate();

要回到我的Copy() - 示例,可以像

一样实现Copy()
myConcreteDocument.Copy(toPath);

但也

Document.Copy(myConcreteDocumentObject, toPath)

Document.Copy(fromPath, toPath)

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

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

但正如Anton Gogolev所说,我认为我的文档课不是一个很好的例子而且没有很好的设计,所以我想我必须看看单一责任原则。

我还可以实现某种与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.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类示例,因为它更像是关于良好类设计的一般性问题。)

11 个答案:

答案 0 :(得分:16)

我们走了。

首先关闭:

  

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

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

就你的Document而言,我不会这样做。您已经列出了Document类的所有职责,其中包括数据汇总,将自身保存到数据库以及页面上的操作和复制。

这是很多方面。每SRP,每个“模块”(此处“模块”用作全能术语)应该只有一个原因需要改变。你的Document有很多责任,因此它有很多改变的理由。这不好。

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


答案 1 :(得分:9)

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

考虑如何仅使用静态方法实现以下功能?

interface IDocument 
{
   void Print(IDevice targetDevice);
}

IDocument instance;

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

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

答案 2 :(得分:8)

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

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

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

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

  • 某些实例中的静态方法(Java)无法覆盖/子类化,因此它们更适合 实现不需要更改的情况
  • 有些人认为静态方法是intrinsically difficult to test

我还会参考这个threadsimple google search,坦率地就这个主题提供大量的讨论。

答案 3 :(得分:6)

我的“规则”是:

  • 如果我不需要使用我班级的属性,请将其设为静态。 (换句话说,如果方法没有真正附加到类,只是用于逻辑关联,请使用静态)

答案 4 :(得分:3)

一般情况下,如果您有以下方法:

Document.Copy(myDocumentObject, toPath);

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

答案 5 :(得分:2)

通常,在使用OO思维模式编程时,您将要避免使用静态方法。在OOP中,我们的想法是将所有东西都表示为对象,并为每个对象提供一组清晰的能力来代表其核心抽象。静态方法“打破”这种抽象。

使用复制方法讨论Document类的示例就是一个很好的例子。我认为正确的OO实现是第一种方式。也就是说,要将副本作为这样的实例方法:

document1.copy(toPath)

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

Document.copy(fromPath, toPath)

答案 6 :(得分:1)

如果你不得不问,请不要使用静力学。

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

  • 如果有问题的课程可以多次存在,那么它不是静态的。

  • 如果相关方法针对实例信息起作用,则它不是静态的。

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

根据这些指南,很明显文件和文档是倍数,复制是针对实例的行为。该方法不应该是静态的。

答案 7 :(得分:1)

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

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

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

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

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

   return docErrors; 
} 

这是一个真正的测试难题,因为每次运行它,并且它将验证传递给数据库时,您的Biz逻辑可能不会生成错误,但您的DAL层可能会生成错误,因此不会仅测试其功能您还必须测试DAL层的Biz层,并将您的对象,您的Biz层和Dal紧密耦合在一起,这使得测试和维护非常困难。

答案 8 :(得分:0)

一般来说,我会说,就对象而言,“复制”自己通常意味着将一个人的数据克隆到一个新对象中。这里描述的“复制”是文件系统代表你做的事情,而不是对象。因此,我将它作为静态方法而不是Document实例上的方法。

答案 9 :(得分:0)

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

答案 10 :(得分:0)

如果您使用任何其他对象,则将shokld默认为实例级方法,以便您可以使用Dependancy Injection配置这些依赖项。

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

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

一个有用的“红线”是,如果你触摸另一个进程(数据库服务器,Web服务等),那么我会认为静态方法在100%的时间都是不好的,因为这会使单元测试更加困难。