객체 지향 후기 바인딩


11

에서 객체 지향의 앨런 Kays 정의 부분적으로 이해하지 않는 것이이 정의가있다 :

나에게 OOP는 메시징, 로컬 보존 및 상태 프로세스 숨기기 및 모든 것의 극단적 인 후기 바인딩만을 의미합니다.

그러나 "LateBinding"은 무엇을 의미합니까? 이것을 C #과 같은 언어에 어떻게 적용 할 수 있습니까? 왜 이것이 그렇게 중요한가?



2
C #의 OOP는 Alan Kay가 염두에 둔 종류가 아닐 수 있습니다.
Doc Brown

나는 절대적으로 ... 예는 어떤 언어로 환영합니다, 당신과 동의
루카 Zulian

답변:


14

"바인딩"은 분석법 이름을 불가피한 코드로 해석하는 행위를 말합니다. 일반적으로 함수 호출은 컴파일 타임이나 링크 타임에 해결 될 수 있습니다. 정적 바인딩을 사용하는 언어의 예는 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와 함수 프로그래밍이 동일한 이유의 절반). V와 같은 함수 포인터를 지원하는 모든 언어로 Vtable을 쉽게 구현할 수 있습니다.

#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을 추가하고 해당 vtable을 가진 객체를에 전달할 수 있습니다 sayHelloToMeredith().

이것은 늦은 바인딩이지만 Kay가 선호하는“최후의 바인딩”은 아닙니다. 개념 함수 "함수 포인터를 통한 메소드 디스패치"대신 "메시지 전달을 통한 메소드 디스패치"를 사용합니다. 메시지 전달이 훨씬 일반적이기 때문에 이는 중요한 차이점입니다. 이 모델에서 각 개체에는 다른 개체가 메시지를 넣을 수있는받은 편지함이 있습니다. 그러면 수신 객체가 해당 메시지를 해석하려고 시도 할 수 있습니다. 가장 잘 알려진 OOP 시스템은 WWW입니다. 여기서 메시지는 HTTP 요청이고 서버는 객체입니다.

예를 들어 programmers.stackexchange.se 서버에 요청할 수 있습니다 GET /questions/301919/. 이것을 표기법과 비교하십시오 programmers.get("/questions/301919/"). 서버는이 요청을 거부하거나 오류를 다시 보내거나 귀하의 질문을 처리 할 수 ​​있습니다.

메시지 전달의 힘은 확장 성이 뛰어나다는 것입니다. 데이터가 공유되지 않고 (전송 만) 모든 것이 비동기 적으로 발생할 수 있으며 개체는 원하는대로 메시지를 해석 할 수 있습니다. 따라서 메시지 전달 OOP 시스템을 쉽게 확장 할 수 있습니다. 모든 사람이 이해할 수없는 메시지를 보내고 예상 결과 나 오류를 다시받을 수 있습니다. 객체는 어떤 메시지에 응답할지 미리 선언 할 필요가 없습니다.

이것은 캡슐화라고도 알려진 생각의 수신자에게 정확성을 유지할 책임이 있습니다. 예를 들어 HTTP 메시지를 통해 요청하지 않고 HTTP 서버에서 파일을 읽을 수 없습니다. 이를 통해 HTTP 서버가 내 요청을 거부 할 수 있습니다 (예 : 권한이없는 경우). 더 작은 규모의 OOP에서 이것은 객체의 내부 상태에 대한 읽기 / 쓰기 액세스 권한이 없지만 공개 메소드를 거쳐야 함을 의미합니다. HTTP 서버도 파일을 제공 할 필요가 없습니다. DB에서 컨텐츠를 동적으로 생성 할 수 있습니다. 실제 OOP에서는 사용자가 알지 못하고 개체가 메시지에 응답하는 방식의 메커니즘을 전환 할 수 있습니다. 이것은 "반사"보다 강력하지만 일반적으로 완전한 메타 객체 프로토콜입니다. 위의 C 예제는 런타임에 디스패치 메커니즘을 변경할 수 없습니다.

디스패치 메커니즘을 변경하는 기능은 모든 메시지가 사용자 정의 가능 코드를 통해 라우팅되므로 바인딩이 지연됩니다. 메타 객체 프로토콜이 주어지면 클래스, 프로토 타입, 상속, 추상 클래스, 인터페이스, 특성, 다중 상속, 다중 디스패치, 측면 지향 프로그래밍, 리플렉션, 원격 메소드 호출과 같은 기능을 추가 할 수 있습니다. 프록시 객체 등을 이러한 기능으로 시작하지 않는 언어로 이러한 발전의 힘은 C #, Java 또는 C ++와 같은 정적 언어에는 전혀 없습니다.


4

후기 바인딩은 객체가 서로 통신하는 방법을 나타냅니다. Alan이 달성하고자하는 이상은 객체를 가능한 한 느슨하게 결합하는 것입니다. 다시 말해, 객체는 다른 객체와 통신하기 위해 가능한 최소한을 알아야한다는 것입니다.

왜? 이는 시스템의 일부를 독립적으로 변경하는 기능을 장려하고 유기적으로 성장하고 변경할 수있게하기 때문입니다.

예를 들어 C #에서는 obj1다음과 같은 메소드를 작성할 수 있습니다 obj2.doSomething(). 와 obj1통신하는 것으로 볼 수 있습니다 obj2. 이것이 C #에서 발생 obj1하려면에 대해 약간의 지식이 필요합니다 obj2. 수업을 알아야 할 것입니다. 클래스에 메소드 doSomething가 있고 매개 변수가 0 인 메소드의 버전 이 있는지 확인했을 것 입니다.

이제 네트워크 나 이와 유사한 것을 통해 메시지를 보내는 시스템을 상상해보십시오. 당신은 같은 것을 쓸 수 있습니다 Runtime.sendMsg(ipAddress, "doSomething"). 이 경우 통신중인 컴퓨터에 대해 많이 알 필요가 없습니다. IP를 통해 연결될 수 있으며 "doSomething"문자열을 수신하면 무언가를 수행합니다. 그러나 그렇지 않으면 당신은 거의 알지 못합니다.

이제 그것이 물체와 통신하는 방식이라고 상상해보십시오. 당신은 주소를 알고 있으며 어떤 종류의 "포스트 박스"기능으로 그 주소로 임의의 메시지를 보낼 수 있습니다. 이 경우에 obj1대해 많이 알 필요는 없으며 obj2주소입니다. 이해한다는 사실조차 알 필요가 없습니다 doSomething.

그것은 늦은 바인딩의 핵심입니다. 이제 Smalltalk 및 ObjectiveC와 같이이를 사용하는 언어에는 일반적으로 우편함 기능을 숨길 약간의 구문 설탕이 있습니다. 그러나 그렇지 않으면 아이디어는 동일합니다.

C #에서는 Runtime객체 참조 및 문자열을 허용하고 리플렉션을 사용하여 메소드를 찾고 호출 하는 클래스 를 사용하여 복제 할 수 있습니다 (인수 및 반환 값으로 복잡해지기 시작하지만 가능할 수는 있습니다) 추한).

편집 : 늦은 바인딩의 의미와 관련하여 약간의 혼란을 완화합니다. 이 답변에서 Alan Kay가 의미하고 Smalltalk에서 구현했음을 이해하면서 늦은 바인딩을 언급하고 있습니다. 일반적으로 동적 디스패치를 ​​지칭하는 용어가 더 일반적이고 현대적인 용어는 아닙니다. 후자는 런타임까지 정확한 방법을 해결하는 데 필요한 지연을 다루지 만 여전히 컴파일 타임에 수신자에 대한 일부 유형 정보가 필요합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.