固い原則とコード構造


150

最近の就職の面接で、私はSOLIDについての質問に答えることができませんでした-さまざまな原則の基本的な意味を提供する以外に。本当にバグです。数日かけて掘り下げましたが、まだ十分な要約を出していません。

インタビューの質問は:

SOLIDの原則に厳密に従っていると言った.Netプロジェクトを見るとしたら、プロジェクトとコード構造の観点から何を期待しますか?

私は少しもがいて、質問に本当に答えなかったので、爆撃されました。

この質問をどのようにうまく処理できますか?


3
私は中に明確ではないかと思ったSOLIDためのwikiページ
BЈовић

拡張可能な抽象ビルディングブロック。
rwong

SOLID Principles of Object Oriented Designに従うことにより、クラスは自然に小さく、十分にファクタリングされ、簡単にテストされる傾向があります。ソース:docs.asp.net/en/latest/fundamentals/…– WhileTrueSleep 16
1

回答:


188

S =単一責任の原則

したがって、フォルダ/ファイル構造とオブジェクト階層が適切に整理されていることを期待しています。各クラス/機能には、その機能が非常に明白であるという名前を付ける必要があり、そのタスクを実行するロジックのみを含める必要があります。

数千行のコードを持つ巨大なマネージャークラスを見た場合、それは単一の責任が守られていないことを示しています。

O =オープン/クローズドプリンシプル

これは基本的に、既存の機能に最小限の影響を与える/変更を必要とする新しいクラスを介して新しい機能を追加するという考え方です。

オブジェクトの継承、サブタイピング、インターフェース、抽象クラスを使用して、機能のデザインを実際の実装から分離し、他の人が影響を受けずに他のバージョンを一緒に実装できるようにすることを期待しています元の。

L =リスコフ置換原理

これは、サブタイプを親タイプとして扱う機能に関係しています。適切な継承オブジェクト階層を実装している場合、これはC#ですぐに使用できます。

サブタイプ自体をインスタンス化して処理するのではなく、共通オブジェクトをベースタイプとして扱い、ベース/抽象クラスのメソッドを呼び出すコードを期待しています。

I =インターフェイス分離の原則

これはSRPに似ています。基本的に、あなたは(インタフェースとしての機能の小さなサブセットを定義し、デカップリングあなたのシステムを維持するために、これらと連携例えばFileManagerI / Oファイルを扱うの単一responsibiltyがあるかもしれませんが、それは実現可能性がありIFileReaderIFileWriter読書のための具体的な方法の定義を含んでいましたファイルの書き込み)。

D =依存性反転の原理。

繰り返しますが、これはシステムを分離した状態に保つことに関連しています。おそらく、Unityまたはなどのソリューションで使用されている.NET Dependency Injectionライブラリ、NinjectまたはのようなServiceLocatorシステムの使用に目を光らせているでしょうAutoFacServiceLocator


36
私はC#で多くのLSP違反を見てきましたが、誰かが特定のサブタイプが特殊であると判断するたびに、インターフェースの一部を実装する必要はなく、代わりにその部分に例外をスローします...これは一般的なジュニアアプローチです誤解インターフェイスの実装と設計の問題解決に
ジミー・ホッファを

2
@JimmyHoffaそれが、コードコントラクトの使用を強くお勧めする主な理由の1つです。契約を設計する思考プロセスを経ることは、人々がその悪い習慣から抜け出すのに大いに役立ちます。
アンディ

12
「LSPがC#ですぐに使用できるようになった」とDIPを依存性注入の練習と同等と見なすのは好きではありません。
陶酔

3
+1、ただし依存関係の反転<>依存関係の注入。これらはうまく連携しますが、依存関係の反転は、単なる依存関係の注入以上のものです。参照:野生のDIP
マルジャンヴェネマ

3
@Andy:すべての実装者(インスタンス化できる/インスタンス化されるクラス)に対してテストされるインターフェースで定義されたユニットテストも役立ちます。
マルジャンヴェネマ

17

依存性注入を伴う小さなクラスとインターフェースがたくさんあります。おそらく大きなプロジェクトでは、IoCフレームワークを使用して、これらすべての小さなオブジェクトのライフタイムを構築および管理することもできます。https://stackoverflow.com/questions/21288/which-net-dependency-injection-frameworks-are-worth-looking-intoを参照してください

堅固な原則に厳密に従う大きな.NETプロジェクトは、すべての人が使用できる優れたコードベースを必ずしも意味しないことに注意してください。インタビュアーが誰であるかに応じて、彼/彼女は、SOLIDの意味を理解していること、および/または設計原則に従って独断的にチェックしていることを示すことを望んでいたかもしれません。

確かに、従う必要があります:

Sイングル責任の原則、あなたがそれらのそれぞれが、一つのことをやって多くの小さなクラスを持つことになりますので、のみ

Oペンクローズの原則。これは通常.NETで依存性注入を使用して実装されます。これには、以下のIとDも必要です。

L iskovの置換原理は、おそらくc#で1ライナーで説明することは不可能です。幸いなことに、これに対処する他の質問があります。たとえば、https//stackoverflow.com/questions/4428725/can-you-explain-liskov-substitution-principle-with-a-good-c-sharp-example

私は棲み分け原理は、オープン・クローズの原則と連携して動作しますnterface。文字通り従えば、少数の「大きな」インターフェースではなく、非常に多くの非常に小さなインターフェースを好むことを意味します。

Dの高レベルのクラスは、低レベルのクラスに依存してはならないependency反転原理は、両方のは、抽象化に依存しなければなりません。


SRPは「1つのことだけを行う」という意味ではありません。
ロバートハーヴェイ

13

日常業務でSOLIDを支持しているショップのコードベースで私が期待する基本的なこと:

  • 多くの小さなコードファイル-.NETのベストプラクティスとしてファイルごとに1つのクラスがあり、単一の責任原則により小さなモジュールクラス構造が奨励されているため、それぞれが1つの小さなクラスを含む多くのファイルが表示されます。
  • 多数のアダプターおよび複合パターン-多くのアダプターパターン(別のインターフェイスの機能に「パススルー」することで1つのインターフェイスを実装するクラス)を使用して、1つの目的のために開発された依存関係のプラグインをわずかに合理化することを期待しますその機能も必要とされるさまざまな場所。コンソールロガーをファイルロガーに置き換えるだけの簡単な更新は、使用するファイル名を指定する手段を公開するためにインターフェイスが更新された場合、LSP / ISP / DIPに違反します。代わりに、ファイルロガークラスは追加のメンバーを公開し、アダプターは新しいものを非表示にすることでファイルロガーをコンソールロガーのように見せます。そのため、これらすべてをスナップするオブジェクトのみが違いを知る必要があります。

    同様に、オブジェクト(OCP)の変更を避けるために、クラスが既存のものと同様のインターフェースの依存関係を追加する必要がある場合、通常の答えは、Composite / Strategyパターン(依存関係インターフェースを実装し、複数のそのインターフェイスの実装。クラスがさまざまな量のロジックを使用して、1つ、いくつか、またはすべての実装に呼び出しを渡すことができます。

  • 多数のインターフェースとABC-DIPでは抽象化が必ず必要であり、ISPはこれらを狭いスコープにすることを推奨しています。したがって、インターフェイスと抽象基本クラスがルールであり、コードベースの共有された依存関係機能をカバーするには、それらの多くが必要になります。厳密なSOLIDはすべてを注入する必要がありますが、どこかで作成する必要があることは明らかです。したがって、GUIフォームが親に対して何らかのアクションを実行することによって1つの親フォームの子としてのみ作成される場合、子フォームを更新する心配はありません親内のコードから直接。私は通常、そのコードを独自のメソッドにしているため、同じフォームの2つのアクションがウィンドウを開いた場合は、メソッドを呼び出すだけです。
  • 多くのプロジェクト-このすべてのポイントは、変更の範囲を制限することです。変更には、再コンパイルの必要性が含まれます(比較的些細な作業ですが、モバイル環境への更新プログラムの展開など、プロセッサおよび帯域幅が重要な多くの操作では依然として重要です)。プロジェクト内の1つのファイルを再構築する必要がある場合、すべてのファイルが再構築されます。つまり、インターフェイスを実装と同じライブラリに配置すると、ポイントが失われます。インターフェースの実装を変更する場合は、すべての使用法を再コンパイルする必要があります。これは、インターフェース定義自体も再コンパイルするため、使用法は結果のバイナリの新しい場所を指す必要があるためです。したがって、インターフェースを用途とは別にして 実装は、一般的な使用領域によってさらに分離しますが、典型的なベストプラクティスです。
  • 「Gang of Four」の用語に多くの注意が払われました-1994年の書籍Design Patternsで特定されたデザインパターンは、SOLIDが作成しようとしている一口サイズのモジュラーコードデザインを強調しています。たとえば、依存性反転の原則とオープン/クローズの原則は、その本で特定されているパターンのほとんどの中心にあります。そのため、SOLIDの原則に強く準拠したショップでは、ギャングオブフォーの本の用語も採用し、「AbcFactory」、「XyzRepository」、「DefToXyzAdapter」など、それらのラインに沿った機能に応じてクラスに名前を付けます。 "、" A1Command "など。
  • 汎用リポジトリ-一般的に理解されているISP、DIP、およびSRPに沿って、リポジトリは検索/永続化メカニズムの特定の知識を必要とせずに抽象化された方法でデータクラスを要求するコードを使用できるため、SOLID設計でほぼどこにでもあります。 DAOパターンとは対照的に、これを行うコードを1か所に配置します(たとえば、Invoiceデータクラスがある場合、そのタイプのハイドレートオブジェクトを生成するInvoiceDAOなどもあります。コードベース/スキーマ内のすべてのデータオブジェクト/テーブル)。
  • IoCコンテナー-私はこれを追加することをdoします。実際、IoCフレームワークを使用して依存関係の注入の大部分を行っていないためです。それはすぐに、すべてをコンテナに投げ入れ、それを揺さぶり、注入されたファクトリーメソッドを介して必要な垂直に水和された依存関係を注ぐという、God Objectのアンチパターンになります。構造が非常にモノリシックになることを理解するまでは素晴らしいと思いますが、登録情報を含むプロジェクトは、「流fluent」であれば、ソリューションのすべてについてすべてを知る必要があります。それは変更する多くの理由です。流fluentでない場合(設定ファイルを使用した後期登録)、プログラムの重要な部分は「マジックストリング」に依存します。これは、まったく異なるcan o 'ワームです。

1
なぜダウン投票ですか?
キース

これは良い答えだと思います。これらの用語についての多くのブログ投稿に似ているのではなく、その使用法と価値を示す例と説明をリストしました
-Crowie

10

SOLIDの「O」が「役に立たず、あまり理解されていない」というJon Skeetの議論に注意をそらし、Alistair Cockburnの「保護されたバリエーション」とJosh Blochの「継承のための設計、または禁止」について話させます。

Skeetの記事の短い要約(元のブログ投稿を読まずに彼の名前を落とすことはお勧めしませんが!):

  • ほとんどの人は、「オープンクローズドプリンシパル」の「オープン」と「クローズド」が何を意味するのかを知りません。
  • 一般的な解釈は次のとおりです。
    • そのモジュールは、常に実装の継承を通じて拡張する必要があります。または
    • 元のモジュールのソースコードを変更することはできません。
  • OCPの根底にある意図とBertrand Meyerの元の定式化はうまくいきます。
    • そのモジュールは、クライアントが依存できる明確に定義されたインターフェース(必ずしも「インターフェース」の技術的な意味である必要はありません)を持つ必要がありますが
    • これらのインターフェースを壊すことなく、できることを拡張できるはずです。
  • しかし、「オープン」と「クローズ」という言葉は、たとえ発音の良い頭字語を作っているとしても、問題を混乱させるだけです。

OPは、「どうすればこの質問に対処できたでしょうか?」と尋ねました。面接を行うシニアエンジニアとして、さまざまなコードデザインスタイルの長所と短所について知的に話すことができる候補者に、箇条書きのリストからガタガタすることができる人よりも、非常に興味があります。

別の良い答えは、「まあ、それは彼らがそれをどれだけよく理解しているかに依存する。もし彼らが知っているのがSOLID流行語だけなら、継承の濫用、依存性注入フレームワークの乱用、どれもどれも100万個の小さなインターフェースはないだろう」製品管理との通信に使用されるドメイン語彙を反映します。...」


6

さまざまな時間でこれに答えることができる方法はおそらくいくつかあります。ただし、これは「SOLIDの意味を知っていますか?」の行に沿っていると思います。そのため、この質問に答えることは、要点を突き止めて、プロジェクトの観点から説明することになるでしょう。

したがって、次のように表示されます。

  • クラスには単一の責任があります(たとえば、顧客のデータアクセスクラスは、顧客データベースから顧客データのみを取得します)。
  • クラスは、既存の動作に影響を与えることなく簡単に拡張できます。機能を追加するためにプロパティや他のメソッドを変更する必要はありません。
  • 派生クラスを基本クラスに置き換えることができ、これらの基本クラスを使用する関数は、それらを処理するために基本クラスをより具体的な型にアンラップする必要はありません。
  • インターフェイスは小さく、理解しやすいです。クラスがインターフェイスを使用する場合、タスクを達成するためにいくつかのメソッドに依存する必要はありません。
  • コードは十分に抽象化されているため、高レベルの実装は特定の低レベルの実装に具体的に依存しません。高レベルのコードに影響を与えずに低レベルの実装を切り替えることができるはずです。たとえば、アプリケーションの残りの部分に影響を与えることなく、WebサービスベースのSQLデータアクセスレイヤーに切り替えることができます。

4

これは素晴らしい質問ですが、難しいインタビューの質問だと思います。

SOLIDの原則は、クラスとインターフェイス、およびそれらの相互関係を実際に管理します。

この質問は、実際にはファイルに関係するものであり、必ずしもクラスに関係するものではありません。

簡単な観察または答えは、一般にインターフェイスだけを保持するファイルが表示されることです。多くの場合、慣例では大文字のIで始まります。さらに、ファイルのコードが重複していないこと(特にモジュール、アプリケーション、またはライブラリ内)、およびモジュール、アプリケーション、またはライブラリ間の特定の境界を越えてコードが慎重に共有されることに言及します。

Robert Martinは、Boochメソッドを使用したオブジェクト指向C ++アプリケーションの設計(Cohesion、Closure、Reusabilityのセクションを参照)およびClean Codeで、C ++の領域でこのトピックについて説明しています。


.NETコーダーIMEは通常、「ファイルごとに1クラス」ルールに従い、フォルダー/名前空間構造もミラーリングします。Visual Studio IDEは両方のプラクティスを推奨しており、ReSharperなどのさまざまなプラグインがそれらを実施できます。したがって、クラス/インターフェース構造をミラーリングするプロジェクト/ファイル構造が表示されると予想されます。
キース
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.