Hướng đối tượng Binding muộn


11

Trong Định nghĩa về đối tượng của Alan Kays có định nghĩa này mà một phần tôi không hiểu:

OOP với tôi có nghĩa là chỉ nhắn tin, duy trì và bảo vệ cục bộ và che giấu quá trình của nhà nước, và quá muộn tất cả mọi thứ.

Nhưng "lateBinding" nghĩa là gì? Làm cách nào tôi có thể áp dụng điều này trên một ngôn ngữ như C #? Và tại sao điều này rất quan trọng?



2
OOP trong C # có lẽ không phải là loại OOP Alan Kay có trong tâm trí.
Doc Brown

Tôi đồng ý với bạn, hoàn toàn ... ví dụ được chào đón bằng bất kỳ ngôn ngữ nào
Luca Zulian

Câu trả lời:


14

Binding Liên kết đề cập đến hành động giải quyết một tên phương thức thành một đoạn mã bất khả xâm phạm. Thông thường, cuộc gọi chức năng có thể được giải quyết tại thời gian biên dịch hoặc tại thời gian liên kết. Một ví dụ về ngôn ngữ sử dụng liên kết tĩnh là C:

int foo(int x);

int main(int, char**) {
  printf("%d\n", foo(40));
  return 0;
}

int foo(int x) { return x + 2; }

Ở đây, cuộc gọi foo(40)có thể được giải quyết bởi trình biên dịch. Điều này sớm cho phép tối ưu hóa nhất định như nội tuyến. Những lợi thế quan trọng nhất là:

  • chúng ta có thể kiểm tra kiểu
  • chúng ta có thể tối ưu hóa

Mặt khác, một số ngôn ngữ trì hoãn chức năng phân giải đến thời điểm cuối cùng có thể. Một ví dụ là Python, nơi chúng ta có thể xác định lại các biểu tượng một cách nhanh chóng:

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

Đây là một ví dụ về ràng buộc muộn. Mặc dù việc kiểm tra kiểu nghiêm ngặt không hợp lý (chỉ có thể thực hiện kiểm tra kiểu trong thời gian chạy), nhưng nó linh hoạt hơn nhiều và cho phép chúng tôi thể hiện các khái niệm không thể diễn đạt trong giới hạn của kiểu gõ tĩnh hoặc ràng buộc sớm. Ví dụ, chúng ta có thể thêm các chức năng mới trong thời gian chạy.

Phương thức gửi như thường được thực hiện trong các ngôn ngữ OOP tĩnh tĩnh ở đâu đó nằm ở giữa hai thái cực này: Một lớp khai báo loại của tất cả các hoạt động được hỗ trợ lên phía trước, vì vậy những ngôn ngữ này được biết đến tĩnh và có thể được đánh máy. Sau đó chúng ta có thể xây dựng một bảng tra cứu đơn giản (VTable) chỉ ra việc thực hiện thực tế. Mỗi đối tượng chứa một con trỏ tới một vtable. Hệ thống loại đảm bảo rằng bất kỳ đối tượng nào chúng tôi nhận được sẽ có một vtable phù hợp, nhưng chúng tôi không biết tại thời điểm biên dịch giá trị của bảng tra cứu này là gì. Do đó, các đối tượng có thể được sử dụng để truyền các hàm xung quanh dưới dạng dữ liệu (một nửa lý do tại sao OOP và lập trình hàm là tương đương). Vtables có thể được thực hiện dễ dàng bằng bất kỳ ngôn ngữ nào hỗ trợ các con trỏ hàm, chẳng hạn như 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!"
}

Kiểu tra cứu phương thức này còn được gọi là chuyển động năng động, và một nơi nào đó ở giữa ràng buộc sớm và ràng buộc muộn. Tôi coi công văn phương thức động là thuộc tính xác định trung tâm của lập trình OOP, với bất kỳ thứ gì khác (ví dụ: đóng gói, phân nhóm, đánh) là thứ yếu. Nó cho phép chúng tôi đưa tính đa hình vào mã của mình và thậm chí thêm hành vi mới vào một đoạn mã mà không phải biên dịch lại! Trong ví dụ C, bất kỳ ai cũng có thể thêm một vtable mới và truyền một đối tượng với vtable đó sayHelloToMeredith().

Mặc dù đây là ràng buộc muộn, nhưng đây không phải là ràng buộc muộn cực kỳ được ưu ái bởi Kay được Kay ưa chuộng. Thay vì mô hình khái niệm gửi công thức mô hình qua các con trỏ hàm, ông sử dụng phương thức gửi đi qua tin nhắn qua truyền tin. Đây là một sự khác biệt quan trọng bởi vì thông điệp truyền đi là tổng quát hơn nhiều. Trong mô hình này, mỗi đối tượng có một hộp thư đến nơi các đối tượng khác có thể đặt tin nhắn. Đối tượng nhận sau đó có thể cố gắng diễn giải thông điệp đó. Hệ thống OOP nổi tiếng nhất là WWW. Ở đây, tin nhắn là các yêu cầu HTTP và máy chủ là các đối tượng.

Ví dụ: tôi có thể yêu cầu máy chủ lập trình.stackexchange.se GET /questions/301919/. So sánh điều này với ký hiệu programmers.get("/questions/301919/"). Máy chủ có thể từ chối yêu cầu này hoặc gửi lại cho tôi một lỗi hoặc nó có thể phục vụ cho câu hỏi của bạn.

Sức mạnh của việc truyền thông điệp là nó có tỷ lệ rất tốt: không có dữ liệu nào được chia sẻ (chỉ được truyền), mọi thứ đều có thể xảy ra không đồng bộ và các đối tượng có thể diễn giải các thông điệp theo ý muốn. Điều này làm cho một thông điệp đi qua hệ thống OOP dễ dàng mở rộng. Tôi có thể gửi tin nhắn mà không phải ai cũng có thể hiểu và nhận lại kết quả mong đợi hoặc lỗi. Đối tượng không cần phải khai báo trước những thông điệp mà nó sẽ phản hồi.

Điều này đặt trách nhiệm duy trì tính đúng đắn đối với người nhận thông điệp, một ý nghĩ còn được gọi là đóng gói. Ví dụ: tôi không thể đọc tệp từ máy chủ HTTP mà không yêu cầu tệp đó qua tin nhắn HTTP. Điều này cho phép máy chủ HTTP từ chối yêu cầu của tôi, ví dụ: nếu tôi thiếu quyền. Trong OOP quy mô nhỏ hơn, điều này có nghĩa là tôi không có quyền truy cập đọc-ghi vào trạng thái bên trong của đối tượng, mà phải thông qua các phương thức công khai. Một máy chủ HTTP cũng không phải cung cấp cho tôi một tệp. Nó có thể được tạo động nội dung từ DB. Trong OOP thực, cơ chế về cách một đối tượng trả lời tin nhắn có thể được tắt mà không cần người dùng nhận thấy. Điều này mạnh hơn so với phản xạ của hồi giáo, nhưng thường là một giao thức đối tượng meta đầy đủ. Ví dụ C của tôi ở trên không thể thay đổi cơ chế điều phối trong thời gian chạy.

Khả năng thay đổi cơ chế điều phối ngụ ý ràng buộc muộn, vì tất cả các tin nhắn được định tuyến thông qua mã xác định người dùng. Và điều này cực kỳ mạnh mẽ: được cung cấp một giao thức siêu đối tượng, tôi có thể thêm các tính năng như lớp, nguyên mẫu, kế thừa, lớp trừu tượng, giao diện, đặc điểm, đa kế thừa, đa gửi, lập trình hướng theo khía cạnh, phản xạ, gọi phương thức từ xa, các đối tượng proxy, vv đến một ngôn ngữ không bắt đầu với các tính năng này. Sức mạnh để phát triển này hoàn toàn không có ở các ngôn ngữ tĩnh hơn như C #, Java hoặc C ++.


4

Ràng buộc muộn đề cập đến cách các đối tượng giao tiếp với nhau. Lý tưởng mà Alan đang cố gắng đạt được là để các đối tượng được ghép lỏng lẻo nhất có thể. Nói cách khác, một đối tượng cần biết mức tối thiểu có thể để giao tiếp với đối tượng khác.

Tại sao? Bởi vì điều đó khuyến khích khả năng thay đổi các bộ phận của hệ thống một cách độc lập và giúp nó phát triển và thay đổi một cách hữu cơ.

Ví dụ, trong C #, bạn có thể viết theo phương thức cho obj1một cái gì đó như obj2.doSomething(). Bạn có thể xem điều này như obj1giao tiếp với obj2. Để điều này xảy ra trong C #, obj1cần phải biết một chút công bằng về obj2. Nó sẽ cần phải biết lớp học của nó. Nó sẽ kiểm tra xem lớp có một phương thức được gọi không doSomethingvà có một phiên bản của phương thức đó có tham số không.

Bây giờ hãy tưởng tượng một hệ thống mà bạn đang gửi tin nhắn qua mạng hoặc tương tự. bạn có thể viết một cái gì đó như Runtime.sendMsg(ipAddress, "doSomething"). Trong trường hợp này, bạn không cần biết nhiều về máy bạn đang liên lạc; nó có lẽ có thể được liên lạc qua IP và sẽ làm một cái gì đó khi nhận được chuỗi "doS Something". Nhưng nếu không thì bạn biết rất ít.

Bây giờ hãy tưởng tượng đó là cách các đối tượng giao tiếp. Bạn biết một địa chỉ và bạn có thể gửi tin nhắn tùy ý đến địa chỉ đó với một số loại chức năng "hộp thư". Trong trường hợp này, obj1không cần biết nhiều obj2, chỉ là địa chỉ. Nó thậm chí không cần phải biết rằng nó hiểu doSomething.

Đó là khá nhiều mấu chốt của ràng buộc muộn. Bây giờ, trong các ngôn ngữ sử dụng nó, như Smalltalk và ObjectiveC, thường có một chút đường cú pháp để ẩn chức năng hộp thư. Nhưng nếu không thì ý tưởng là như nhau.

Trong C #, bạn có thể sao chép nó, bằng cách có một Runtimelớp chấp nhận một đối tượng ref và một chuỗi và sử dụng sự phản chiếu để tìm phương thức và gọi nó (nó sẽ bắt đầu trở nên phức tạp với các đối số và trả về giá trị nhưng điều đó có thể xảy ra xấu xí).

Chỉnh sửa: để xóa bỏ một số nhầm lẫn liên quan đến ý nghĩa của ràng buộc muộn. Trong câu trả lời này, tôi đang đề cập đến ràng buộc muộn vì tôi hiểu Alan Kay có nghĩa là nó và thực hiện nó trong Smalltalk. Nó không phải là cách sử dụng phổ biến, hiện đại hơn của thuật ngữ mà thường đề cập đến công văn động. Cái sau bao gồm sự chậm trễ trong việc giải quyết phương thức chính xác cho đến khi chạy nhưng vẫn yêu cầu một số thông tin loại cho người nhận tại thời điểm biên dịch.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.