Binding Terlambat Berorientasi Objek


11

Dalam Alan Kays Definisi Objek Berorientasi ada definisi ini yang sebagian saya tidak mengerti:

OOP bagi saya berarti hanya olahpesan, penyimpanan lokal dan perlindungan dan menyembunyikan proses negara, dan LateBinding ekstrim dari semua hal.

Tetapi apa arti "Keterlambatan"? Bagaimana saya bisa menerapkan ini pada bahasa seperti C #? Dan mengapa ini sangat penting?



2
OOP di C # mungkin bukan jenis OOP yang Alan Kay pikirkan.
Doc Brown

Saya setuju dengan Anda, tentu saja ... contohnya diterima dalam bahasa apa pun
Luca Zulian

Jawaban:


14

"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 ++.


4

Mengikat terlambat mengacu pada bagaimana benda berkomunikasi satu sama lain. Cita-cita yang dicoba untuk dicapai oleh Alan adalah agar objek dapat digabungkan dengan longgar. Dengan kata lain bahwa suatu objek perlu mengetahui seminimal mungkin untuk dapat berkomunikasi dengan objek lain.

Mengapa? Karena itu mendorong kemampuan untuk mengubah bagian-bagian sistem secara mandiri dan memberinya kemampuan untuk tumbuh dan berubah secara organik.

Misalnya, dalam C # Anda dapat menulis dalam metode untuk obj1sesuatu seperti obj2.doSomething(). Anda dapat melihat ini sebagai obj1berkomunikasi dengan obj2. Agar ini terjadi dalam C #, obj1perlu tahu sedikit tentang obj2. Itu perlu mengetahui kelasnya. Itu akan memeriksa bahwa kelas memiliki metode yang dipanggil doSomethingdan bahwa ada versi metode yang mengambil parameter nol.

Sekarang bayangkan sebuah sistem di mana Anda mengirim pesan melalui jaringan atau sejenisnya. Anda mungkin menulis sesuatu seperti Runtime.sendMsg(ipAddress, "doSomething"). Dalam hal ini Anda tidak perlu tahu banyak tentang mesin yang berkomunikasi dengan Anda; itu mungkin dapat dihubungi melalui IP dan akan melakukan sesuatu ketika menerima string "doSomething". Tetapi sebaliknya Anda hanya tahu sedikit.

Sekarang bayangkan bagaimana benda berkomunikasi. Anda tahu alamat dan bahwa Anda dapat mengirim pesan sewenang-wenang ke alamat itu dengan semacam fungsi "kotak pos". Dalam hal ini, obj1tidak perlu tahu banyak tentang obj2, hanya alamatnya saja. Bahkan tidak perlu tahu bahwa itu mengerti doSomething.

Itulah inti dari ikatan yang terlambat. Sekarang, dalam bahasa yang menggunakannya, seperti Smalltalk dan ObjectiveC, biasanya ada sedikit gula sintaksis untuk menyembunyikan fungsi kotak pos. Tetapi sebaliknya idenya sama.

Dalam C # Anda bisa meniru itu, semacam, dengan memiliki Runtimekelas yang menerima objek ref dan string dan menggunakan refleksi untuk menemukan metode dan memohonnya (itu akan mulai menjadi rumit dengan argumen dan mengembalikan nilai-nilai tetapi akan mungkin dilakukan meskipun jelek).

Sunting: untuk menghilangkan beberapa kebingungan sehubungan dengan arti dari pengikatan akhir. Dalam jawaban ini saya merujuk pada pengikatan yang terlambat karena saya mengerti Alan Kay bermaksud dan menerapkannya di Smalltalk. Ini bukan penggunaan yang lebih umum, modern dari istilah yang umumnya merujuk pada pengiriman dinamis. Yang terakhir mencakup penundaan dalam menyelesaikan metode yang tepat sampai runtime tetapi masih memerlukan beberapa jenis informasi untuk penerima pada waktu kompilasi.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.