OOPの前に、データ構造体のメンバーは公開されていましたか?


44

OOP言語を使用してデータ構造(キューなど)を実装する場合、データ構造の一部のメンバーはプライベートにする必要があります(キュー内のアイテム数など)。

キューは、structおよびで動作する一連の関数を使用して手続き型言語で実装することもできますstruct。ただし、手続き型言語では、メンバーをstructプライベートにすることはできません。手続き型言語で実装されたデータ構造のメンバーは公開されていましたか、それとも非公開にするためのトリックがありましたか?


75
「データ構造の一部のメンバーはプライベートである必要があります」「おそらくあるべき」と「必要な」の間には大きな違いがあります。OO言語を教えてください。すべてのメンバーとメソッドが公開されていても、その自由をすべて悪用しない限り、完璧に機能するキューを作成できます。
8bittree

48
@ 8bittreeの発言に別の言い方をすれば、あなたのコードを使用している人々があなたが設定したインターフェースに固執するのに十分な規律を持っていれば、すべてを公開することは問題ありません。プライベートなメンバー構造は、自分が所属していない場所から鼻を出せない人々のために生まれました。
Blrfl

20
「カプセル化が普及する前に」という意味ですか?カプセル化は、OO言語が普及する前に非常に普及していました。
フランクヒルマン

6
@FrankHileman私はそれが実際に問題の核心だと思います:OPは、Simula / Smalltalk / C ++の前に手続き言語にカプセル化が存在するかどうかを知りたい
18

18
これが卑劣だと思われる場合は、事前に申し訳ありませんが、そうではありません。他の言語を学ぶ必要があります。プログラミング言語は、マシンを実行するためではなく、プログラマーが考えるためのものです。彼らは必然的にあなたの考え方を形作ります。JavaScriptを1日中仕事で終わらせたとしても、JavaScript / Python / Ocaml / Clojureでの作業にかなりの時間を費やしていれば、この質問はありません。私が取り組んでいる1つのC ++オープンソースプロジェクト(とにかくほとんどCです)以外は、大学以来アクセス修飾子を持つ言語を実際に使用していません。見逃していません。
ジャレッド・スミス

回答:


139

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には便利なカプセル化メカニズムがなかったと思います。


11
のエラーを参照してくださいAdder self = malloc(sizeof(Adder));?typedefingポインターにsizeof(TYPE)は理由があり、一般的に眉をひそめています。
デュプリケータ

10
型ではないので、型ではないsizeof(*Adder)ので、単に書くことはできません。この表現は慣用的で正しいものです。編集をご覧ください。*Adder*int *T t = malloc(sizeof *t)
-wchargin

4
Pascalには、そのユニットの外部からは見えないユニット変数がありました。事実上、ユニット変数はprivate staticJavaの変数と同等でした。Cと同様に、不透明なポインターを使用して、データを何も宣言せずにPascalで渡すことができます。レコード(データ構造)のパブリック部分とプライベート部分が一緒に渡される可能性があるため、従来のMacOSは多くの不透明なポインターを使用していました。Window Recordの一部は公開されていましたが、いくつかの内部情報も含まれていたため、Window Managerがこれを多く行ったことを覚えています。
マイケルショップシン

6
おそらく、パスカルより良い例は、次のような命名規則に頼る、オブジェクト指向が、ノーカプセル化をサポートするPythonの、ある_private_memberoutput_property_、またはimutableオブジェクトを作成するための、より高度な技術を。
-Mephy

11
OOD文献には、すべての設計原則をOO設計原則として提示する迷惑な傾向があります。(学術的ではない)OODの文献は、誰もがすべて間違ったことをしている「暗黒時代」の絵を描く傾向があり、OOP開業医は光をもたらします。私が知る限り、これは主に無知に由来しています。たとえば、私が知る限り、ボブ・マーティンはほんの数年前に関数型プログラミングを真剣に考えました。
デレクエルキンス

31

まず、手続き型とオブジェクト指向の関係は、パブリックとプライベートとは関係ありません。多数のオブジェクト指向言語には、アクセス制御の概念がありません。

第二に、ほとんどの人がオブジェクト指向ではなく手続き型と呼ぶ「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で使用されるメモリへの汎用ハンドルであり、実装を効果的にプライベートにします。


1
私のサンプルは、前方宣言された構造体を使用して、より良い(C)アプローチを示しました。void *アプローチを使用するには、typedefを使用します。.hファイルでtypedef void *キューと言います。次に、.cファイルで、struct queueの名前をstruct queueImplに変更すると、すべての引数がキューになり(struct queue *と同じ)、各関数のコードの最初の行はstruct queueImpl * qi =(struct queueImpl *)q
Lewisになりますプリングル

7
うーん。実装(foo.cファイル)以外の場所から「キュー」のフィールドにアクセス(読み取りまたは書き込み)できないため、プライベートになります。プライベートとはどういう意味ですか?ところで-typedef void *アプローチと(より良い)前方宣言構造アプローチの両方に当てはまる
ルイスプリングル

5
smalltalk-80に関する本を読んでからほぼ40年が経過したことを告白しなければなりませんが、パブリックまたはプライベートのデータメンバーの概念を思い出すことはできません。CLOSにもそのような概念はなかったと思います。Object Pascalにはそのような概念はありませんでした。Simulaが(おそらくStroustrupがアイデアを得た場所で)行ったこと、そしてC ++が持っているほとんどのオブジェクト指向言語を思い出します。とにかく-カプセル化と個人データは良いアイデアです。元の質問者でさえ、その点については明らかでした。彼はただ尋ねました-古い人たちはどのようにしてpre C ++言語でカプセル化をしましたか
ルイスプリングル

5
@LewisPringleは、リフレクションを使用しない限り、すべての「インスタンス変数」(データメンバー)がプライベートであるため、Smalltalk-80のパブリックデータメンバーについて言及していません。AFAIU Smalltalkerは、公開したいすべての変数に対してアクセサーを作成します。
18

4
@LewisPringleは対照的に、Smalltalkのすべての「メソッド」(関数メンバー)は公開されています(それらをプライベートとマークするための不器用な規則があります)
18

13

「不透明なデータ型」は、30年前にコンピューターサイエンスの学位を取得したときによく知られた概念でした。OOPは当時一般的に使用されておらず、「関数型プログラミング」がより適切であると見なされていたため、OOPについては説明しませんでした。

Modula-2はそれらを直接サポートしていました。https://www.modula2.org/reference/modules.phpを参照してください

Lewis Pringleは、Cで構造体の前方宣言をどのように使用できるかについて既に説明しました。モジュール2とは異なり、オブジェクトを作成するにはファクトリー関数を提供する必要がありました。(仮想メソッドは、構造体の最初のメンバーがメソッドへの関数ポインターを含む別の構造体へのポインターであることにより、Cで簡単に実装できました。)

多くの場合、慣習も使用されました。たとえば、「_」で始まるフィールドには、データを所有していたファイルの外部からアクセスしないでください。これは、カスタムチェックツールを作成することで簡単に実施できました。

私が取り組んだすべての大規模プロジェクト(C ++に移行する前、C#に移行する前)には、間違ったコードが「プライベート」データにアクセスするのを防ぐためのシステムがありました。現在よりも少し標準化されていませんでした。


9

メンバーをプライベートとしてマークする組み込み機能を持たない多くのオブジェクト指向言語があることに注意してください。これは慣例により行うことができ、コンパイラーがプライバシーを強制する必要はありません。たとえば、多くの場合、プライベート変数の前にアンダースコアが付きます。

「プライベート」変数へのアクセスを困難にする手法がありますが、最も一般的なのはPIMPLイディオムです。これにより、プライベート変数が別の構造体に配置され、パブリックヘッダーファイルにポインターのみが割り当てられます。これは、余分な逆参照と、プライベート変数を取得するためのキャストを意味し((private_impl)(obj->private))->actual_valueます。


4

データ構造には「メンバー」はなく、データフィールドのみがありました(レコードタイプであると仮定)。通常、可視性はタイプ全体に設定されました。ただし、関数はレコードの一部ではなかったため、考えているほど制限されない場合があります。

ここに戻って少し歴史を見てみましょう...

OOP以前の支配的なプログラミングパラダイムは、構造化プログラミングと呼ばれていました。これの最初の主な目標は、非構造化ジャンプステートメント(「goto」)の使用を避けることでした。これは制御フロー指向のパラダイムですが(OOPはデータ指向です)、コードのように論理的に構造化されたデータを維持しようとするのが自然な拡張でした。

構造化プログラミングの別のアウトシュートは情報隠蔽でした。これは、コードの構造の実装(かなり頻繁に変更される可能性が高い)をインターフェイス(理想的にはほとんど変更しない)とは別に保つべきだという考えです。今では定説ですが、昔は、多くの人がすべての開発者がシステム全体の詳細を知る方が良いと考えていたので、これはかつては議論の余地のあるアイデアでした。BrookのThe Mythical Man Monthのオリジナル版は、実際には情報の隠蔽に反対しました。

優れた構造化プログラミング言語(Modula-2やAdaなど)になるように明示的に設計された後のプログラミング言語には、一般に、基本的な概念として情報隠蔽が含まれ、関数(および任意の型、定数、彼らが必要とするかもしれないオブジェクト)。Modula-2では、これらは「モジュール」、Adaでは「パッケージ」と呼ばれていました。現代の多くのOOP言語は、同じ概念を「名前空間」と呼んでいます。これらの名前空間は、これらの言語の開発の組織的基盤であり、ほとんどの目的でOOPクラスと同様に使用できます(もちろん、継承は実際にサポートされていません)。

したがって、Modula-2およびAda(83)では、名前空間privateまたはpublicで任意のルーチン、型、定数、またはオブジェクトを宣言できますが、レコード型がある場合、一部のレコードフィールドを public として宣言する(簡単な)方法はありませんでしたおよび他のプライベート。レコード全体が公開されているか、公開されていないかのどちらかです。


私はエイダで働くのにかなりの時間を費やしました。(データ型の一部の)選択的隠蔽は、私たちが常に行っていたことです。含まれるパッケージでは、タイプ自体をプライベートまたは限定プライベートとして定義します。パッケージインターフェイスは、パブリックフィールド/プロシージャを公開して、内部フィールドを取得または設定します。もちろん、これらのルーチンは、プライベートタイプのパラメーターを取る必要があります。私はそれをしなかったし、今はこれを難しいとは思わない。
デビッド

また、ほとんどのOO言語は内部で同じように機能します。つまり、myWidget.getFoo()は実際にgetFoo(myWidget)として実装されます。object.method()呼び出しは、単に構文糖です。重要な私見-メイヤーの統一アクセス/リファレンスの原則を参照してください-しかし、まだ構文上の砂糖です。
デビッド

@David-それは、Ada 95時代の長年のAdaコミュニティの議論でした。彼らはついに屈服し、概念的な飛躍をすることができなかった人々のobject.method()ための代替形式として許可することにより、彼ら自身の議論を証明したと信じていmethod(object, ...) ます。
TED

0

Cでは、他の人が言っているように、宣言されているが未定義の型へのポインタを既に渡し、事実上すべてのフィールドへのアクセスを制限することができます。

モジュールごとにプライベート関数とパブリック関数を使用することもできます。ソースファイルで静的と宣言された関数は、名前を推測しようとしても、外部からは見えません。同様に、静的なファイルレベルのグローバル変数を使用できます。これは一般的に悪い習慣ですが、モジュール単位で分離できます。

言語で強制されたコンストラクトではなく、標準化された慣習としてのアクセス制限がうまく機能することを強調することはおそらく重要です(Pythonを参照)。それに加えて、オブジェクトフィールドへのアクセスを制限することは、作成後にオブジェクト内のデータの値を変更する必要がある場合にのみプログラマを保護します。これはすでにコードの匂いです。間違いなく、constメソッドと関数の引数に対するCおよび特にC ++のキーワードは、Javaのやや貧弱なものよりもはるかにプログラマーの助けになりますfinal


情報隠蔽専用の機能Cのみがstaticグローバルデータと操作でした(つまり、他のコンパイルで使用するためにリンカーに提示されませんでした)。あなたはもっともらしくCはかなりハックではなく、1972年における言語バックのオリジナルデザインの一部であったことから、別に良いソフトウェアデザインの実践のために持っていたすべてのサポートを主張することができます
TED

0

Publicの定義が、任意の時点で独自のコードを介して実装およびデータ/プロパティにアクセスする能力である場合、答えは単純です:はい。ただし、言語に応じて、さまざまな方法で抽象化されました。

これであなたの質問に簡潔に答えていただければ幸いです。


-1

これは非常に単純な反例です。Javaでは、interfacesはオブジェクトを定義しますが、classesは定義しません。Aは、class抽象データ型ではなく、オブジェクトを定義します。

エルゴ、あなたが使用するすべての時間privateclassJavaでは、あなたがオブジェクト指向ではありませんプライベートなメンバーとデータ構造の例を持っています。


7
もちろん、この答えは技術的には正しいのですが、ADTがどのようなもので、ADTがオブジェクトとどのように異なるのかをまだ知らない人にはまったく理解できません。
アモン

1
この答えから何かを学びました。
少しO

3
インターフェイスはオブジェクトを「定義」しません。オブジェクトが実行または実行できる操作/動作のコントラクト指定します。一般により記載されて継承が同じようになることにより、関係および組成有する関係は、インターフェイスは、一般的に記載されている行うことができる関係。
code_dredd
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.