"Binding" mengacu pada tindakan menyelesaikan nama metode ke sepotong kode yang tidak dapat diubah. Biasanya, panggilan fungsi dapat diselesaikan pada waktu kompilasi atau pada waktu tautan. Contoh bahasa yang menggunakan pengikatan statis adalah C:
int foo(int x);
int main(int, char**) {
printf("%d\n", foo(40));
return 0;
}
int foo(int x) { return x + 2; }
Di sini, panggilan foo(40)
dapat diselesaikan oleh kompiler. Awal ini memungkinkan optimasi tertentu seperti inlining. Keuntungan paling penting adalah:
- kita bisa melakukan pengecekan tipe
- kita bisa melakukan optimasi
Di sisi lain, beberapa bahasa menunda resolusi fungsi hingga saat-saat terakhir yang mungkin terjadi. Contohnya adalah Python, di mana kita dapat mendefinisikan kembali simbol dengan cepat:
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
Ini adalah contoh dari ikatan yang terlambat. Meskipun itu membuat pemeriksaan tipe yang ketat menjadi tidak masuk akal (pemeriksaan tipe hanya dapat dilakukan saat runtime), itu jauh lebih fleksibel dan memungkinkan kita untuk mengekspresikan konsep yang tidak dapat diekspresikan dalam batas-batas pengetikan statis atau penjilidan awal. Misalnya, kita dapat menambahkan fungsi baru saat runtime.
Metode pengiriman seperti yang biasa diterapkan dalam bahasa "statis" OOP adalah di antara dua ekstrem ini: Kelas menyatakan jenis semua operasi yang didukung di muka, jadi ini dikenal secara statis dan dapat diketikkan centang. Kami kemudian dapat membangun tabel pencarian sederhana (VTable) yang menunjuk ke implementasi yang sebenarnya. Setiap objek berisi pointer ke vtable. Sistem tipe menjamin bahwa objek apa pun yang kita dapatkan akan memiliki vtable yang sesuai, tetapi kami tidak tahu pada waktu kompilasi berapa nilai tabel pencarian ini. Oleh karena itu, objek dapat digunakan untuk melewatkan fungsi sekitar sebagai data (setengah alasan mengapa OOP dan pemrograman fungsi adalah setara). Vtables dapat dengan mudah diimplementasikan dalam bahasa apa pun yang mendukung pointer fungsi, seperti 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!"
}
Metode pencarian semacam ini juga dikenal sebagai "pengiriman dinamis", dan di suatu tempat di antara mengikat awal dan mengikat terlambat. Saya menganggap pengiriman metode dinamis menjadi properti pendefinisian pusat pemrograman OOP, dengan hal lain (mis. Enkapsulasi, subtyping, ...) sebagai yang kedua. Ini memungkinkan kami untuk memperkenalkan polimorfisme ke dalam kode kami, dan bahkan menambahkan perilaku baru ke sepotong kode tanpa harus mengompilasi ulang! Dalam contoh C, siapa pun dapat menambahkan vtable baru dan meneruskan objek dengan vtable itu ke sayHelloToMeredith()
.
Meskipun ini adalah pengikatan yang terlambat, ini bukan "pengikatan yang sangat terlambat" yang disukai oleh Kay. Alih-alih model konseptual "metode pengiriman melalui pointer fungsi", ia menggunakan "metode pengiriman melalui pesan yang lewat". Ini adalah perbedaan penting karena menyampaikan pesan jauh lebih umum. Dalam model ini, setiap objek memiliki kotak masuk di mana objek lain dapat menaruh pesan. Objek penerima kemudian dapat mencoba menafsirkan pesan itu. Sistem OOP yang paling terkenal adalah WWW. Di sini, pesan adalah permintaan HTTP, dan server adalah objek.
Sebagai contoh, saya dapat menanyakan server programmers.stackexchange.se GET /questions/301919/
. Bandingkan ini dengan notasi programmers.get("/questions/301919/")
. Server dapat menolak permintaan ini atau mengirim saya kembali kesalahan, atau dapat melayani saya dengan pertanyaan Anda.
Kekuatan penyampaian pesan adalah bahwa ia berskala sangat baik: tidak ada data yang dibagikan (hanya ditransfer), semuanya dapat terjadi secara tidak sinkron, dan objek dapat menafsirkan pesan sesuka mereka. Ini membuat pesan lewat sistem OOP mudah diperpanjang. Saya dapat mengirim pesan yang mungkin tidak semua orang mengerti, dan mendapatkan kembali hasil yang diharapkan atau kesalahan. Objek tidak perlu menyatakan di muka pesan mana yang akan ditanggapi.
Ini menempatkan tanggung jawab menjaga kebenaran pada penerima pesan, sebuah pemikiran yang juga dikenal sebagai enkapsulasi. Misalnya saya tidak bisa membaca file dari server HTTP tanpa memintanya melalui pesan HTTP. Ini memungkinkan server HTTP untuk menolak permintaan saya, misalnya jika saya tidak memiliki izin. Dalam OOP skala kecil, ini berarti bahwa saya tidak memiliki akses baca-tulis ke keadaan internal objek, tetapi harus melalui metode publik. Server HTTP tidak harus melayani saya file, baik. Ini bisa berupa konten yang dihasilkan secara dinamis dari DB. Dalam OOP nyata, mekanisme bagaimana suatu objek merespons pesan dapat dihilangkan, tanpa pengguna sadari. Ini lebih kuat dari "refleksi", tetapi biasanya protokol meta-objek penuh. Contoh C saya di atas tidak dapat mengubah mekanisme pengiriman saat runtime.
Kemampuan untuk mengubah mekanisme pengiriman menyiratkan keterlambatan mengikat, karena semua pesan dialihkan melalui kode yang dapat ditentukan pengguna. Dan ini sangat kuat: diberikan protokol meta-objek, saya dapat menambahkan fitur seperti kelas, prototipe, warisan, kelas abstrak, antarmuka, sifat, pewarisan berganda, multi-pengiriman, pemrograman berorientasi aspek, refleksi, pemanggilan metode jarak jauh, objek proxy dll ke bahasa yang tidak dimulai dengan fitur-fitur ini. Kekuatan untuk berevolusi ini sama sekali absen dari bahasa yang lebih statis seperti C #, Java, atau C ++.