13

「デメテルの法則」とタグ付けされた問題はほぼすべて読みました。私の特定の質問は、これらの他の質問のいずれにも答えられていませんが、非常に似ています. 主に私の質問は、構成のレイヤーを持つオブジェクトがある場合ですが、さまざまなオブジェクトからプロパティ値を取得する必要がある場合です。これをどのように達成し、なぜあるアプローチを別のアプローチよりも優先するのでしょうか?

次のように、他のオブジェクトで構成された非常に標準的なオブジェクトがあるとします。

public class Customer {
  private String name;
  private ContactInfo primaryAddress;
  private ContactInfo workAddress;
  private Interests hobbies;
  //Etc...

  public getPrimaryAddress() { return primaryAddress; }
  public getWorkAddress() { return workAddress; }
  public getHobbies() { return hobbies; }
  //Etc...
}

private ContactInfo {
  private String phoneNumber;
  private String emailAddress;
  //Etc...

  public getPhoneNumber() { return phoneNumber; }
  public getEmailAddress() { return emailAddress; }
  //Etc...
}

private Interests {
  private List listOfInterests;
}

以下は両方ともデメテルの法則に違反します。

System.out.println("Phone: " + customer.getPrimaryAddress().getPhoneNumber());
System.out.println("Hobbies: " + customer.getHobbies().getListOfInterests().toString());

これはまた、デメテルの法則に違反すると思います (明確化?):

ContactInfo customerPrimaryAddress = customer.getPrimaryAddress();
System.out.println("Phone: " + customerPrimaryAddress.getPhoneNumber());

したがって、おそらく、「getPrimaryPhoneNumber()」メソッドを Customer に追加します。

public getPrimaryPhoneNumber() {
  return primaryAddress.getPhoneNumber();
}

そして、次のように呼び出します: System.out.println("Phone: " + customer.getPrimaryPhoneNumber());

しかし、これを時間をかけて行うと、実際には多くの問題が発生し、デメテルの法則の意図に反するように思えます。これにより、Customer クラスは、独自の内部クラスについてあまりにも多くの知識を持つ getter と setter の巨大なバッグになります。たとえば、Customer オブジェクトは、ある日、さまざまなアドレス (「プライマリ」アドレスと「勤務先」アドレスだけでなく) を持つ可能性があるようです。おそらく、Customer クラスでさえ、特定の名前の ContactInfo オブジェクトではなく、単に ContactInfo オブジェクトの List (またはその他のコレクション) を持つことになります。その場合、どのようにデメテルの法則に従い続けますか? 抽象化の目的を無効にしているように見えます。たとえば、Customer が ContactInfo アイテムの List を持っているような場合、これは妥当と思われます。

Customer.getSomeParticularAddress(addressType).getPhoneNumber();

一部の人々が携帯電話と固定電話を持っていることを考えると、これはさらにおかしなことになり、ContactInfo は電話番号のコレクションを持たなければならないように思えます。

Customer.getSomeParticularAddress(addressType).getSomePhoneNumber(phoneType).getPhoneNumber();

その場合、オブジェクト内のオブジェクト内のオブジェクトを参照するだけでなく、有効な addressType と phoneType が何であるかを知る必要もあります。これには間違いなく問題がありますが、回避する方法がわかりません。特に、これを呼び出しているクラスが何であれ、問題の顧客の「プライマリ」アドレスの「携帯」電話番号を取得したいことをおそらく知っているでしょう。

デメテルの法則に準拠するためにこれをどのようにリファクタリングできますか?また、それがなぜ良いのでしょうか?

4

3 に答える 3

2

私の経験では、Customer示されている例は「他のオブジェクトで構成された標準オブジェクト」ではありません。この例では、構成部分を内部クラスとして実装し、さらにそれらの内部クラスを非公開にするという追加の手順を実行しているためです。それは悪いことではありません。

一般に、プライベート アクセス修飾子は、デメテルの法則の基礎となる情報の隠蔽を増加させます。プライベート クラスを公開することは矛盾しています。実際、NetBeans IDE には、「パブリック API を介して非パブリック タイプをエクスポートしています」というデフォルトのコンパイラ警告が含まれています。

囲んでいるクラスの外にプライベート クラスを公開することは、常に悪いことだと私は断言します。それは、情報の隠蔽を減らし、デメテルの法則に違反します。ContactInfooutside ofのインスタンスを返すことに関する明確化の質問に答えるにはCustomer: はい、それは違反です。

getPrimaryPhoneNumber()メソッドを追加する提案されたソリューションCustomerは、有効なオプションです。混乱はここにあります: 「顧客は... 自身の内部クラスについてあまりにも多くの知識を持っています。」それは可能ではありません; そのため、この例が標準的な構成の例ではないことが重要です。

囲んでいるクラスは、ネストされたクラスについて 100% の知識を持っています。いつも。それらのネストされたクラスが外側のクラス (または他の場所) でどのように使用されているかに関係なく。そのため、囲んでいるクラスは、ネストされたクラスのプライベート フィールドとメソッドに直接アクセスできます。囲んでいるクラスは、内部に実装されているため、それらに関するすべてを本質的に認識しています。

ネストされたクラス Bar を持ち、ネストされたクラス Baz を持ち、ネストされたクラス Qux を持つクラス Foo のばかげた例を考えると、Foo が (内部的に) bar.baz.qux を呼び出すことは Demeter の違反にはなりません。 。方法()。Foo は、Bar、Baz、および Qux について知っておくべきことをすべて知っています。それらのコードは Foo 内にあるため、長いメソッド チェーンを介して追加の知識が渡されることはありません。

その場合の解決策は、デメテルの法則によるとCustomer、内部実装に関係なく、中間オブジェクトを返さないことです。つまりCustomer、複数のネストされたクラスを使用して実装するか、まったく使用しないかに関係なく、クライアント クラスが最終的に必要とするものだけを返す必要があります。

たとえば、最後のコード スニペットは次のようにリファクタリングできます。 customer.getPhoneNumber(addressType, phoneType);

または、選択肢が少ない場合は、 customer.getPrimaryMobilePhoneNumber();

どちらのアプローチでも、Customerクラスのユーザーはその内部実装に気付かず、それらのユーザーが直接関心のないオブジェクトを介して呼び出す必要がなくなります。

于 2015-06-28T20:22:47.880 に答える
1

デメテルの法則 、その名前にもかかわらず、ガイドラインであり、実際の法律ではないことを覚えておくことが重要です。ここで何をするのが正しいのかを判断するには、もう少し深いレベルでその目的を調べる必要があります。

デメテルの法則の目的は、外部のオブジェクトが別のオブジェクトの内部にアクセスできないようにすることです。内部へのアクセスには 2 つの問題があります。1) オブジェクトの内部構造に関する情報が多すぎることと、2) 外部オブジェクトがクラスの内部を変更することも許可してしまうことです。

この問題に対する正しい対応は、Customer メソッドから返されるオブジェクトを内部表現から分離することです。つまり、ContactInfo 型のプライベート内部オブジェクトへの参照を返す代わりに、新しいクラス UnmodifiableContactInfo を定義し、getPrimaryAddress に UnmodifiableContactInfo を返させて、それを作成し、必要に応じて入力します。

これにより、デメテルの法則の両方の利点が得られます。返されたオブジェクトはもはや Customer の内部オブジェクトではありません。つまり、Customer は内部ストレージを好きなだけ変更でき、UnmodifiableContactInfo に対して何をしても Customer の内部には影響しません。

(実際には、内部クラスの名前を変更し、外部クラスを ContactInfo のままにしますが、それは些細な点です)

これでデメテルの法則の目的は達成されましたが、まだ破っているように見えます。これについて私が考える方法は、getAddress メソッドが ContactInfo オブジェクトを返すのではなく、インスタンス化するということです。これは、Demeter ルールの下で ContactInfo のメソッドにアクセスできることを意味し、上記のコードは違反ではありません。

もちろん、顧客にアクセスするコードで「デメテルの法則の違反」が発生しましたが、修正は顧客で行う必要があることに注意する必要があります。一般に、修正は良いことです。複数の「ドット」を使用してアクセスするかどうかに関係なく、内部オブジェクトへのアクセスを提供することは悪いことです。

いくつかのメモ。デメテルの法則を過度に厳密に適用すると、たとえば次のように禁止するなど、ばかげたことにつながることは明らかです。

int nameLength = myObject.getName().length()

私たちのほとんどが毎日行っている技術的な違反です。誰もが次のことも行います。

mylist.get(0).doSomething();

これは技術的に違反です。しかし実際には、外部コードが取得したオブジェクトに基づいてメイン オブジェクト (Customer) の動作に影響を与えることを実際に許可しない限り、これらのいずれも問題にはなりません。

概要

コードは次のようになります。

public class Customer {
    private class InternalContactInfo {
        public ContactInfo createContactinfo() {
            //creates a ContactInfo based on its own values...
        }
        //Etc....
    }
   private String name;
   private InternalContactInfo primaryAddress;
   //Etc...

   public Contactinfo getPrimaryAddress() { 
      // copies the info from InternalContactInfo to a new object
      return primaryAddress.createContactInfo();
   }
   //Etc...
}

public class ContactInfo {
   // Not modifiable
   private String phoneNumber;
   private String emailAddress;
   //Etc...

   public getPhoneNumber() { return phoneNumber; }
   public getEmailAddress() { return emailAddress; }
   //Etc...
}

}

于 2015-07-27T18:10:38.083 に答える
0

のような名前の別のクラスを作成するのはどうですかCustomerInformationProviderCustomerオブジェクトをコンストラクターパラメーターとして新しいクラスに渡すことができます。そして、このクラス内に電話、住所などを取得するためのすべての特定のメソッドを記述できますが、Customerクラスはクリーンなままです。

于 2015-06-29T17:09:39.517 に答える