Initialize方法是代码味道吗?

时间:2011-10-31 20:42:44

标签: c# coding-style

我正在编写一堆系统。它们不是来自通用接口。

一些示例系统:MusicSystemPhysicsSystemInputSystem等等。

目前,MusicSystem在其构造函数中加载了大量音频文件,因此,首次创建对象时可能会有一些短暂的延迟。

因此,加载所有音频文件的代码是否应该放在Initialize()方法中?这允许程序员确定何时加载音频文件,但如果他忘记调用Initialize(),程序将崩溃。

因为并非所有系统都需要Initialize()方法,程序员必须查看每个系统以查看该类是否具有Initialize()方法,如果是,则调用它。这有点麻烦。

在一般设计原则方面哪种方法更可取?

16 个答案:

答案 0 :(得分:21)

考虑您编写代码的其他API。 API的最后一次是否需要程序员知道来调用init方法,否则会在运行时崩溃?

作为API的使用者,如果我必须知道在构造对象后调用init方法,它会驱动我坚果。我会推荐一个我亲眼看到并使用过的替代方法:记录昂贵的对象实例化。如果程序不需要崩溃,那么推迟昂贵的初始化是什么意思?

答案 1 :(得分:12)

基本上,您正在尝试平衡程序效率与程序 mer 效率。因此,在这种情况下,它不一定是代码气味,具体取决于哪个更重要。您是希望使代码更容易使用,更难破解,还是让程序加载更快?

但是,您可以尝试的替代方法是lazy-loading

然后,你可以吃蛋糕并吃掉它:

  • 客户端代码无需致电Initialize
  • 除非需要,否则不会加载数据

答案 2 :(得分:11)

将重度初始化移出构造函数不是代码味道。

然而,依靠外部调用者来调用初始化是一种气味 - 它被称为时间耦合

在您的课程上创建Initialize()方法,但如果必须,请将其设为private(或protected

还编写一个EnsureInitialized()方法,在需要时触发初始化,但每个实例只触发一次。

然后,您班级的每个公共入口点都应该在开始时调用EnsureInitialized() - 您的初始化将被推迟到首次使用时。

有了这个,如果你对锁感到满意,你可以将对EnsureInitalized()的调用转移到后台线程,以便在后台完成工作,对前景影响最小。

答案 3 :(得分:5)

我不认为初始化方法是一种代码味道,但如果用户无法调用初始化方法,那么崩溃(而不是优雅地失败)确实听起来像是一个。

如果您确保所有系统都采用Initialize()方法(即使它什么都不做),那么您的用户无需担心是否要调用它。

答案 4 :(得分:4)

这里几点。

首先,虽然使用Initialize方法编写接口并不一定是错误的,但通常认为依靠服务/ API的使用者在能够使用之前自行配置某种状态通常是不好的做法提供的功能。

其次,我不确定“一些短暂的滞后”是什么意思。如果问题是您的UI在加载时失去响应,则可以使用后台任务来消除延迟。例如,查看BackgroundWorker,或实现一些其他多线程行为以获得您想要的位置。

答案 5 :(得分:3)

只是一个想法:在构造函数中添加一个bool - 调用者是否想在构造函数中初始化。

答案 6 :(得分:2)

如何执行初始化的private / protected方法,并在执行需要初始化的任何方法时在内部调用initialize?

E.g。

public class MyClass
{
  private bool _isInitialized;

  public MyClass()
  {
    ... only basic initializations...
  }

  private void initialize()
  {
    if (_isInitialized)
      return;

    // initialize here
  }

  public void SimpleMethod()
  {
    // doesn't need to initialize
  }

  public void ComplexMethod()
  {
    initialize();

    // do something...
  }
}

答案 7 :(得分:2)

我认为一个好主意是记录特定类对于实例来说是“昂贵的”。然后,程序员只有在绝对需要使用时才会实例化该对象。

答案 8 :(得分:1)

为什么不重载构造函数,使用布尔值来允许程序员指定是否要通过昂贵的初始化方法。

然后,您不必检查是否存在特殊方法。

答案 9 :(得分:1)

如果音频文件始终相同。您可以尝试从静态属性加载它们。一旦对象加载音频文件。如果音频文件列表存储在静态属性中,则所有人都可以使用它们。

答案 10 :(得分:1)

您的构造函数可以启动执行加载的后台线程。然后,类中需要使用该数据的任何内容只是检查异步加载是否已完成,如果没有等待。

这使得类构造更快,它隐藏了类消费者的所有多线程细节,并且它摆脱了丑陋的init模式。

答案 11 :(得分:1)

根本不是“代码味道”。有这样的东西并不罕见。例如,查看.NET中的SqlConnection类。有一个没有参数的默认构造函数,并且有一个构造函数接受一个连接字符串。如果要连接到数据库并使用连接超时的默认值等,那么获取连接字符串的方法很方便。但是如果要更改这些属性,或者如果要准备连接实例但控制更多确切地说,当它连接时,您调用默认构造函数,设置属性,然后在准备好连接时调用Open

答案 12 :(得分:1)

起初我会说它不是代码味道,因为它允许程序员决定何时初始化一个对象,比如,通过使用工作线程并行执行所有对象。

但后来我想,这是代码味道,因为同步或异步构造的选择可以很容易地在构造函数本身中实现,可能有一个构造函数参数更喜欢一个而不是另一个。

然后我想,那些不可能抛出异常的场景呢?通过在构造函数中设置所有数据,如果没有除IsInitialised之类的访问器之外没有异常功能,则无法指示失败。 (Symbian是不支持异常的操作系统示例。)

所以我觉得臭味真的取决于你所处的环境以及你个人的喜好。

答案 13 :(得分:1)

你需要考虑你的*系统的碎片,因为看起来你有一个超级主类型,它统治全世界并在构造函数中完成大部分工作。哪个不是很好,看起来很臭并且破坏了SOLID原则。

您可以将长时间运行和IO操作移出到专门的包装器中,然后您可以将其作为参数传递,例如.Net中的Stream,Connection,IDataReader等。有了这个,如果操作可能消耗大量的CPU,内存或IO吞吐量,那么它将更容易预测。

答案 14 :(得分:0)

如果你有一个没有意义的“MusicSystem”(并通过炸毁来表达)而没有初始化,我很难理解用例来实例化这个对象而不是初始化它。

如果这是一个从一个地方到另一个地方传递它的问题,我可能会建议您查看Generic Lazy,在那里你可以懒惰加载该对象。

这样,你就可以避免必须实例化的不自然耦合,然后立即知道调用Initialize(),但是在实际使用对象之前你会得到没有费用的好处。

至于代码气味,我个人认为Initialize()方法是代码气味。当然,这并不总是表明这是错误的,但它通常告诉我,依赖倒置的机会正在被忽略。如果我必须实例化一些东西然后调用Initialize(),我想知道为什么我不能实例化该对象然后传递它所需要的任何东西以便被认为是初始化(或者为什么它不要求初始化的Foo in它的构造函数)。

答案 15 :(得分:0)

当然不是。根据MSDN的指导原则,构造函数应该做最少的工作。因此,资源分配或繁重的对象初始化等事情需要放在其他地方,如Initialize()。

https://msdn.microsoft.com/en-us/library/ms229060(v=vs.110).aspx

  

除了捕获构造函数参数之外,构造函数不应该做很多工作。任何其他处理的成本应该延迟到需要。

相关问题