デメテルの法則

はじめに



現時点では、開発者が適切に管理され、柔軟で読みやすいコードを作成するのに役立つ多くの実績のあるプラクティスがあります。 デメテルの法則はそのような習慣の1つです。



良いコードを書くという文脈でこの法律について話しているので、それを検討する前に、良いコードがどうあるべきかを理解したいと思います。 McConnellのPerfect Codeを読んだ後、ソフトウェア開発の主な技術的必須事項は複雑さの管理であると固く信じていました。 複雑さの概念はプロジェクトのどのレベルでも適用できるため、複雑さの管理はかなり広範なトピックです。 プロジェクトの全体的なアーキテクチャのコンテキスト、プロジェクトモジュールの相互接続のコンテキスト、個別のモジュール、個別のクラス、個別のメソッドのコンテキストの複雑さについて話すことができます。 しかし、おそらく、ほとんどの場合、開発者は個々のクラスおよびメソッドのレベルで複雑さに直面しているため、次のように複雑さを管理するための一般的な単純化されたルールを定式化します。コード。」 つまり、個々のフラグメントを順番に処理しても安全になるように、プログラムコードを編成する必要があります(可能であれば、フラグメントの残りの部分を考慮せずに)。



おそらく、ソフトウェアを書く上で最も重要なことは、柔軟性、つまり迅速に変更を加える能力だと言うでしょう。 これは事実ですが、私の意見では、これは上記の結果です。 コードの個々のフラグメントで作業することが可能で、コードを整理して、それを見るようにするには、少なくとも他の情報を頭に入れておく必要があります。ほとんどの場合、変更の導入は簡単です。



デメテルの法則は、複雑さとの戦いに役立つレシピの1つです(したがって、コードの柔軟性が向上します)。



デメテルの法則



デメテルの法則は、子供の頃に両親が言っていたのと同じことを教えてくれます:「見知らぬ人と話をしないでください。」 そして、ここで誰かと話すことができます:



-オブジェクト自体のメソッド。

-オブジェクトが直接依存するオブジェクトのメソッドを使用します。

-作成されたオブジェクト。

-メソッドとしてパラメーターとして入力されるオブジェクトを使用します。

-グローバル変数を使用する(グローバル変数を使用すると全体的な複雑さが大幅に増加するため、個人的には真実ではないようです)



特定の例を見て、Demeterの法則がどのように良いコードを書くのに役立つかについて結論を導きます。



public class Seller { private PriceCalculator priceCalculator = new PriceCalculator(); public void sell(Set<Product> products, Wallet wallet) throws NotEnoughMoneyException { Money actualSum = wallet.getMoney(); //   ,      (. 4) Money requiredSum = priceCalculator.calculate(products); //  ,    ,      (. 2) if (actualSum.isLessThan(requiredSum)) { //  . throw new NotEnoughMoneyException(actualSum, requiredSum); } else { Money balance = actualSum.subtract(requiredSum); //  . wallet.setMoney(balance); } } }
      
      





法律に違反しているのは、パラメータ(Wallet)を持つメソッドに到達するオブジェクトから別のオブジェクト(actualSum)を取得し、後でそのメソッド(isLessThan)を呼び出すためです。 つまり、最終的にチェーンを取得します:wallet.getMoney()。IsLessThan(otherMoney)。



おそらくあなたは、私のように、ウォレットを引数として受け取り、そこからお金を引き出す方法を見て、ある種の不快感を抱いているでしょう。



法律に準拠したより正確なバージョンは次のようになります。



 public class Seller { private PriceCalculator priceCalculator = new PriceCalculator(); public Money sell(Set<Product> products, Money moneyForProducts) throws NotEnoughMoneyException { Money requiredSum = priceCalculator.calculate(products); if (moneyForProducts.isLessThan(requiredSum)) { throw new NotEnoughMoneyException(moneyForProducts, requiredSum); } else { return moneyForProducts.subtract(requiredSum); } } }
      
      





次に、販売する製品のリストとこれらの製品のお金を販売メソッドに渡します。 このコードはより自然で理解しやすいように思われ、メソッドの抽象化レベルが向上します。



 sell(Set<Product> products, Wallet wallet) VS sell(Set<Product> products, Money moneyForProducts ).
      
      





これで、このコードのテストが簡単になりました。 テストのために、Moneyオブジェクトを作成するだけで十分です。その前はWalletオブジェクトを作成してからMoneyオブジェクトを作成し、次にMoneyをWalletに入れる必要がありました(おそらく、Walletオブジェクトは非常に複雑で、モックを書く必要があります。全体的なテストの複雑さ)。



おそらく最も重要なことは、ペアリングが減少し、メソッドがはるかに使いやすくなったことです。 現在、販売オプションはウォレットの可用性に依存していません-お金の可用性にのみ依存しています。 お金がどこから来ても-財布、ポケット、クレジットカードから、この方法はお金の「金庫」に結び付けられません。



デメテルの法則について読んだとき、良いコードを書くための他の原則/ツール(SOLID、DRY、KISS、パターンなど)に従うだけでは、この法則に違反する状況に至らないことがよくありました。



個人的には、デメテルの法則は「ルール1」ではありません。 むしろ、この法律に違反した場合、これは私がすべてを正しく行っているかどうかを考える機会です。



法律自体にはセマンティクスがなく、法律の盲目的な遵守はコードを複雑にするだけであるため、法律の遵守はそれ自体で終わりではないことが重要です。 例:



 public class Seller { private PriceCalculator priceCalculator = new PriceCalculator(); public void sell(Set<Product> products, Wallet wallet ) throws NotEnoughMoneyException { Money requiredSum = priceCalculator.calculate(products); //   , . 2 Money actualSum = wallet.getMoney(); //   , . 4 Money balance = subtract( actualSum, requiredSum); wallet.setMoney(balance); } private Money subtract(Money first, Money second) throws NotEnoughMoneyException { if (first.isLessThan(second)) { //   , . 4 throw new NotEnoughMoneyException( first, second); } else { return first.subtract(second); //   , . 4 } } }
      
      





正式には、各方法はデメテルの法則を満たします。 実際、最初のオプションにあったすべての同じ欠点があります(パブリックメソッド。このような場合に頻繁に出会ったアドバイスは、法の順守だけでなく、クラスの依存関係の数、クラスのメソッドの数、および数も監視することですメソッドの引数。



結論



ここで、法律を遵守するときに得られるメリットを正確に要約しましょう。



-コード接続の削減。 これは、クラスがそれらの近い親族(それ自体、メソッドの引数、および直接の依存関係)とのみ通信するという事実により実現されます。



-(構造)情報の隠蔽。 Demeterの法則のおかげで、オブジェクトの一部(この例ではウォレットからのお金)を引き出して操作する状況を回避できます。 法律を適用した後、これらの部分のみ(金銭のみ)を操作します。 これにより、必要なものだけを使用して不必要な知識を避けることができます(そして、一般に、抽象化の一般的なレベルが向上します)。



-情報のローカライズ。 法律に従い、someClass.getOther()。GetAnother()。CallMethod()の形式でチェーンを除外し、コミュニケーションの参加者の輪を制限します。 これにより、記述されたコードを簡単にナビゲートし、コードを読み取る際の知的ストレスを軽減できます。



-クラスの義務がより明確になっています。 あまりにも多くの責任を引き受けるほとんどすべてのクラスには、呼び出しの長いチェーン、または多数のクラスの依存関係があります(多くの場合、これとそれ)。 原則として、法律を遵守すると、多くの依存関係を持つ1つの大きなクラスが、より少ない依存関係とより明確な責任を持つ複数の小さなクラスにリファクタリングされるという事実につながります。



デメテルの法則を使用できないのはいつですか?



-コアjdkクラスと対話する場合。 たとえば、seller.toString()。ToLowerCase()は正式にDemeterの法則に違反しますが、たとえばロギングのコンテキストで使用される場合、心配する必要はありません。



-DTOクラス。 DTOクラスを作成する理由はオブジェクトの転送であり、DTOクラスの呼び出しメソッドのチェーンはDemeterの法則に矛盾しますが、DTOオブジェクト自体の概念に適合しています。



-コレクション内。 warehouses.get(i).getName()-正式には法律にも矛盾しますが、コレクションの概念には矛盾しません。



-常識を使用;)



All Articles