web-dev-qa-db-ja.com

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

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

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

45
Christopher

OOPはカプセル化を発明せず、カプセル化と同義ではありません。多くのOOP言語にはC++/Javaスタイルのアクセス修飾子がありません。多くの非OOP言語には、カプセル化を提供するために利用できるさまざまな手法があります。

カプセル化の1つの古典的なアプローチは、関数型プログラミングで使用されるclosuresです。これは、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には、functorsと呼ばれるモジュールへの興味深いアプローチが含まれています。 OOP影に覆われ、大部分がモジュール化されたプログラミングですが、OOPの多くの主張されている利点は、オブジェクト指向よりもモジュール性についてです。

OOP C++のような言語またはJavaはオブジェクトには使用されないことが多い(レイトバインディング/動的ディスパッチを通じて操作を解決するエンティティの意味で)抽象データ型の場合(内部実装の詳細を非表示にするパブリックインターフェイスを定義)論文データの抽象化の理解について、再考(Cook、2009)は、この違いについて詳しく説明しています。

しかし、はい、多くの言語にはカプセル化メカニズムがまったくありません。これらの言語では、構造体のメンバーは公開されたままです。多くても、使用を思いとどまらせる命名規則があります。例えば。 Pascalには有用なカプセル化メカニズムがなかったと思います。

139
amon

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

第二に、「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で使用されるメモリへの汎用ハンドルになり、実装をプライベートに効果的にします。

31
Lewis Pringle

「不透明なデータ型」は、30年前にコンピュータサイエンスの学位を取得したときの有名な概念でした。 OOPは当時は一般的に使用されておらず、「関数型プログラミング」の方が正しいと考えられていたため、ここでは取り上げませんでした。

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

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

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

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

13
Ian

多くのOO言語には、メンバーにプライベートのマークを付ける機能がありません。これは、コンパイラーがプライバシーを強制する必要なしに、慣例により行うことができます。たとえば、多くの場合、下線付きのプライベート変数。

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

9
Karl Bielefeldt

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

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

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

構造化プログラミングのもう1つの成果は 情報を隠す でした。これは、コードの構造の実装(かなり頻繁に変更される可能性が高い)をインターフェイス(理想的にはほとんど変更されない)から分離する必要があるという考えです。多く)。それは今では教義ですが、昔は多くの人が実際にはすべての開発者がシステム全体の詳細を知ることがより良いと考えていたため、これは実際には論争の的となっていました。 Brookのオリジナル版The Mythical Man Monthは、実際には情報の隠蔽に反対しています。

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

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

5
T.E.D.

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

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

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

0
Kafein

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

これがあなたの質問に簡潔に答えてくれることを願っています。

0
RobMac