抽象データ型とオブジェクトの違いは何ですか?


11

Programmers.SEの答えは、クック(エッセイ特徴づけるオブジェクトはのADTではありませんが言うように)

  • オブジェクトは、代数としてではなく、型の値に対する特性関数のように動作します。オブジェクトは型の抽象化ではなく手続き型の抽​​象化を使用します

  • ADTは通常、プログラム内で固有の実装を持っています。言語にモジュールがある場合、ADTの複数の実装を持つことは可能ですが、それらは通常相互運用できません。

クックのエッセイでは、クックの論文で使用されているセットの特定の例では、オブジェクトは特徴的な関数と見なすことができるように思えます。オブジェクトは、一般的に特徴的な機能と見なすことはできないと思います。

また、Aldritchの論文相互運用性の力:なぜオブジェクトが避けられないのか ¹が示唆する

クックの定義は、本質的に動的ディスパッチをオブジェクトの最も重要な特性として識別します

これとアランケイが彼が言ったときに同意する

私にとってのOOPとは、メッセージング、ローカルでの保持と状態プロセスの保護と非表示、およびすべてのものの極端な遅延バインディングのみを意味します。

ただし、Aldritchの論文に対するこれらの関連する講義スライドでは、Java クラスはADTであり、Javaインターフェースはオブジェクトであることが示唆されています。実際、インターフェース「オブジェクト」を使用すると相互運用できます(上記の箇条書きのいずれかで示されるOOPの主要機能の1つ) )。

私の質問は

  1. 特徴的な関数はオブジェクトの主要な特徴ではなく、フランクシアラーは誤解していると私は言いますか?

  2. 動的ディスパッチを使用していなくても、Javaインターフェースを介して互いに通信するデータはオブジェクトの例ですか?どうして?(私の理解は、動的ディスパッチの方がより柔軟であり、そのインターフェースは、objective-C / smalltalk / erlangスタイルのメッセージングに向けたステップであることです。)

  3. 依存関係の逆転の原則の考え方は、ADTとオブジェクトの区別に関連していますか?(WikipediaのページまたはThe Talking Objects:A Tale About Message-Oriented Programmingを参照してください)私はこの概念に不慣れですが、プログラムの「レイヤー」間にインターフェースを追加することを含むことを理解しています(Wikipediaのページ図を参照)。

  4. 必要に応じて、オブジェクトとADTの違いの他の例/説明を提供してください。

¹ (2013年発行)本論文では、読みやすいですし、Javaの例でクックの2009年の論文をまとめたものです。この質問に答えるのではなく、少なくともそれを流し読みすることを強くお勧めします。


5
興味深いですが、投稿ごとに1つの質問に限定するようにしてください。
ラファエル

それはいくぶん(微妙な?)学術的な区別/議論のようです。どうやらADTは、たとえばJavaやその他の最新のOOP言語では、ほとんどオブジェクトです。多くのOOP言語では、抽象化はオブジェクトが現実の世界を(制約付き/集中的に)モデル化する方法と見なされています。OOPの「抽象化」の定義についても混乱していますソフトウェアエンジニアリング
vzn

回答:


8

グーグルは私が非常に良いと思う答えで同様の質問を持ち出しました。以下に引用しました。

ここに潜んでいる別の区別があり、それは私がリンクしたクックエッセイで説明されています。

オブジェクトは、抽象化を実装する唯一の方法ではありません。すべてがオブジェクトではありません。オブジェクトは、手続き型データ抽象化と呼ばれるものを実装しています。抽象データ型は、異なる形式の抽象化を実装します。

バイナリメソッド/関数を検討すると、重要な違いが現れます。手続き型データの抽象化(オブジェクト)を使用すると、Intセットインターフェイスに対して次のような記述を行うことができます。

interface IntSet {
  void unionWith(IntSet s);
  ...
}

次に、IntSetの2つの実装について考えてみましょう。1つはリストによってサポートされ、もう1つはより効率的なバイナリツリー構造によってサポートされます。

class ListIntSet implements IntSet {
  void unionWith(IntSet s){ ... }
} 
class BSTIntSet implements IntSet {
  void unionWith(IntSet s){ ... }
}

unionWithはIntSet引数を取る必要があることに注意してください。ListIntSetやBSTIntSetのようなより具体的なタイプではありません。これは、BSTIntSet実装は、その入力がBSTIntSetであると想定できず、その事実を使用して効率的な実装を提供できないことを意味します。(それはそれをチェックするためにいくつかの実行時タイプ情報を使用し、それがより効率的なアルゴリズムを使用する可能性がありますが、それでもListIntSetが渡され、より効率の悪いアルゴリズムにフォールバックする必要があります)。

これをADTと比較してください。シグネチャファイルまたはヘッダーファイルに次のようなものを記述できます。

typedef struct IntSetStruct *IntSetType;
void union(IntSetType s1, IntSetType s2);

このインターフェイスに対してプログラミングします。特に、型は抽象のままです。あなたはそれが何であるかを知ることはありません。次に、BST実装があり、具体的な型と操作を提供します。

struct IntSetStruct {
 int value;
 struct IntSetStruct* left;
 struct IntSetStruct* right;
}

void union(IntSetType s1, IntSetType s2){ ... }

これでunionは実際にs1とs2の両方の具体的な表現を知っているので、これを利用して効率的な実装を行うことができます。リストに裏付けされた実装を記述して、代わりにそれとリンクすることを選択することもできます。

私はC(ish)構文を書きましたが、たとえば、適切に行われた抽象データ型の標準MLを確認する必要があります(たとえば、型を修飾することによって、同じプログラムで実際にADTの複数の実装を実際に使用できます:BSTImpl。 IntSetStructおよびListImpl.IntSetStructなど)

これとは逆に、手続き型のデータ抽象化(オブジェクト)を使用すると、古い実装で機能する新しい実装を簡単に導入できます。たとえば、独自のカスタムLoggingIntSet実装を作成し、それをBSTIntSetと結合できます。しかし、これはトレードオフです。バイナリメソッドの有益な型を失うことになります。多くの場合、ADT実装よりも多くの機能と実装の詳細をインターフェイスに公開する必要があります。クックのエッセイを書き直しているような気がするので、実際に読んでください!

これに例を追加したいと思います。

クックは、抽象データ型の例はCのモジュールであることを示唆しています。実際、Cのモジュールには情報の非表示が含まれます。これは、ヘッダーファイルを通じてエクスポートされるパブリック関数と、そうでない静的(プライベート)関数があるためです。さらに、多くの場合、コンストラクター(list_new()など)とオブザーバー(list_getListHead()など)があります。

たとえば、LIST_MODULE_SINGLY_LINKEDと呼ばれるリストモジュールをADTにする重要な点は、モジュールの関数(たとえば、list_getListHead())は、入力されるデータがLIST_MODULE_SINGLY_LINKEDのコンストラクターによって作成されたものであり、 "リストの実装(例:LIST_MODULE_DYNAMIC_ARRAY)。これは、LIST_MODULE_SINGLY_LINKEDの関数が、それらの実装において、特定の表現(たとえば、単一リンクリスト)を想定できることを意味します。

LIST_MODULE_SINGLY_LINKEDはオブジェクトの表現であると見なすため、LIST_MODULE_SINGLY_LINKEDはオブジェクトと見なされるため、LIST_MODULE_SINGLY_LINKEDはオブジェクトと見なされるため、LIST_MODULE_SINGLY_LINKEDはオブジェクトと見なされます。

これは、抽象代数の2つの異なるグループが相互運用できない方法に似ています(つまり、あるグループの要素と別のグループの要素の積をとることはできません)。これは、グループがグループのクロージャプロパティを想定しているためです(グループ内の要素の積はグループ内にある必要があります)。ただし、2つの異なるグループが実際には別のグループGのサブグループであることを証明できる場合、Gの積を使用して、2つのグループのそれぞれから1つずつ、2つの要素を追加できます。

ADTとオブジェクトの比較

  • Cookは、ADTとオブジェクトの違いを部分的に表現の問題に結び付けます。大まかに言えば、ADTは関数型プログラミング言語で実装されることが多い汎用関数と結合され、オブジェクトはインターフェースを介してアクセスされるJava「オブジェクト」と結合されます。このテキストでは、ジェネリック関数は、いくつかの引数ARGSと型TYPE(前提条件)を取る関数です。TYPEに基づいて適切な関数を選択し、それをARGS(事後条件)で評価します。ジェネリック関数とオブジェクトはどちらもポリモーフィズムを実装しますが、プログラマーはジェネリック関数のコードを調べずに、ジェネリック関数によってどの関数が実行されるかを知っています。一方、オブジェクトの場合、プログラマーは、オブジェクトのコードを調べない限り、オブジェクトが引数を処理する方法を知りません。

  • 通常、表現の問題は「多くの表現があるか」という観点から考えられます。対「表現が少ない関数がたくさんありますか」。最初のケースでは、コードを表現別に編成する必要があります(特にJavaで最も一般的です)。2番目のケースでは、関数ごとにコードを編成する必要があります(つまり、単一の汎用関数で複数の表現を処理します)。

  • あなたが表現してコードを整理する場合は、余分な機能を追加したい場合は、その後、あなたがされて強制的にオブジェクトのすべての表現に機能を追加します。この意味で、機能の追加は「追加」ではありません。あなたが機能してコードを整理する場合は、余分な表現を追加したい場合は、その後、 -あなたがしている強制的にすべてのオブジェクトに表現を追加します。この意味で、表現の追加は「付加的」ではありません。

オブジェクトに対するADTの利点

  • 機能の追加は付加的です

  • パフォーマンスのためにADTの表現に関する知識を活用すること、またはADTが前提条件を前提として何らかの事後条件を保証することを証明することが可能です。つまり、ADTを使用したプログラミングは、正しいことを正しい順序で実行することです(事前条件と事後条件を「ゴール」事後条件にチェーンする)。

ADTに対するオブジェクトの利点

  • 加算で表現を追加する

  • オブジェクトは相互運用できます

  • オブジェクトの事前/事後条件を指定し、ADTの場合のようにこれらを一緒にチェーンすることが可能です。この場合、オブジェクトの利点は、(1)インターフェースを変更せずに表現を簡単に変更できること、および(2)オブジェクトが相互運用できることです。ただし、これはsmalltalkの意味でのOOPの目的に反します。(セクション「アラン・ケイのOOPバージョン」を参照)

動的ディスパッチはOOPの鍵です

オブジェクト指向プログラミングには動的ディスパッチ(つまり、遅延バインディング)が不可欠であることは明らかです。これは、特定の表現を前提としない一般的な方法でプロシージャを定義できるようにするためです。具体的に言うと、特定の表現を前提としない方法でオブジェクトのメソッドをプログラミングできるため、Pythonではオブジェクト指向プログラミングが簡単です。これが、PythonがJavaのようなインターフェースを必要としない理由です。

Javaでは、クラスはADTです。ただし、実装するインターフェースを介してアクセスされるクラスはオブジェクトです。

補遺:アラン・ケイのOOPバージョン

Alan Kayはオブジェクトを明示的に「代数の族」と呼び、CookはADTが代数であることを示唆しています。したがって、ケイはおそらくオブジェクトがADTのファミリーであることを意味していました。つまり、オブジェクトは、Javaインターフェースを満たすすべてのクラスのコレクションです。

ただし、クックによって描かれたオブジェクトの写真は、アランケイのビジョンよりもはるかに制限されています。彼は、オブジェクトがネットワーク内のコンピューターとして、または生体細胞として動作することを望んでいました。アイデアは、プログラミングへの最小のコミットメントの原則を適用することでした。これにより、ADTの低レベルレイヤーを使用して、高レベルレイヤーがいったん構築されると、簡単に変更できます。この図を念頭に置いておくと、オブジェクトがメッセージの意味を解釈したり、メッセージを完全に無視したりできないため、Javaインターフェースは制限が厳しすぎます。

要約すると、ケイにとってのオブジェクトの重要なアイデアは、オブジェクトが代数のファミリーであるということではありません(クックによって強調されています)。むしろ、Kayの重要なアイデアは、大規模(ネットワーク内のコンピューター)で機能するモデルを小規模(プログラム内のオブジェクト)に適用することでした。

編集:ケイのバージョンのOOPに関するもう1つの明確化:オブジェクトの目的は、宣言的な理想に近づくことです。手続き型プログラミングやADTで慣例となっているように、オブジェクトを何をすべきかを伝える必要があります- マイクロ管理によって状態をどのように伝えるかではなく、詳細情報を見つけることができ、ここでここではここでは、とここに

編集:私はここで、Alan KayによるOOPの定義の非常に良い説明を見つけました。


3

ADTの支持者を見ると、彼らはADTをOOPがクラスと呼ぶもの(内部、プライベート状態、許可された操作の限定されたセット)であると見なしますが、クラス間の関係(基本的に継承なし)は考慮されません。代わりに、異なる実装でも同じ動作が得られることがポイントです。たとえば、セットはリスト、配列またはハッシュテーブルの要素、または何らかのツリーとして実装できます。


2

私はいつもこのようにそれを理解しました:

  1. ADTはインターフェイスです。ADTはメソッドとその型シグネチャのコレクションであり、前後の条件がある場合があります。

  2. クラスは、ADTで指定されたメソッドの実際の実装を提供することにより、1つ以上のADTを実装できます。

  3. オブジェクトはクラスのインスタンスであり、非静的変数の独自のコピーを持っています。

文献では区別が異なる可能性がありますが、これはコンピュータサイエンスで聞く「標準」の用語です。

たとえば、Javaでは、CollectionはADTでArrayListあり、クラスでありArrayListnew演算子を使用してオブジェクトを作成できます。

通常、ADTには1つの実装しかないという記述については、多くの場合そうではありません。たとえば、格納する内容に応じて、プログラムでツリーベースの辞書とハッシュテーブルベースの辞書の両方を使用することができます。それらはADTを共有しますが、異なる実装を使用します。


1
関数型プログラミングの観点から見ると、ADTにはクラス一般にはない特定の制限がないのではないですか?
ラファエル

@ラファエルはどうですか?
jmite 2016年

1
これはADTの一般的な見方であり、妥当な概算です。ただし、私が理解しているように、APLはPLの文献で検討され、正式に定義されているため、実際にはより具体的な意味があります。ADTは、一種のデータ構造の仕様です。ADTの実装方法やデータの表現方法ではなく、ADTへのインターフェイス(実行できる操作の種類)、各操作の動作/セマンティクスです。したがって、それは単なるJavaインターフェース(型シグニチャーを持つメソッドのリスト)ではなく、それらの動作の仕様でもあります。
DW

1
たとえば、私の印象は、Java CollectionインターフェースはADTではないということです。メソッドのリストを提供しますが、それらのセマンティクスは指定しません。セットのセマンティクスを提供しますか?マルチセット(バッグ)?順序付きリスト?それは不特定のままです。したがって、ADTとしてカウントされるかどうかはわかりません。それが私の印象ですが、私の理解が間違っている可能性は十分にあります...
DW

私がリンクした講義スライドでは、クラスにはプライベート部分とパブリック部分の両方があるため、Java クラス(インターフェースでさえも!)はADTと見なされます(クラスの一部は非公式に指定されると思いますが、わかりません) 。一方、インターフェースを介してアクセスされるクラスはオブジェクトと見なされ、インターフェースによって定義されるメソッドは「メッセージ」(高レベルの意図)です。オブジェクトが意図を介して互いに通信する場合、オブジェクトの異なる実装が互いに「通信」できます。
LMZ、2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.