どちらがより良い習慣ですか-インスタンスまたは静的としてのヘルパーメソッド?


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

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

あなたはどちらか好む独自のコードでやってそうするためのあなたの理由は何ですか?


3
この質問からC ++を削除します。C++では、明らかに、自由にメソッドにすることができます。オブジェクトに任意に隠しておく意味がないため、ADLが無効になります。
マチューM.

1
@MatthieuM .:自由な方法かどうか、それは問題ではありません。私の質問の意図は、ヘルパーメソッドをインスタンスメソッドとして宣言することに利点があるかどうかを尋ねることでした。したがって、C ++では、インスタンスメソッドとフリーメソッド/関数を選択できます。ADLについて良い点があります。それで、あなたは無料の方法を使うことを好むと言っているのですか?ありがとう!
イリアン

2
C ++の場合、無料のメソッドをお勧めします。それらはカプセル化を増やし(間接的に、パブリックインターフェイスにのみアクセスできるため)、ADLのおかげで(特にテンプレートで)うまく動作します。ただし、これがJava / C#に適用できるかどうかはわかりませんが、C ++はJava / C#とは大きく異なるため、答えが異なると予想されます(したがって、3つの言語を一緒に要求することは理にかなっていないと思います)。
マチューM.


1
誰かを悪化させようとせずに、静的メソッドを使用しない正当な理由を聞いたことはありません。メソッドが何をするのかをより明確にするので、できる限りそれらを使用します。
スリダールサルノバト

回答:


23

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

それらが複雑な場合、SOLID、より具体的にはSI、および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();
      }
}

これはあなたの問題についてでしょう。あなたはできる作り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クッキー配分ルールの複雑さについて知っている必要はありません。ルールを実装するオブジェクトのみが必要になります。実装は抽象化されてオブジェクトになります。オブジェクトの唯一の責任はルールを適用することです。このオブジェクトは単独でテストできます。OurHouse単なるモックを使用してテストできますCookieDistributor。また、Cookieの配布ルールを簡単に変更できます。

ただし、無理をしないように注意してください。たとえば、30クラスの複雑なシステムがの​​実装として機能するCookieDistributor場合、各クラスは単に小さなタスクを実行するだけで、実際には意味がありません。SRPの私の解釈は、各クラスが1つの責任しか持たないことを指示するだけでなく、1つのクラスが1つの責任を実行する必要があるということです。

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

そのため、問題に応じて、さまざまな方法で対処できます。


18

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

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

更新

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

この場合、コードの匂いを感じます:クラスBのインスタンスで主に動作するクラスAのメソッドは、実際にはクラスBでより良い場所を持っているかもしれません。シンプルで読みやすいので。


依存関係を静的ヘルパーメソッドに渡すことはできませんか?
イリアン

1
@JackOfAllTrades、メソッドの唯一の目的が外部リソースを扱うことである場合、その依存関係を注入するポイントはほとんどありません。それ以外の場合、それは解決策かもしれません(後者の場合、メソッドはおそらく単一責任原則を破るので、リファクタリングの候補になります)。
ペテルトレック

1
staticは簡単に「モック」されます-Microsoft MolesまたはFakes(VS2010またはVS2012 +に依存)を使用すると、テストで静的メソッドを独自の実装に置き換えることができます。古いモックフレームワークを廃止します。(従来のモックも実行し、具象クラスもモックできます)。拡張機能マネージャーを介してMSから解放されます。
gbjbaanb

4

あなたはあなた自身のコードでどちらをするのが好きですか

this->DoSomethingWithBarImpl();もちろんヘルパーメソッドがインスタンスのdata / implementationにアクセスする必要がない限り、私は#1()を好みますstatic t_image OpenDefaultImage()

そして、そうする理由は何ですか?

パブリックインターフェイスとプライベート実装およびメンバーは分離されています。#2を選択した場合、プログラムは読みにくくなります。#1では、常にメンバーとインスタンスを使用できます。多くのオブジェクト指向ベースの実装と比較して、私は多くのカプセル化を使用する傾向があり、クラスごとに非常に少ないメンバーです。副作用に関しては-私はそれで問題ありません、多くの場合、依存関係を拡張する状態検証があります(たとえば、渡す必要がある引数)。

#1は、読み取り、保守がはるかに簡単で、優れたパフォーマンス特性を備えています。もちろん、実装を共有できる場合があります。

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

#2がコードベースで適切な一般的なソリューション(たとえば、デフォルト)である場合、デザインの構造が心配になります。クラスが多すぎますか?十分なカプセル化または再利用が不十分ですか?抽象化レベルは十分に高いですか?

最後に、突然変異がサポートされているオブジェクト指向言語(constメソッドなど)を使用している場合、#2は冗長に見えます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.