2009-04-15 15 views
77

クラスの内部状態を追跡する必要がない場合、クラス内のすべてのメソッドをstaticとして宣言する傾向があります。たとえば、AをBに変換する必要があり、変化する内部状態Cに依存しない場合、私は静的変換を作成します。調整できるようにしたい内部状態Cがある場合は、コンストラクタを追加してCを設定し、静的変換は使用しません。多くの静的メソッドを使用していますか?

静的メソッドを過度に使用しないように、さまざまな推奨事項(StackOverflowを含む)を読みましたが、上記の経験則で何が間違っているかを理解できません。

これは合理的なアプローチですか、そうではありませんか?

答えて

119

共通の静的メソッドの2種類があります:

  • 「安全な」静的メソッドは、常に同じ入力に対して同じ出力が得られますが。これはグローバルを変更せず、どのクラスの "安全でない"静的メソッドも呼び出さない。基本的には、限定された種類の関数型プログラミングを使用しています。これらを恐れず、うまくいきます。
  • "安全でない"静的メソッドは、グローバル状態、またはグローバルオブジェクトへのプロキシ、またはその他のテスト不可能な動作を変更します。これらは手続き型プログラミングの後退であり、可能な限りリファクタリングする必要があります。

「安全でない」統計は、たとえばシングルトンパターンなどでよく使用されますが、名前を付けてもグローバル変数を変更するだけであることに注意してください。危険な静電気を使用する前に注意深く考えてください。

+0

これは私が解決しなければならなかったまさに問題だった - シングルトンオブジェクトの使用、またはむしろ誤用。 – overslacked

+0

そのすばらしい答えをありがとう。私の質問は、シングルトンが静的メソッドにパラメータとして渡された場合、静的メソッドが安全でないことになりますか? –

+0

"純関数"と "不純な関数"という用語は、関数型プログラミングで与えられた名前で、 "安全"と "安全でない"という統計に与えられた名前です。 – Omnimike

3

これは合理的なアプローチのようです。あまりにも多くの静的なクラス/メソッドを使用したくない理由は、オブジェクト指向プログラミングから構造化プログラミングの領域に移行することになります。あなたは、単にBに変換されて、あなたの場合は

は、私たちがやっているすべては、その後の静的メソッドは、理にかなって

"hello" =>(transform)=> "<b>Hello!</b>" 

から行くに変換するテキストであると言います。

しかし、オブジェクト上でこれらの静的メソッドを頻繁に呼び出すと、多くの呼び出しで固有の傾向があります(たとえば、使用方法は入力に依存します)。または、その静的メソッドはオブジェクトの固有の動作の一部です。それをオブジェクトの一部とし、オブジェクトの状態を維持することが賢明でしょう。これを行う1つの方法は、インターフェイスとして実装することです。

class Interface{ 
    method toHtml(){ 
     return transformed string (e.g. "<b>Hello!</b>") 
    } 

    method toConsole(){ 
     return transformed string (e.g. "printf Hello!") 
    } 
} 


class Object implements Interface { 
    mystring = "hello" 

    //the implementations of the interface would yield the necessary 
    //functionality, and it is reusable across the board since it 
    //is an interface so... you can make it specific to the object 

    method toHtml() 
    method toConsole() 
} 

編集:静的メソッドを活用する良い例の1つは、Asp.Net MVCまたはRubyのhtmlヘルパーメソッドです。それらは、オブジェクトの動作に結びついていないhtml要素を作成するため、静的です。

編集2:機能プログラミングを構造化プログラミングに変更しました(何らかの理由で私が混乱しました)、それを指摘するTorstenの小道具です。

+2

静的メソッドの使用は関数型プログラミングとは言えないと思うので、構造化プログラミングを意味すると思います。 –

14

内部状態のないオブジェクトは不審なものです。

通常、オブジェクトは状態と動作をカプセル化します。ビヘイビアのみをカプセル化するオブジェクトは奇妙です。場合によってはの軽量の例ですまたはフライウェイトです。

これ以外のときは、オブジェクト言語で行われる手続き型設計です。

+5

私はあなたが言っていることを聞いていますが、Mathオブジェクトのようなものは動作以外の何かをカプセル化できますか? – JonoW

+8

彼はちょうど疑わしい、間違っていないと言った、と彼は絶対に正しいです。 –

+2

@JonoW:数学は多くのステートレス関数がある非常に特殊なケースです。もちろん、JavaでFunctional Programmingをやっているのであれば、多くのステートレス関数があります。 –

1

内部状態が再生されない限り、これは問題ありません。通常、静的メソッドはスレッドセーフであると予想されるため、ヘルパーデータ構造を使用する場合は、スレッドセーフな方法でそれらを使用してください。

5

他のオプションは、元のオブジェクトの非静的メソッドとしてそれらを追加することです:

すなわち、変更:

public class Bar { 
    ... 
    public Foo transform() { ...} 
} 

public class BarUtil { 
    public static Foo transform(Bar toFoo) { ... } 
} 

をしかし、多くの状況で、このISNを可能ではない、XSD/WSDL /などからの通常のクラスコード生成)、それはクラスを非常に長くし、複雑なオブジェクトに対しては変換メソッドが本当に苦痛になることがあります。だから、私はユーティリティクラスに静的メソッドを持っています。

3

静的メソッドから警告される理由は、それらを使用するとオブジェクトの利点の1つが失われるためです。オブジェクトはデータのカプセル化を目的としています。これにより、バグを回避する予期しない副作用を防止します。静的メソッドにはカプセル化されたデータ*がないため、このメリットは得られません。

つまり、内部データを使用していない場合は、使用するのに問題はなく、実行には少し時間がかかります。あなたがグローバルデータに触れていないことを確認してください。

  • 一部の言語には、データと静的メソッドのカプセル化を可能にするクラスレベルの変数もあります。
  • 1

    私は最近、最初に静的クラスとして実装されていたいくつかのクラスを削除/修正するアプリケーションをリファクタリングしました。時間がたつにつれ、これらのクラスはあまりにも多くを獲得し、人々はちょうどインスタンスが浮かんでいなかったので、新しい関数を静的なものとしてタグ付けし続けました。

    私の答えは、静的なクラスは本質的に悪いことではないが、インスタンスを作成してから後でリファクタリングする方が簡単かもしれないということです。

    0

    を知らない場合は、はCの内部状態を使用する必要はありません。将来的に変化するはずですが、メソッドを非静的にする必要があります。それが静的でない場合は、最初に内部状態を無視することができます。

    1

    私は静的メソッドの束とシングルトンを持つクラスの間を行き来していました。どちらも問題を解決しますが、シングルトンは複数のものではるかに簡単に置き換えることができます。 (プログラマはいつも何かが1つしかないと確信しているので、非常に限られたいくつかのケースを除いて、静的メソッドを完全にあきらめるのに十分な時間が間違っていました)。

    とにかく、シングルトンは後で何かをファクトリに渡して別のインスタンスを取得し、リファクタリングせずにプログラム全体の動作を変更することができます。静的メソッドのグローバルクラスを異なる「バッキング」データまたは少し異なる動作(子クラス)を持つものに変更することは、バットの大きな苦痛です。

    静的メソッドには同様の利点はありません。

    はい、悪いです。

    9

    これは本当にジョンミリキンの偉大な答えのフォローアップです。


    それは静的(ほとんど機能している)ステートレスなメソッドを作るために安全であることができますが、それは時々変更することは困難であるカップリングにつながることができます。次のような静的メソッド持って考えてみましょう:

    public class StaticClassVersionOne { 
        public static void doSomeFunkyThing(int arg); 
    } 
    

    お電話のように:あなたは変更する必要がどこにケースに遭遇するまで、すべてが順調と良い、と非常に便利である

    StaticClassVersionOne.doSomeFunkyThing(42); 
    

    静的メソッドの振る舞いを確認し、あなたがしっかりとStaticClassVersionOneにバインドされていることがわかります。コードを修正しても問題ないかもしれませんが、古い動作に依存している他の呼び出し元があった場合は、メソッドの本体で説明する必要があります。場合によっては、メソッド本体がこれらの動作のバランスを取ろうとすると、かなり醜いか維持不能になることがあります。メソッドを分割する場合は、コードをいくつかの場所で変更して、それを考慮したり、新しいクラスを呼び出す必要があります。

    しかし、メソッドを提供するためのインターフェイスを作成して呼び出し元に渡した場合、動作を変更する必要があるときに、新しいクラスを作成してインターフェイスを実装することができます。より保守的であり、その代わりに発信者に与えられる。このシナリオでは、呼び出しクラスを変更する必要はなく、再コンパイルする必要もなく、変更はローカライズされています。

    おそらくそうかもしれませんが、それは考慮する価値があると思います。

    +4

    私は、これが起こりうるシナリオではないと主張しています。これは、統計が最後の手段になります。統計はTDDを悪夢にしている。どこでも静的を使用すると、モックアップできないので、無関係なクラスをテストするために入力と出力が何であるかを知る必要があります。今、静的な動作を変更すると、その静的な静的なクラスを使用するテストは壊れます。また、潜在的に重要な依存関係を開発者に通知するためにコンストラクタを渡すことができない隠された依存関係になります。 –

    2

    私はそれをデザインの匂いと考えています。ほとんど静的メソッドを使って自分自身を見つけた場合は、おそらく非常に良いオブジェクト指向設計を持っていないでしょう。それは必ずしも悪いわけではありませんが、すべてのにおいと同様に、私はそれを止めて再評価するでしょう。それはあなたがより良いオブジェクト指向設計を行うことができるかもしれないということを示唆しているかもしれません。

    4

    静的クラスは、適切な場所で使用されている限り、問題ありません。

    「リーフ」メソッド(状態を変更しないで、入力を何らかの形で変換するだけのメソッド)。これの良い例はPath.Combineのようなものです。これらの種類のものは便利であり、より簡潔な構文にします。静と私が持っている

    問題が多数である:まず

    、あなたは静的なクラスを持っている場合、依存関係が隠されています。以下を考慮してください:

    public static class ResourceLoader 
    { 
        public static void Init(string _rootPath) { ... etc. } 
        public static void GetResource(string _resourceName) { ... etc. } 
        public static void Quit() { ... etc. } 
    } 
    
    public static class TextureManager 
    { 
        private static Dictionary<string, Texture> m_textures; 
    
        public static Init(IEnumerable<GraphicsFormat> _formats) 
        { 
         m_textures = new Dictionary<string, Texture>(); 
    
         foreach(var graphicsFormat in _formats) 
         { 
           // do something to create loading classes for all 
           // supported formats or some other contrived example! 
         } 
        } 
    
        public static Texture GetTexture(string _path) 
        { 
         if(m_textures.ContainsKey(_path)) 
          return m_textures[_path]; 
    
         // How do we know that ResourceLoader is valid at this point? 
         var texture = ResourceLoader.LoadResource(_path); 
         m_textures.Add(_path, texture); 
         return texture; 
        } 
    
        public static Quit() { ... cleanup code }  
    } 
    

    TextureManagerを見ると、どのような初期化ステップがコンストラクタを見ることによって実行されなければならないかはわかりません。依存関係を見つけて、正しい順序で物事を初期化するには、クラスを掘り下げて調べる必要があります。この場合、実行する前にResourceLoaderを初期化する必要があります。この依存関係の悪夢を拡大し、おそらく何が起こるかを推測することができます。明示的な初期化の順序がない場合、コードを維持しようとしているとします。これをインスタンスとの依存関係注入と対照的にしてください。その場合、コードはでも、依存関係が満たされていない場合をコンパイルしません。

    さらに、状態を変更する統計情報を使用すると、カードのようなものになります。誰が何にアクセスできるか分からず、デザインはスパゲッティモンスターに似ています。

    最後に、重要なことに、統計を使用すると特定の実装にプログラムが結び付けられます。静的コードは、テスト容易性のための設計の逆です。静的に包括されたテストコードは悪夢です。静的な呼び出しは、静的型を模倣するために特別に設計されたテストフレームワークを使用していない限り、テストの倍精度型ではスワップできません。したがって、静的なシステムは、それを使用するすべてのものを即時統合テストにします。

    簡潔に言えば、いくつかのものは静的であり、小さなツールや使い捨てのコードでは、私はその使用を妨げません。しかし、それを超えて、メンテナンス性、デザイン性、テスト容易性という点では、厄介な悪夢です。ここで

    は問題には良い記事です:http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/

    1

    それはユーティリティメソッドなら、それはそれは静的にするためにうれしいです。 GuavaとApache Commonsはこの原則に基づいて構築されています。

    私の意見は純粋に実用的です。それがあなたのアプリケーションコードの場合、静的メソッドは一般的には最良のものではありません。静的メソッドには重大なユニットテストの制限があります。簡単に嘲笑することはできません。他のいくつかのテストには黙った静的機能を注入できません。また、通常、静的メソッドに関数を挿入することはできません。

    私のアプリケーションロジックでは、通常、小さな静的ユーティリティーライクなメソッド呼び出しがあります。私。メリットの

    static cutNotNull(String s, int length){ 
        return s == null ? null : s.substring(0, length); 
    } 
    

    1私はまあ

    1

    :-)このような方法をテストしていないということです、もちろんの特効薬はありません。静的クラスはほとんどユーティリティ/ヘルパーにとっては問題ありません。しかし、ビジネスロジックプログラミングに静的メソッドを使用することは確かに邪悪です。それはあなたが明示的な依存関係が宣言持っていて、コードに掘っていない場合は、あなたの間の結合を見落とす可能性があるいけない

    • 原因匂いあなたはItemsProcessor.SplitItem(newItem);への静的メソッドの呼び出しを参照してください。次のコード

      public class BusinessService 
          { 
      
           public Guid CreateItem(Item newItem, Guid userID, Guid ownerID) 
           { 
            var newItemId = itemsRepository.Create(createItem, userID, ownerID); 
            **var searchItem = ItemsProcessor.SplitItem(newItem);** 
            searchRepository.Add(searchItem); 
            return newItemId; 
           } 
          } 
      

      を考えてみましょうクラスと静的メソッドコンテナ

    • BusinessServiceItemsProcessorから隔離してテストすることはできません(ほとんどのテストツールは静的クラスをモックしません)。ユニットテストなし==低品質
    0

    スタティックメソッドは、一般的にステートレスコードでも悪い選択です。代わりに、一度インスタンス化され、メソッドを使用したいクラスに注入されるこれらのメソッドを持つシングルトンクラスを作成します。そのようなクラスは模擬とテストが容易です。彼らははるかにオブジェクト指向です。必要に応じてプロキシでラップすることができます。 Staticsはオブジェクト指向をもっと難しくしています。ほとんどの場合、それらを使用する理由はありません。 100%ではなく、ほとんどすべて。

    関連する問題