我似乎有一种奇怪的习惯......据我的同事说,至少。我们一直在一个小项目上工作。我编写类的方式是(简化示例):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| [Serializable ()]
public class Foo
{
public Foo ()
{ }
private Bar _bar ;
public Bar Bar
{
get
{
if (_bar == null)
_bar = new Bar ();
return _bar ;
}
set { _bar = value; }
}
} |
所以,基本上,我只在调用getter并且字段仍然为null时初始化任何字段。我认为这可以通过不初始化任何地方没有使用的任何属性来减少过载。
ETA:我这样做的原因是我的类有几个属性返回另一个类的实例,而这个属性又具有更多类的属性,依此类推。调用顶级类的构造函数随后将调用所有这些类的所有构造函数,而不是总是需要它们。
除个人偏??好外,是否有任何反对这种做法的反对意见?
更新:我已经考虑了很多关于这个问题的不同意见,我将坚持我接受的答案。但是,我现在对这个概念有了更好的理解,我能够决定何时使用它,何时不能。
缺点:
-
线程安全问题
-
当传递的值为null时,不遵守"setter"请求
-
微优化
-
异常处理应该在构造函数中进行
-
需要在类'代码中检查null
优点:
-
微优化
-
属性永远不会返回null
-
延迟或避免加载"重"物体
大多数缺点不适用于我当前的库,但是我必须测试"微优化"是否实际上是在优化任何东西。
最后更新:
好的,我改变了答案。我最初的问题是这是否是一个好习惯。我现在确信它不是。也许我仍会在我当前代码的某些部分使用它,但不是无条件的,绝对不是所有的时间。因此,在使用它之前,我会失去习惯并思考它。感谢大家!
-
这是懒惰的模式,它并没有给你一个很好的好处,但它仍然是一个好东西imho。
-
如果您具有可衡量的性能影响,或者如果这些成员很少使用并消耗过多的内存,或者如果需要很长时间来实例化它们并且只想按需执行,则惰性实例化是有意义的。无论如何,请务必考虑线程安全问题(当前代码不是)并考虑使用提供的Lazy < T >类。
-
我认为这个问题更适合codereview.stackexchange.com
-
@PLB它不是单例模式。
-
@Machinarius感谢您的投入。我将查找该模式的信息。
-
@PLB这不是单身模式
-
@PLB对不起,我知道单身是什么,这不是它。
-
@ChrisSinclair谢谢,我先看看Lazy< T >。
-
谢谢大家的快速建议。我现在知道我通常不会这样做。我想这永远不会太晚学习!
-
@EduardoBrites谢谢,下次我会考虑任何编码风格问题!
-
我想你需要重新考虑接受的答案。
-
集合很少有Set属性。
-
@AMissico我知道,不好的例子。我会编辑这个问题。
-
不知道它是否是线程安全的?如果不是如何使其线程安全?
-
我很惊讶没有人提到这个代码的严重错误。你有一个公共财产,我可以从外面设置它。如果我将其设置为NULL,您将始终创建一个新对象并忽略我的setter访问。这可能是一个非常严重的错误。对于私人物业,这可能是好的。就个人而言,我不喜欢做出这样的过早优化。增加复杂性,没有额外的好处。
-
@SolutionYogi;代码没有问题。这是代码模式。例如,如果Bar是一个集合,那么你将没有Set方法("setter")。如果线程安全很重要,您可以添加lock。
-
@Amitd;你会用lock(_bar)语句块包围if语句块。
-
@AMissico正如我已经提到的,如果它是一个没有二传手的私有财产或财产,那就没关系了。但是问题中列出的代码有一个严重的错误,你要丢弃由调用代码设置的'null'。
-
@SolutionYogi你是对的,这个例子只是一个例子。我应该考虑好一点,因为我知道可能出现的错误。我的库中的属性永远不会公开,大多数都没有setter。
-
看看你的专业论点,他们都错了。微优化:从不优化早期,它是所有邪恶的根源(特别聪明的,如懒惰的初始化)。属性永远不会返回null:您可以在构造函数中处理正确实现的setter,因此这不是参数。延迟或避免重物:如果你习惯这样做,那么你甚至不会看它们是否是重物。如果您认为具有30个属性的对象很重,那么您应该再次查看。你有一个很好的问题,但我必须为你选择的可怕答案做出投票。
-
更不用说你正在将一个简单的单行声明(自动属性)变成一个11行代码大亨,这是人们必须维护的代码的10倍。你为什么要这样对待别人?没有专业论据可以克服你所做的大量利弊。
-
随机观察:if(foo == null) foo = new Foo()可以简化为foo = foo ?? new Foo()
-
Objective-C有一个名为"lazy initializer"的约定,基本上它也在getter中初始化对象。
-
如果代码不够快,您应该只优化代码。不要提前做出假设。您是否使用过分析工具来显示此属性?代码的其他区域是否可以使用改进?虽然Lazy初始化有它的位置,但是以这种方式在get访问器中使用if语句乱丢代码确实会产生丑陋的代码。简短的回答?如果您的代码因此得到显着改善,请保留它,否则将其删除。
-
因为这个问题仍然收到流量我修改了我的答案。你可能想看看它,以便更好地理解为什么我反对无条件地使用延迟初始化。
-
@DanielHilgarth经过深思熟虑和阅读你的新增内容后,我决定选择你的答案。我的许多经验比我更多的同事似乎同意你的意见,并建议我失去这种模式,除非绝对必要。
-
@JohnWillemse:我很高兴我可以说服你,这种模式比以前接受的答案显示更多。
-
@JohnWillemse我很放心你改变了主意:)
-
如何在优缺点列表中进行微优化?
你在这里有一个 - 天真 -"懒惰初始化"的实现。
简短回答:
无条件地使用延迟初始化不是一个好主意。它有它的位置,但必须考虑到这个解决方案的影响。
背景和解释:
具体实施:
让我们首先看看你的具体样本,以及为什么我认为它的实现是天真的:
它违反了最低惊喜原则(POLS)。将值分配给属性时,应返回此值。在您的实现中,null不是这种情况:
1 2
| foo.Bar = null;
Assert.Null(foo.Bar); // This will fail |
它引入了一些线程问题:在不同线程上有两个foo.Bar的调用者可能会获得两个不同的Bar实例,其中一个实例没有连接到Foo实例。对该Bar实例所做的任何更改都会无声地丢失。
这是违反POLS的另一个案例。当只访问属性的存储值时,它应该是线程安全的。虽然你可以说这个类本身不是线程安全的 - 包括你的属性的getter - 你必须正确记录这个,因为这不是正常的情况。此外,我们将很快看到这个问题的引入是不必要的。
一般来说:
现在是时候看一般的懒惰初始化了:
延迟初始化通常用于延迟构建需要很长时间构建的对象,或者一旦完全构造就占用大量内存。
这是使用延迟初始化的一个非常有效的原因。
但是,这些属性通常没有setter,这摆脱了上面提到的第一个问题。
此外,将使用线程安全的实现 - 如Lazy< T > - 以避免第二个问题。
即使在执行惰性属性时考虑这两点,以下几点也是这种模式的一般问题:
对象的构造可能不成功,导致属性getter的异常。这是对POLS的又一次违反,因此应该避免。甚至"开发类库的设计指南"中的属性部分也明确指出属性getter不应抛出异常:
Avoid throwing exceptions from property getters.
Ok.
Property getters should be simple operations without any preconditions. If a getter might throw an exception, consider redesigning the property to be a method.
Ok.
编译器的自动优化是有害的,即内联和分支预测。有关详细说明,请参阅Bill K的答案。
这些要点的结论如下:
对于懒惰实施的每个单一属性,您应该考虑这些要点。
这意味着,这是一个案例决定,不能作为一般的最佳实践。
这种模式有它的位置,但在实现类时它不是一般的最佳实践。由于上述原因,不应无条件使用。
在本节中,我想讨论其他人作为无条件使用延迟初始化的参数提出的一些观点:
连载:
EricJ在一条评论中说:
An object that may be serialized will not have it's contructor invoked when it is deserialized (depends on the serializer, but many common ones behave like this). Putting initialization code in the constructor means that you have to provide additional support for deserialization. This pattern avoids that special coding.
Ok.
这个论点有几个问题:
大多数对象永远不会被序列化。在不需要时为其添加某种支持会违反YAGNI。
当一个类需要支持序列化时,存在启用它的方法,而没有与第一眼看上去与序列化无关的解决方法。
微优化:
您的主要论点是,您只想在有人实际访问它们时构造对象。所以你实际上是在谈论优化内存使用情况。
我不同意这个论点,原因如下:
在大多数情况下,内存中的一些对象对任何事物都没有任何影响。现代计算机有足够的内存。如果没有分析器确认的实际问题,这是预先成熟的优化,并且有充分的理由反对它。
我承认有时候这种优化是合理的。但即使在这些情况下,延迟初始化似乎也不是正确的解决方案。反对它的原因有两个:
延迟初始化可能会损害性能。也许只是轻微的,但正如比尔的回答所显示的那样,影响比乍看之下的影响要大。所以这种方法基本上交换了性能与内存。
如果你的设计只是部分类的常见用例,这暗示了设计本身的问题:有问题的类很可能有不止一个责任。解决方案是将类拆分为几个更集中的类。
好。
-
因为会有许多相同类的实例(这比我作为示例所示的复杂得多)并且属性的使用取决于用户输入/请求。
-
@JohnWillemse:这是你的架构问题。您应该以更小,更专注的方式重构您的课程。不要为5个不同的事物/任务创建一个类。改为创建5个类。
-
@JohnWillemse或许可以认为这是一个过早优化的案例。除非你有一个测量的性能/内存瓶颈,否则我建议不要这样做,因为它增加了复杂性并引入了线程问题。
-
@DanielHilgarth感谢您的建议。
-
空检查是微不足道的(有些人甚至认为它是防御性编程)。但是,根据构造函数中的内容,它可能需要大量的时间(与空检查相比)。
-
为"引入线程问题"+1
-
如果不遵循此模式但需要序列化对象,则必须为反序列化添加额外的支持,如果使用不调用对象构造函数的序列化程序(许多常见的序列化程序的行为类似)。
-
+1,对于95%的课程来说这不是一个好的设计选择。延迟初始化有其优点,但不应针对所有属性进行推广。它增加了复杂性,难以阅读代码,线程安全问题......在99%的情况下都没有可察觉的优化。此外,正如SolutionYogi所说,OP的代码是错误的,这证明这种模式实现起来并不容易,除非实际需要延迟初始化,否则应该避免使用。
-
-1表示线程安全问题。按惯例,实例成员不保证是线程安全的,并且试图使它们安全是浪费时间和线程锁定。
-
@DanielHilgarth感谢您一路上写下(几乎)无条件使用此模式的所有错误。很好!
-
@DanielHilgarth很棒的回答。即使我同意主要论点,我也有一些细节评论。首先要注意的是POLS在这种情况下非常重要,因为类是Serializable(!)我的第二个评论是Lazy < T >也违反了POLS,正如我在stackoverflow.com/questions/2550925/上所解释的那样(它违反了这一要求)其奇怪的锁定和异常行为,遗憾的是并不为人所知。最后是Q / A和Lazy < T >都不是(设计)模式;但是,如果它全部作为代理实现,它可能是。
-
@StefandeBruijn:感谢您的评论。我将从后面开始:我不认为我曾经称之为设计模式......在我看来,"模式"和"设计模式"是两个不同的东西。"模式"只是编写某些代码的某种方式。"设计模式"与架构有关。感谢Lazy< T >的问题提示,我不知道它们。最后,我不明白你的第一句话。我同意,POLS非常重要,我在答案中指出了这一点。所以我真的没有看到你在这里告诉我:)
-
@DanielHilgarth实际上你称之为模式,IMO很容易将其误认为设计模式。但这也可能只是我的经验:-)至于POLS,Foo可序列化的事实意味着他可能无法控制何时调用get和set;如果你只是序列化和反序列化类,由于'null'的情况,你已经得到了不同的对象。在序列化类中添加lazy也没有任何意义:该属性几乎总是由反序列化器调用。 POLS通常只是一种网络礼节 - 在这里它会产生意想不到的行为并且没有意义。
-
所以我不是想说你错了;相反:我试图告诉它比在这个特定情况下注意到的更重要,因为该类被标记为Serializable。
-
@StefandeBruijn:啊,现在我明白了:)非常好点。关于"设计"与"设计模式":我猜你写的时候我更新了原来的评论。
-
@StefandeBruijn:我认为序列化案例中的POLS违规是一个普遍问题的子案例,你永远不会从这样的属性中获得null。
-
@DanielHilgarth恩,是的,不。这是违规问题,是的。但也"不",因为POLS严格地说是你可能不会对代码感到惊讶的原则。如果Foo没有暴露在您的程序之外,那么您可以承担与否的风险。在这种情况下,我几乎可以保证您最终会感到惊讶,因为您无法控制属性的访问方式。风险刚刚成为一个错误,你对null案件的争论变得更加强烈。 :-)
这是一个很好的设计选择。强烈推荐用于库代码或核心类。
它通过一些"延迟初始化"或"延迟初始化"来调用,并且通常认为它是一个很好的设计选择。
首先,如果在类级别变量或构造函数的声明中初始化,那么在构造对象时,您将有创建可能永远不会使用的资源的开销。
其次,只有在需要时才会创建资源。
第三,避免垃圾收集未使用的对象。
最后,更容易处理属性中可能发生的初始化异常,然后处理类级变量或构造函数初始化期间发生的异常。
这条规则有例外。
关于"get"属性中初始化的附加检查的性能参数,它是无关紧要的。初始化和处理对象比使用跳转的简单空指针检查更重要。
开发类库的设计指南,网址为http://msdn.microsoft.com/en-US/library/vstudio/ms229042.aspx
关于Lazy< T >
通用Lazy< T >类是根据海报的需要创建的,请参阅http://msdn.microsoft.com/en-us/library/dd997286(v=vs.100).aspx上的Lazy Initialization。如果您使用的是旧版本的.NET,则必须使用问题中说明的代码模式。这种代码模式已经变得非常普遍,以至于微软认为在最新的.NET库中包含一个类可以更容易地实现该模式。此外,如果您的实现需要线程安全,那么您必须添加它。
原始数据类型和简单类
显而易见,您不会对原始数据类型或像List这样的简单类使用进行延迟初始化。
在评论懒惰之前
Lazy< T >是在.NET 4.0中引入的,所以请不要添加关于此类的其他注释。
在评论微优化之前
在构建库时,必须考虑所有优化。例如,在.NET类中,您将看到整个代码中用于布尔类变量的位数组,以减少内存消耗和内存碎片,仅举两个"微优化"。
关于用户界面
您不会对用户界面直接使用的类使用延迟初始化。上周,我花了大部分时间来删除在组合框的视图模型中使用的八个集合的延迟加载。我有一个LookupManager来处理任何用户界面元素所需的集合的延迟加载和缓存。
"二传手"
我从未对任何延迟加载的属性使用set-property("setters")。因此,您永远不会允许foo.Bar = null;。如果你需要设置Bar,那么我将创建一个名为SetBar(Bar value)的方法,而不是使用延迟初始化
集合
声明时,类集合属性始终初始化,因为它们永远不应为null。
复杂类
让我重复一遍,你对复杂的类使用延迟初始化。通常是设计不良的课程。
最后
我从来没有说过要为所有课程或所有情况都这样做。这是一个坏习惯。
-
我将尝试在框架设计指南中找到推荐这种做法的参考。
-
谢谢,这就是我对它的看法,在我阅读所有其他答案之前,现在我又回到了困惑;)这是一个我使用它的类库。我将只检查一些人警告过的线程安全问题。
-
@JohnWillemse,在这种情况下,我认为线程安全不是课堂的责任。
-
如果你可以在不同的线程中多次调用foo.Bar而没有任何干预值设置但得到不同的值,那么你就有一个糟糕的穷人类。
-
我认为这是一个不好的经验法则,没有多少考虑因素。除非Bar是一个知道资源,否则这是一个不必要的微优化。在Bar是资源密集型的情况下,.net中内置了线程安全的Lazy < T >。
-
@ach,Lazy< T >是为海报想要的而创建的。如果您使用的是旧版本的.NET,则必须使用问题中说明的代码模式。这种代码模式已经变得非常普遍,以至于微软认为在最新的.NET库中包含一个类可以更容易实现。
-
@ach否,在使用Foo时知道Bar是否是资源占用增加了它们之间的心理耦合。最好保持简单并使用这种模式(无论是在问题中发布还是使用Lazy< T >;只是不要在构造函数中执行(这是问题所在))。
-
"更容易处理属性中可能出现的初始化异常,然后在类级别变量或构造函数初始化期间发生异常。" - 好吧,这很傻。如果某个对象由于某种原因无法初始化,我想尽快知道。即当它被建造时立即。对于使用延迟初始化有很好的论据,但我不认为普遍使用它是个好主意。
-
另外,重新:"资源只在需要时才被创建"和"你避免垃圾收集一个没有使用过的对象" - 它甚至不是远程给定的,因为它的性能增益将超过你创建对象时复杂化的事实需要。至少当你使用线程安全(因此可能更复杂)Lazy< T >时。这就是为什么过早的微观刺激被不赞成的原因;这不仅仅是因为他们没有什么好处值得打扰,还因为你不知道他们是否会在第一时间提供帮助。
-
@millimoose; >> exceptions ...构造函数"这很愚蠢。<<为什么?这取决于类的实现。例如,我想在访问属性时处理任何Bar构造函数异常。不在给出的Foo构造函数中我可能永远不会访问Bar属性。重构,返工,维护最难的代码包含做"繁重工作"的类构造函数。而且,如果对这类类进行单元测试非常困难,但你可以轻松进行单元测试延迟初始化方法/属性。
-
@lzkata,谢谢你指出问题是什么。似乎有些人真的没有阅读这个问题。
-
我真的很担心其他开发者会看到这个答案,并认为这确实是一个很好的做法(哦,小男孩)。如果您无条件使用它,这是一种非常糟糕的做法。除了已经说过的话,你们每个人的生活都会变得更加困难(对于客户开发人员和维护开发人员来说)获得如此微不足道的收益(如果有任何收益的话)。你应该从专业人士那里听到:Donald Knuth,在"计算机程序设计艺术"系列中,着名地说"过早优化是所有邪恶的根源。"你正在做的不仅是邪恶,而且是恶作剧!
-
有很多指标你选择了错误的答案(以及错误的编程决定)。你的名单中的专业人士比专业人士更多。你有更多的人担保它而不是它。在这个问题上发布的这个网站(@BillK和@DanielHilgarth)经验丰富的成员反对它。你的同事已经告诉你这是错的。说真的,这是错的!如果我抓住我的团队的一名开发人员(我是团队领导)这样做,他将花费5分钟的超时时间,然后进行演讲,为什么他不应该这样做。
-
Con列表中的另一个点,以及"库代码"推理的计数器参数是您的消费者不会意识到所有属性都是Lazy加载的。他们可能会合理地期望一个属性立即返回(例如在UI绑定中)在你的代码中,Bar的懒惰初始化很容易变得非常重要。你应该考虑改变接受的答案和练习。
-
可以序列化的对象在反序列化时不会调用它的构造函数(取决于序列化程序,但许多常见的行为都是这样的)。将初始化代码放在构造函数中意味着您必须为反序列化提供额外的支持。这种模式避免了这种特殊的编码。
-
从来没有见过这么多错误的回答有这么多的赞成并被接受作为答案!
-
@EricJ。不再调用构造函数是件好事。毕竟你要反序列化已经构造的对象。根据定义,构造函数应该只调用一次,如果你想调用它两次,那么它不再是一个构造函数,是吗?这种模式并没有避免这种特殊的编码,它实际上使它变得更加复杂。您必须知道在序列化之前是否已初始化某个属性。再一遍代码中的一堆if语句来处理这种情况。你刚刚证明了另一个Con,这种模式使序列化更难。
-
@Peri我很困惑,人们在不考虑后果的情况下不断提高这个答案。用户在他的问题中明确指出"这是一种奇怪的习惯"。我觉得新开发者的可怜的灵魂将会学到很难的原因,为什么这不仅是奇怪的,而且确实是一个非常坏的习惯。
-
@Alex:有些序列化程序会调用构造函数,有些则不会。这不一致。某些序列化程序仅序列化公共属性的状态(通常不是对象的整个状态),而其他序列化程序序列化所有内容。使用适用于任何可能的序列化器的对象模式增加了灵活性。
-
@EricJ。我猜那是Pro。与此处讨论的所有其他缺点相比,我仍然没有看到这有何帮助。对我来说,这显然是错误的答案,显然是错误的信息发送给其他开发人员。我不在乎列出了多少小优点,这样的早期微优化不应该受到赞扬。
-
@Alex:我不会为了微优化而这样做。我会这样做,因为我已经在构造函数中初始化集合之类的东西,只是让对象(很多)稍后由不运行构造函数的序列化程序序列化,并将空集合初始化为null而不是新的集合实例。实际上,如果属性公开接口而不是具体的集合实现,那么不考虑私有字段的序列化程序就不可能这样做。
-
-1,对于95%的课程来说,这不是一个好的设计选择。延迟初始化有其优点,但不应针对所有属性进行推广。它增加了复杂性,难以阅读代码,线程安全问题......在99%的情况下都没有可察觉的优化。此外,正如SolutionYogi所说,OP的代码是错误的,这证明这种模式实现起来并不容易,除非你真的需要延迟初始化,否则应该避免。
-
@EricJ。这毫无意义。你可能有几个理由使用懒惰的内容,我并不是说它们很糟糕。我说没有充分理由(作为习惯)使用它们!无论你有什么参数都是针对你的问题的。这是否意味着每次编写新类时都会使用lazy init,以防类被序列化?没有!特别是因为这样做的代价是巨大的,所以你必须有一个很好的理由来使用它。这里的重点是,这不是你应该使用的模式,不需要进一步考虑。所以这不是一个好的整体习惯,我们不应该教那个。
-
忽略句子上方的所有论点,人们通常认为这是一个很好的设计选择。是非常误导的,上面的评论证明了这一点。
您是否考虑使用Lazy< T >实现此类模式?
除了轻松创建延迟加载的对象之外,还可以在初始化对象时获得线程安全性:
-
http://msdn.microsoft.com/en-us/library/dd642331.aspx
正如其他人所说,如果对象非常耗费资源,或者在对象构建期间加载它们需要一些时间,那么就懒得加载对象。
-
谢谢,我现在明白了,我现在肯定会研究Lazy< T >,并且不要使用我以前常用的方式。
-
@JohnWillemse这是一个好主意!
-
你没有获得神奇的线程安全......你仍然需要考虑它。来自MSDN:Making the Lazy< T > object thread safe does not protect the lazily initialized object. If multiple threads can access the lazily initialized object, you must make its properties and methods safe for multithreaded access.
-
@EricJ。当然,当然。初始化对象时只能获得线程安全性,但稍后您需要像其他任何对象一样处理同步。
-
@EricJ。我已经更新了答案以反映这一点!
我认为这取决于你的初始化。我可能不会为列表做这个,因为构建成本非常小,所以它可以进入构造函数。但如果它是一个预先填充的列表,那么我可能不会在第一次需要它之前。
基本上,如果构建成本超过对每个访问进行条件检查的成本,那么懒惰创建它。如果没有,请在构造函数中执行。
我可以看到的缺点是,如果你想询问Bars是否为null,它将永远不会,并且你将在那里创建列表。
-
我不认为这是一个缺点。
-
为什么这是一个缺点?只需检查任何而不是null。如果(!Foo.Bars.Any())
-
@PeterPorfy:它违反了POLS。你把null放入,但不要把它取回来。通常情况下,您假设您获得与放入房产相同的价值。
-
@DanielHilgarth再次感谢。这是一个非常有效的论点,我以前没有考虑过。
-
@DanielHilgarth这是真的。我认为答案告诉我,如果某些东西总是返回一个值而从不为null那么那就错了。你说的是完全有效的。
-
POLS,你在开玩笑吗?我想有人刚刚做到了。
-
@AMissico:如果您不了解POLS / POLA,您可能想点击链接。在设计用户界面(API是)时,这是一个非常重要的原则。
-
@AMissico:它不是一个概念化的。与推动前门旁边的按钮预期敲响门铃的方式大致相同,看起来像属性的东西预计会像属性一样。打开脚下的活板门是一种令人惊讶的行为,特别是如果按钮没有这样标记的话。
-
@insta,我是讽刺的。
我只想对丹尼尔的回答发表评论,但老实说,我认为这远远不够。
虽然这是在某些情况下使用的非常好的模式(例如,当从数据库初始化对象时),但这是一个可怕的习惯。
关于对象的最好的事情之一是它提供了一个安全,可信赖的环境。最好的情况是如果你创建尽可能多的字段"Final",用构造函数填充它们。这使你的课程非常防弹。允许通过setter更改字段的情况稍微少一些,但并不可怕。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class SafeClass
{
String name="";
Integer age=0;
public void setName(String newName)
{
assert(newName != null)
name=newName;
}// follow this pattern for age
...
public String toString() {
String s="Safe Class has name:"+name+" and age:"+age
}
} |
使用您的模式,toString方法如下所示:
1 2 3 4 5 6 7 8
| if(name == null)
throw new IllegalStateException ("SafeClass got into an illegal state! name is null")
if(age == null)
throw new IllegalStateException ("SafeClass got into an illegal state! age is null")
public String toString () {
String s ="Safe Class has name:"+name +" and age:"+age
} |
不仅如此,你需要在你的类中可能使用该对象的任何地方进行空检查(由于getter中的null检查,你的类之外是安全的,但你应该主要在类中使用你的类成员)
此外,你的类永远处于不确定的状态 - 例如,如果你决定通过添加一些注释使该类成为一个hibernate类,你会怎么做?
如果你根据一些没有要求和测试的微观验证做出任何决定,那几乎肯定是错误的决定。事实上,即使在最理想的情况下,你的模式实际上很有可能实际上减慢了系统的速度,因为if语句会导致CPU上的分支预测失败,这将使事情减慢许多倍。只是在构造函数中指定一个值,除非您创建的对象相当复杂或来自远程数据源。
有关brance预测问题的例子(您反复发生,仅发生一次),请参阅这个令人敬畏的问题的第一个答案:为什么处理排序数组比处理未排序数组更快?
-
感谢您的输入。在我的例子中,没有一个类有任何可能需要检查null的方法,所以这不是问题。我会考虑你的其他异议。
-
我真的不明白。这意味着您没有在存储它们的类中使用您的成员 - 您只是将这些类用作数据结构。如果是这种情况,您可能需要阅读javaworld.com/javaworld/jw-01-2004/jw-0102-toolbox.html,它很好地描述了如何通过避免外部操纵对象状态来改进代码。如果您在内部操作它们,那么如何在不重复检查所有内容的情况下这样做?
-
这个答案的部分内容很好,但部分内容似乎很人为。通常在使用此模式时,toString()将调用getName(),而不是直接使用name。
-
@BillK是的,这些类是一个巨大的数据结构。所有工作都是在静态类中完成的。我将在链接中查看该文章。谢谢!
-
@izkata实际上在课堂上看起来似乎是一个折腾天气你是否使用吸气剂,我工作过的大多数地方直接使用了这个成员。除此之外,如果你总是使用getter,那么if()方法会更有害,因为分支预测失败会更频繁地发生,并且由于分支,运行时可能会更难解决getter问题。然而,对于约翰的启示,他们是数据结构和静态类,这是我最关心的事情,这一切都没有实际意义。
惰性实例化/初始化是一种完全可行的模式。但请记住,作为一般规则,API的消费者不希望getter和setter从最终用户POV(或失败)中获取可辨别的时间。
-
我同意,我已经编辑了一些问题。我希望完整的底层构造函数链比仅在需要时实例化类需要更多的时间。
让我再向其他人提出的许多好点再补充一点......
调试器将(在默认情况下)在单步执行代码时评估属性,这可能比仅通过执行代码通常会更快地实例化Bar。换句话说,仅仅调试的行为就是改变程序的执行。
这可能是也可能不是问题(取决于副作用),但需要注意。
你确定Foo应该实例化任何东西吗?
对我来说,让Foo实例化任何东西似乎都很臭(尽管不一定是错误的)。除非Foo明确表示要成为工厂,否则它不应该实例化它自己的协作者,而是将它们注入构造函数中。
但是,如果Foo的目的是创建Bar类型的实例,那么我没有看到懒惰地做这件事有什么不妥。
-
这是一个单身人士模式......
-
@BenjaminGruenbaum不,不是真的。恭敬地,即使它是,你想要做什么?