メインコンテンツに移動

Tell, Don't Askとは?オブジェクト指向設計における重要原則を徹底解説

オブジェクト指向設計では、データと振る舞いを一緒に管理することが重要です。オブジェクトは単なるデータの入れ物ではなく、自分の状態を持ち、その状態に基づいて適切な処理を実行する存在として設計されます。しかし実務では、オブジェクトから値を取得し、その値を外部のクラスや関数で判定して処理する実装が多く見られます。このような設計では、ロジックがオブジェクトの外側に散らばり、保守性が低下しやすくなります。

Tell, Don't Askは、このような設計上の問題を改善するための原則です。直訳すると「尋ねるのではなく、命じる」という意味であり、オブジェクトの内部状態を外部から取得して判断するのではなく、オブジェクト自身に処理を依頼するべきだという考え方を示します。これはカプセル化、責務分離、保守性向上と深く関係する重要な設計原則です。

本記事では、Tell, Don't Askの基本概念、TellとAskの違い、なぜこの原則が必要なのか、オブジェクト指向設計との関係、Ask中心設計が抱える問題、Tell中心設計がもたらす効果、Feature EnvyやAnemic Domain Modelとの関係、Law of DemeterやSOLID原則とのつながりまで体系的に解説します。

1. Tell, Don't Askとは?

Tell, Don't Askとは、オブジェクトの状態を外部から取得して判断するのではなく、オブジェクトに対して「何をしてほしいか」を伝え、処理をオブジェクト自身に任せるという設計原則です。オブジェクトが持つデータに基づく判断や処理は、そのデータを持つオブジェクト自身が担当するべきだという考え方が中心にあります。

この原則を守ることで、オブジェクトの内部状態を外部にさらす必要が減り、カプセル化を維持しやすくなります。また、判断ロジックが外部へ分散しにくくなるため、仕様変更時の修正範囲を小さくできます。Tell, Don't Askは、オブジェクト指向らしい設計を実現するための実践的な考え方です。

1.1 オブジェクト指向との関係

オブジェクト指向では、データと振る舞いを同じオブジェクトにまとめることが基本です。たとえば、注文オブジェクトが注文金額や注文状態を持っているなら、その注文がキャンセル可能かどうか、割引対象かどうか、支払い済みかどうかといった判断も、注文オブジェクト自身が扱うのが自然です。外部の処理が注文状態を取得して条件分岐する設計では、データと振る舞いが分離してしまいます。

Tell, Don't Askは、オブジェクトに責任を持たせることで、オブジェクト指向の本来の考え方に近づける原則です。外部から「状態を教えて」と尋ねるのではなく、「この処理を実行して」と依頼することで、オブジェクトは自分の状態を使って適切に判断できます。これにより、コードの責務が明確になり、変更にも強い設計になります。

1.2 なぜ重要なのか

Tell, Don't Askが重要なのは、状態を取得して外部で判断する設計が、保守性を低下させやすいからです。外部の複数箇所で同じ状態を参照し、似たような条件分岐を行うと、仕様変更時に多くの箇所を修正する必要があります。たとえば、注文のキャンセル条件が変わった場合、外部に散らばった条件分岐をすべて探して修正しなければなりません。

一方、キャンセル可否の判断を注文オブジェクトに集約しておけば、条件変更時には注文オブジェクトのメソッドを修正するだけで済みます。Tell, Don't Askは、ロジックの分散を防ぎ、変更理由を適切な場所に集めるために重要です。これはカプセル化だけでなく、テスト容易性やコードレビューのしやすさにもつながります。

2. TellとAskの違い

TellとAskの違いは、オブジェクトに処理を依頼するか、オブジェクトから情報を取り出して外部で判断するかにあります。Tellでは、外部のコードはオブジェクトに対して「実行してほしいこと」を伝えます。Askでは、外部のコードがオブジェクトの状態を取得し、その状態を使って自分で判断します。

Askが常に悪いわけではありませんが、オブジェクトの内部状態に強く依存した条件分岐が外部に増えると、設計は壊れやすくなります。Tell中心の設計では、判断ロジックをデータに近い場所へ配置できるため、責務が明確になり、変更にも対応しやすくなります。

観点TellAsk
基本姿勢オブジェクトに処理を依頼するオブジェクトから状態を取得して外部で判断する
注文にキャンセル処理を依頼する注文状態を取得して外部でキャンセル可否を判断する
ロジックの場所オブジェクト内部に集まりやすい外部に分散しやすい
カプセル化維持しやすい弱くなりやすい
保守性変更範囲を限定しやすい修正箇所が増えやすい

2.1 Tellの考え方

Tellの考え方では、外部のコードはオブジェクトに対して具体的な処理を依頼します。たとえば、order.cancel()のように注文オブジェクトへキャンセル処理を依頼すれば、注文オブジェクトは自分の状態を確認し、キャンセル可能であれば状態を変更し、必要であれば例外や結果を返します。外部のコードは、注文の内部状態を細かく知る必要がありません。

この設計では、注文に関する判断が注文オブジェクトの中に集まります。キャンセル条件が変わった場合も、外部の呼び出し側を大きく変更せず、注文オブジェクトの内部ロジックを修正できます。Tellは、データを持つオブジェクトにそのデータを使った振る舞いを任せる設計です。

2.2 Askの考え方

Askの考え方では、外部のコードがオブジェクトから状態を取得し、その状態に基づいて判断します。たとえば、order.getStatus()で注文状態を取得し、外部でif status == "paid"のように条件分岐する設計です。この方法は一見分かりやすく見えますが、同じような条件分岐が複数箇所に広がると問題になります。

Ask中心の設計では、オブジェクトの内部状態に外部コードが依存しやすくなります。状態の持ち方や条件が変わると、外部の多くの処理も修正が必要になります。これはカプセル化を弱め、仕様変更に弱いコードを生みやすい設計です。

2.3 設計への影響

Tell中心の設計では、責任がオブジェクト内部に集まりやすくなります。データと振る舞いが近い場所にあるため、コードの意図が分かりやすくなり、仕様変更時の影響範囲も限定しやすくなります。オブジェクトが自律的に振る舞うため、オブジェクト指向らしい設計になります。

Ask中心の設計では、判断ロジックが外部へ広がりやすくなります。複数のクラスが同じオブジェクトの状態を参照し、それぞれ独自に条件分岐を行うと、ロジックの一貫性が失われます。設計全体としては、データだけを持つオブジェクトと、外部に散らばる手続き的なロジックの組み合わせになりやすくなります。

3. なぜTell, Don't Askが必要なのか

Tell, Don't Askが必要とされる理由は、オブジェクト指向設計で重要なカプセル化を守るためです。オブジェクトの内部状態を外部へ公開しすぎると、外部のコードがその状態に依存し、オブジェクトの変更が難しくなります。状態をどのように保持するかは本来オブジェクト内部の都合であるべきです。

また、Tell, Don't Askは責務の適切な配置にも関係します。あるデータに基づいて判断する処理は、そのデータを最もよく知っているオブジェクトに置く方が自然です。責任を正しい場所に配置することで、コードの重複を防ぎ、保守性を高めることができます。

3.1 カプセル化の維持

カプセル化とは、オブジェクトの内部状態や実装詳細を外部から隠し、必要な操作だけを公開する考え方です。Tell, Don't Askを守ると、外部のコードはオブジェクトの内部状態を細かく取得せず、オブジェクトに処理を依頼する形になります。これにより、内部状態を外部に漏らす必要が減ります。

カプセル化が維持されている設計では、オブジェクト内部のデータ構造を変更しても、外部への影響を抑えられます。たとえば、注文状態を文字列で管理していたものを状態オブジェクトへ変更しても、外部がcancel()のようなメソッドだけを使っていれば、呼び出し側の修正は少なく済みます。

3.2 責務の適切な配置

Tell, Don't Askは、責務を適切な場所に配置するための考え方でもあります。オブジェクトの状態に基づく判断は、その状態を持つオブジェクト自身が担当する方が自然です。外部のサービスやコントローラーがオブジェクトの状態を取得して判断すると、本来オブジェクトが持つべき責任が外へ漏れてしまいます。

責務が適切に配置されていると、コードの意味が分かりやすくなります。注文に関するルールは注文オブジェクトに、会員ランクに関するルールは会員オブジェクトに、在庫に関するルールは在庫オブジェクトに置くことで、変更時にどこを見ればよいかが明確になります。

3.3 保守性向上

Tell, Don't Askを実践すると、保守性が向上します。ロジックが適切なオブジェクトに集約されるため、仕様変更時に修正箇所を探しやすくなります。外部に散らばった条件分岐を修正する必要が減り、変更漏れのリスクも下がります。

保守性が高いコードは、長期運用に強いコードです。プロジェクトが成長すると、状態やルールは変化します。そのとき、データと振る舞いが適切にまとまっていれば、変更に対応しやすくなります。Tell, Don't Askは、将来の変更に備えるための設計原則です。

4. オブジェクト指向設計との関係

Tell, Don't Askは、オブジェクト指向設計の本質に近い考え方です。オブジェクト指向では、オブジェクトが自分のデータと振る舞いを持ち、他のオブジェクトとメッセージをやり取りしながら処理を進めます。外部から状態を取り出して判断する設計は、オブジェクトを単なるデータ構造として扱う方向に近づきます。

この原則を意識すると、オブジェクトを「データの入れ物」ではなく「振る舞いを持つ主体」として設計しやすくなります。オブジェクトに適切な責任を与えることで、コードはより自然な構造になり、ドメイン知識も表現しやすくなります。

4.1 データと振る舞いの統合

オブジェクト指向では、データと振る舞いを統合することが重要です。たとえば、銀行口座オブジェクトが残高を持つなら、入金、出金、残高不足の判定も銀行口座オブジェクトが担当するのが自然です。外部コードが残高を取得し、出金可能かどうかを判断する設計では、口座のルールが外へ漏れてしまいます。

データと振る舞いを統合すると、ルールの一貫性を保ちやすくなります。出金条件が変わった場合も、口座オブジェクトのメソッドを修正すれば済みます。Tell, Don't Askは、データに関する判断をデータの近くに置くことで、オブジェクト指向の強みを活かす原則です。

4.2 オブジェクトへの責任委譲

Tell, Don't Askでは、外部のコードがすべてを判断するのではなく、適切なオブジェクトへ責任を委譲します。責任委譲とは、その処理を最もよく知っているオブジェクトに任せることです。これにより、外部のコードは細かな状態やルールを知らずに済みます。

責任委譲がうまく行われている設計では、各オブジェクトが自分の役割を持ち、協力しながら処理を進めます。これは、巨大な管理クラスがすべてを制御する設計とは異なります。Tell, Don't Askは、オブジェクト同士の自然な協調を促す考え方です。

4.3 メッセージ駆動設計

オブジェクト指向では、オブジェクト同士がメッセージを送り合うことで処理を進めると考えられます。Tell, Don't Askは、このメッセージ駆動の考え方に近い原則です。外部のコードは「状態を教えて」と尋ねるのではなく、「この処理をしてほしい」とメッセージを送ります。

メッセージ駆動の設計では、呼び出し側は相手の内部構造を知る必要がありません。必要なのは、相手がどのような操作を受け付けるかだけです。これにより、オブジェクト間の結合度が下がり、内部実装を変更しやすくなります。

5. Ask中心の設計が抱える問題

Ask中心の設計では、オブジェクトの状態を取得し、外部で条件分岐や判断を行います。この設計は短期的には分かりやすく見えることがありますが、プロジェクトが大きくなるほど問題が表面化します。特にGetter依存、外部での条件分岐、ロジックの分散が大きな課題になります。

Ask中心の設計が進むと、オブジェクトは自分自身で判断しないデータコンテナのようになります。その結果、判断ロジックはサービス層やコントローラー層に集まり、同じような条件分岐が複数箇所に出現します。これは保守性を下げる典型的な原因です。

5.1 Getter依存

Getter依存とは、オブジェクトの内部状態を取得するGetterメソッドに外部コードが強く依存している状態です。Getter自体が悪いわけではありませんが、Getterで取得した値を使って外部で多くの判断を行う場合、オブジェクトの内部構造が外部に漏れている可能性があります。

Getter依存が強いと、内部状態の変更が難しくなります。たとえば、単純なステータス値を返していたGetterを廃止したい場合、その値を参照しているすべての外部コードを修正する必要があります。Tell, Don't Askでは、状態を取得する代わりに、意味のある操作メソッドを用意することで依存を減らします。

5.2 外部での条件分岐

Ask中心の設計では、外部コードがオブジェクトの状態を見て条件分岐を行います。たとえば、注文状態が支払い済みなら発送し、キャンセル済みなら何もしない、といった判断が外部のサービスに書かれることがあります。この条件分岐が複数箇所に増えると、仕様変更時の修正漏れが起こりやすくなります。

外部での条件分岐が増えると、オブジェクト自身の責任が薄くなります。本来、注文状態に基づく発送可否の判断は注文オブジェクトが知っているべきです。Tell中心の設計では、外部コードはorder.ship()のように処理を依頼し、注文オブジェクトが内部で適切に判断します。

5.3 ロジックの分散

ロジックの分散は、Ask中心設計の大きな問題です。同じ状態に基づく判断が、コントローラー、サービス、バッチ処理、画面処理などに散らばると、どこに本当のルールがあるのか分かりにくくなります。これは変更時の調査コストを大きくします。

ロジックが分散していると、同じルールが微妙に異なる形で実装されることもあります。ある画面ではキャンセル可能と判断されるのに、別の処理ではキャンセル不可と判断されるような不整合が起こり得ます。Tell, Don't Askは、こうしたロジックの分散を防ぐために役立ちます。

6. Tell中心の設計がもたらす効果

Tell中心の設計では、外部コードがオブジェクトに処理を依頼し、オブジェクト自身が内部状態に基づいて判断します。この設計により、責務が明確になり、結合度が下がり、コードの可読性も向上します。オブジェクトが自律的に振る舞うため、設計全体が整理されやすくなります。

また、Tell中心の設計は変更に強い構造を作ります。仕様変更が発生した場合、そのルールを持つオブジェクトを修正すればよく、外部の呼び出し側を大きく変更する必要がありません。これは、保守性と拡張性の両面で大きなメリットになります。

6.1 責務の明確化

Tell中心の設計では、どのオブジェクトが何を担当するのかが明確になります。注文に関する判断は注文オブジェクト、会員ランクに関する判断は会員オブジェクト、在庫に関する判断は在庫オブジェクトに置くことで、責務が自然に整理されます。コードを読む人も、ルールの場所を見つけやすくなります。

責務が明確になると、機能追加や仕様変更の際にも迷いが少なくなります。新しい注文ルールを追加するなら注文オブジェクトを確認する、在庫判定を変更するなら在庫オブジェクトを確認する、という判断ができます。これはチーム開発でも大きなメリットです。

6.2 結合度の低下

Tell中心の設計では、外部コードがオブジェクトの内部状態を詳しく知る必要がなくなります。外部コードは、オブジェクトが公開している操作メソッドだけを使えばよいため、内部実装への依存が減ります。これにより、オブジェクト間の結合度が下がります。

結合度が低い設計では、内部実装を変更しやすくなります。たとえば、注文状態の管理方法を文字列から状態オブジェクトへ変更しても、外部がcancel()ship()のような操作メソッドだけを使っていれば、影響範囲を抑えられます。Tell, Don't Askは、疎結合な設計を支える原則です。

6.3 可読性向上

Tell中心のコードは、意図が読み取りやすくなります。if order.status == "paid"のように外部で細かく判断するよりも、order.ship()のように処理の目的を表すメソッドを呼び出す方が、コードの意味が明確になります。操作名が業務上の意味を表すため、読み手にとって理解しやすくなります。

可読性が向上すると、レビューやデバッグも効率化されます。処理の目的がメソッド名に表れていれば、細かな条件分岐を読む前に全体の流れを把握できます。Tell, Don't Askは、コードをオブジェクト指向らしくするだけでなく、読みやすいコードを書くためにも役立ちます。

7. カプセル化との関係

Tell, Don't Askは、カプセル化と非常に深い関係があります。カプセル化とは、オブジェクトの内部状態や実装の詳細を外部から隠し、必要な操作だけを公開する設計のことです。Ask中心の設計では内部状態を外部に見せる必要が増えるため、カプセル化が弱くなりやすくなります。

Tell中心の設計では、外部はオブジェクトに処理を依頼するだけでよく、内部状態を細かく知る必要がありません。これにより、オブジェクトの自律性が高まり、内部実装を変更しやすくなります。Tell, Don't Askは、カプセル化を実践するための具体的な指針でもあります。

7.1 内部状態の隠蔽

内部状態の隠蔽とは、オブジェクトが持つデータを外部へ直接公開しないことです。状態を隠すことで、外部コードが内部構造に依存するのを防げます。たとえば、会員ランクを数値で管理しているのか、文字列で管理しているのか、専用オブジェクトで管理しているのかは、外部が知る必要のない情報です。

Tell, Don't Askでは、内部状態を取得するのではなく、意味のある操作を呼び出します。たとえば、member.getRank()でランクを取得して外部で判定するのではなく、member.canUsePremiumFeature()のようなメソッドを用意すれば、ランク判定の詳細を会員オブジェクト内部に隠せます。

7.2 情報漏洩の防止

Ask中心の設計では、オブジェクトの内部情報が外部へ漏れやすくなります。外部コードが判断するために多くのGetterが必要になり、結果としてオブジェクトの内部構造が公開されます。これは情報漏洩であり、カプセル化を弱める原因になります。

情報漏洩が起こると、内部構造を変更しにくくなります。外部コードが内部データに依存しているため、データ構造を変えると多くの箇所に影響が出ます。Tell, Don't Askでは、外部に公開する情報を減らし、操作メソッドを通じてやり取りすることで、情報漏洩を防ぎます。

7.3 オブジェクトの自律性

オブジェクトの自律性とは、オブジェクトが自分の状態を理解し、自分に関する判断や処理を実行できることです。自律性の高いオブジェクトは、外部から細かく指示されなくても、自分の責任範囲内で適切に振る舞います。これはオブジェクト指向設計において重要な性質です。

Tell, Don't Askは、オブジェクトの自律性を高めます。外部が状態を取得して判断するのではなく、オブジェクト自身が判断するためです。自律的なオブジェクトが増えると、システム全体が責任の明確な部品の集まりになり、保守しやすい構造になります。

8. Feature Envyとの関係

Feature Envyとは、あるクラスや関数が他のオブジェクトのデータを過度に参照し、そのデータを使って多くの処理を行っている状態を指すコードスメルです。これはTell, Don't Askに反する典型的な状態です。他のオブジェクトの状態を詳しく尋ねて判断しているため、責任の配置が不自然になっています。

Feature Envyが発生している場合、その処理はデータを持っているオブジェクト側へ移動できる可能性があります。Tell, Don't Askの観点で見ると、外部で判断している処理を、適切なオブジェクトのメソッドとして移すことが改善の方向になります。

8.1 他オブジェクトへの過度な依存

Feature Envyでは、ある処理が他のオブジェクトのGetterを大量に呼び出し、その値を使って判断や計算を行います。これは、処理を行っている側が本来持つべきではない知識を持っている状態です。他オブジェクトの内部情報に強く依存しているため、相手側の変更に弱くなります。

他オブジェクトへの過度な依存は、結合度を高めます。データ構造が少し変わるだけで、多くの外部処理が影響を受ける可能性があります。Tell, Don't Askでは、データを持つオブジェクトに処理を任せることで、この依存を減らします。

8.2 責務配置の問題

Feature Envyは、責務配置が間違っているサインです。あるオブジェクトのデータを使って判断する処理が別のクラスにあるなら、その処理はデータを持つオブジェクト側に置いた方が自然かもしれません。責務が正しい場所にないため、ロジックが分散し、保守性が低下します。

責務配置を見直すときは、「この判断に必要な情報を最もよく知っているのはどのオブジェクトか」を考えると分かりやすくなります。必要な情報を持っているオブジェクト自身が処理を担当すれば、外部から多くの情報を引き出す必要がなくなります。

8.3 リファクタリングの方向性

Feature Envyを改善するリファクタリングでは、外部にあるロジックを適切なオブジェクトへ移動します。たとえば、顧客オブジェクトの属性を外部で取得し、優良顧客かどうかを判定しているなら、customer.isPremium()のようなメソッドを顧客オブジェクトに持たせることが考えられます。

このようなリファクタリングにより、ロジックがデータの近くに移動し、カプセル化が強化されます。外部コードは細かな条件を知らずに、意味のあるメソッドを呼び出せばよくなります。Tell, Don't Askは、Feature Envyを改善するための有効な判断基準になります。

9. Anemic Domain Modelとの関係

Anemic Domain Modelとは、ドメインオブジェクトがデータだけを持ち、業務ロジックをほとんど持たない設計を指します。ロジックはServiceやManagerなどの外部クラスに置かれ、ドメインオブジェクトはGetterとSetterだけを持つデータコンテナのようになります。これはTell, Don't Askと対立しやすい設計です。

Anemic Domain Modelでは、外部のサービスがドメインオブジェクトの状態を取得し、条件分岐や処理を行います。そのため、Ask中心の設計になりやすく、ロジックが分散しやすくなります。Tell, Don't Askは、ドメインオブジェクトに振る舞いを戻すための重要な考え方です。

9.1 データ中心設計

Anemic Domain Modelでは、オブジェクトがデータの保持を中心に設計されます。顧客、注文、商品などのクラスは存在していても、それらは属性を持つだけで、実際の判断や処理は外部のサービスが担当します。これは一見シンプルですが、オブジェクト指向の強みを十分に活かせていません。

データ中心設計では、オブジェクトが自分に関するルールを持たないため、外部コードがそのルールを知る必要があります。結果として、同じデータに関するロジックが複数の場所に散らばりやすくなります。Tell, Don't Askは、データ中心ではなく振る舞い中心の設計へ移行するための指針になります。

9.2 ロジックの外部化

Anemic Domain Modelでは、業務ロジックが外部化されます。たとえば、注文がキャンセル可能かどうか、会員が特典を利用できるかどうか、商品が販売可能かどうかといった判断が、Service側に書かれることがあります。これにより、ドメインオブジェクトは自分自身のルールを持たない状態になります。

ロジックが外部化されると、Serviceが肥大化しやすくなります。多くのドメインオブジェクトの状態を取得し、それぞれの条件を判定するため、Serviceが巨大な手続き的処理の集まりになることがあります。Tell, Don't Askを適用すると、こうしたロジックをドメインオブジェクト側へ戻しやすくなります。

9.3 Tell, Don't Askとの対比

Tell, Don't Askは、Anemic Domain Modelとは逆方向の設計を促します。データを外部へ取り出して判断するのではなく、データを持つオブジェクト自身に処理を依頼します。これにより、ドメインオブジェクトはデータと振る舞いを持つ豊かなモデルになります。

ただし、すべてのロジックを無条件にドメインオブジェクトへ入れればよいわけではありません。複数のドメインオブジェクトをまたぐユースケース制御はServiceに置く方が自然な場合もあります。重要なのは、オブジェクト自身の状態に基づく判断を、できるだけそのオブジェクトへ配置することです。

10. Rich Domain Modelとの関係

Rich Domain Modelとは、ドメインオブジェクトがデータだけでなく、業務ルールや振る舞いも持つ設計です。注文、顧客、商品、在庫などのオブジェクトが、自分に関する判断や処理を持つため、ドメイン知識がモデル内に集約されます。Tell, Don't Askは、Rich Domain Modelと相性の良い考え方です。

Rich Domain Modelでは、外部のServiceは細かな状態判定を行うのではなく、ドメインオブジェクトに処理を依頼します。これにより、ドメインルールがモデルに集まり、保守しやすい構造になります。DDDを実践する場合にも、この考え方は重要です。

10.1 振る舞いの集約

Rich Domain Modelでは、データに関する振る舞いをオブジェクト内部に集約します。たとえば、注文オブジェクトがキャンセル、発送、支払い確認などの操作を持つことで、注文に関するルールが注文オブジェクト内にまとまります。外部コードは注文状態を細かく取得する必要がありません。

振る舞いを集約すると、仕様変更時の修正範囲を限定できます。注文のキャンセル条件が変わった場合、注文オブジェクトのキャンセル処理を修正すればよくなります。Tell, Don't Askは、振る舞いを適切な場所へ集めるための原則として機能します。

10.2 ドメイン知識の保持

Rich Domain Modelの大きな特徴は、ドメイン知識をモデルが保持することです。ドメイン知識とは、業務上のルールや判断基準のことです。これをServiceやControllerに散らばらせるのではなく、関連するドメインオブジェクトに持たせることで、知識の場所が明確になります。

ドメイン知識がモデルに集約されていると、コードが業務の言葉に近づきます。たとえば、order.canCancel()customer.canUseDiscount()のようなメソッドは、業務上の意味を直接表現します。Tell, Don't Askは、コードをドメインに近づけるためにも役立ちます。

10.3 DDDとの親和性

DDDでは、ドメインの概念やルールをコードに反映することが重視されます。Tell, Don't Askは、ドメインオブジェクトに振る舞いを持たせることで、DDDの考え方と親和性があります。エンティティや値オブジェクトが自分に関するルールを持つことで、ドメインモデルが表現力を持つようになります。

DDDにおいてServiceが必要になる場面もありますが、すべてのロジックをServiceへ集めるとAnemic Domain Modelになりやすくなります。Tell, Don't Askを意識することで、ドメインオブジェクトに持たせるべき責任と、Serviceが担当すべき責任を分けやすくなります。

11. Law of Demeterとの関係

Law of Demeterは、「直接の友達とだけ話すべき」という考え方で、オブジェクト間の依存を減らすための原則です。Tell, Don't Askと同じく、オブジェクトの内部構造へ深く入り込む設計を避ける点で関係があります。どちらも疎結合な設計を目指す考え方です。

Tell, Don't Askでは、外部から状態を取り出して判断するのではなく、オブジェクトに処理を依頼します。Law of Demeterでは、オブジェクトの連鎖的な呼び出しを避けます。両者を組み合わせることで、内部構造への依存が少ない設計を作りやすくなります。

11.1 オブジェクト間の依存削減

Law of Demeterは、オブジェクトが遠くのオブジェクトに直接依存しないようにする考え方です。たとえば、order.getCustomer().getAddress().getZipCode()のような呼び出しは、注文、顧客、住所の内部構造に外部コードが依存している状態です。このような設計は変更に弱くなります。

Tell, Don't Askの観点では、外部コードが郵便番号を取得して判断するのではなく、必要な処理を注文や顧客に依頼できないかを考えます。たとえば、配送可能かどうかを判断したいなら、order.canShipToRegion()のようなメソッドにまとめることで、内部構造への依存を減らせます。

11.2 Message Chain回避

Message Chainとは、オブジェクトのメソッド呼び出しが連鎖している状態です。長いメソッドチェーンは、外部コードが内部構造を詳しく知っているサインです。構造が変わると呼び出し側も壊れやすくなるため、保守性の面で問題があります。

Tell, Don't Askでは、必要な情報を深く掘り出すのではなく、目的を表すメソッドを用意することを考えます。呼び出し側が複数段階のオブジェクトをたどらなくても、適切なオブジェクトに処理を依頼できるようにすれば、Message Chainを減らせます。

11.3 疎結合化

Tell, Don't AskとLaw of Demeterは、どちらも疎結合化に貢献します。外部コードがオブジェクトの内部状態や関連オブジェクトの構造を知らなくてよい設計では、変更の影響範囲が小さくなります。これは長期的な保守性にとって重要です。

疎結合な設計では、オブジェクトの内部実装を変更しやすくなります。内部のデータ構造や関連オブジェクトを変更しても、公開されている操作メソッドが維持されていれば、外部コードへの影響を抑えられます。Tell, Don't Askは、実務で疎結合を実現するための分かりやすい指針です。

12. SOLID原則との関係

Tell, Don't AskはSOLID原則そのものではありませんが、SOLID原則と深く関係しています。特にSRP、OCP、DIPとは相性がよく、責務分離、拡張性、依存関係の整理に影響します。Tell, Don't Askを意識すると、オブジェクトが適切な責任を持つようになり、SOLID原則も適用しやすくなります。

一方で、Tell, Don't Askを無理に適用しすぎると、オブジェクトに責任を詰め込みすぎる場合もあります。そのため、SOLID原則と合わせて、責任の粒度や依存関係を慎重に設計する必要があります。重要なのは、状態を持つオブジェクトに適切な振る舞いを持たせることです。

12.1 SRPとの関係

SRPは、1つのクラスやモジュールが1つの責任だけを持つべきだという原則です。Tell, Don't Askは、責任を適切なオブジェクトに配置する考え方であるため、SRPと強く関係します。状態に基づく判断を外部へ散らばらせず、適切なオブジェクトへ集めることで、責任が明確になります。

ただし、すべての処理を1つのオブジェクトへ入れると、逆にSRP違反になることがあります。たとえば、注文オブジェクトにキャンセルや発送の判断を持たせるのは自然ですが、メール送信やPDF生成まで持たせると責任が増えすぎます。Tell, Don't AskとSRPは、バランスを取りながら使う必要があります。

12.2 OCPとの関係

OCPは、拡張に対して開かれ、変更に対して閉じているべきだという原則です。Tell, Don't Askを守ると、外部に散らばった条件分岐が減り、変更箇所を限定しやすくなります。これはOCPを実現するための土台になります。

たとえば、外部の複数箇所で注文状態を判定している場合、新しい状態を追加すると多くの条件分岐を修正する必要があります。一方、注文オブジェクトや状態オブジェクトに振る舞いを持たせていれば、新しい状態の追加を局所化しやすくなります。Tell, Don't Askは、拡張に強い設計にもつながります。

12.3 DIPとの関係

DIPは、上位モジュールが下位モジュールの具体実装に依存せず、抽象に依存するべきだという原則です。Tell, Don't Askによって責務が整理されると、どの部分を抽象化すべきかが見えやすくなります。オブジェクトに適切な操作を定義することは、抽象的なインターフェース設計にもつながります。

ただし、Tell, Don't Askを実現するために、すべてを具象クラスへ直接依存させると柔軟性が下がる場合があります。実務では、操作の意味を表すインターフェースを設計し、上位層はその抽象に依存する形にすると、Tell, Don't AskとDIPを両立しやすくなります。

13. Tell, Don't Askを実現する方法

Tell, Don't Askを実現するには、外部にある判断ロジックをオブジェクトへ移動し、Getterの乱用を避け、意味のあるメソッドを設計することが重要です。単にGetterを減らすだけではなく、オブジェクトがどのような責任を持つべきかを考える必要があります。

実務では、既存コードを一度に大きく変える必要はありません。まず、同じような条件分岐が複数箇所にある部分や、Getterを多用して外部で判断している部分を見つけます。その処理を少しずつオブジェクト側へ移動することで、段階的にTell中心の設計へ改善できます。

13.1 振る舞いをオブジェクトへ移動する

Tell, Don't Askを実現する最も基本的な方法は、外部にある振る舞いをオブジェクトへ移動することです。たとえば、外部で注文状態を確認してキャンセル処理を行っているなら、注文オブジェクトにcancel()メソッドを追加し、キャンセル可否の判断も内部で行うようにします。

振る舞いを移動するときは、その処理が本当にそのオブジェクトの責任かを確認します。データを持っているからといって、すべての処理をそのオブジェクトへ入れるべきではありません。オブジェクト自身の状態に基づく判断や操作であれば、そのオブジェクトへ移動する価値があります。

13.2 Getterの乱用を避ける

Getterは便利ですが、乱用するとAsk中心の設計になりやすくなります。特に、Getterで取得した値を外部で条件分岐している場合、その判断をオブジェクトへ移動できないか検討するべきです。Getterは必要な場面もありますが、内部状態を外部へ公開しすぎないよう注意が必要です。

Getterを減らす代わりに、業務上の意味を持つメソッドを用意します。たとえば、getStatus()で状態を取得して外部で判定するのではなく、canCancel()isShippable()のようなメソッドを用意します。これにより、状態の詳細を隠しながら、必要な判断をオブジェクトへ任せられます。

13.3 適切なメソッド設計を行う

Tell, Don't Askを実現するには、メソッド設計が重要です。メソッドは単なるデータ取得ではなく、オブジェクトに依頼したい操作や判断を表す名前にするべきです。calculateDiscount()cancel()approve()canShip()のように、業務上の意味が分かる名前を付けると、コードの意図が明確になります。

適切なメソッド設計では、呼び出し側が内部状態を知らなくても目的を達成できることが理想です。メソッドの中で必要な状態を確認し、正しい処理を実行することで、外部の条件分岐を減らせます。これにより、オブジェクトの責任が明確になり、カプセル化も強化されます。

14. 実務でのベストプラクティス

実務でTell, Don't Askを活用するには、データではなく振る舞いを設計する、オブジェクトに責任を持たせる、継続的にリファクタリングするという姿勢が重要です。最初から完璧な設計を目指すよりも、コードの成長に合わせて責任の配置を見直す方が現実的です。

また、Tell, Don't Askは絶対的なルールではありません。単純なデータ表示やDTOのように、状態を取得することが自然な場面もあります。重要なのは、業務ロジックや判断が外部に散らばっていないかを確認し、必要に応じてオブジェクトへ責任を戻すことです。

14.1 データではなく振る舞いを設計する

Tell, Don't Askを実践するには、まずデータ構造だけでなく振る舞いを設計することが重要です。クラスを作るときに、どの属性を持つかだけを考えるのではなく、そのオブジェクトが何を判断し、何を実行できるべきかを考えます。これにより、単なるデータコンテナではなく、意味のあるオブジェクトを設計できます。

振る舞いを設計すると、コードは業務の言葉に近づきます。注文がキャンセルできる、会員が特典を利用できる、商品が販売可能であるといった概念をメソッドとして表現できます。これにより、コードの可読性が高まり、ドメイン知識も自然にコードへ反映されます。

14.2 オブジェクトに責任を持たせる

オブジェクトに責任を持たせるとは、そのオブジェクトが自分に関する判断や処理を担当するように設計することです。外部のServiceやControllerがすべてを判断するのではなく、適切なオブジェクトへ処理を委譲します。これにより、責任の場所が明確になります。

責任を持たせるときは、過剰に詰め込まないことも重要です。オブジェクト自身の状態に関する処理は持たせるべきですが、外部通知、永続化、画面表示など別の責任まで持たせると、SRPに反する場合があります。Tell, Don't Askは、責任を正しい場所へ置くための考え方です。

14.3 継続的にリファクタリングする

Tell, Don't Askは、継続的なリファクタリングによって実現しやすくなります。最初はシンプルに実装したコードでも、機能追加が続くうちにGetter依存や外部条件分岐が増えることがあります。そのタイミングで、ロジックをオブジェクトへ移動できないか見直すことが重要です。

リファクタリングでは、同じような条件分岐が複数箇所にないか、Feature Envyが発生していないか、ドメインオブジェクトがデータだけになっていないかを確認します。小さな改善を積み重ねることで、Tell中心の設計へ近づけることができます。

おわりに

Tell, Don't Askは、オブジェクトの状態を外部から取得して判断するのではなく、オブジェクトに処理を依頼する設計原則です。この考え方は、オブジェクト指向設計におけるカプセル化、責務分離、保守性向上と深く関係しています。外部の条件分岐を減らし、データを持つオブジェクトに振る舞いを持たせることで、変更に強い設計を作りやすくなります。

Ask中心の設計では、Getter依存、外部での条件分岐、ロジックの分散が起こりやすくなります。その結果、仕様変更時の修正箇所が増え、バグのリスクも高まります。一方、Tell中心の設計では、判断ロジックが適切なオブジェクトに集まり、責任が明確になります。これはFeature EnvyやAnemic Domain Modelの改善にもつながります。

Tell, Don't Askは、Rich Domain ModelやDDDとも相性がよく、ドメイン知識をオブジェクトへ集約するための重要な考え方です。また、Law of DemeterやSOLID原則とも関係し、疎結合で保守しやすい設計を支えます。ただし、すべてのGetterを排除すればよいわけではなく、表示用データやDTOのように状態取得が自然な場面もあります。

実務では、データではなく振る舞いを設計し、オブジェクトに適切な責任を持たせ、継続的にリファクタリングすることが重要です。Tell, Don't Askを意識することで、オブジェクト指向らしい設計に近づき、保守性・拡張性・可読性の高いコードベースを実現できます。

LINE Chat