如何在C#静态和非静态方法之间做出决定?
[编辑]
我的原始问题是“为什么要在静态和非静态之间做出决定?两者都是一样的……”
不幸的是,它被编辑成一个C#特定的问题,我真的想避免。
所以,让我做一些补充:
当我说接口时,我不是指C#-keyword接口,而是我理解的东西,比如C ++接口:一组定义良好的函数来操作我的对象。 当说削弱我的界面时,我的意思是我有不同的function(静态/非静态)做同样的事情。 当有不同的function来做同样的事情时,我的界面不再明确定义。
所以,正如看门人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类示例,因为它更像是一个关于良好类设计的一般性问题。)
吻。 如果你不必调用构造函数,那就更好了。
另外,一个静态的方法应该告诉你一些函数的运作方式:
- 它不会对传递给它的变量进行操作。
- 除了调用方法之外,它不需要任何内存(不计算从函数返回的内容)
还有一些重要的事情需要注意:
- 某些实例中的静态方法(Java)无法覆盖/子类化,因此它们更适合于不需要更改实现的情况。
- 有些人认为静态方法本质上难以测试 。
我也会参考这个主题和一个简单的谷歌搜索 ,坦率地提供大量关于这个话题的讨论。
开始了。
首先:
所以我倾向于经常使用静态方法(独立于具体实例 – 独立总是好事)。
恰恰相反:使用静态方法时,您非常依赖于具体实例。
就你的Document
而言,我不会这样做。 您已经列出了Document
类的所有职责,其中包括数据聚合,将自身保存到数据库以及页面上的操作和复制。
这是很多的方式。 根据SRP ,每个“模块”(此处“模块”用作全能术语)应该只有一个原因需要改变。 您的Document
有很多责任,因此它有很多改变的理由。 这不好。
考虑到这一点,我会将所有逻辑移到其他具有严格定义职责的类中。 我相信,Herb Sutter或Andrei Alexandrescu引入了或多或少可接受的移动标准,如下所示:所有可以通过公共合同与对象一起执行的操作(思考方法)应该移到外面有问题的对象。
您不能使用静态方法来实现接口,也不能覆盖静态方法。 所以使用静态方法意味着你根本就不做OOP。
想想如何仅使用静态方法实现以下function?
interface IDocument { void Print(IDevice targetDevice); } IDocument instance; instance = new PdfDocument(); instance.Print(printer); instance = new WordDocument(); instance.Print(printer);
我的“规则”是:
- 如果我不需要使用我class级的属性,请将其设为静态。 (换句话说,如果方法没有真正附加到类,只是用于逻辑关联,使用静态)
一般来说,如果你有这样的方法:
Document.Copy(myDocumentObject, toPath);
我认为最好使用非静态方法,因为第一个参数是Document,表明它实际上是对文档的操作。
通常,在使用OO思维模式编程时,您将要避免使用静态方法。 在OOP中,我们的想法是将所有东西都表示为对象,并为每个对象提供一组清晰的能力来代表其核心抽象。 静态方法“打破”这种抽象。
您使用复制方法讨论Document类的示例就是一个很好的例子。 我认为正确的OO实现是第一种方式 。 也就是说,要将副本作为这样的实例方法:
document1.copy(toPath)
有意义的是,复制自身的能力是文档核心抽象的一部分。 通过这种方式,发送复制消息的客户端代码只需指定要复制到的位置 ,因为可以理解,Document会跟踪其内部位置。 没有必要在其他任何地方复制该信息,这是您提供的第三个选项的主要问题,如下所示:
Document.copy(fromPath, toPath)
如果你不得不问,不要使用静力学。
实际的经验法则(有很多真正的技术原因,但我发现这有助于解释这些概念):
-
如果有问题的类可以多次存在,那么它不是静态的。
-
如果所讨论的方法对实例信息起作用,则它不是静态的。
-
如果方法或类是关于元信息的,那么它是静态的。
根据这些指导原则,文件和文档很明显是倍数,复制是针对实例的行为。 该方法不应该是静态的。
静态方法可能非常有用,我喜欢扩展方法,但它们强制耦合,如果使用不当可能会使测试成为一场噩梦!
何时使用静态的一个很好的例子是你想要这样做validation
public static errors Validate(Document myDoc) { ..some validation code }
这是非常可测试的,并且它不会将您的方法与对象紧密耦合。 使用静态方法的不好的地方是当它给其他东西加剂然后只返回一些东西时,一个例子是在validation对象的Biz层中,如果它通过validation它将数据保存到DB
public static errors ValidateAndSave(Document myDoc) { errors docErrors = Validate(myDoc); if(docErrors.count==0) { docErrors = SaveToDB(myDoc); } return docErrors; }
这是一个真正的测试难题,因为每次运行它,并且它将validation传递给数据库时,您的Biz逻辑可能不会生成错误,但您的DAL层可能会生成错误,因此不会仅测试Biz层的function您还需要测试DAL层,并将您的对象,Biz层和Dal紧密耦合在一起,这使得测试和维护非常困难。
一般来说,我会说,就对象而言,“复制”自己通常意味着将一个人的数据克隆到一个新对象中。 这里描述的“复制”是文件系统代表你做的事情,而不是对象。 因此,我将它作为静态方法而不是Document实例上的方法。
与altCongnito相同,我将添加每个人将使用的fileObject.Copy,而不是对象fileObject。 静态的函数与类具有理想的关系而不是函数的依赖性。
如果您使用任何其他对象,则将shokld默认为实例级方法,以便您可以使用Dependancy Injection配置这些依赖项。
例如,如果其中一个图像是SVG图像,那么您可能依赖于XML解析器(至少在Java中)具有许多实现,同样对于我想象的SVG渲染器,许多其他组成图像类型可能需要类似的安排,随着对象的状态演变或必须在不同的使用场景中进行更改(例如测试,生产,重新使用代码的不同项目)。
闪烁的琥珀色警告灯表示您可能正在使用不属于框架默认库的类,因此您可以选择第三方组件,如果使用静态,则无法修改该决策。
一个有用的“红线”是,如果你触摸另一个进程(数据库服务器,Web服务等),那么我认为静态方法在100%的时间都是坏的,因为这会使unit testing更加困难。