什么时候应该和不应该使用'new'关键字?


15

我看了Misko Hevery 在Google Tech Talk上有关单元测试的演讲,他说避免new在业务逻辑代码中使用关键字。

我写了一个程序,最终还是在new各处使用了关键字,但是它们主要用于实例化保存数据的对象(即,它们没有任何功能或方法)。

我想知道,当我在程序中使用new关键字时,我做错什么了吗?我们在哪里可以打破“规则”?


2
您可以添加与编程语言相关的标签吗?新功能以多种语言(C ++,Java,Ruby等)存在,并且具有不同的语义。
sakisk

Answers:


16

这比一成不变的规则更具指导意义。

通过在生产代码中使用“ new”,可以将类与其协作者耦合在一起。如果某人想使用其他协作者,例如某种模拟协作者进行单元测试,则不能这样做-因为协作者是在您的业务逻辑中创建的。

当然,有人需要创建这些新对象,但这通常最好放在两个地方之一:像Spring这样的依赖注入框架,或者是在通过构造函数注入的任何类实例化您的业务逻辑类的地方。

当然,您可以将这个范围扩大。如果您想返回一个新的ArrayList,那么可能就可以了-尤其是这将是一个不可变的List时。

您应该问自己的主要问题是:“这段代码的主要责任是创建这种类型的对象,或者这仅仅是我可以合理地转移到其他地方的实现细节?”


1
好吧,如果您希望它成为不可变的列表,则应该使用Collections.unmodifiableList或其他东西。但是我知道你的意思是:)
MatrixFrog 2011年

是的,但是您需要某种方式来创建原始列表,然后将其转换为不可修改...
Bill Michell

5

这个问题的关键是在结尾:我想知道,当我在程序中使用new关键字时是否做错了什么。我们在哪里可以打破“规则”?

如果您能够为您的代码编写有效的单元测试,那么您没有做错任何事情。如果您对new代码的使用很难或不可能进行单元测试,则应重新评估对new的使用。您可以将此分析扩展到与其他类的交互,但是编写可靠的单元测试的能力通常是足够好的代理。


5

简而言之,无论何时使用“ new”,都将包含该代码的类与正在创建的对象紧密结合在一起。为了实例化这些对象之一,进行实例化的类必须知道要实例化的具体类。因此,在使用“ new”时,应考虑放置实例化的类是否是该知识所在的“好”地方,并且如果愿意,可以愿意在此领域进行更改。被实例化的对象将发生变化。

紧密耦合,即具有另一种具体知识的对象,并非总是要避免的。在某种程度上,即使是其他所有东西都通过从其他地方获得的副本来处理该对象,某些东西也必须知道如何创建该对象。但是,当创建的类发生更改时,任何知道该类具体实现的类都必须更新,以正确处理该类的更改。

您应该经常问的问题是:“让此类知道如何创建另一个类别将成为维护应用程序时的责任吗?” 两种主要的设计方法(SOLID和GRASP)通常都会出于不同的原因回答“是”。但是,它们只是方法,并且两者都具有极端的局限性,即它们不是基于对您的独特程序的了解而制定的。因此,它们只能犯错,并认为紧密耦合的任何点都将在本质上引起您与更改此点的任一侧或两侧有关的问题。您必须知道三件事才能做出最终决定;理论上的最佳实践(这是将所有内容松散耦合,因为任何事情都可能发生变化);实施理论上的最佳实践的成本(其中可能包括几个新的抽象层,这些抽象层将缓解一种类型的更改而阻碍另一种类型的更改);以及现实世界中您所期望的变化类型的可能性将是必要的。

一些一般准则:

  • 避免已编译代码库之间的紧密耦合。DLL(或EXE及其DLL)之间的接口是紧密耦合会带来不利影响的主要地方。如果对DLL X中的类A进行了更改,并且主EXE中的类B知道了类A,则必须重新编译并释放两个二进制文件。在单个二进制文件中,更严格的耦合通常是允许的,因为无论如何更改都必须重建整个二进制文件。有时,不可避免地必须重建多个二进制文件,但是您应该对代码进行结构化,以便可以在可能的情况下避免使用它,特别是在带宽非常宝贵的情况下(例如部署移动应用程序;在升级中推送新的DLL便宜得多)而不是推送整个程序)。

  • 避免在程序的主要“逻辑中心”之间紧密耦合。您可以认为结构合理的程序由水平和垂直切片组成。水平切片可能是传统的应用程序层,例如UI,Controller,Domain,DAO,Data;可以为单个窗口或视图或单个“用户故事”(例如,创建某种基本类型的新记录)定义垂直切片。在结构良好的系统中进行向上,向下,向左或向右移动的呼叫时,通常应抽象化该呼叫。例如,当验证需要检索数据时,它不应直接访问数据库,而应调用用于数据检索的接口,该接口由知道如何执行此操作的实际对象支持。当某些UI控件需要执行涉及另一个窗口的高级逻辑时,它应该通过事件和/或回调抽象该逻辑的触发;它不必知道将要执行的操作,从而无需更改触发它的控件就可以更改将要执行的操作。

  • 无论如何,请考虑进行更改的难易程度,以及上述更改的可能性。如果您正在创建的对象仅在一个地方使用过,并且您没有预见到会发生变化,那么紧密耦合通常是允许的,在这种情况下甚至比松散耦合更优越。松散耦合需要抽象,这是一个额外的层,可以防止必须更改依赖项的实现时更改依赖项。但是,如果接口本身必须更改(添加新方法调用或向现有方法调用添加参数),则接口实际上会增加进行更改所需的工作量。您必须权衡不同类型的更改影响设计的可能性,


3

仅保存数据的对象或DTO(数据传输对象)可以整天创建这些对象。你想避免的地方new是执行操作的类,您希望使用类对接口进行编程,在接口上他们可以调用该逻辑并使用它,但不负责实际创建包含该逻辑的类的实例。那些执行动作或包含类的逻辑是依赖项。Misko的演讲面向您注入这些依赖关系,并让其他实体负责实际创建它们。


2

其他人已经提到了这一点,但想从Bob叔叔(鲍勃·马丁)的“清洁代码”中引用这一点,因为它可能使理解这一概念更容易:

“将构造与使用分开的强大机制是依赖注入(DI),即将控制反转(IoC)应用于依赖项管理。控制反转将次要责任从一个对象移到专用于此目的的其他对象,从而提供支持在单一职责原则,在依赖管理的语境中,对象不应该承担实例依赖自身的责任,而是应该通过这个责任的另一个“权威”的机制,从而反向控制。由于设置是一个全球关注的问题,这权威机制通常是“主要”例程或专用容器。”

我认为遵循此规则总是一个好主意。其他人提到了更重要的一个IMO->它将您的类与其依赖项(协作者)分离。第二个原因是它使您的类更小,更简洁,更易于理解,甚至可以在其他上下文中重用。

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.