International Organization for Standardization によって作成された Open Distributed Processing の参照モデルでは、次の概念が定義されています。
エンティティ: 興味のある具体的または抽象的なもの。
オブジェクト: エンティティのモデル。オブジェクトは、その動作によって特徴付けられ、二重に、その状態によって特徴付けられます。
(オブジェクトの) 動作: 発生する可能性がある一連の制約を持つアクションのコレクション。
インターフェース: オブジェクトの相互作用のサブセットと、それらがいつ発生するかに関する一連の制約から構成される、オブジェクトの動作の抽象化。
カプセル化: オブジェクトに含まれる情報は、オブジェクトがサポートするインターフェースでの相互作用を通じてのみアクセスできるという特性。
これらは非常に幅広いものです。ただし、関数内に機能を配置することが、これらの用語でカプセル化を構成すると論理的に見なすことができるかどうかを見てみましょう。
まず、関数は明らかに「関心のあるもの」のモデルであり、(おそらく) 実行したいアルゴリズムを表し、そのアルゴリズムは解決しようとしている問題に関連しています (したがって、そのモデルです)。 .
関数には動作がありますか? 確かにそうです。関数を実行する前にどこかから呼び出さなければならないという制約の下で実行されるアクションのコレクション (任意の数の実行可能なステートメントである可能性があります) が含まれています。関数は、因果関係なしにいつでも自発的に呼び出されることはありません。法律用語っぽい?もちろんです。それでも、進みましょう。
関数にはインターフェースがありますか? 確かにそうです: 名前と仮パラメーターのコレクションがあり、関数に含まれる実行可能ステートメントにマップされます。関数が呼び出されると、名前とパラメーターのリストが理解されて、実行可能のコレクションを一意に識別します。呼び出し側が実際のステートメントを指定せずに実行されるステートメント。
関数には、関数に含まれる情報が、オブジェクトによってサポートされているインターフェイスでの相互作用を介してのみアクセスできるというプロパティがありますか? うーん、まあ、それはできます。
一部の情報はそのインターフェイスを介してアクセスできるため、一部の情報は関数内で非表示にしてアクセスできないようにする必要があります。(このような情報が示す特性は情報隠蔽と呼ばれ、モジュールは難しい決定と変更される可能性のある決定の両方を隠蔽するように設計されるべきであると主張して Parnas が定義したものです。) では、どの情報が関数内に隠されているのでしょうか?
これを確認するには、まずスケールを考慮する必要があります。たとえば、Java クラスをパッケージ内にカプセル化できると主張するのは簡単です。一部のクラスは公開され (したがって、パッケージのインターフェースになります)、一部はパッケージ非公開になります (したがって、パッケージ内に情報が隠されます)。 . カプセル化理論では、クラスはノードを形成し、パッケージはカプセル化された領域を形成し、全体がカプセル化されたグラフを形成します。クラスとパッケージのグラフは、3 番目のグラフと呼ばれます。
関数 (またはメソッド) 自体がクラス内にカプセル化されていると主張するのも簡単です。繰り返しになりますが、一部の関数は公開され (したがって、クラスのインターフェイスの一部になります)、一部の関数は非公開になります (したがって、クラス内に情報が隠されます)。関数とクラスのグラフは、2 番目のグラフと呼ばれます。
次に、関数について説明します。関数自体がカプセル化の手段である場合、関数には、他の関数に公開されている情報と、関数内に隠されている情報が含まれている必要があります。この情報は何でしょうか?
McCabe から 1 人の候補者が提示されました。Thomas McCabe は、循環的複雑性に関する画期的な論文で、ソース コードについて次のように説明しています。「グラフの各ノードはプログラム内のコード ブロックに対応し、フローはシーケンシャルであり、アークはプログラム内の分岐に対応します。」
順次実行の McCabian ブロックを、関数内にカプセル化できる情報の単位として考えてみましょう。関数内の最初のブロックは常に最初で唯一の実行が保証されているブロックであるため、最初のブロックは他の関数によって呼び出される可能性があるという点で公開されていると見なすことができます。ただし、関数内の他のすべてのブロックは、他の関数から呼び出すことができないため (フローの途中で関数にジャンプできる言語を除く)、これらのブロックは関数内に情報が隠されていると見なされる場合があります。
これらの (おそらく少し曖昧な) 定義を使用すると、「はい」と言うことができます。関数内に機能を配置することは、カプセル化を構成します。関数内のブロックのカプセル化は、最初のグラフです。
ただし、注意点があります。すべてのクラスが public であるパッケージをカプセル化すると考えますか? 上記の定義によれば、パッケージへのインターフェイス (つまり、すべてのパブリック クラス) が実際にパッケージの動作のサブセットを他のパッケージに提供していると言えるので、テストに合格します。ただし、この場合のサブセットはパッケージ全体の動作であり、情報が隠されているクラスはありません。したがって、上記の定義を厳格に満たしているにもかかわらず、定義の精神を満たしていないと感じています。真のカプセル化を主張するには、何かが情報に隠されている必要があります。
あなたが与える例についても同じことが言えます。n = n + 1 は単一の McCabian ブロックであると見なすことができます。これは、それ (および return ステートメント) が単一の連続した実行フローであるためです。ただし、これを配置する関数にはブロックが 1 つしか含まれておらず、そのブロックが関数の唯一のパブリック ブロックであるため、提案された関数内に情報が隠されているブロックはありません。ですから、カプセル化の定義は満たしているかもしれませんが、精神を満たしていないと言えます。
もちろん、カプセル化のような利点を証明できない限り、これはすべて学術的なものです。
カプセル化を促進する 2 つの力があります: セマンティックと論理です。
セマンティック カプセル化とは、カプセル化されたノード (一般的な用語を使用する場合) の意味に基づくカプセル化を意味します。したがって、「アニマル」と「ミネラル」という名前の 2 つのパッケージがあることを伝え、犬、猫、ヤギの 3 つのクラスを与え、これらのクラスをどのパッケージにカプセル化するかを尋ねると、次のようになります。システムのセマンティクスは、3 つのクラスが「鉱物」ではなく「動物」パッケージ内にカプセル化されていることを示唆していると主張するのは完全に正しいでしょう。
ただし、カプセル化のもう 1 つの動機はロジックです。
システムの構成は、システムの各ノードとそれが存在するカプセル化された領域の正確かつ網羅的な識別です。Java システムの特定の構成は、(3 番目のグラフで) システムのすべてのクラスを識別し、各クラスが存在するパッケージを指定することです。
システムを論理的にカプセル化するということは、その構成に依存するシステムの数学的な特性を特定し、その特性が数学的に最小化されるようにそのシステムを構成することを意味します。
カプセル化理論は、カプセル化されたすべてのグラフが最大可能エッジ数 (MPE) を表すことを提案しています。たとえば、クラスとパッケージの Java システムでは、MPE は、そのシステムのすべてのクラス間で存在できるソース コードの依存関係の潜在的な最大数です。同じパッケージ内の 2 つのクラスは、互いに情報を隠すことができないため、両方が相互に依存関係を形成する可能性があります。ただし、個別のパッケージ内の 2 つのパッケージ プライベート クラスは、相互に依存関係を形成しない場合があります。
カプセル化理論は、MPE を最小化するために、特定の数のクラスに対して必要なパッケージの数を教えてくれます。エンティティのコレクションを変換する際の最大の潜在的な負荷は、変換されるエンティティの最大の潜在的な数の関数であると、負荷の原則の弱い形式が述べているため、これは便利です。クラスを変更すると、特定の更新を行う潜在的なコストが大きくなります。したがって、MPE を最小化すると、更新の潜在的な最大コストが最小化されます。
n 個のクラスと、パッケージごとに p 個のパブリック クラスの要件がある場合、カプセル化理論は、MPE を最小化する必要があるパッケージの数 r は、r = sqrt(n/p) という式で与えられることを示しています。
これは、システム内の McCabian ブロックの総数 n を考えると、持つべき関数の数にも当てはまります。上で述べたように、関数には常に 1 つの public ブロックしかないため、システムに含める関数の数 r の式は、r = sqrt(n) に単純化されます。
確かに、カプセル化を実践するときにシステム内のブロックの総数を考慮した人はほとんどいませんが、クラス/パッケージ レベルでは容易に実行できます。さらに、MPE を最小化することはほぼ完全に直感的です: それは、パブリック クラスの数を最小化し、パッケージにクラスを均一に分散しようとすることによって行われます (または、少なくとも、ほとんどのパッケージに 30 個のクラスを含め、1 つのモンスター パッケージに 500 個のクラスを含めることは避けます。その場合、後者の内部 MPE は、他のすべての MPE を簡単に圧倒する可能性があります)。
したがって、カプセル化には、セマンティックと論理のバランスを取ることが含まれます。
とても楽しいです。