ではオブジェクト指向のアラン・ケイズ定義が部分的に私は理解していないことを、この定義は次のとおりです。
私にとってOOPとは、メッセージング、ローカルでの保持と状態プロセスの保護と非表示、およびすべてのものの極端なLateBindingのみを意味します。
しかし、「LateBinding」とはどういう意味ですか?これをC#などの言語に適用するにはどうすればよいですか?そして、なぜこれがそれほど重要なのでしょうか?
ではオブジェクト指向のアラン・ケイズ定義が部分的に私は理解していないことを、この定義は次のとおりです。
私にとってOOPとは、メッセージング、ローカルでの保持と状態プロセスの保護と非表示、およびすべてのものの極端なLateBindingのみを意味します。
しかし、「LateBinding」とはどういう意味ですか?これをC#などの言語に適用するにはどうすればよいですか?そして、なぜこれがそれほど重要なのでしょうか?
回答:
「バインド」とは、メソッド名を呼び出し可能なコードに解決する行為を指します。通常、関数呼び出しはコンパイル時またはリンク時に解決できます。静的バインディングを使用する言語の例はCです。
int foo(int x);
int main(int, char**) {
printf("%d\n", foo(40));
return 0;
}
int foo(int x) { return x + 2; }
ここで、呼び出しfoo(40)
はコンパイラーによって解決できます。これにより、インライン化などの特定の最適化が早期に可能になります。最も重要な利点は次のとおりです。
一方、一部の言語は関数の解決を可能な限り最後まで延期します。例はPythonであり、その場でシンボルを再定義できます。
def foo():
""""call the bar() function. We have no idea what bar is."""
return bar()
def bar():
return 42
print(foo()) # bar() is 42, so this prints "42"
# use reflection to overwrite the "bar" variable
locals()["bar"] = lambda: "Hello World"
print(foo()) # bar() was redefined to "Hello World", so it prints that
bar = 42
print(foo()) # throws TypeError: 'int' object is not callable
これは遅延バインディングの例です。厳密な型チェックを不当に行います(型チェックは実行時にのみ実行できます)がはるかに柔軟であり、静的型付けや事前バインディングの範囲内では表現できない概念を表現できます。たとえば、実行時に新しい関数を追加できます。
「静的」OOP言語で一般的に実装されているメソッドディスパッチは、これらの2つの極端な中間にあります。クラスは、サポートされるすべての操作のタイプを事前に宣言するため、これらは静的に認識され、型チェックできます。次に、実際の実装を指す単純なルックアップテーブル(VTable)を作成できます。各オブジェクトには、vtableへのポインタが含まれています。型システムは、取得したすべてのオブジェクトが適切なvtableを持つことを保証しますが、このルックアップテーブルの値が何であるかは、コンパイル時にわかりません。したがって、オブジェクトを使用して関数をデータとして渡すことができます(OOPと関数プログラミングが同等である理由の半分)。Vtablesは、Cなどの関数ポインターをサポートする任意の言語で簡単に実装できます。
#define METHOD_CALL(object_ptr, name, ...) \
(object_ptr)->vtable->name((object_ptr), __VA_ARGS__)
typedef struct {
void (*sayHello)(const MyObject* this, const char* yourname);
} MyObject_VTable;
typedef struct {
const MyObject_VTable* vtable;
const char* name;
} MyObject;
static void MyObject_sayHello_normal(const MyObject* this, const char* yourname) {
printf("Hello %s, I'm %s!\n", yourname, this->name);
}
static void MyObject_sayHello_alien(const MyObject* this, const char* yourname) {
printf("Greetings, %s, we are the %s!\n", yourname, this->name);
}
static MyObject_VTable MyObject_VTable_normal = {
.sayHello = MyObject_sayHello_normal,
};
static MyObject_VTable MyObject_VTable_alien = {
.sayHello = MyObject_sayHello_alien,
};
static void sayHelloToMeredith(const MyObject* greeter) {
// we have no idea what the VTable contents of my object are.
// However, we do know it has a sayHello method.
// This is dynamic dispatch right here!
METHOD_CALL(greeter, sayHello, "Meredith");
}
int main() {
// two objects with different vtables
MyObject frank = { .vtable = &MyObject_VTable_normal, .name = "Frank" };
MyObject zorg = { .vtable = &MyObject_VTable_alien, .name = "Zorg" };
sayHelloToMeredith(&frank); // prints "Hello Meredith, I'm Frank!"
sayHelloToMeredith(&zorg); // prints "Greetings, Meredith, we are the Zorg!"
}
この種のメソッドルックアップは「動的ディスパッチ」とも呼ばれ、アーリーバインディングとレイトバインディングの間のどこかにあります。動的メソッドディスパッチはOOPプログラミングの中心的な定義プロパティであり、それ以外のもの(たとえば、カプセル化、サブタイピングなど)は二次的であると考えています。これにより、コードにポリモーフィズムを導入したり、コードを再コンパイルすることなくコードに新しい動作を追加したりすることができます。Cの例では、誰でも新しいvtableを追加し、そのvtableを持つオブジェクトをに渡すことができますsayHelloToMeredith()
。
これは遅めのバインディングですが、ケイが好む「極端なレイトバインディング」ではありません。概念モデル「関数ポインタによるメソッドディスパッチ」の代わりに、「メッセージパッシングによるメソッドディスパッチ」を使用しています。メッセージパッシングははるかに一般的であるため、これは重要な違いです。このモデルでは、各オブジェクトに他のオブジェクトがメッセージを入れることができる受信ボックスがあります。受信オブジェクトは、そのメッセージを解釈しようとすることができます。最もよく知られているOOPシステムはWWWです。ここで、メッセージはHTTPリクエストであり、サーバーはオブジェクトです。
たとえば、programmers.stackexchange.se serverに質問できますGET /questions/301919/
。これを表記と比較してくださいprogrammers.get("/questions/301919/")
。サーバーはこのリクエストを拒否するか、エラーを返信するか、質問に回答することができます。
メッセージパッシングの力は、それが非常によく拡張できることです。データは共有されず(転送のみ)、すべてが非同期で発生し、オブジェクトは好きなようにメッセージを解釈できます。これにより、メッセージパッシングOOPシステムを簡単に拡張できます。誰もが理解できるとは限らないメッセージを送信して、期待した結果またはエラーを返すことができます。オブジェクトは、応答するメッセージを事前に宣言する必要はありません。
これは、メッセージの受信者に正確性を維持する責任を課します。これは、カプセル化とも呼ばれます。たとえば、HTTPメッセージで要求しないと、HTTPサーバーからファイルを読み取ることができません。これにより、たとえばアクセス許可がない場合などに、HTTPサーバーがリクエストを拒否できます。小規模のOOPでは、これはオブジェクトの内部状態への読み取り/書き込みアクセス権はないが、パブリックメソッドを経由する必要があることを意味します。HTTPサーバーもファイルを提供する必要はありません。DBから動的に生成されたコンテンツである可能性があります。実際のOOPでは、オブジェクトがメッセージにどのように応答するかのメカニズムを、ユーザーに気付かれずに切り替えることができます。これは「リフレクション」よりも強力ですが、通常は完全なメタオブジェクトプロトコルです。上記のCの例では、実行時にディスパッチメカニズムを変更できません。
すべてのメッセージはユーザー定義可能なコードを介してルーティングされるため、ディスパッチメカニズムを変更する機能は遅延バインディングを意味します。そしてこれは非常に強力です:メタオブジェクトプロトコルを指定すると、クラス、プロトタイプ、継承、抽象クラス、インターフェイス、トレイト、多重継承、マルチディスパッチ、アスペクト指向プログラミング、リフレクション、リモートメソッド呼び出しなどの機能を追加できます。これらの機能で開始されない言語へのプロキシオブジェクトなど。この進化する力は、C#、Java、C ++などの静的言語にはまったくありません。
レイトバインディングとは、オブジェクトが相互に通信する方法を指します。アランが達成しようとしている理想は、オブジェクトを可能な限り疎結合にすることです。言い換えると、オブジェクトは、別のオブジェクトと通信するために、可能な限り最小のものを知る必要があるということです。
どうして?それは、システムの一部を独立して変更する機能を奨励し、有機的に成長および変更できるようにするためです。
たとえば、C#では、のobj1
ようなメソッドを記述できますobj2.doSomething()
。これはとobj1
通信していると見なすことができますobj2
。これがC#で発生obj1
するには、についてかなり知っている必要がありますobj2
。そのクラスを知る必要があります。クラスが呼び出されたメソッドを持っていること、doSomething
およびそのメソッドにパラメーターをまったく使用しないバージョンがあることを確認します。
次に、ネットワークなどを介してメッセージを送信するシステムを想像してください。のようなものを書くかもしれませんRuntime.sendMsg(ipAddress, "doSomething")
。この場合、通信しているマシンについて多くを知る必要はありません。おそらくIP経由で接続でき、「doSomething」文字列を受信すると何かを行います。しかし、そうでなければあなたはほとんど知りません。
オブジェクトが通信する方法を想像してみてください。あなたはアドレスを知っており、なんらかの「ポストボックス」機能を使用してそのアドレスに任意のメッセージを送信できることがわかります。この場合、obj1
はobj2
アドレスについてだけ知っている必要はありません。それが理解していることを知る必要すらありませんdoSomething
。
それが遅延バインディングの核心です。さて、SmalltalkやObjectiveCなどのそれを使用する言語では、通常、postbox関数を非表示にするために少し構文上の砂糖があります。しかし、それ以外は考え方は同じです。
C#ではRuntime
、オブジェクトの参照と文字列を受け入れ、リフレクションを使用してメソッドを見つけて呼び出すクラスを作成することで、それを一種の方法で複製できます(引数と戻り値で複雑になり始めますが、それでも可能です)醜い)。
編集:遅延バインディングの意味に関していくつかの混乱を和らげるために。この回答では、Alan Kayがそれを意味し、Smalltalkに実装したことを理解しているので、遅延バインディングについて言及しています。これは、一般的に動的ディスパッチを指す用語のより一般的で現代的な使用ではありません。後者は、実行時まで正確なメソッドを解決する際の遅延をカバーしていますが、コンパイル時にレシーバーのタイプ情報が必要です。