哪种更好的做法-辅助方法是实例还是静态?


48

这个问题是主观的,但是我很好奇大多数程序员是如何做到这一点的。下面的示例使用伪C#,但这也应适用于Java,C ++和其他OOP语言。

无论如何,当在我的类中编写辅助方法时,我倾向于将它们声明为静态方法,并且仅在辅助方法需要它们时才传递字段。例如,给定以下代码,我更喜欢使用方法调用#2

class Foo
{
  Bar _bar;

  public void DoSomethingWithBar()
  {
    // Method Call #1.
    DoSomethingWithBarImpl();

    // Method Call #2.
    DoSomethingWithBarImpl(_bar);
  }

  private void DoSomethingWithBarImpl()
  {
    _bar.DoSomething();
  }

  private static void DoSomethingWithBarImpl(Bar bar)
  {
    bar.DoSomething();
  }
}

我这样做的原因是,它使(至少在我看来)清楚地知道了helper方法可能对其他对象产生副作用-即使没有阅读其实现方法。我发现我可以快速掌握使用该实践的方法,从而帮助我进行调试。

更喜欢在自己的代码中执行哪些操作?这样做的原因是什么?


3
我将从这个问题中删除C ++:显然,在C ++中,您可以将其设为一种免费方法,因为将其任意地放入对象中没有意义:它会禁用ADL。
Matthieu M.

1
@MatthieuM .:是否有免费方法,没关系。我的问题的目的是询问将辅助方法声明为实例方法是否有任何好处。因此,在C ++中,选择可以是实例方法还是自由方法/函数。您对ADL有一个很好的了解。那么,您是说您更喜欢使用自由方法吗?谢谢!
伊莲

2
对于C ++,建议使用免费方法。它们增加了封装(间接地,因为只能访问公共接口),并且由于ADL(特别是在模板中)而仍然表现良好。但是我不知道这是否适用于Java / C#,C ++与Java / C#有很大的不同,所以我希望答案会有所不同(因此,我认为同时要求这3种语言是没有道理的)。
Matthieu M.


1
如果不尝试加剧任何人的负担,我从未听说过不使用静态方法的充分理由。我会尽可能使用它们,因为它们使方法的作用更加明显。
Sridhar Sarnobat 2015年

Answers:


23

这真的取决于。正如Péter指出的那样,如果您的助手使用的值是基元,则静态方法是一个不错的选择。

如果它们是复杂的,那么SOLID适用,更具体的小号,在ð

例:

class CookieJar {
      function takeCookies(count:Int):Array<Cookie> { ... }
      function countCookies():Int { ... }
      function ressuplyCookies(cookies:Array<Cookie>
      ... // lot of stuff we don't care about now
}

class CookieFan {
      function getHunger():Float;
      function eatCookies(cookies:Array<Cookie>):Smile { ... }
}

class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieJar;
      function makeEveryBodyAsHappyAsPossible():Void {
           //perform a lot of operations on jake, jane and the cookies
      }
      public function cookieTime():Void {
           makeEveryBodyAsHappyAsPossible();
      }
}

这将与您的问题有关。您可以创建makeEveryBodyAsHappyAsPossible一个静态方法,该方法将使用必要的参数。另一个选择是:

interface CookieDistributor {
    function distributeCookies(to:Array<CookieFan>):Array<Smile>;
}
class HappynessMaximizingDistributor implements CookieDistributor {
    var jar:CookieJar;
    function distributeCookies(to:Array<CookieFan>):Array<Smile> {
         //put the logic of makeEveryBodyAsHappyAsPossible here
    }
}
//and make a change here
class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieDistributor;

      public function cookieTime():Void {
           cookies.distributeCookies([jake, jane]);
      }
}

现在OurHouse无需了解Cookie分发规则的复杂性。它现在仅必须是一个对象,该对象将实现规则。该实现被抽象为一个对象,对象的全部责任是应用规则。可以单独测试该对象。OurHouse可以仅使用的模拟进行测试CookieDistributor。而且,您可以轻松地决定更改Cookie分配规则。

但是,请注意不要过度使用它。例如,有一个由30个类组成的复杂系统充当的实现CookieDistributor,而每个类仅完成一项微小的任务,就没有任何意义。我对SRP的解释是,它不仅规定每个班级可能只有一个责任,而且还要求由一个班级承担单个责任。

对于基元或像基元一样使用的对象(例如,表示空间中的点,矩阵或某物的对象),静态助手类很有用。如果您有选择,并且确实有道理,那么您实际上可以考虑将一种方法添加到表示数据的类中,例如Point,拥有一个add方法是明智的。同样,不要过度使用它。

因此,根据您的问题,有不同的解决方法。


18

声明实用程序类的方法是众所周知的习惯用法static,因此无需实例化此类。遵循这个习惯用法使您的代码更易于理解,这是一件好事。

但是,这种方法有一个严重的局限性:此类方法/类不容易被模拟(尽管AFAIK至少对于C#而言,有一些模拟框架甚至可以实现这一目的,但是它们并不常见,至少其中一些是商业)。因此,如果一个辅助方法具有任何外部依赖关系(例如,数据库),使它(即其调用方)难以进行单元测试,则最好将其声明为非静态的。这允许依赖项注入,从而使方法的调用者更易于进行单元测试。

更新资料

澄清:上面讨论了实用程序类,它仅包含低级辅助方法,(通常)不包含状态。有状态的非实用程序类中的Helper方法是另一个问题。对于误解OP表示歉意。

在这种情况下,我感觉到代码的味道:主要在类B的实例上运行的类A的方法实际上可能在类B中有更好的位置。但是,如果我决定将其保留在原处,则我更喜欢选项1,因为它更容易阅读。


您不能仅将依赖项传递给静态帮助器方法吗?
伊莲

1
@JackOfAllTrades,如果该方法的唯一目的是处理外部资源,则几乎没有必要注入该依赖项。否则,它可能是一个解决方案(尽管在后一种情况下,该方法可能违反了单一职责原则,因此是重构的一种候选方法)。
彼得Török

1
静态变量很容易被“模拟”-Microsoft Moles或Fakes(取决于VS2010或VS2012 +)允许您在测试中用自己的实现替换静态方法。使旧的模拟框架过时。(它也可以进行传统的模拟,也可以模拟具体的类)。它可以通过扩展管理器从MS中免费获得。
gbjbaanb 2013年

4

您更喜欢用自己的代码执行哪些操作

我更喜欢#1(this->DoSomethingWithBarImpl();),除非当然hel​​per方法不需要访问实例的数据/实现static t_image OpenDefaultImage()

您这样做的原因是什么?

公共接口和私有实现以及成员是分开的。如果我选择了#2,程序将更加难以阅读。与#1,我总是有成员和实例可用。与许多基于OO的实现相比,我倾向于使用大量的封装,每个类的成员很少。至于副作用-我可以接受,经常有状态验证可以扩展依赖关系(例如,一个参数需要传递)。

#1更易于阅读,维护并具有良好的性能特征。当然,在某些情况下,您可以共享一个实现:

static void ValidateImage(const t_image& image);
void validateImages() const {
    ValidateImage(this->innerImage());
    ValidateImage(this->outerImage());
}

如果#2是代码库中的一个很好的通用解决方案(例如默认值),那么我将关注设计的结构:这些类是否做得太多?没有足够的封装或没有足够的重用?抽象水平足够高吗?

最后,如果您使用支持突变的OO语言(例如const方法),则#2似乎是多余的。

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.