同僚と私は顧客向けのシステムを設計しました。私たちの意見では、すっきりした設計ができたと思います。しかし、私たちが導入したいくつかの結合に問題があります。私たちの設計と同じ問題を含むサンプル設計を作成することもできますが、お許しいただければ、質問をサポートするために私たちの設計の抜粋を作成します。
私たちは、患者の特定の治療を登録するためのシステムを開発しています。画像へのリンクが壊れないように、概念的な UML クラス図を c# スタイルのクラス定義として説明します。
class Discipline {}
class ProtocolKind
{
Discipline;
}
class Protocol
{
ProtocolKind;
ProtocolMedication; //1..*
}
class ProtocolMedication
{
Medicine;
}
class Medicine
{
AdministrationRoute;
}
class AdministrationRoute {}
設計について少し説明します。プロトコルは新しい治療のテンプレートです。プロトコルは特定の種類で、投与する必要がある薬剤があります。プロトコルごとに、同じ薬剤でも投与量が異なる場合があるため (他の点でも)、これは ProtocolMedication クラスに保存されます。AdministrationRoute は薬剤の投与方法で、プロトコル管理とは別に作成/更新されます。
デメテルの法則に違反する場所を次に示します。
デメテルの法則の違反
BLLの内部
例えば、ProtocolMedicationのビジネスロジックの中には、薬のAdministrationRoute.Solubleプロパティに依存するルールがあります。コードは次のようになります。
if (!Medicine.AdministrationRoute.Soluble)
{
//validate constrains on fields
}
リポジトリの内部
特定の分野内のすべてのプロトコルを一覧表示するメソッドは、次のように記述されます。
public IQueryable<Protocol> ListQueryable(Discipline discipline)
{
return ListQueryable().Where(p => (p.Kind.Discipline.Id == discipline.Id)); // Entity Frameworks needs you to compare the Id...
}
ユーザーインターフェースの内部
システムのインターフェースには ASP.NET (MVC なし) を使用していますが、このレイヤーには現在最も違反が多いと感じています。グリッドビューのデータバインディング (プロトコルの Discipline を表示する列は Kind.Discipline.Name にバインドする必要があります) は文字列であり、コンパイル時のエラーは発生しません。
<asp:TemplateField HeaderText="Discipline" SortExpression="Kind.Discipline.Name">
<ItemTemplate>
<%# Eval("Kind.Discipline.Name")%>
</ItemTemplate>
</asp:TemplateField>
ですから、実際の質問は、いつそれをデメテルの暗示として見てもよいのか、そしてデメテルの法則の違反を解決するために何ができるのか、ということだと思います。
私自身、いくつかのアイデアを持っていますが、それらを回答として投稿して、個別にコメントしたり投票したりできるようにします。(これが SO の方法かどうかはわかりません。そうでない場合は、回答を削除して質問に追加します)。
ベストアンサー1
デメテルの法則の結果についての私の理解は、DrJokepu の理解とは異なるようです。私がこれをオブジェクト指向コードに適用すると、手続き型コードのコントラクト パスに追加のゲッターが追加されるのではなく、カプセル化と結合が強化されます。
Wikipediaには次のようなルールがある。
より正式には、関数のデメテルの法則では、オブジェクト O のメソッド M は次の種類のオブジェクトのメソッドのみを呼び出すことができると規定されています。
- O自体
- Mのパラメータ
- M内で作成/インスタンス化されたオブジェクト
- Oの直接構成要素オブジェクト
'kitchen' をパラメータとして受け取るメソッドがある場合、Demeter は、キッチンのコンポーネントを検査することはできないと述べており、直接のコンポーネントのみを検査できるとは述べていません。
このようにデメテルの法則を満たすためだけにたくさんの関数を書く
Kitchen.GetCeilingColour()
私にとっては時間の無駄のように見えますが、実際にはそれが物事を成し遂げる方法です
Kitchen の外部のメソッドにキッチンが渡された場合、厳密な Demeter により、GetCeilingColour() の結果に対してメソッドを呼び出すこともできません。
しかし、いずれにしても、ポイントは、構造の表現を連鎖メソッドのシーケンスからメソッド名に移動するのではなく、構造への依存を取り除くことです。Dog クラスに MoveTheLeftHindLegForward() などのメソッドを作成しても、Demeter の実現にはつながりません。代わりに、dog.walk() を呼び出して、犬が自分の足を自分で扱えるようにします。
たとえば、要件が変わり、天井の高さも必要になった場合はどうなりますか?
部屋と天井を操作できるようにコードをリファクタリングします。
interface RoomVisitor {
void visitFloor (Floor floor) ...
void visitCeiling (Ceiling ceiling) ...
void visitWall (Wall wall ...
}
interface Room { accept (RoomVisitor visitor) ; }
Kitchen.accept(RoomVisitor visitor) {
visitor.visitCeiling(this.ceiling);
...
}
または、さらに進んで、ceiling のパラメーターを visitCeiling メソッドに渡すことで、ゲッターを完全に排除することもできますが、これにより脆弱な結合が生じることがよくあります。
これを医療の例に当てはめると、SolubleAdminstrationRoute が薬を検証できるか、または検証に必要な情報が薬のクラスにカプセル化されている場合は、少なくとも薬の validateForSolubleAdministration メソッドを呼び出すことができると予想されます。
しかし、Demeter は、データがデータを操作するオブジェクト内にカプセル化される OO システムに適用されます。これは、あなたが話しているシステムとは異なります。このシステムにはさまざまなレイヤーがあり、データはダムのナビゲート可能な構造でレイヤー間をやり取りされます。Demeter は、モノリシック システムやメッセージ ベースのシステムほど簡単には適用できないと思います。(メッセージ ベースのシステムでは、メッセージのグラムに含まれていないものにはナビゲートできないため、好むと好まざるとにかかわらず Demeter に縛られることになります)