このブログ投稿 は、いくつかの賛成票とともにHacker Newsに投稿されました。 C++から来たこれらの例のほとんどは、私が教えたものに反するようです。
例2など:
悪い:
def check_for_overheating(system_monitor)
if system_monitor.temperature > 100
system_monitor.sound_alarms
end
end
対良い:
system_monitor.check_for_overheating
class SystemMonitor
def check_for_overheating
if temperature > 100
sound_alarms
end
end
end
C++でのアドバイスは、カプセル化を増やすため、メンバー関数ではなくフリー関数を使用することです。これらはどちらも意味的に同一なので、なぜより多くの状態にアクセスできる選択肢を好むのでしょうか。
例4:
悪い:
def street_name(user)
if user.address
user.address.street_name
else
'No street name on file'
end
end
対良い:
def street_name(user)
user.address.street_name
end
class User
def address
@address || NullAddress.new
end
end
class NullAddress
def street_name
'No street name on file'
end
end
無関係なエラー文字列をフォーマットするのはなぜUser
の責任なのですか?印刷以外のことをしたい場合'No street name on file'
通りがない場合は?通りに同じ名前が付けられているとどうなりますか?
「Tell、Do n't Ask」の利点と根拠について誰かに教えてもらえませんか?私はどちらが良いかを探していませんが、代わりに理解しようとしています著者の視点。
オブジェクトにその状態について尋ね、オブジェクトの外部で行われた決定に基づいてそのオブジェクトのメソッドを呼び出すことは、オブジェクトがリークのある抽象化であることを意味します。その動作の一部はオブジェクトのoutsideにあり、内部状態は(おそらく不必要に)外界に公開されています。
オブジェクトに何をしてほしいかを伝えるように努力する必要があります。自分の状態について質問したり、決定を下したりしないでください。
問題は、呼び出し元として、呼び出されたオブジェクトの状態に基づいて決定を下すべきではなく、その結果、オブジェクトの状態が変更されるということです。実装しているロジックは、おそらくあなたではなく、呼び出されたオブジェクトの責任です。オブジェクトの外部で決定を行うことは、そのカプセル化に違反します。
もちろん、それは明らかです。そのようなコードを書くことはありません。それでも、参照されているオブジェクトを調べ、その結果に基づいてさまざまなメソッドを呼び出すことは非常に簡単です。しかし、それはそれを行うための最善の方法ではないかもしれません。必要なものをオブジェクトに伝えます。それを行う方法を理解させてください。手続き的にではなく、宣言的に考えてください!
責任に基づいてクラスを設計することから始めると、この罠から離れた方が簡単です。その後、オブジェクトの状態を通知するクエリとは対照的に、クラスが実行できるコマンドの指定に自然に進むことができます。
一般に、この作品は、他の人が推論するためにメンバー国を公開すべきではないことを示唆しています自分で推論できる場合。
ただし、明確に述べられていないのは、推論が特定のクラスの責任をはるかに超えている場合、この法律はveryの明らかな制限に該当するということです。たとえば、特定の値を保持する、または特定の値(特に一般的な値)を提供することを目的とするクラス、またはクラスが拡張する必要のある動作を提供するクラス。
たとえば、システムがtemperature
をクエリとして提供する場合、明日、クライアントはSystemMonitor
を変更せずにcheck_for_underheating
を実行できます。これは、SystemMonitor
がcheck_for_overheating
自体を実装する場合には当てはまりません。したがって、温度が高すぎるときにアラームを発生させるジョブのSystemMonitor
クラスはこれに従いますが、別のコードが温度を読み取れるようにするジョブのSystemMonitor
クラスは、たとえばTurboBoostなどを制御できることはできません。
また、2番目の例ではNull Object Anti-patternを無意味に使用しています。
過熱の例の本当の問題は、過熱と見なされるもののルールがシステムによって簡単に変化しないことです。システムAは現状のままである(temp> 100は過熱している)が、システムBはよりデリケートである(temp> 93が過熱している)とします。システムのタイプをチェックするように制御機能を変更してから、正しい値を適用しますか?
if (system is a System_A and system_monitor.temp >100)
system_monitor.sound_alarms
else if (system is a System_B and system_monitor.temp > 93)
system_monitor.sound_alarms
end
それとも、各タイプのシステムに暖房能力を定義させていますか?
編集:
system.check_for_overheating
class SystemA : System
def check_for_overheating
if temperature > 100
sound_alarms
end
end
end
class SystemB : System
def check_for_overheating
if temperature > 93
sound_alarms
end
end
end
前者の方法では、より多くのシステムを扱い始めると、制御機能が醜くなります。後者は、時間が経過しても制御機能を安定させます。
最初に、私はあなたの例を「悪い」と「良い」として特徴付けすることに例外をとらなければならないように感じます。この記事では「あまり良くない」と「より良い」という用語を使用しています。これらの用語は理由のために選択されたと思います。これらはガイドラインであり、状況によっては「あまり良くない」アプローチが適切な場合もあれば、唯一の解決策である場合もあります。
選択肢が与えられたら、クラスにのみ依存する機能を含めるためにpreferenceを与える必要がありますinクラスの外側ではなくクラス-理由はカプセル化のためであり、時間の経過とともにクラスを進化させることが容易になるという事実。クラスはまた、無料の関数の束よりも、その機能を宣伝するのに優れています。
決定はクラス外の何かに依存しているため、またはそれは単にクラスのほとんどのユーザーに実行させたくないものであるため、場合によっては指示する必要があります。場合によっては、振る舞いがクラスにとって直感に反し、クラスのほとんどのユーザーを混乱させたくないので、伝えたいことがあります。
たとえば、エラーメッセージを返す通りの住所に不満がある場合は、エラーではなく、デフォルト値を提供しています。ただし、デフォルト値が適切でない場合があります。これが都道府県または市の場合、レコードをセールスマンまたは調査係に割り当てるときにデフォルトを使用して、すべての未知数が特定の人に送られるようにすることができます。一方、封筒を印刷する場合は、配達できない手紙の用紙を無駄にしないようにする例外または警備員を好むかもしれません。
そのため、「あまり良くない」という方法が適している場合もありますが、一般的には、「より良い」の方がましです。
Data/Object Anti-Symmetry
他の人が指摘したように、Tell-Dont-Askは、質問した後にオブジェクトの状態を変更する場合に特に当てはまります(たとえば、このページの他の場所に投稿されたPragprogテキストを参照してください)。これは常にそうであるとは限りません。 'user'オブジェクトは、user.addressを要求された後も変更されません。したがって、これがTell-Dont-Askを適用する適切なケースであるかどうかは議論の余地があります。
Tell-Dont-Askは責任に関係しており、正当にクラス内にあるはずのクラスからロジックを引き出しません。しかし、オブジェクトを処理するすべてのロジックが必ずしもそれらのオブジェクトのロジックであるとは限りません。これは、Tell-Dont-Askを超えて、より深いレベルでほのめかしているので、そのことについて簡単に説明します。
建築設計の問題として、実際にはプロパティの単なるコンテナーであり、不変でさえあるオブジェクトを用意し、そのようなオブジェクトのコレクションに対してさまざまな関数を実行し、コマンドを送信するのではなく、それらを評価、フィルタリング、または変換する必要があります(これは詳細は、Tell-Dont-Askのドメイン)。
問題に対してより適切な決定は、安定したデータ(宣言オブジェクト)を期待するかどうかに依存しますが、関数側で変更/追加を行います。または、そのような関数の安定した制限されたセットがあることを期待しているが、オブジェクトレベルでより多くのフラックスを期待している場合。新しいタイプを追加する。最初の状況では、2番目のオブジェクトメソッドで、フリー関数を使用します。
ボブ・マーティンは、彼の著書「クリーンコード」でこれを「データ/オブジェクトの反対称性」(p.95ff)と呼んでいます。他のコミュニティはそれを「 式の問題 」と呼ぶかもしれません。
このパラダイムは‘Tell、do n't ask’と呼ばれることがあります。つまり、オブジェクトに何をすべきかを伝え、その状態については尋ねません。 ‘尋ねる、教えない’のように、オブジェクトに何かをするように依頼し、オブジェクトの状態を伝えないでください。どちらの方法でもベストプラクティスは同じです。オブジェクトがアクションを実行する方法は、呼び出し元のオブジェクトではなく、そのオブジェクトの問題です。インターフェースは、(アクセサーやパブリックプロパティなどを介して)状態を公開することを避け、代わりに、実装が不透明な「実行」メソッドを公開する必要があります。他の人は実用的なプログラマーへのリンクでこれをカバーしました。
このルールは、「ダブルドット」または「ダブルアロー」コードを回避することに関するルールに関連しています。これは、「直接の友達とのみ話す」と呼ばれることが多く、foo->getBar()->doSomething()
が悪いと述べ、代わりにfoo->doSomething();
は、バーの機能のラッパー呼び出しであり、単純にreturn bar->doSomething();
として実装されます— foo
がbar
の管理を担当している場合は、それを許可します!
「教えて、聞かないでください」についての他の良い答えに加えて、役立つかもしれない特定の例についてのいくつかの解説:
C++でのアドバイスは、カプセル化を増やすため、メンバー関数ではなくフリー関数を使用することです。これらはどちらも意味的に同一なので、なぜより多くの状態にアクセスできる選択肢を好むのでしょうか。
その選択では、より多くの州にアクセスできません。どちらも同じ量の状態を使用して作業を行いますが、「悪い」例では、作業を行うためにクラスの状態をパブリックにする必要があります。さらに、「悪い」例でのそのクラスの動作は自由関数に広がっており、見つけにくく、リファクタリングが難しくなっています。
無関係なエラー文字列をフォーマットするのはなぜユーザーの責任なのですか?ストリートがない場合に「ファイルにストリート名がありません」以外の印刷をしたい場合はどうすればよいですか?通りに同じ名前が付けられているとどうなりますか?
'street_name'が 'get street name'と 'provide error message'の両方を行うのはなぜですか?少なくとも「良い」バージョンでは、各作品に1つの責任があります。それでも、それは良い例ではありません。
これらの回答は非常に優れていますが、強調するもう1つの例を次に示します。これは通常、重複を回避する方法であることに注意してください。たとえば、次のようなコードを持つ場所がいくつかあるとします。
Product product = productMgr.get(productUuid)
if (product.userUuid != currentUser.uuid) {
throw BlahException("This product doesn't belong to this user")
}
つまり、次のようなものがよいでしょう。
Product product = productMgr.get(productUuid, currentUser)
その複製は、インターフェースのほとんどのクライアントが、あちこちで同じロジックを繰り返すのではなく、新しいメソッドを使用することを意味するためです。自分で行うために必要な情報を要求するのではなく、デリゲートに実行したい作業を与えます。
これは、高レベルのオブジェクトを作成する場合はより当てはまると思いますが、より深いレベルに下がる場合はそれほど当てはまりません。すべてのクラスの消費者を満足させるためにすべての単一のメソッドを書くことは不可能であるため、クラスライブラリ。
たとえば#2は、単純化しすぎだと思います。実際にこれを実装する場合、SystemMonitorは、同じクラスに埋め込まれた低レベルのハードウェアアクセス用のコードと高レベルの抽象化用のロジックを持つことになります。残念ながら、それを2つのクラスに分けようとすると、「Tell、Do n't ask」自体に違反します。
例4はほぼ同じです-データ層にUIロジックを埋め込んでいます。次に、アドレスがない場合にユーザーが見たいものを修正する場合、データ層のオブジェクトを修正する必要があります。また、2つのプロジェクトがこの同じオブジェクトを使用していて、nullアドレスに異なるテキストを使用する必要がある場合はどうでしょうか。
すべてについて「教えて、聞かないで」を実装できれば、それは非常に役立つことに同意します。実生活で尋ねる(そして自分でやる)のではなく、伝えるだけでいいのであれば、私も幸せです!ただし、実際と同様に、ソリューションの実現可能性は高レベルのクラスに非常に制限されます。