web-dev-qa-db-ja.com

インスタンスまたは静的ヘルパーメソッドのどちらが優れていますか。

この質問は主観的ですが、私はほとんどのプログラマがこれにどのように取り組んでいるのか知りたく思いました。以下のサンプルは疑似C#にありますが、これはJava、C++、およびその他のOOP言語にも適用されます。

とにかく、私のクラスでヘルパーメソッドを作成するとき、私はそれらを静的として宣言し、ヘルパーメソッドで必要な場合はフィールドを渡す傾向があります。たとえば、以下のコードを考えると、私はMethod Call#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();
  }
}

これを行う理由は、ヘルパーメソッドが他のオブジェクトに副作用を及ぼす可能性があることを(少なくとも私の目には)明らかにしているためです。この方法を使用するメソッドをすばやく理解できるため、デバッグに役立ちます。

あなたはあなた自身のコードでどちらをするpreferし、そうする理由は何ですか?

48
Ilian Pinzon

これは本当に依存します。ヘルパーが操作する値がプリミティブである場合、Péterが指摘したように、静的メソッドが適切な選択です。

それらが複雑な場合、 [〜#〜] solid [〜#〜] が適用され、より具体的には [〜#〜] s [〜#〜] 、- [〜#〜] i [〜#〜] および [〜#〜] d [〜#〜]

例:

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();
      }
}

これはあなたの問題です。あなたはcanmakeEveryBodyAsHappyAsPossibleを静的メソッドにすることができ、必要なパラメータを受け取ります。別のオプションは次のとおりです。

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の解釈は、各クラスが1つの責任しか持たない可能性があるだけでなく、1つの責任は1つのクラスによって実行される必要があることを示しています。

プリミティブのように使用するプリミティブまたはオブジェクト(たとえば、空間内の点、行列などを表すオブジェクト)の場合、静的ヘルパークラスは非常に有効です。選択肢があり、それが本当に理にかなっている場合は、実際に、データを表すクラスにメソッドを追加することを検討できます。 Pointaddメソッドを持つことは賢明です。繰り返しますが、無理しないでください。

したがって、問題に応じて、さまざまな方法で対処できます。

23
back2dos

ユーティリティクラスstaticのメソッドを宣言することはよく知られているイディオムなので、そのようなクラスをインスタンス化する必要はありません。このイディオムに従うと、コードが理解しやすくなります。これは良いことです。

ただし、このアプローチには深刻な制限があります:そのようなメソッド/クラスは簡単にモックできない(少なくともC#のAFAIKには、これを実現できるモックフレームワークがありますが、一般的ではありませんそして少なくともそれらのいくつかは商業的です)。したがって、ヘルパーメソッドに外部依存関係(DBなど)がある場合、その呼び出し元-ユニットテストが困難な場合は、静的でないと宣言することをお勧めします。これにより、依存関係の注入が可能になり、メソッドの呼び出し元の単体テストが容易になります。

更新

明確化:上記は、低レベルのヘルパーメソッドのみを含み、(通常)状態を含まないユーティリティクラスについて説明しています。ステートフル非ユーティリティクラス内のヘルパーメソッドは別の問題です。 OPの誤解についての謝罪。

この場合、コードのにおいを感じます。主にクラスBのインスタンスで動作するクラスAのメソッドは、実際にはクラスBでより適切な場所にある可能性があります。シンプルで読みやすいため。

18
Péter Török

あなたはあなた自身のコードでどちらをしたいですか

もちろん、ヘルパーメソッドがインスタンスのデータ/実装this->DoSomethingWithBarImpl();にアクセスする必要がない限り、私は#1(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は冗長に見えます。

4
justin