«Связывание» означает преобразование имени метода в фрагмент кода, который можно вызвать. Обычно вызов функции может быть разрешен во время компиляции или во время соединения. Примером языка, использующего статическое связывание, является 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
Это пример позднего связывания. Хотя строгая проверка типов необоснованна (проверка типов может выполняться только во время выполнения), она гораздо более гибкая и позволяет выражать понятия, которые не могут быть выражены в рамках статической типизации или раннего связывания. Например, мы можем добавлять новые функции во время выполнения.
Диспетчеризация методов, как это обычно реализуется в «статических» языках ООП, находится где-то посередине между этими двумя крайностями: класс заранее объявляет тип всех поддерживаемых операций, поэтому они статически известны и могут быть проверены на наличие типов. Затем мы можем построить простую таблицу поиска (VTable), которая указывает на фактическую реализацию. Каждый объект содержит указатель на vtable. Система типов гарантирует, что любой объект, который мы получим, будет иметь подходящую vtable, но мы не знаем во время компиляции, каково значение этой таблицы поиска. Следовательно, объекты могут использоваться для передачи функций в виде данных (половина причины, по которой ООП и программирование функций эквивалентны). 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!"
}
Этот вид поиска метода также известен как «динамическая диспетчеризация» и находится где-то между ранним и поздним связыванием. Я считаю, что динамическая диспетчеризация методов является центральным определяющим свойством программирования ООП, а все остальное (например, инкапсуляция, подтипы и т. Д.) Является вторичным. Это позволяет нам вводить полиморфизм в наш код и даже добавлять новое поведение к коду без необходимости его перекомпиляции! В примере C любой может добавить новый vtable и передать объект с этим vtable sayHelloToMeredith()
.
Несмотря на то, что это поздняя привязка, это не «крайняя поздняя привязка», которую предпочитает Кей. Вместо концептуальной модели «диспетчеризация метода через указатели на функции» он использует «диспетчеризацию метода через передачу сообщения». Это важное различие, потому что передача сообщений гораздо более общая. В этой модели каждый объект имеет папку входящих сообщений, в которую другие объекты могут помещать сообщения. Получающий объект может затем попытаться интерпретировать это сообщение. Наиболее известной системой ООП является WWW. Здесь сообщения - это HTTP-запросы, а серверы - объекты.
Например, я могу попросить сервер programmers.stackexchange.se GET /questions/301919/
. Сравните это с обозначениями programmers.get("/questions/301919/")
. Сервер может отклонить этот запрос или отправить мне сообщение об ошибке, или он может ответить на ваш вопрос.
Сила передачи сообщений заключается в том, что она очень хорошо масштабируется: никакие данные не передаются (только передаются), все может происходить асинхронно, а объекты могут интерпретировать сообщения так, как им нравится. Это делает систему передачи сообщений ООП легко расширяемой. Я могу отправлять сообщения, которые не все могут понять, и либо получить ожидаемый результат, либо ошибку. Объект не должен заранее объявлять, на какие сообщения он будет отвечать.
Это возлагает ответственность за поддержание правильности на получателя сообщения, мысль, также известная как инкапсуляция. Например, я не могу прочитать файл с HTTP-сервера, не запросив его через HTTP-сообщение. Это позволяет HTTP-серверу отклонить мой запрос, например, если у меня нет разрешений. В меньшем масштабе ООП это означает, что у меня нет доступа на чтение и запись к внутреннему состоянию объекта, но я должен пройти через открытые методы. HTTP-сервер тоже не должен обслуживать мне файл. Это может быть динамически генерируемый контент из БД. В реальном ООП механизм реагирования объекта на сообщения может быть отключен без уведомления пользователя. Это сильнее, чем «отражение», но обычно это полный протокол мета-объекта. Мой пример C выше не может изменить механизм отправки во время выполнения.
Возможность изменения механизма диспетчеризации подразумевает позднюю привязку, поскольку все сообщения направляются через определяемый пользователем код. И это чрезвычайно мощно: учитывая протокол мета-объекта, я могу добавить такие функции, как классы, прототипы, наследование, абстрактные классы, интерфейсы, признаки, множественное наследование, многократное диспетчеризация, аспектно-ориентированное программирование, отражение, удаленный вызов метода, объекты прокси и т. д. на языке, который не начинается с этими функциями. Эта способность развиваться полностью отсутствует в более статичных языках, таких как C #, Java или C ++.