“ การผูก” หมายถึงการกระทำของการแก้ไขชื่อวิธีการกับชิ้นส่วนของรหัสที่เรียกคืนไม่ได้ โดยทั่วไปการเรียกใช้ฟังก์ชันสามารถแก้ไขได้ในเวลารวบรวมหรือเวลาลิงก์ ตัวอย่างของภาษาที่ใช้การรวมแบบสแตติกคือ 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 "คงที่" อยู่ที่ไหนสักแห่งระหว่างสองขั้ว: คลาสประกาศประเภทของการดำเนินการที่รองรับทั้งหมดล่วงหน้าดังนั้นสิ่งเหล่านี้จึงเป็นที่รู้จักและสามารถพิมพ์ได้ จากนั้นเราสามารถสร้างตารางการค้นหาอย่างง่าย (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 sayHelloToMeredith()
ใหม่และผ่านวัตถุที่มีที่
ในขณะที่สิ่งนี้มีผลผูกพันช่วงปลายนี่ไม่ใช่ "การผูกปลายสุดขีด" ซึ่ง Kay ชื่นชอบ แทนที่จะเป็นรูปแบบแนวคิด“ วิธีการแจกจ่ายผ่านตัวชี้ฟังก์ชั่น” เขาใช้“ วิธีการส่งผ่านข้อความผ่าน” นี่คือความแตกต่างที่สำคัญเพราะการส่งข้อความเป็นเรื่องทั่วไปมากขึ้น ในรุ่นนี้แต่ละวัตถุมีกล่องจดหมายเข้าซึ่งวัตถุอื่นสามารถใส่ข้อความได้ วัตถุที่รับนั้นสามารถลองตีความข้อความนั้นได้ ระบบ OOP ที่เป็นที่รู้จักมากที่สุดคือ WWW ที่นี่ข้อความเป็นคำขอ HTTP และเซิร์ฟเวอร์เป็นวัตถุ
GET /questions/301919/
ตัวอย่างเช่นผมสามารถขอเซิร์ฟเวอร์ programmers.stackexchange.se programmers.get("/questions/301919/")
เปรียบเทียบนี้เพื่อสัญกรณ์ เซิร์ฟเวอร์สามารถปฏิเสธคำขอนี้หรือส่งข้อผิดพลาดกลับมาให้ฉันหรือให้ฉันตอบคำถามของคุณ
พลังของการส่งข้อความคือมันปรับขนาดได้ดีมาก: ไม่มีการแชร์ข้อมูล (ถ่ายโอนเท่านั้น) ทุกอย่างสามารถเกิดขึ้นแบบอะซิงโครนัสและวัตถุสามารถตีความข้อความได้ตามต้องการ สิ่งนี้ทำให้ข้อความที่ส่งผ่านระบบ OOP สามารถขยายได้อย่างง่ายดาย ฉันสามารถส่งข้อความที่ทุกคนไม่เข้าใจและรับผลที่คาดหวังหรือข้อผิดพลาดกลับมา วัตถุไม่จำเป็นต้องประกาศล่วงหน้าว่าจะตอบกลับข้อความใด
เรื่องนี้ทำให้ความรับผิดชอบในการรักษาความถูกต้องไปยังผู้รับข้อความความคิดที่รู้จักกันว่า encapsulation เช่นฉันไม่สามารถอ่านไฟล์จากเซิร์ฟเวอร์ HTTP โดยไม่ต้องขอผ่านทางข้อความ HTTP สิ่งนี้ทำให้เซิร์ฟเวอร์ HTTP ปฏิเสธคำขอของฉันเช่นหากฉันไม่มีสิทธิ์ ในขนาดที่เล็กกว่า OOP นี่หมายความว่าฉันไม่มีสิทธิ์อ่าน - เขียนสำหรับสถานะภายในของวัตถุ แต่ต้องผ่านวิธีการสาธารณะ เซิร์ฟเวอร์ HTTP ไม่จำเป็นต้องให้บริการไฟล์ด้วยเช่นกัน มันอาจจะสร้างเนื้อหาแบบไดนามิกจากฐานข้อมูล ใน OOP จริงกลไกของวิธีการที่วัตถุตอบสนองต่อข้อความสามารถเปลี่ยนได้โดยไม่ต้องแจ้งให้ผู้ใช้ทราบ สิ่งนี้แข็งแกร่งกว่า "การสะท้อน" แต่โดยทั่วไปแล้วจะเป็นโปรโตคอลเมตาวัตถุ ตัวอย่าง C ของฉันด้านบนไม่สามารถเปลี่ยนกลไกการส่งที่รันไทม์
ความสามารถในการเปลี่ยนกลไกการจัดส่งบ่งบอกถึงการผูกปลายเนื่องจากข้อความทั้งหมดจะถูกส่งผ่านรหัสที่ผู้ใช้กำหนดได้ และนี่เป็นสิ่งที่ทรงพลังอย่างยิ่ง: เนื่องจากโปรโตคอลเมตาวัตถุฉันสามารถเพิ่มคุณสมบัติเช่นคลาส, ต้นแบบ, การสืบทอด, คลาสนามธรรม, อินเตอร์เฟส, ลักษณะ, การสืบทอดหลาย, การเขียนโปรแกรมเชิงกว้าง, การสะท้อน, การเรียกใช้วิธีการจากระยะไกล วัตถุพรอกซี ฯลฯ เพื่อภาษาที่ไม่ได้เริ่มต้นด้วยคุณสมบัติเหล่านี้ พลังในการพัฒนานี้ขาดหายไปอย่างสิ้นเชิงจากภาษาแบบคงที่เช่น C #, Java หรือ C ++