OOP言語を使用してデータ構造(キューなど)を実装する場合、データ構造の一部のメンバーはプライベートにする必要があります(キュー内のアイテム数など)。
キューは、struct
およびで動作する一連の関数を使用して手続き型言語で実装することもできますstruct
。ただし、手続き型言語では、メンバーをstruct
プライベートにすることはできません。手続き型言語で実装されたデータ構造のメンバーは公開されていましたか、それとも非公開にするためのトリックがありましたか?
OOP言語を使用してデータ構造(キューなど)を実装する場合、データ構造の一部のメンバーはプライベートにする必要があります(キュー内のアイテム数など)。
キューは、struct
およびで動作する一連の関数を使用して手続き型言語で実装することもできますstruct
。ただし、手続き型言語では、メンバーをstruct
プライベートにすることはできません。手続き型言語で実装されたデータ構造のメンバーは公開されていましたか、それとも非公開にするためのトリックがありましたか?
回答:
OOPはカプセル化を考案しておらず、カプセル化と同義ではありません。多くのOOP言語には、C ++ / Javaスタイルのアクセス修飾子がありません。多くの非OOP言語には、カプセル化を提供するためのさまざまな手法があります。
機能プログラミングで使用されるように、カプセル化のための1つの古典的なアプローチは閉鎖です。これはOOPよりもかなり古いですが、ある意味では同等です。たとえば、JavaScriptで次のようなオブジェクトを作成できます。
function Adder(x) {
this.add = function add(y) {
return x + y;
}
}
var plus2 = new Adder(2);
plus2.add(7); //=> 9
上記のplus2
オブジェクトには、直接アクセスできるメンバーはありませんx
。完全にカプセル化されています。このadd()
メソッドは、x
変数に対するクロージャーです。
Cの言語は、そのを通じてカプセル化のいくつかの種類をサポートするヘッダファイル機構、特に不透明ポインタ技術。Cでは、メンバーを定義せずに構造体名を宣言できます。その時点では、その構造体の型の変数は使用できませんが、その構造体へのポインターを自由に使用できます(構造体ポインターのサイズはコンパイル時にわかっているため)。たとえば、次のヘッダーファイルを考えます。
#ifndef ADDER_H
#define ADDER_H
typedef struct AdderImpl *Adder;
Adder Adder_new(int x);
void Adder_free(Adder self);
int Adder_add(Adder self, int y);
#endif
フィールドにアクセスせずに、このAdderインターフェイスを使用するコードを作成できるようになりました。例:
Adder plus2 = Adder_new(2);
if (!plus2) abort();
printf("%d\n", Adder_add(plus2, 7)); /* => 9 */
Adder_free(plus2);
そして、完全にカプセル化された実装の詳細は次のとおりです。
#include "adder.h"
struct AdderImpl { int x; };
Adder Adder_new(int x) {
Adder self = malloc(sizeof *self);
if (!self) return NULL;
self->x = x;
return self;
}
void Adder_free(Adder self) {
free(self);
}
int Adder_add(Adder self, int y) {
return self->x + y;
}
モジュールレベルのインターフェイスに焦点を合わせたモジュールプログラミング言語のクラスもあります。ML言語ファミリーを含む。OCamlには、ファンクターと呼ばれるモジュールへの興味深いアプローチが含まれています。OOPはモジュール式プログラミングを覆い隠し、大部分は包み込みましたが、OOPの多くの利点は、オブジェクト指向よりもモジュール性に関するものです。
また、C ++やJavaなどのOOP言語のクラスは、オブジェクト(遅延バインディング/動的ディスパッチを通じて操作を解決するエンティティの意味で)ではなく、単に抽象データ型(非表示にするパブリックインターフェイスを定義するエンティティ)で使用されることが多いという観察もあります内部実装の詳細)。「データの抽象化の理解に関する再考」(Cook、2009)の論文では、この違いについてさらに詳しく説明しています。
しかし、はい、多くの言語にはカプセル化メカニズムがまったくありません。これらの言語では、構造体のメンバーは公開されています。せいぜい、使用を思いとどまらせる命名規則があるでしょう。たとえば、Pascalには便利なカプセル化メカニズムがなかったと思います。
Adder self = malloc(sizeof(Adder));
?typedefingポインターにsizeof(TYPE)
は理由があり、一般的に眉をひそめています。
sizeof(*Adder)
ので、単に書くことはできません。この表現は慣用的で正しいものです。編集をご覧ください。*Adder
*int *
T t = malloc(sizeof *t)
private static
Javaの変数と同等でした。Cと同様に、不透明なポインターを使用して、データを何も宣言せずにPascalで渡すことができます。レコード(データ構造)のパブリック部分とプライベート部分が一緒に渡される可能性があるため、従来のMacOSは多くの不透明なポインターを使用していました。Window Recordの一部は公開されていましたが、いくつかの内部情報も含まれていたため、Window Managerがこれを多く行ったことを覚えています。
_private_member
とoutput_property_
、またはimutableオブジェクトを作成するための、より高度な技術を。
まず、手続き型とオブジェクト指向の関係は、パブリックとプライベートとは関係ありません。多数のオブジェクト指向言語には、アクセス制御の概念がありません。
第二に、ほとんどの人がオブジェクト指向ではなく手続き型と呼ぶ「C」には、効果的にプライベートにするために使用できる多くのトリックがあります。非常に一般的なのは、不透明(たとえばvoid *)ポインターを使用することです。または-オブジェクトを前方宣言できますが、ヘッダーファイルで定義するだけではありません。
foo.h:
struct queue;
struct queue* makeQueue();
void add2Queue(struct queue* q, int value);
...
foo.c:
struct queue {
int* head;
int* head;
};
struct queue* makeQueue() { .... }
void add2Queue(struct queue* q, int value) { ... }
Windows SDKを見てください!HANDLEとUINT_PTRを使用し、そのようなものはAPIで使用されるメモリへの汎用ハンドルであり、実装を効果的にプライベートにします。
「不透明なデータ型」は、30年前にコンピューターサイエンスの学位を取得したときによく知られた概念でした。OOPは当時一般的に使用されておらず、「関数型プログラミング」がより適切であると見なされていたため、OOPについては説明しませんでした。
Modula-2はそれらを直接サポートしていました。https://www.modula2.org/reference/modules.phpを参照してください。
Lewis Pringleは、Cで構造体の前方宣言をどのように使用できるかについて既に説明しました。モジュール2とは異なり、オブジェクトを作成するにはファクトリー関数を提供する必要がありました。(仮想メソッドは、構造体の最初のメンバーがメソッドへの関数ポインターを含む別の構造体へのポインターであることにより、Cで簡単に実装できました。)
多くの場合、慣習も使用されました。たとえば、「_」で始まるフィールドには、データを所有していたファイルの外部からアクセスしないでください。これは、カスタムチェックツールを作成することで簡単に実施できました。
私が取り組んだすべての大規模プロジェクト(C ++に移行する前、C#に移行する前)には、間違ったコードが「プライベート」データにアクセスするのを防ぐためのシステムがありました。現在よりも少し標準化されていませんでした。
メンバーをプライベートとしてマークする組み込み機能を持たない多くのオブジェクト指向言語があることに注意してください。これは慣例により行うことができ、コンパイラーがプライバシーを強制する必要はありません。たとえば、多くの場合、プライベート変数の前にアンダースコアが付きます。
「プライベート」変数へのアクセスを困難にする手法がありますが、最も一般的なのはPIMPLイディオムです。これにより、プライベート変数が別の構造体に配置され、パブリックヘッダーファイルにポインターのみが割り当てられます。これは、余分な逆参照と、プライベート変数を取得するためのキャストを意味し((private_impl)(obj->private))->actual_value
ます。
データ構造には「メンバー」はなく、データフィールドのみがありました(レコードタイプであると仮定)。通常、可視性はタイプ全体に設定されました。ただし、関数はレコードの一部ではなかったため、考えているほど制限されない場合があります。
ここに戻って少し歴史を見てみましょう...
OOP以前の支配的なプログラミングパラダイムは、構造化プログラミングと呼ばれていました。これの最初の主な目標は、非構造化ジャンプステートメント(「goto」)の使用を避けることでした。これは制御フロー指向のパラダイムですが(OOPはデータ指向です)、コードのように論理的に構造化されたデータを維持しようとするのが自然な拡張でした。
構造化プログラミングの別のアウトシュートは情報隠蔽でした。これは、コードの構造の実装(かなり頻繁に変更される可能性が高い)をインターフェイス(理想的にはほとんど変更しない)とは別に保つべきだという考えです。今では定説ですが、昔は、多くの人がすべての開発者がシステム全体の詳細を知る方が良いと考えていたので、これはかつては議論の余地のあるアイデアでした。BrookのThe Mythical Man Monthのオリジナル版は、実際には情報の隠蔽に反対しました。
優れた構造化プログラミング言語(Modula-2やAdaなど)になるように明示的に設計された後のプログラミング言語には、一般に、基本的な概念として情報隠蔽が含まれ、関数(および任意の型、定数、彼らが必要とするかもしれないオブジェクト)。Modula-2では、これらは「モジュール」、Adaでは「パッケージ」と呼ばれていました。現代の多くのOOP言語は、同じ概念を「名前空間」と呼んでいます。これらの名前空間は、これらの言語の開発の組織的基盤であり、ほとんどの目的でOOPクラスと同様に使用できます(もちろん、継承は実際にサポートされていません)。
したがって、Modula-2およびAda(83)では、名前空間privateまたはpublicで任意のルーチン、型、定数、またはオブジェクトを宣言できますが、レコード型がある場合、一部のレコードフィールドを public として宣言する(簡単な)方法はありませんでしたおよび他のプライベート。レコード全体が公開されているか、公開されていないかのどちらかです。
object.method()
呼び出しは、単に構文糖です。重要な私見-メイヤーの統一アクセス/リファレンスの原則を参照してください-しかし、まだ構文上の砂糖です。
object.method()
ための代替形式として許可することにより、彼ら自身の議論を証明したと信じていmethod(object, ...)
ます。
Cでは、他の人が言っているように、宣言されているが未定義の型へのポインタを既に渡し、事実上すべてのフィールドへのアクセスを制限することができます。
モジュールごとにプライベート関数とパブリック関数を使用することもできます。ソースファイルで静的と宣言された関数は、名前を推測しようとしても、外部からは見えません。同様に、静的なファイルレベルのグローバル変数を使用できます。これは一般的に悪い習慣ですが、モジュール単位で分離できます。
言語で強制されたコンストラクトではなく、標準化された慣習としてのアクセス制限がうまく機能することを強調することはおそらく重要です(Pythonを参照)。それに加えて、オブジェクトフィールドへのアクセスを制限することは、作成後にオブジェクト内のデータの値を変更する必要がある場合にのみプログラマを保護します。これはすでにコードの匂いです。間違いなく、const
メソッドと関数の引数に対するCおよび特にC ++のキーワードは、Javaのやや貧弱なものよりもはるかにプログラマーの助けになりますfinal
。
static
グローバルデータと操作でした(つまり、他のコンパイルで使用するためにリンカーに提示されませんでした)。あなたはもっともらしくCはかなりハックではなく、1972年における言語バックのオリジナルデザインの一部であったことから、別に良いソフトウェアデザインの実践のために持っていたすべてのサポートを主張することができます
これは非常に単純な反例です。Javaでは、interface
sはオブジェクトを定義しますが、class
esは定義しません。Aは、class
抽象データ型ではなく、オブジェクトを定義します。
エルゴ、あなたが使用するすべての時間private
にclass
Javaでは、あなたがオブジェクト指向ではありませんプライベートなメンバーとデータ構造の例を持っています。