好或坏的做法?在getter中初始化对象 [英] Good or bad practice? Initializing objects in getter

查看:182
本文介绍了好或坏的做法?在getter中初始化对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个奇怪的习惯,似乎...根据我的同事至少。我们一起在一个小项目上工作。我写这个类的方式是(简化的例子):

  [Serializable()] 
public class Foo
{
public Foo()
{}

private Bar _bar;

public Bar Bar
{
get
{
if(_bar == null)
_bar = new Bar();

返回_bar;
}
set {_bar = value; }
}
}

所以,基本上,我只是初始化任何字段调用一个getter并且该字段仍然为null。我认为这样做可以通过不初始化任何不被使用的任何属性来减少重载。



ETA:我这样做的原因是我的类有几个属性返回另一个类的实例,反过来又具有更多类的属性,等等。调用顶级的构造函数随后将调用所有这些类的所有构造函数,当它们不是始终需要时。



有没有反对这种做法,而不是个人喜好?



更新:我已经考虑了关于这个问题的许多不同意见,我会坚持我接受的答案。然而,我现在对这个概念有了更好的了解,我可以决定何时使用它,何时不能。



缺点:




  • 线程安全问题

  • 当值传递为null时,不遵守setter请求

  • 微型优化

  • 异常处理应该在构造函数中进行

  • 需要在类'代码



优点:




  • 微型优化

  • 属性永不返回null

  • 延迟或避免加载重对象



大部分的缺点不适用于我当前的图书馆,不过我必须先测试一下,看看微型优化是否实际上是优化任何东西。



最近更新:



好的,我改变了我的答案。我原来的问题是这是否是一个好习惯。而现在我确信这不是。也许我仍然会在我现在的代码的一些部分使用它,但不是无条件地,绝对不是所有的时间。所以我会失去我的习惯,在使用它之前先考虑一下。感谢大家!

解决方案

你在这里是一个天真的 - 懒惰初始化的实现。
$ b

简短的答案:



使用惰性初始化无条件不是一个好主意。它有它的地方,但一个必须考虑到这个解决方案的影响。



背景和解释:



具体实现:

我们先看看你的具体示例,为什么我认为它的实现是天真的:


  1. 它违反了最不起眼的原则(POLS)。当一个值分配给一个属性时,预期该值被返回。在您的实现中,这不是 null

      foo。 Bar = null; 
    Assert.Null(foo.Bar); //这将失败


  2. 它引入了一些线程问题:两个呼叫者不同线程上的foo.Bar 可能会获得两个不同的 Bar 实例,其中一个将不连接到 Foo 实例。对 Bar 实例所做的任何更改都将静默丢失。

    这是另一种违反POLS的情况。当只访问一个属性的存储值时,预期它是线程安全的。虽然你可以认为这个类根本不是线程安全的,包括你的属性的getter,但是你必须正确地记录它,因为这不是正常的情况。此外,这个问题的引入是不必要的,我们将很快看到。

一般来说:

现在是时候看一般的懒惰初始化:

懒惰初始化通常用于延迟构建需要很长时间的对象的构造,或者需要很多时间一个完全构造的内存。

这是使用延迟初始化的一个非常有效的原因。



但是,这样的属性通常不具有设置器,可以摆脱上面指出的第一个问题。

此外,线程安全将使用实施,例如 Lazy< T> - 以避免第二个问题。



即使在实施懒惰财产时考虑这两点,以下几点是这种模式的一般问题:


  1. 对象的构造可能不成功,导致属性getter发生异常。这是另一个违反POLS的行为,因此应该避免。即使属性部分在开发类库的设计指南中明确指出,属性getter不应抛出异常:


    避免抛出异常属性getter。



    属性getter应该是没有任何前提条件的简单操作。如果一个getter可能会抛出一个异常,可以考虑将该属性重新设计为一种方法。



  2. 编译器自动优化受到伤害即内联和分支预测。详情请参阅 Bill K的回答


这些要点的结论如下:

对于每个单独实施的属性,您应该考虑这些要点。

这意味着这是一个一个案例的决定,不能被视为一般的最佳做法。



此模式有其所在,但实现类并不是一般的最佳做法。由于上述原因,不能无条件地使用






在本节中,我想讨论其他人提出的一些要点,作为无条件使用延迟初始化的论据:


  1. 序列化:

    EricJ在一个评论中说:


    可以序列化的对象在反序列化时不会被调用(取决于序列化程序,但许多常见的行为就像这样)。将初始化代码放在构造函数中意味着必须为反序列化提供额外的支持。这种模式避免了特殊编码。


    这个参数有几个问题:


    1. 大多数对象永远不会被序列化。在不需要的情况下为其添加某种支持会违反 YAGNI

    2. 当一个类需要支持序列化时,有一些方法可以启用它,而不需要与乍看之下与序列化无关的解决方法。


  2. 微优化:
    您的主要参数是只有在有人实际访问对象时才想构建对象。所以你实际上在谈论优化内存使用情况。

    我不同意这个观点,原因如下:


    1. 在大多数情况下,内存中的几个对象对任何内容都没有任何影响。现代电脑有足够的记忆力。没有一个由分析器确认的实际问题的情况,这是预成熟优化,那里是很好的理由反对它。

    2. 我承认有时这种优化是有道理的事实。但即使在这些情况下,懒惰初始化似乎也不是正确的解决方案。有两个原因反对:


      1. 懒惰初始化可能会伤害性能。也许只有一点,但正如比尔的答案所表明的那样,影响力大于乍一看。所以这个方法基本上是表现与内存的交换。

      2. 如果你有一个常见的用例,只使用这个类的一部分,那么这就暗示了设计本身的一个问题:有问题的班最有可能有一个以上的责任。解决方案是将课程分成几个更专注的课程。




I have a strange habit it seems... according to my co-worker at least. We've been working on a small project together. The way I wrote the classes is (simplified example):

[Serializable()]
public class Foo
{
    public Foo()
    { }

    private Bar _bar;

    public Bar Bar
    {
        get
        {
            if (_bar == null)
                _bar = new Bar();

            return _bar;
        }
        set { _bar = value; }
    }
}

So, basically, I only initialize any field when a getter is called and the field is still null. I figured this would reduce overload by not initializing any properties that aren't used anywhere.

ETA: The reason I did this is that my class has several properties that return an instance of another class, which in turn also have properties with yet more classes, and so on. Calling the constructor for the top class would subsequently call all constructors for all these classes, when they are not always all needed.

Are there any objections against this practice, other than personal preference?

UPDATE: I have considered the many differing opinions in regards to this question and I will stand by my accepted answer. However, I have now come to a much better understanding of the concept and I'm able to decide when to use it and when not.

Cons:

  • Thread safety issues
  • Not obeying a "setter" request when the value passed is null
  • Micro-optimizations
  • Exception handling should take place in a constructor
  • Need to check for null in class' code

Pros:

  • Micro-optimizations
  • Properties never return null
  • Delay or avoid loading "heavy" objects

Most of the cons are not applicable to my current library, however I would have to test to see if the "micro-optimizations" are actually optimizing anything at all.

LAST UPDATE:

Okay, I changed my answer. My original question was whether or not this is a good habit. And I'm now convinced that it's not. Maybe I will still use it in some parts of my current code, but not unconditionally and definitely not all the time. So I'm going to lose my habit and think about it before using it. Thanks everyone!

解决方案

What you have here is a - naive - implementation of "lazy initialization".

Short answer:

Using lazy initialization unconditionally is not a good idea. It has its places but one has to take into consideration the impacts this solution has.

Background and explanation:

Concrete implementation:
Let's first look at your concrete sample and why I consider its implementation naive:

  1. It violates the Principle of Least Surprise (POLS). When a value is assigned to a property, it is expected that this value is returned. In your implementation this is not the case for null:

    foo.Bar = null;
    Assert.Null(foo.Bar); // This will fail
    

  2. It introduces quite some threading issues: Two callers of foo.Bar on different threads can potentially get two different instances of Bar and one of them will be without a connection to the Foo instance. Any changes made to that Bar instance are silently lost.
    This is another case of a violation of POLS. When only the stored value of a property is accessed it is expected to be thread-safe. While you could argue that the class simply isn't thread-safe - including the getter of your property - you would have to document this properly as that's not the normal case. Furthermore the introduction of this issue is unnecessary as we will see shortly.

In general:
It's now time to look at lazy initialization in general:
Lazy initialization is usually used to delay the construction of objects that take a long time to be constructed or that take a lot of memory once fully constructed.
That is a very valid reason for using lazy initialization.

However, such properties normally don't have setters, which gets rid of the first issue pointed out above.
Furthermore, a thread-safe implementation would be used - like Lazy<T> - to avoid the second issue.

Even when considering these two points in the implementation of a lazy property, the following points are general problems of this pattern:

  1. Construction of the object could be unsuccessful, resulting in an exception from a property getter. This is yet another violation of POLS and therefore should be avoided. Even the section on properties in the "Design Guidelines for Developing Class Libraries" explicitly states that property getters shouldn't throw exceptions:

    Avoid throwing exceptions from property getters.

    Property getters should be simple operations without any preconditions. If a getter might throw an exception, consider redesigning the property to be a method.

  2. Automatic optimizations by the compiler are hurt, namely inlining and branch prediction. Please see Bill K's answer for a detailed explanation.

The conclusion of these points is the following:
For each single property that is implemented lazily, you should have considered these points.
That means, that it is a per-case decision and can't be taken as a general best practice.

This pattern has its place, but it is not a general best practice when implementing classes. It should not be used unconditionally, because of the reasons stated above.


In this section I want to discuss some of the points others have brought forward as arguments for using lazy initialization unconditionally:

  1. Serialization:
    EricJ states in one comment:

    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.

    There are several problems with this argument:

    1. Most objects never will be serialized. Adding some sort of support for it when it is not needed violates YAGNI.
    2. When a class needs to support serialization there exist ways to enable it without a workaround that doesn't have anything to do with serialization at first glance.

  2. Micro-optimization: Your main argument is that you want to construct the objects only when someone actually accesses them. So you are actually talking about optimizing the memory usage.
    I don't agree with this argument for the following reasons:

    1. In most cases, a few more objects in memory have no impact whatsoever on anything. Modern computers have way enough memory. Without a case of actual problems confirmed by a profiler, this is pre-mature optimization and there are good reasons against it.
    2. I acknowledge the fact that sometimes this kind of optimization is justified. But even in these cases lazy initialization doesn't seem to be the correct solution. There are two reasons speaking against it:

      1. Lazy initialization potentially hurts performance. Maybe only marginally, but as Bill's answer showed, the impact is greater than one might think at first glance. So this approach basically trades performance versus memory.
      2. If you have a design where it is a common use case to use only parts of the class, this hints at a problem with the design itself: The class in question most likely has more than one responsibility. The solution would be to split the class into several more focused classes.

这篇关于好或坏的做法?在getter中初始化对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆