「変化するものをカプセル化する」と言うとき、それはどういう意味ですか?


25

私が出会ったOOP原則の1つは次のとおりです。-さまざまなものをカプセル化します。

私はフレーズの文字通りの意味が何であるかを理解しています。しかし、私はそれがより良いデザインにどの程度貢献するのかわかりません。誰かが良い例を使って説明できますか?


よく説明されているen.wikipedia.org/wiki/Encapsulation_(computer_programming)を参照してください。時々定数をカプセル化する必要があるため、「変化するもの」は正しくないと思います。
qwerty_so 16

I don't know how exactly would it contribute to a better design詳細のカプセル化は、「モデル」と実装の詳細との間の疎結合に関するものです。実装の詳細に対する「モデル」の結び付きが少ないほど、ソリューションの柔軟性が高まります。そして、それはそれをより簡単に進化させます。「詳細から遠ざける」。
ライヴ

@Laivでは、「変化する」とは、ソフトウェアライフサイクルの過程で変化するもの、またはプログラムの実行中に変化するもの、あるいはその両方を指しますか?
ハリスガウリ

2
@HarisGhauri両方。一緒に変化するものをグループ化します。独立して変化するものを分離します。決して変わらないと思われるものに疑いを抱いてください。
candied_orange 16

1
「抽象的」と考える@laivは良い点です。それを行うのは圧倒的に感じることができます。いずれかのオブジェクトでは、1つの責任があると思われます。それについての良いところは、ここでその1つのことだけを注意深く考える必要があるということです。問題の残りの部分の詳細が他の誰かの問題である場合は、物事が簡単になります。
candied_orange 16

回答:


30

次のようなコードを書くことができます。

if (pet.type() == dog) {
  pet.bark();
} else if (pet.type() == cat) {
  pet.meow();
} else if (pet.type() == duck) {
  pet.quack()
}

または、次のようなコードを記述できます。

pet.speak();

変化するものがカプセル化されている場合、それについて心配する必要はありません。必要なものと使用しているものについて心配するだけで、さまざまなものに基づいて本当に必要なことを行う方法がわかります。

さまざまなものをカプセル化すれば、さまざまなものを気にするコードを拡散する必要はありません。ペットをそのタイプとして話す方法を知っている特定のタイプに設定するだけで、その後、どのタイプを忘れて、ペットのように扱うことができます。どのタイプを尋ねる必要はありません。

型にアクセスするにはゲッターが必要なため、型はカプセル化されていると考えるかもしれません。しません。ゲッターはカプセル化されていません。誰かがあなたのカプセル化を破ると、彼らはただたたきます。これらは、デバッグコードとして最もよく使用されるアスペクト指向フックのような素晴らしいデコレータです。どのようにスライスしても、あなたはまだタイプを公開しています。

この例を見て、多型とカプセル化を混同していると思うかもしれません。私は違います。「変化するもの」と「詳細」を混同しています。

あなたのペットが犬であるという事実は詳細です。異なる場合があります。ないかもしれないもの。しかし、確かに人によって異なる場合があります。このソフトウェアが犬の愛好家によってのみ使用されると信じない限り、犬を細部として扱い、カプセル化するのは賢明です。そうすることで、システムの一部は犬をまったく気づかずに認識し、「オウムは私たちです」と合併しても影響を受けません。

残りのコードから詳細を分離、分離、非表示にします。システムの詳細についての知識を広めないでください。「変化するものをカプセル化する」だけで問題なく追跡できます。


3
それは本当に奇妙です。私にとって「変化するものをカプセル化する」とは、グローバル変数を持たないなど、状態の変化を隠すことを意味します。しかし、あなたはそれが:)カプセル化よりも多型に多くの答えを感じている場合でもなりますが、あまりにも感じる答える
デビッドアルノ

2
@DavidArnoポリモーフィズムは、この機能を実現する1つの方法です。if構造をペットに詰め込んだだけで、ペットのカプセル化のおかげでここで物事が良く見えるかもしれません。しかし、それはクリーンアップするのではなく、単に混乱を動かすだけです。
candied_orange 16

1
「変化するものをカプセル化する」とは、状態の変化を隠すことを意味します。いや、いや。COのコメントが好きです。 デリック・エルキンの答えはより深く、度も読んでください。@JacquesBが言ったように、「この原理は実際には非常に深い」
レーダーボブ

16

ここでの「変化する」とは、「要件の変更により、時間とともに変化する可能性がある」ことを意味します。これは、設計の核となる原則です。将来的に個別に変更する必要のあるコードまたはデータの断片を分離および分離します。1つの要件が変更された場合、理想的には、関連するコードを1か所で変更することのみが必要です。しかし、コードベースの設計が不適切な場合、つまり相互接続性が高く、要件のロジックが多くの場所に広がっている場合、変更は困難であり、予期しない影響を引き起こすリスクが高くなります。

多くの場所で消費税計算を使用するアプリケーションがあるとします。売上税率が変更された場合、何を希望しますか?

  • 売上税率は、売上税が計算されるアプリケーションのすべての場所でハードコーディングされたリテラルです。

  • 売上税率はグローバルな定数であり、売上税が計算されるアプリケーションのあらゆる場所で使用されます。

  • calculateSalesTax(product)売上税率が使用される唯一の場所であると呼ばれる単一のメソッドがあります。

  • 消費税率は構成ファイルまたはデータベースフィールドで指定されます。

売上税率は、他の要件とは独立した政治的決定により変更される可能性があるため、構成に分離して、コードに影響を与えずに変更できるようにすることをお勧めします。しかし、売上税を計算するためのロジックが変わる可能性も考えられます。たとえば、製品ごとにレートが異なるため、計算ロジックをカプセル化することも必要です。グローバル定数は良い考えのように思えるかもしれませんが、実際には悪いです。単一の場所ではなく、プログラム内のさまざまな場所で消費税を使用することを奨励する可能性があるためです。

次に、コード内の多くの場所でも使用されている別の定数Piについて考えます。同じ設計原則が適用されますか?いいえ、Piは変更されないためです。構成ファイルまたはデータベースフィールドに抽出すると、不必要に複雑になります(他のすべてが等しい場合、最も単純なコードを優先します)。矛盾を避けて読みやすくするために、複数の場所でハードコードするのではなく、グローバル定数にする方が理にかなっています。

重要なのは、プログラムの現在の仕組みだけを見ると、売上税率とPiは同等で、どちらも定数であるということです。将来変化する可能性があるものを考慮する場合にのみ、デザインでそれらを異なる方法で処理する必要があることに気付きます。

この原則は、実際には非常に深いものです。これは、コードベースが今日行うことを想定しているだけでなく、変更を引き起こす可能性のある外力を考慮し、要件の背後にあるさまざまな利害関係者を理解する必要があるためです。


2
税金は良い例です。法律と税金の計算は、日によって変わる可能性があります。税務申告システムを実装している場合、この種の変更に強く結びついています。また、あるロケールから別のロケールに変更します(国、県、...)
Laiv

「パイは変わらない」と私は笑いました。確かに、Piが変更されることはほとんどありませんが、それを使用することはもう許可されていないとしますか?一部の人々が自分のやり方を持っている場合、Piは廃止されます。それが要件になると仮定しますか?あなたが思っている幸せなタウの日。いい答えですね。本当に深い。
candied_orange 16

14

現在の回答はどちらも部分的にしか成功していないようで、中心的なアイデアを曇らせる例に焦点を当てています。これは(単独で)OOPの原則ではなく、一般的なソフトウェア設計の原則でもあります。

このフレーズで「変化する」のはコードです。クリストフは、それが何か普通であると言っに点にあることがありますが、多くの場合で異なり、予想されるこれは。目標は、コードの将来の変更から身を守ることです。これは、インターフェイスに対するプログラミングに密接に関連しています。ただし、クリストフはこれを「実装の詳細」に限定するのは正しくありません。実際、このアドバイスの価値はしばしば要件の変更によるものです

これは、状態のカプセル化に間接的に関連しているだけです。これは、David Arnoが考えていることです。このアドバイスは、常に状態をカプセル化することを示唆しているわけではありませんが(多くの場合そうです)、このアドバイスは不変オブジェクトにも当てはまります。実際、単に定数に名前を付けることは、さまざまなものをカプセル化する(非常に基本的な)形式です。

CandiedOrangeは、「変化するもの」を「詳細」で明示的に圧縮します。これは部分的に正しいだけです。変化するコードは何らかの意味で「詳細」ですが、「詳細」を定義してトートロジーを作成しない限り、「詳細」は変化しないことに同意します。変化しない詳細をカプセル化する理由があるかもしれませんが、このthisは1つではありません。大まかに言って、「犬」、「猫」、「アヒル」だけが対処する必要があると確信している場合、このディクタムはCandiedOrangeが実行するリファクタリングを示唆していません。

CandiedOrangeの例を別のコンテキストでキャストする場合、Cのような手続き型言語があると仮定します。以下を含むコードがある場合:

if (pet.type() == dog) {
  pet.bark();
} else if (pet.type() == cat) {
  pet.meow();
} else if (pet.type() == duck) {
  pet.quack()
}

このコードは将来変更されると合理的に予想できます。新しい手順を定義するだけで、それを「カプセル化」できます。

void speak(pet) {
  if (pet.type() == dog) {
    pet.bark();
  } else if (pet.type() == cat) {
    pet.meow();
  } else if (pet.type() == duck) {
    pet.quack()
  }
}

コードブロックの代わりにこの新しいプロシージャを使用します(つまり、「抽出メソッド」リファクタリング)。この時点で、「牛」タイプまたはspeakプロシージャの更新のみが必要なものを追加します。もちろん、OO言語では、代わりにCandiedOrangeの答えが示すように動的ディスパッチを活用できます。これはpet、インターフェイスを介してアクセスする場合に自然に発生します。動的ディスパッチを介して条件付きロジックを排除することは、この手続き型の表現を作成した理由の一部であった直交的な懸念です。また、これにはOOPに固有の機能は必要ないことを強調したいと思います。オブジェクト指向言語であっても、変化するものをカプセル化しても、必ずしも新しいクラスまたはインターフェースを作成する必要があるわけではありません。

より典型的な例(より近いが完全にはオブジェクト指向ではない)として、リストから重複を削除したいとします。リストを繰り返し処理して、今までに別のリストで見た項目を追跡し、見た項目をすべて削除するとしましょう。少なくともパフォーマンス上の理由から、見たアイテムを追跡する方法を変更したいと思うかもしれません。さまざまなものをカプセル化するという考え方は、見たアイテムのセットを表す抽象データ型を構築する必要があることを示唆しています。アルゴリズムはこの抽象Setデータ型に対して定義されており、バイナリ検索ツリーに切り替えることにした場合、アルゴリズムを変更したり気にしたりする必要はありません。OO言語では、クラスまたはインターフェイスを使用してこの抽象データ型をキャプチャできます。SML / O 'のような言語で

要件駆動型の例では、いくつかのビジネスロジックに関していくつかのフィールドを検証する必要があるとします。現在、特定の要件があるかもしれませんが、それらが進化することを強く疑います。現在のロジックを独自のプロシージャ/関数/ルール/クラスにカプセル化できます。

これは「変化するものをカプセル化する」の一部ではない直交問題ですが、多くの場合、カプセル化されたロジックによって抽象化、つまりパラメーター化されます。通常、これによりコードがより柔軟になり、カプセル化されたロジックを変更するのではなく、代替実装に置き換えることでロジックを変更できます。


ほろ苦い皮肉。はい、これは単にOOPの問題ではありません。あなたは言語のパラダイムの詳細が私の答えを汚すようにさせ、パラダイムを「変化させる」ことによってそれを正当に罰したことを私に捕らえました。
candied_orange

「でも、オブジェクト指向言語では、どのような変化しても、必ずしも作成される新しいクラスやインタフェースの必要性を意味するものではありませんカプセル化する」 -それは、新しいクラスやインターフェイスを作成していないことはSRPに違反しないとの状況を想像するのは難しい
taurelas

11

「変化するものをカプセル化する」とは、変更および進化する可能性のある実装の詳細を隠すことを指します。

例:

たとえば、クラスがregister()できることCourseを追跡するとStudentsします。を使用して実装しLinkedList、コンテナを公開して繰り返しを許可できます。

class Course { 
    public LinkedList<Student> Atendees; 
    public bool register (Student s);  
    ...
}

しかし、これは良い考えではありません。

  • 第一に、人々は良い振る舞いに欠け、それをセルフサービスとして使用し、register()メソッドを経由せずに、学生をリストに直接追加します。
  • しかし、さらに厄介なことに、これにより、使用されるクラスの内部実装の詳細に対する「コードの使用」の依存関係が作成されます。これにより、たとえば、配列、ベクトル、座席番号付きのマップ、または独自の永続データ構造を使用する場合など、クラスの将来の進化が妨げられる可能性があります。

さまざまなものをカプセル化すると(または、むしろ何が変わると言うか)、使用するコードとカプセル化されたクラスの両方が自由にお互いを進化させることができます。これが、OOPの重要な原則である理由です。

追加の読み物:

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.