SOLID原則の適用


13

私は、SOLIDの設計原則にまったく慣れていません。私はそれらの原因と利点を理解していますが、SOLID原則を使用するための実践的な演習としてリファクタリングしたい小規模なプロジェクトにそれらを適用できません。完全に機能するアプリケーションを変更する必要はありませんが、とにかくそれをリファクタリングして、将来のプロジェクトの設計経験を積みたいと思います。

アプリケーションには次のタスクがあります(実際にはそれ以上のことですが、簡単にしましょう):データベーステーブル/列/ビューなどの定義を含むXMLファイルを読み取り、作成するために使用できるSQLファイルを作成する必要がありますORACLEデータベーススキーマ。

(注:なぜそれが必要なのか、なぜXSLTを使用しないのかなどについて議論することは控えてください。理由はありますが、トピックから外れています。)

はじめに、テーブルと制約のみを見ることにしました。列を無視する場合、次のように記述できます。

制約はテーブルの一部(より正確にはCREATE TABLEステートメントの一部)であり、制約は別のテーブルを参照する場合もあります。

最初に、アプリケーションがどのように見えるかを説明します(SOLIDを適用しない):

現時点では、アプリケーションには、テーブルが所有する制約へのポインターのリストと、このテーブルを参照する制約へのポインターのリストを含む「テーブル」クラスがあります。接続が確立されるたびに、逆方向の接続も確立されます。このテーブルには、各制約のcreateStatement()関数を順に呼び出すcreateStatement()メソッドがあります。このメソッド自体は、名前を取得するために所有者テーブルと参照先テーブルへの接続を使用します。

明らかに、これはSOLIDにはまったく適用されません。たとえば、循環的な依存関係があり、必要な「追加」/「削除」メソッドといくつかの大きなオブジェクトデストラクタに関してコードが肥大化しました。

そのため、いくつか質問があります。

  1. 依存性注入を使用して循環依存関係を解決する必要がありますか?もしそうなら、Constraintはそのコンストラクタで所有者(およびオプションで参照される)テーブルを受け取るべきだと思います。しかし、単一のテーブルの制約のリストをどのように実行できますか?
  2. Tableクラスが自身の状態(テーブル名、テーブルコメントなど)とConstraintsへのリンクの両方を保存する場合、これらの1つまたは2つの「責任」は、単一責任の原則と考えられますか?
  3. ケース2.が正しい場合、リンクを管理する論理ビジネスレイヤーに新しいクラスを作成するだけですか?その場合、1は明らかに関連しなくなります。
  4. 「createStatement」メソッドは、Table / Constraintクラスの一部である必要がありますか、それともそれらを移動する必要がありますか?もしそうなら、どこへ?各データストレージクラス(つまり、テーブル、制約など)ごとに1つのマネージャークラスですか?または、リンクごとにマネージャークラスを作成します(3と同様)。

これらの質問に答えようとするたびに、どこかで輪になって走っています。

列やインデックスなどを含めると、問題は明らかにはるかに複雑になりますが、単純なテーブル/制約のことで手伝ってくれれば、残りは自分で解決できるかもしれません。


3
使用している言語は何ですか?少なくともいくつかのスケルトンコードを投稿できますか?実際のコードを見ずにコードの品質と可能なリファクタリングを議論することは非常に困難です。
ペテルトレック

私はC ++を使用していますが、私はあなたが任意の言語で、この問題を持っている可能性があるので議論のそれを維持しようとした
ティム・マイヤー

はい。ただし、パターンとリファクタリングの適用は言語に依存します。例えば、@ back2dosは以下の回答でAOPを提案しましたが、これは明らかにC ++には適用されません。
ペテルトレック

SOLID原則の詳細については、programmers.stackexchange.com
questions / 155852 /を

回答:


8

別の観点から始めて、ここで「単一責任の原則」を適用することもできます。あなたが私たちに見せたのは、(多かれ少なかれ)あなたのアプリケーションのデータモデルだけです。ここでのSRPとは、データモデルがデータの保持のみを担当することを意味します。

したがって、XMLファイルを読み取り、そのファイルからデータモデルを作成し、SQLを作成する場合は、XMLまたはSQL固有のクラスに何かを実装しないでくださいTable。データフローは次のようになります。

[XML] -> ("Read XML") -> [Data model of DB definition] -> ("Write SQL") -> [SQL]

したがって、XML固有のコードを配置する必要がある唯一の場所は、たとえば、という名前のクラスRead_XMLです。SQL固有のコードの唯一の場所は、のようなクラスでなければなりませんWrite_SQL。もちろん、これらの2つのタスクをさらにサブタスクに分割する(そして、クラスを複数のマネージャークラスに分割する)かもしれませんが、「データモデル」はそのレイヤーの責任を負わないはずです。したがってcreateStatement、データモデルクラスにa を追加しないでください。これにより、SQLに対するデータモデルの責任が与えられます。

テーブルがそのすべての部分(名前、列、コメント、制約など)を保持する責任があることを説明するとき、私は問題を見ません。それがデータモデルの背後にある考え方です。ただし、「テーブル」はその一部のメモリ管理にも責任があると説明しました。これはC ++固有の問題であり、JavaやC#のような言語ではそれほど簡単には直面しません。これらの責任を取り除くC ++の方法は、スマートポインターを使用して、所有権を別のレイヤー(たとえば、boostライブラリまたは独自の「スマート」ポインターレイヤー)に委任することです。ただし、循環的な依存関係によって、スマートポインターの実装が「イライラ」する可能性があることに注意してください。

SOLIDの詳細:ここに素敵な記事があります

http://cre8ivethought.com/blog/2011/08/23/software-development-is-not-a-jenga-game

簡単な例でソリッドを説明します。それをあなたのケースに適用してみましょう:

  • クラスRead_XMLとだけでなくWrite_SQL、これら2つのクラスの相互作用を管理する3番目のクラスも必要です。それをaと呼びましょうConversionManager

  • DI原理を適用すると、ここで意味するかもしれません:ConversionManagerはのインスタンスを作成しないでください Read_XMLWrite_SQL、それ自体を。代わりに、これらのオブジェクトはコンストラクターを介して注入できます。そして、コンストラクターはこのような署名を持つ必要があります

    ConversionManager(IDataModelReader reader, IDataModelWriter writer)

where IDataModelReaderRead_XML継承元のインターフェースで、についてIDataModelWriterも同じですWrite_SQL。これにより、ConversionManager拡張機能を開くことができます(さまざまなリーダーまたはライターを簡単に提供できます)。変更する必要はありません。したがって、Open / Closed原則の例を示します。別のデータベースベンダーをサポートする場合に変更する必要があるものについて考えてください。理想的には、データモデルを変更する必要はなく、代わりに別のSQL-Writerを提供するだけです。


これはSOLIDの非常に合理的な演習ですが、かなり貧弱なデータモデルの取得者と設定者を要求することで、「旧式のKay / Holub OOP」に違反することに注意してください。それはまた悪名高いスティーブ・イェッジの暴言を思い出させます。
user949300

2

この場合、SのSを適用する必要があります。

テーブルには、定義されているすべての制約が保持されます。制約は、参照するすべてのテーブルを保持します。シンプルでシンプルなモデル。

それにこだわるのは、逆ルックアップを実行する機能です。つまり、どの制約によってどのテーブルが参照されているかを把握することができます。
したがって、実際に必要なのはインデックスサービスです。これは完全に異なるタスクであるため、別のオブジェクトによって実行する必要があります。

非常に単純化されたバージョンに分解するには:

class Table {
      void addConstraint(Constraint constraint) { ... }
      bool removeConstraint(Constraint constraint) { ... }
      Iterator<Constraint> getConstraints() { ... }
}
class Constraint {
      //actually I am not so sure these two should be exposed directly at all
      void addReference(Table to) { ... }
      bool removeReference(Table to) { ... }
      Iterator<Table> getReferencedTables() { ... }
}
class Database {
      void addTable(Table table) { ... }
      bool removeTable(Table table) { ... }
      Iterator<Table> getTables() { ... }
}
class Index {
      Iterator<Constraint> getConstraintsReferencing(Table target) { ... }
}

インデックスの実装に関しては、次の3つの方法があります。

  • このgetContraintsReferencingメソッドは実際DatabaseTableインスタンス全体をクロールし、それらConstraintのsをクロールして結果を得ることができます。これのコストと必要な頻度によっては、オプションになる場合があります。
  • キャッシュを使用することもできます。データベースモデルが定義後に変更できる場合、それぞれのインスタンスTableConstraintインスタンスが変更されたときにそれらからシグナルを発することでキャッシュを維持できます。少し簡単な解決策は、作業Indexする全体の「スナップショットインデックス」を作成Databaseし、それを破棄することです。もちろん、アプリケーションが「モデリング時間」と「クエリ時間」を大きく区別している場合にのみ可能です。これら2つを同時に行う可能性が高い場合、これは実行できません。
  • 別のオプションは、AOPを使用して作成呼び出し全体をインターセプトし、それに応じてインデックスを維持することです。

非常に詳細な答え、私はこれまでのところあなたのソリューションが好きです!Tableクラスに対してDIを実行し、構築中に制約のリストを与えたらどう思いますか?とにかく、TableParserクラスがあります。これは、ファクトリとして機能するか、そのケースのファクトリと連携することができます。
ティムマイヤー

@Tim Meyer:DIは必ずしもコンストラクター注入ではありません。DIはメンバー関数でも実行できます。Tableがコンストラクターを介してすべてのパーツを取得するかどうかは、それらのパーツを構築時にのみ追加し、後で変更しないようにするか、テーブルを段階的に作成するかによって異なります。それが設計上の決定の基礎になるはずです。
Doc Brown

1

循環依存関係の解決策は、それらを決して作成しないことを誓います。テストファーストのコーディングは強力な抑止力であることがわかりました。

とにかく、抽象基本クラスを導入することで、循環依存関係を常に破ることができます。これはグラフ表現の典型です。ここで、テーブルはノードであり、外部キー制約はエッジです。したがって、抽象Tableクラスと抽象Constraintクラス、および場合によっては抽象Columnクラスを作成します。その後、すべての実装は抽象クラスに依存できます。これは可能な限り最良の表現ではないかもしれませんが、相互に結合されたクラスの改善です。

しかし、あなたが疑うように、この問題の最良の解決策はオブジェクトの関係を追跡する必要がないかもしれません。XMLをSQLに変換するだけであれば、制約グラフのメモリ内表現は必要ありません。グラフアルゴリズムを実行したい場合、制約グラフは便利ですが、それについて言及しなかったので、それは要件ではないと仮定します。サポートするSQLダイアレクトごとに、テーブルのリストと制約のリストとビジターが必要です。テーブルを生成してから、テーブル外部の制約を生成します。要件が変更されるまで、SQLジェネレーターをXML DOMに結合しても問題はありません。明日のために明日保存します。


これが、「(実際にはそれ以上のことですが、単純にしましょう)」が出てくる場所です。たとえば、テーブルを削除する必要がある場合があるため、このテーブルを参照している制約があるかどうかを確認する必要があります。
ティムマイヤー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.