プログラマーがC#などのオブジェクト指向言語で静的クラスを使用しているのを目にするたびに、彼らが間違っていることに気づきました。主な問題は明らかにグローバルな状態であり、実行時またはテスト中にモック/スタブを使用して実装をスワップすることの難しさです。
好奇心から、十分にテストされたプロジェクトを選択するプロジェクトのいくつかを見て、実際にアーキテクチャとデザインについて考えようと努力しました。私が見つけた静的クラスの2つの使用法は次のとおりです。
ユーティリティクラス-何か 私はむしろ避けたい このコードを今日記述している場合、
アプリケーションキャッシュ:そのための静的クラスの使用は明らかに間違っています。 MemoryCache
またはRedisに置き換えたい場合はどうなりますか?
.NET Frameworkを見ると、静的クラスの有効な使用例も見当たりません。例えば:
File
静的クラスを使用すると、代替クラスに切り替えるのが非常に困難になります。たとえば、 Isolated Storage に切り替える必要がある場合、またはファイルをメモリに直接格納する場合、またはNTFSトランザクションをサポートできる、または少なくとも 259より長いパスを処理できる)プロバイダーが必要な場合文字 ? 共通のインターフェースと複数の実装 を使用することは、File
を直接使用するのと同じくらい簡単に見え、要件が変更されたときにほとんどのコードベースを書き直す必要がないという利点があります。
Console
静的クラスはテストを非常に複雑にします。ユニットテスト内で、メソッドが正しいデータをコンソールに出力することをどのように確認しますか?実行時に挿入される書き込み可能なStream
に出力を送信するようにメソッドを変更する必要がありますか?もう一度、非静的コンソールクラスは、静的クラスと同じくらい単純で、静的クラスのすべての欠点がないようです。
Math
静的クラスも適切な候補ではありません。任意精度の演算に切り替える必要がある場合はどうなりますか?それとも、より新しく、より高速な実装に?または、単体テスト中に、Math
クラスの特定のメソッドが実際にテスト中に呼び出されたことを通知するスタブに?
Programmer.SEで、私は読んだ:
C#で「静的」を使用しないでください? 一部の回答は、静的クラスに対するものです。他の人は、「静的メソッドは使用しても問題がなく、プログラミングで正当な場所がある」と主張しますが、引数でそれをベイクしないでください。
シングルトンを使用する場合と静的クラスを使用する場合 。ユーティリティクラスには問題があると前述したので、ユーティリティクラスについて説明します。
クラスを「静的」にする理由と時期?クラスの「静的」キーワードの目的は何ですか? 言及されている唯一の有効な使用法は、拡張メソッドのコンテナです。すごい。
拡張メソッドとは別に、静的クラスの有効な用途は何ですか?つまり、依存性注入またはシングルトンが不可能な場合、または設計の品質が低下し、拡張性とメンテナンスが困難になる場合?
仮に;仮に;仮に?
真剣に。誰かがFile
またはMath
またはConsole
の異なる実装を使用したい場合は、それを抽象化する苦労を経験することができます。 Javaを見たり使用したりしましたか?!? Java標準ライブラリは、抽象化のために物事を抽象化したときに何が起こるかを示す良い例です。私は本当に私を構築するために3つのステップを実行する必要があります- カレンダーオブジェクト?
そうは言っても、ここに座って静的クラスを強く守るつもりはありません。静的な状態は非常に悪です(たとえそれを時々プール/キャッシュに使用する場合でも)。静的関数は、同時実行性とテスト容易性の問題により、悪影響を及ぼす可能性があります。
しかし、純粋な静的関数はそれほどひどいものではありません。ありますare操作には正解な代替手段がないため、抽象化しても意味のない基本的なこと。ありますareデリゲート/ファンクターを介して既に抽象化されているため、静的であり得る戦略の実装。また、これらの関数にはインスタンスクラスが関連付けられていない場合があるため、静的クラスはそれらを整理するのに役立ちます。
また、拡張メソッドは、哲学的に静的なクラスでなくても、実用的で使用できます。
一言で言えばシンプル。
分離しすぎると、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のようなものを使用することによって非常に簡単に解決できます。
静的メソッドの場合:これ:
_var c = Math.Max(a, Int32.Parse(b));
_
これよりもはるかに単純で明確で読みやすいです:
_IMathLibrary math = new DefaultMathLibrary();
IIntegerParser integerParser = new DefaultIntegerParser();
var c = math.Max(a, integerParser.Parse(b));
_
ここで、依存性注入を追加して明示的なインスタンス化を非表示にし、1行が数十行または数百行に成長するのを確認します。これらすべてが、独自のMax
- implementationを発明するというあり得ないシナリオをサポートし、フレームワークではand代替のMax
- implementationsを透過的に切り替えることができる必要があります。
APIの設計は、シンプルさと柔軟性のトレードオフです。 always間接層(ad infinitum)を追加できますが、一般的なケースの利便性と追加層の利点を比較検討する必要があります。
たとえば、ストレージプロバイダー間を透過的に切り替える必要がある場合は、ファイルシステムAPIの周りに独自のインスタンスラッパーを常に記述できます。しかし、より単純な静的メソッドがないと、everybodyは、ファイルの保存などの単純なことを行う必要があるたびにインスタンスを作成する必要があります。非常にまれなEdgeケースを最適化し、すべてを一般的なケースに複雑にすることは、設計上のトレードオフとしては非常に悪いでしょう。 Console
と同じ-コンソールを抽象化またはモックする必要がある場合は、独自のラッパーを簡単に作成できますが、多くの場合、クイックソリューションまたはデバッグの目的でコンソールを使用するため、出力する最も簡単な方法が必要です。いくつかのテキスト。
さらに、「複数の実装を持つ共通のインターフェース」は優れていますが、必要なすべてのストレージプロバイダーを統合する「正しい」抽象化は必ずしも必要ではありません。ファイルと分離ストレージを切り替えることもできますが、他の誰かがファイル、データベース、ストレージ用のCookieを切り替えることもできます。共通のインターフェースは異なって見え、ユーザーが望む可能性のあるすべての抽象化を予測することは不可能です。 「プリミティブ」として静的メソッドを提供し、ユーザーが独自の抽象化とラッパーを構築できるようにする方がはるかに柔軟です。
Math
の例はさらに疑わしいです。任意精度の数学に変更したい場合は、おそらく新しい数値型が必要になります。これはプログラム全体の根本的な変更となるでしょう。Math.Max()
をArbitraryPrecisionMath.Max()
に変更する必要があるのは、確かに心配することの最も少ないことです。
そうは言っても、stateful staticクラスはほとんどの場合悪であることに同意します。
一般に、静的クラスを使用する唯一の場所は、クラスがシステムの境界を越えない純粋な関数をホストする場所です。境界を越えるものは純粋な意図ではない可能性が高いので、後者は冗長であると思います。私はこの結論に至るのは、グローバルな状態への不信とテストの容易さの両方の観点からです。システム境界(ネットワーク、ファイルシステム、I/Oデバイス)を越えるクラスの単一の実装があると合理的に期待されている場合でも、モックを提供する機能があるため、静的クラスではなくインスタンスを使用することを選択しますユニットテストでの/ fakeの実装は、実際のデバイスに結合しない高速ユニットテストを開発する上で重要です。メソッドが外部システム/デバイスに結合されていない純粋な関数である場合、私は静的クラスが適切であると思うかもしれませんが、それはテスト性を妨げない場合に限られます。既存のフレームワーククラスの外では、ユースケースは比較的まれです。また、C#の拡張メソッドなど、選択の余地がない場合もあります。