面向对象的类设计


12

我想知道好的面向对象的类设计。特别是,我很难在以下两个选项之间做出选择:

  1. 静态实例方法
  2. 没有参数或返回值的方法 vs 有参数和返回值的方法
  3. 重叠独特的方法功能
  4. 私人公共方法

范例1:

此实现使用实例方法,没有返回值或参数,没有重叠的功能,并且所有方法都是公共的

XmlReader reader = new XmlReader(url);
reader.openUrl();
reader.readXml();
Document result = reader.getDocument();

范例2:

此实现使用静态方法,返回值和参数,功能重叠和私有方法

Document result = XmlReader.readXml(url); 

在示例一中,所有方法都是公共实例,这使得它们易于进行单元测试。尽管所有方法都不同,但readXml()依赖于openUrl(),因为必须首先调用openUrl()。所有数据都在实例字段中声明,因此,除了构造函数和访问器之外,任何方法都没有返回值或参数。

在示例二中,只有一种方法是公共的,其余的是私有静态的,这使得它们很难进行单元测试。方法是重叠的,因为readXml()调用openUrl()。没有字段,所有数据均作为方法中的参数传递,并且结果立即返回。

我应该遵循什么原则来进行正确的面向对象编程?


3
当您执行多线程时,静态事情很糟糕。前几天,我有一个静态XMLWriter,例如XMLWriter.write(data,fileurl)。但是,由于它具有私有的静态FileStream,所以同时使用多个线程中的此类会导致第二个线程覆盖第一个线程FileStream,从而导致很难找到的错误。具有静态成员+多线程的静态类是灾难的根源。
Per Alexandersson

1
@Paxinum。您描述的问题是状态问题,而不是“静态”问题。如果您将单例与非静态成员一起使用,则多线程也会遇到相同的问题。
mike30 2013年

2
@Per Alexandersson静态方法相对于并发来说还不错。静态不好。这就是为什么所有方法都是静态的函数式编程在并发情况下可以很好地工作的原因。
尤里·邦纳

Answers:


11

例2对于测试来说是很糟糕的……我并不是说您不能测试内部。您也不能用XmlReader模拟对象代替对象,因为根本没有对象。

示例1毫无用处。关于什么

XmlReader reader = new XmlReader(url);
Document result = reader.getDocument();

它比您的静态方法难用。

诸如打开URL,读取XML,将字节转换为字符串,解析,关闭套接字等之类的事情都是无趣的。创建对象并使用它很重要。

因此,恕我直言,正确的OO设计是仅公开两件事(除非出于某种原因您真的需要中间步骤)。静态是邪恶的。


-1。实际上,您可以用模拟对象代替XmlReader。不是使用脑残的开源moick框架,而是拥有一个良好的工业级框架;)每个开发人员花费几百美元,但在测试您发布的API中的密封功能方面却遇到了奇迹。
TomTom 2012年

2
+1表示不了解TomTom的销售情况。当我想要一支班轮时,我宁愿写Document result = new XmlReader(url).getDocument();为什么?这样我就可以将其升级到其他地方Document result = whateverReader.getDocument();whateverReader交给我。
candied_orange

6

事情就是这样-没有一个正确的答案,也没有“正确的面向对象设计”的绝对定义(有些人会为您提供一个,但是他们很幼稚……给他们时间)。

这一切都取决于您的目标。

您是一位艺术家,论文是空白的。您可以绘制精美的铅笔状黑白侧面肖像,也可以绘制带有大量混合霓虹灯的抽象画。或两者之间的任何东西。

那么您要解决的问题有什么权利呢?需要使用您的类来使用xml的人有什么抱怨?他们的工作有什么困难?他们正在尝试编写什么样的代码来包围对库的调用,您如何帮助它们更好地进行编码?

他们想要更简洁吗?他们是否希望它在找出参数的默认值时非常聪明,所以他们不必指定太多(或任何内容)并且可以正确猜测?您是否可以自动化您的磁带库所需的设置和清理任务,以使他们无法忘记这些步骤?您还能为他们做什么?

地狱,您可能需要做的是用4或5种不同的方式编写代码,然后戴上消费帽,编写使用全部5种代码,然后看看哪种感觉更好。如果您不能为整个库执行此操作,请对一个子集执行此操作。而且,您还需要向列表中添加其他替代方案-流畅的界面,更实用的方法,命名参数或基于DynamicObject的事物,这样您就可以组成有意义的“伪方法”来帮助他们出来吗

为什么jQuery现在是国王?因为Resig和团队遵循此过程,直到他们遇到了一种语法原则,该原则极大地减少了与dom和事件配合使用所需的JS代码量。他们或其他任何人刚开始时都不清楚这种语法原则。他们找到了。

作为程序员,这就是您的最高要求。您在黑暗的尝试中摸索,直到找到它。当您这样做时,您就会知道。您将为用户带来巨大的生产力飞跃。这就是设计(在软件领域)的全部内容。


1
这意味着最佳实践与其他实践之间除了“感觉”之外没有其他区别。这就是您无法解决的难题的结局,因为对于许多开发人员而言,跨越Class边界等等,“感觉”简直太美妙了。
艾米·布兰肯希

@艾米·布兰肯希(Amy Blankenship),我要明确地说,没有任何一种“最佳方法”可以做出OP正在询问的选择。它取决于一百万个事物,并且有一百万个自由度。但是,我确实认为有一个“最佳实践”的地方,那就是在团队环境中,已经做出某些选择,并且我们需要团队中的其他成员与先前的选择保持一致。换句话说,在特定的上下文中,可能有理由将某些事物标记为“最佳实践”。但是OP没有给出任何上下文。他正在建造东西,并且……
查理·弗罗里斯

...他面临所有这些可能的选择。对于这些选择,没有一个“正确答案”。它由系统的目标和痛点驱动。我向您保证,Haskell程序员不会认为所有方法都应该是实例方法。而且Linux内核程序员根本不认为使TDD可访问的东西非常重要。C ++游戏程序员通常宁愿将其数据捆绑到内存中的紧密数据结构中,也不愿将所有内容都封装到对象中。在给定的上下文中,每种“最佳实践”都只是一种“最佳实践”,而在其他一些上下文中,则是一种反模式。
Charlie Flowers

@AmyBlankenship还有一件事:我不同意跨越Class边界“感觉真是太棒了”。它导致无法维持的泥球,令人感到恐惧。我认为您正在尝试解决一些工人草率/缺乏动力/非常缺乏经验的问题。在这种情况下,谨慎,有动力和经验丰富的人应该做出关键选择,并将其称为“最佳实践”。但是,选择这些“最佳实践”的人仍在根据“感觉正确” 做出选择,并且没有正确的答案。您只是在控制做出选择。
Charlie Flowers

我曾与几位认为自己是高级人员的程序员一起工作,并被管理人员认为是高级人员,他们坚信静态和Singletons绝对是处理通信问题的正确方法。这个问题的静态部分甚至都不会被问到是否跨类的边界进行这种开发人员认为“错误”的错误,或者倡导静态替代方案的答案也不会获得任何支持。
艾米·布兰肯希

3

第二个选项更好,因为它使人们更容易使用(即使只是您自己),也更简单。

对于单元测试,如果您真的想将内部结构从私有更改为受保护,则仅测试接口而不是内部结构。


2
如果您的测试用例位于同一包中,则出于单元测试的目的,您还可以将方法包设为私有(默认)。

好点-更好。

3

1.静态与实例

我认为关于什么是好的OO设计有什么非常明确的指导。问题在于,博客圈很难区分好与坏。您可以找到某种参考,甚至可以支持您想到的最差实践。

我能想到的最糟糕的做法是全局状态,包括您提到的静态数据和每个人都喜欢的Singleton。Misko Hevery 关于该主题经典文章的一些摘录。

要真正理解依赖关系,开发人员必须阅读每一行代码。它会引起遥远的怪异动作:运行测试套件时,一个测试中的全局状态发生突变会导致后续或并行测试意外失败。使用手动或Guice依赖注入打破静态依赖。

距离遥远的怪异动作是指我们运行一件我们认为是孤立的事情(因为我们没有传递任何引用),但是意外的交互作用和状态更改发生在系统的遥远位置,而我们并未告知对象。这只能通过全局状态发生。

您以前可能没有这样想过,但是每当您使用静态时,您就在创建秘密的通信通道,并且没有在API中明确它们。距离遥远的鬼动作迫使开发人员阅读每一行代码,以了解潜在的交互作用,降低开发人员的工作效率,并使新的团队成员感到困惑。

归结为,您不应为具有某种存储状态的任何内容提供静态引用。我唯一使用statics的地方是枚举常量,对此我也有疑虑。

2.具有输入参数和返回值的方法与没有参数的方法

您需要意识到的是,没有输入参数且没有输出参数的方法几乎可以保证在某种内部存储状态下运行(否则,它们在做什么?)。有整个语言上构建的避免存储状态的想法。

任何时候存储状态时,都有可能产生副作用,因此请确保始终谨慎使用状态。这意味着您应该首选具有定义的输入和/或输出的功能。

而且,实际上,定义了输入和输出的函数更容易测试-您不必在此处运行函数就可以查看发生了什么,而不必在某个地方设置属性否则,在运行被测函数之前。

您也可以安全地将此类型的函数用作静态函数。但是,我不会,因为如果以后我想在某个地方使用该功能的稍有不同的实现,而不是在新实现中不提供其他实例,那么我将无法替换该功能。

3.重叠与不同

我不明白这个问题。2种重叠方法的优点是什么?

4.私人与公共

不要暴露任何不需要暴露的东西。但是,我也不是私有的忠实拥护者。我不是C#开发人员,而是ActionScript开发人员。我花了很多时间在大约于2007年编写的Adobe Flex Framework代码中。对于私有化的内容,他们做出了一些非常糟糕的选择,这使得扩展类变得有些噩梦。

因此,除非您认为自己比2007年左右的Adobe开发人员更好(从您的问题出发,我想您还有几年的时间才有机会提出这一要求),否则您可能只想默认为protected 。


您的代码示例存在一些问题,这意味着它们的架构不正确,因此无法选择A或B。

一方面,您可能应该将对象创建与use分开。因此,通常您不会在使用new XMLReader()位置旁拥有自己的权利。

另外,正如@djna所说,您应该封装XML Reader使用中使用的方法,因此您的API(实例示例)可以简化为:

_document Document = reader.read(info);

我不知道C#是如何工作的,但是由于我已经使用了多种Web技术,因此我怀疑您不能总是能够立即返回XML文档(除非作为Promise或将来的类型对象),但我无法为您提供有关如何在C#中处理异步加载的建议。

请注意,使用这种方法,您可以创建多个实现,这些实现可以采用一个参数来告诉他们读取和返回XML对象的位置/内容,并根据项目需求交换它们。例如,您可能直接从数据库,本地商店或从URL中读取,就像在原始示例中一样。如果使用静态方法,则无法执行此操作。


2

关注客户的观点。

IReader reader = new XmlReader.readXml(url);  // or injection, or factory or ...
Document document = reader.read();

静态方法往往会限制未来的发展,我们的客户正在使用可能由许多不同实现提供的接口。

开放式/读取式成语的主要问题是,当客户只想完成简单的工作时,他需要知道调用方法的顺序。这里很明显,但是在更大的类中,它远非显而易见。

测试的主要方法是read()。通过将内部方法公开或私有,并使测试置于同一程序包中,可以使内部方法对测试程序可见-测试仍可以与已发布的代码分开。


如果测试套件位于其他项目中,则具有默认可见性的方法仍然可见吗?
siamii 2011年

Java不了解项目。项目是IDE构造。编译器和JVM查看被测试类和测试器类所在的软件包-相同的软件包,允许默认可见性。在Eclipse中,我使用一个具有两个不同源目录的项目。我刚刚尝试了两个项目,它确实起作用。

2

静态与实例方法

在实践中,您会发现静态方法通常只限于实用程序类,不应使您的域对象,管理器,控制器或DAO混乱。当所有必要的引用都可以合理地作为参数传递并提供某些可在许多类中重用的功能时,静态方法最有用。如果发现自己使用静态方法作为对实例对象的引用的变通方法,请问自己为什么不仅仅具有该引用。

没有参数或返回值的方法vs有参数和返回值的方法

如果您不需要方法上的参数,请不要添加它们。返回值也一样。牢记这一点将简化您的代码,并确保您不会针对永远不会发生的多种情况进行编码。

重叠与独特的方法功能

最好避免功能重叠。有时可能很困难,但是当需要更改逻辑时,更改一个可重用的方法要比更改一堆具有相似功能的方法要容易得多

私人与公共方法

通常,获取器,设置器和构造器应该是公共的。除非有其他类需要执行它,否则您将尝试保持私有性。将方法默认设置为私有将有助于维护Encapsulation。字段也是如此,默认情况下习惯于私有


1

我不会回答您的问题,但是我认为您所使用的术语会引起问题。例如

XmlReader.read => twice "read"

我认为您需要XML,因此我将创建一个可以从文本类型创建的对象XML(我不知道C#...在Java中称为String)。例如

class XML {
    XML(String text) { [...] }
}

您可以对其进行测试,这很清楚。然后,如果需要工厂,则可以添加工厂方法(它可以像第二个示例一样是静态的)。例如

class XML {
    XML(String text) { [...] }

    static XML fromUrl(url) { [...] }

}

0

您可以遵循一些简单的规则。如果您了解规则的原因,它将很有帮助。

静态与实例方法

对于方法,您不必自觉地做出此决定。如果您的方法似乎没有使用任何字段成员(您喜欢的分析器应该告诉您),则应添加static关键字。

没有参数或返回值的方法vs有参数和返回值的方法

由于范围,第二种选择更好。您应该始终保持范围狭窄。伸出所需的东西是不好的,您应该输入信息,在本地进行处理并返回结果。出于相同的原因,您的第一个选择也很糟糕,因为全局变量通常很糟糕:它们对您的代码的一部分有意义,但是它们在其他任何地方都可见(因此有噪声),并且可能在任何地方被篡改。这使您难以全面了解您的逻辑。

重叠与独特的方法功能

我认为这不是问题。如果将其他任务分割成较小的,功能明确的块,则调用其他方法的方法就可以了。

私人与公共方法

除非您需要公开所有内容,否则将所有内容设为私有。您班上的用户可以无噪音地工作,他只想看对他重要的事情。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.