静的クラスの有効な用途は何ですか?


8

C#などのオブジェクト指向言語で静的クラスを使用しているプログラマーを目にするたびに、彼らが間違っていることに気づきました。主な問題は明らかにグローバルな状態であり、実行時に、またはテスト中にモック/スタブを使用して実装を交換することの難しさです。

好奇心から、十分にテストされたプロジェクトを選択するプロジェクトのいくつかを見て、実際にアーキテクチャとデザインについて考えようと努力しました。私が見つけた静的クラスの2つの使用法は次のとおりです。

  • ユーティリティクラス— このコードを今日作成している場合は避けたいものですが

  • アプリケーションキャッシュ:そのための静的クラスの使用は明らかに間違っています。MemoryCacheまたはRedis に置き換えたい場合はどうなりますか?

.NET Frameworkを見ると、静的クラスの有効な使用例も見当たりません。例えば:

  • File静的クラスを使用すると、代替クラスに切り替えるのが非常に困難になります。たとえば、分離ストレージに切り替えるか、ファイルをメモリに直接格納する必要がある場合、またはNTFSトランザクションをサポートできる、または少なくとも259文字を超えるパスを処理できるプロバイダーが必要な場合はどうでしょうか。持つ共通のインタフェースと複数の実装は、使用して同じように簡単現れるFileコードベース要件の変更のほとんどを書き換えることではないの恩恵で、直接。

  • Console静的クラスはテストを非常に複雑にします。ユニットテスト内で、メソッドが正しいデータをコンソールに出力することをどのように確認すべきですか?Stream実行時に注入される書き込み可能に出力を送信するようにメソッドを変更する必要がありますか?非静的コンソールクラスは、静的クラスと同じくらい単純で、静的クラスのすべての欠点がないように思われます。

  • Math静的クラスも適切な候補ではありません。任意精度の演算に切り替える必要がある場合はどうなりますか?それとも、より新しく、より高速な実装ですか?または、単体テスト中にMathクラスの特定のメソッドが実際にテスト中に呼び出されたことを通知するスタブに?

Programmer.SEで、私は読んだ:

拡張メソッドとは別に、静的クラスの有効な用途は何ですか。つまり、依存性注入またはシングルトンが不可能であるか、設計の品質が低下し、拡張性と保守が難しくなる場合ですか。


Groovyで?「テストしているGroovyクラスが別のGroovyクラスで静的メソッドを呼び出す場合、メソッド、コンストラクター、プロパティ、静的メソッドを動的に追加できるExpandoMetaClassを使用できます...」
gnat


これはあなたの質問に答えません(それがコメントである理由です)そしておそらく人気のない意見になるでしょうが、私はあなたがJava / C#が単純に提供できないモジュール性のレベルを探していると思います(ジャンプすることなしに)サーカスの動物よりも多くのフープ)。静的メソッドはハードコードされた依存関係であることはすでに説明しました。より一般的には、どのクラスもハードコードされた依存関係です。
Doval '19

1
したがって、すべてをインターフェイスの背後に配置してモジュールとして扱うことができますが、外部コードを取り込むとき、またはさまざまな機能を1つのインターフェイスにまとめるか、サブセットを選択するときに、インターフェイス間で変換するアダプタが必要になります。インターフェイスの機能の。また、有用なバイナリメソッドを(少なくともで不正行為を行わずにinstanceof)使用する能力が失われます。これは、2つのインスタンスが与えられた場合、IFooそれらが同じクラスによってサポートされている保証がないためです。
ドヴァル'19

2
またはあなたが言う、あなたの腕を投げること@Magus YAGNI、そしてあなたが文字列の異なる種類が必要な場合、あなたは自分のIDEは、のすべてのインスタンスを変更するために買ってあげることを受け入れるcom.lang.lib.Stringにはcom.someones.elses.String、コードベース全体に。
Doval 2014

回答:


20

仮に; 仮に; 仮に?

YAGNI

真剣に。誰かが異なるの実装を使用したい場合FileMathまたはConsoleそれらはその離れを抽象化の痛みを通過することができます。Javaを見たり使用したりしましたか?!?Java標準ライブラリは、抽象化のために物事を抽象化するときに何が起こるかを示す良い例です。Calendarオブジェクトを作成するために本当に 3つのステップを実行する必要がありますか?

そうは言っても、私はここに座って静的クラスを強く守るつもりはありません。静的な状態は非常に悪です(それを時々プール/キャッシュに使用する場合でも)。静的関数は、同時実行性とテスト容易性の問題により、悪影響を及ぼす可能性があります。

しかし、純粋な静的関数はそれほどひどいものではありません。操作に正気な代替手段がないため、抽象化しても意味がない基本的なものがあります。そこです、彼らはすでに、デリゲート/ファンクタを経由して抽象化しているため、静的可能な戦略の実装は。また、これらの関数にはインスタンスクラスが関連付けられていない場合があるため、静的クラスはそれらを整理するのに役立ちます。

また、拡張メソッドは、哲学的に静的なクラスではない場合でも、実用的です。


1
質問の一部は、静的クラスの有効な使用法があると言う人がいる一方で、そのステートメントを例で裏付けていないという不満のようです。もしそうなら、この答えは改善されるでしょう。
Magus、

@Magus-OPがそれ以外のことを主張しているにもかかわらず、質問で与えられた3つの例は完全に適切な使用法です。
Telastyn、2014

改善の余地があるかもしれないというだけで、それは悪い答えではないと思います。OPの例が唯一のものである場合、それは問題ありません。将来の検索のために、答えでそのように言うのは理にかなっています。どちらにせよ、それは私の賛成票を持っています。
Magus 14

1
フレームワークが後から考えるのではなく、スレッドの静的な状態のサポートをいくらか改善してほしいと思います。のようなものConsoleは、システム全体の状態の一部であってはなりませんが、絶えず渡されるオブジェクトの一部であってはなりません。代わりに、Console他の何かを使用するように書き込むルーチンが必要な場合は、各スレッドに「現在のコンソール」プロパティがあり、簡単に保存および復元できると言う方が理にかなっているようです。
スーパーキャット、2014

1
@BenjaminHodgson-デザインはめったに定量的ではありません。とにかく、拡張メソッドは別の方法で実装でき(JavaScriptプロトタイプ操作を考えてください)、ユーザーは賢明ではありません。それらが静的関数であることは、実装の詳細であり、その本質の本質的な部分ではありません。
Telastyn

18

つまり、シンプルさ。

分離しすぎると、Hello Worldが地獄から消えてしまいます。

void main(String[] args) {
    TextOutputFactory outputFactory = new TextOutputFactory();
    OutputStream stream = outputFactory.CreateStdOutputStream();

    Encoding encoding = new EncodingFactory.CreateUtf8Encoding();
    stream.Encoding = encoding;

    SystemConstant constant = new SystemConstant();
    stream.LineEnding = constant.PlatformLineEnding;

    stream.FlushOnEachLine = true;

    String greeting = new FixedSizeInMemoryString(); // We may have to switch to a file based string later!"
    greeting.SetContent("Hello world");
    greeting.Initialize();

    stream.SendContent(greeting);
    stream.EndCurrentLine();

    ProcessCompletion completion = new ProcessCompletion();
    completion.Status = constant.SuccessStatus;
    completion.ExitProgram();
}

しかし、ちょっと、少なくともXML構成はありません。

静的クラスを使用すると、迅速かつ一般的なケースに合わせて最適化できます。あなたが言及したほとんどのポイントは、それらの上に独自の抽象化を導入するか、Console.Outのようなものを使用することによって非常に簡単に解決できます。


5
これは、ローカリゼーションのさまざまな懸念を説明するGNU Hello Worldを思い出させます。
Telastyn '19

1
-1の場合の不足 XML構成。+2一言で言えば、
s1lv3r 2014

1
Hello Worldは、超グローバルシナリオを可能にするために本当に一般化されるべきです。結局のところ、私は他の世界に挨拶しているかもしれません。結局。まず、いくつかの一般的な通信形式を決定する必要があります...バイナリの素数で、AM搬送波信号によって送信されます...しかし、どのような周波数ですか?知っている、シリコン原子の基本モード!

10

静的メソッドの場合:これ:

var c = Math.Max(a, Int32.Parse(b));

これよりもはるかに単純で明確で読みやすいです:

IMathLibrary math = new DefaultMathLibrary();
IIntegerParser integerParser = new DefaultIntegerParser();
var c = math.Max(a, integerParser.Parse(b));

今、明示的なインスタンスを非表示にするには、依存性注入を追加し、ラインの数十または数百にワンライナーが成長を参照してください-あなたはあなた自身の考案とは考えにくいシナリオをサポートするために、すべてMax大幅に高速化フレームワークの1よりも-Implementationをし、あなたが必要とします代替のMax実装を透過的に切り替えることができるようにします。

APIの設計は、シンプルさと柔軟性のトレードオフです。間接的な追加レイヤー(ad infinitum)をいつでも導入できますが、一般的なケースの利便性と追加レイヤーの利点を比較検討する必要があります。

たとえば、ストレージプロバイダー間を透過的に切り替える必要がある場合は、ファイルシステムAPIの周りに独自のインスタンスラッパーを常に記述できます。しかし、より単純な静的メソッドなければ、誰もがファイルの保存などの単純なことをしなければならないたびにインスタンスを作成しなければなりません。非常にまれなエッジケースに対して最適化し、すべてを一般的なケースに対して複雑にすることは、設計上のトレードオフとしては非常に悪いでしょう。同じConsoleです-コンソールを抽象化またはモックする必要がある場合は、独自のラッパーを簡単に作成できますが、多くの場合、迅速な解決策やデバッグの目的でコンソールを使用するため、テキストを出力するための最も簡単な方法が必要です。

さらに、「複数の実装を備えた共通のインターフェース」は優れていますが、必要なすべてのストレージプロバイダーを統合する「正しい」抽象化は必ずしもありません。ファイルと分離ストレージを切り替えることもできますが、他の誰かがファイル、データベース、ストレージ用のCookieを切り替えることもできます。共通のインターフェースは異なって見え、ユーザーが望む可能性のあるすべての抽象化を予測することは不可能です。静的メソッドを「プリミティブ」として提供し、ユーザーが独自の抽象化とラッパーを構築できるようにする方がはるかに柔軟です。

あなたのMath例はさらに疑わしいです。任意精度の数学に変更したい場合は、おそらく新しい数値型が必要になります。これはプログラム全体の根本的な変更であり、変更Math.Max()する必要ArbitraryPrecisionMath.Max()があることは確かにあなたの心配の中で最も少ないです。

そうは言っても、ステートフルな静的クラスはほとんどの場合悪であることに同意します。


0

一般に、静的クラスを使用する唯一の場所は、クラスがシステムの境界を越えない純粋な関数をホストする場所だけです。境界を越えるものは純粋な意図ではない可能性が高いため、後者は冗長であることを理解しています。私はこの結論に達しましたが、世界の国家に対する不信感とテストの容易さの両方の観点からです。システム境界(ネットワーク、ファイルシステム、I / Oデバイス)を越えるクラスの単一の実装があると合理的に期待されている場合でも、モックを提供する機能があるため、静的クラスではなくインスタンスを使用することを選択しますユニットテストでの/ fakeの実装は、実際のデバイスに結合しない高速ユニットテストを開発する上で重要です。メソッドが外部システム/デバイスに結合されていない純粋な関数である場合、静的クラスが適切であると考えられますが、それはテスト性を妨げない場合に限られます。既存のフレームワーククラスの外では、ユースケースは比較的まれです。また、C#の拡張メソッドなど、選択の余地がない場合もあります。

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