클래스 덩어리에 대한 고찰
Kroisse(사실 엄밀한 학문적인 분석이라기보다는 그냥 어렴풋한 느낌에 의한 추론인지라 불명확하거나 심지어는 오류가 있을지는 모르겠지만, 일단 하나의 의견을 제시한다고 생각하고 올려봅니다.)
Java 등의 보편적인 정적 타입의 객체지향 프로그래밍 언어에서의 클래스는 두 가지의 용도가 혼재된 상태로 존재합니다. 첫째는 객체 인스턴스를 만들어내는 틀로써, 객체의 내부 구현을 정의하는 역할입니다. 모든 객체가 클래스로부터 만들어지고 클래스에 따라 역할과 관계가 맺어지는 클래스 기반 객체지향 프로그래밍 모델에서는 더할 나위 없이 중요한 역할이지요. 둘째는 정적 타입 시스템을 가진 언어이기 때문에 가지는, 객체 값에 대한 타입으로써 객체의 외부 인터페이스를 규정짓는 역할입니다. 정적 타입 언어이기 때문에, 변수형, 함수 반환형과 인자의 타입 등 값이 쓰일 수 있는 모든 곳에 타입이 관여하게 되고, 객체는 -- 마치 정수 값이 정수 타입에 묶여 정수 연산의 지배를 받는 것처럼 -- 타입의 역할을 수행하는 클래스에 묶이고, 클래스에 명시된 인터페이스를 내놓게 됩니다.
제가 지적하고 싶은 사항은, 클래스가 가지고 있는 이 두 역할이 서로 밀접하게 관련된 것 같아도, 사실 따지고 보면 둘을 분리할 수 있지 않나 하는 것입니다.
예를 들어, 명시적으로 타입을 제한하지 않는 동적 타입 언어들을 봅시다. Python이나 Ruby와 같은 이런 언어들은 비록 클래스는 있지만, 객체가 실제로 자신의 인터페이스를 내놓는 문제에 대해서는 비교적 클래스에 대해 자유롭습니다. 이들 언어에서 어떤 객체의 메서드를 부르는 데 필요한 것은, 단지 적절한 메서드 이름과 인자들뿐이지요. 심지어 Ruby의 경우에는 이른바 싱글턴 메서드라고 불리는 방식으로 클래스와는 독자적인 자신만의 메서드를 가질 수도 있습니다. 이런 언어들의 경우, 위에서 설명한 클래스의 두 번째 역할이 매우 약화되었거나 없다고 볼 수 있습니다.
다른 예로, 이미 Java나 C#에 존재하는 interface 개념을 들 수 있습니다. 이것은 실질적으로 앞에서 얘기한 두 번째 역할만을 전담하는 요소로, 실제로 Java를 위시한 주류 객체지향 그룹에서는 구체적인 클래스를 타입으로 쓰는 것을 자제하고 가능한 interface 위주로 프로그래밍하는 것을 권장하기도 하죠.
저는 이 생각을 발전시켜서, 클래스란 사실 명백히 분리 가능한 두 개의 역할을 하나로 뭉쳐놓은 덩어리(blob)고, 이것이 객체지향 초보자가 잘못된 설계로 빠지고, 몇몇 객체 라이브러리가 비직관적이 되는 원인 중 하나였다고 생각합니다. 객체지향 설계 쪽 얘기를 빌리자면, 말하자면 클래스 자체가 SRP1을 위반하고 있었다는 얘기가 되겠지요. 때문에, 저는 이 둘의 역할이 명확하게 분리되어야 한다고 생각합니다. Java로 치자면, 클래스를 타입으로써 사용하는 것을 완전히 금지하고, 모든 객체의 타입은 오로지 interface로만 지정할 수 있게 하는 것이지요. 만약 프로토타입 기반 객체지향 언어처럼 객체끼리 상속과 유사한 관계를 만들어 낼 수 있고, 스스로 자기 구현을 정의할 수 있다면 클래스라는 것 자체가 필요없어질지도 모릅니다. 그것까지 가지 않더라도, 초심자에게 인터페이스라는 개념을 명확히 심어주고, 올바른 설계를 유도하는 데에 제 제안은 충분히 의미가 있다고 생각합니다.
-
단일 책임 원칙(Single Responsibility Principle). 그러니까 저는 어쩌면 지금까지 객체 지향 프로그래밍에서 가장 큰 축을 담당했던 요소 자체가 객체지향 원칙을 위반하고 있었다는 이상한 얘기를 하고 있는지도 모릅니다. ↩
저도 마침 오늘 그런 생각을 했는데1, Io 같은 언어를 보면 클래스가 없고 객체 사이에 원형 관계만 존재합니다만 이게 인터페이스를 뺀 OO의 모습이 아닐까 합니다.
자세한 것은 오늘자 IRC(#langdev) 로그를 확인하세요. ↩
유감스럽게도 IRC 로그를 쌓아둘 수 있는 처지는 아니어서 확인해보기가 좀 곤란할 것 같군요...
다른 얘기지만, 인터페이스 개념을 완전히 제거해버리는 것도 별로 바람직하다고 생각하진 않습니다. 설명하자면 길어지겠지만, 마치 네임스페이스가 없는 절차지향 언어를 보는 것 같다고 할까요.
생각하시는 종류의 언어는 예전부터 많이 시도되었습니다. 그런데 문제는 프로그래밍 하기가 매우 불편하다는 겁니다. OO패러다임은 다른 패러다임에 비해 코딩양이 상대적으로 많은 편에 속합니다. 인터페이스를 클래스 만들때마다 만들면 코딩양이 상당히 증가합니다. 그래도 많은 코드에도 패턴이 다수 존재하리라 생각되므로, 만약 eclipse와 유사한 코딩을 감소시켜주는 툴을 사용하면 크게 불편하지는 않을 것 같습니다.
그리고 프로토타입기반의 언어는 클래스를 타입으로 해석하지 않기 때문에, 인터페이스가 필요없기는 하겠지요. 그런데, 인터페이스 인터페이스, 그러니깐 인터페이스를 쉽게 만들거나 활용할 수 있는 문법이 없으면 많이 불편한 것 같습니다. "내가 만들려고 하는 것은 이거야! 틀리면 안돼!"라고 선언하는 건 꽤나 매력적이거든요.
저는 오히려 인터페이스는 존재하되, 클래스와 완전히 다른 완벽한 인터페이스가 존재했으면 하는 생각을 합니다. 그러니까 인터페이스를 상속받아서 클래스를 만드는 것이 아니라, 플러그를 끼우듯 클래스+인터페이스를 하면 사용하는 것이죠.
예를들어, 클래스에 a b c d e f의 6개의 멤버함수를 정의합니다. 모든 멤버함수는 기본적으로 private입니다. 여기에 a c e라는 메소드를 가진 인터페이스를 끼우면 a c e만 public으로 풀려서 사용할 수 있게 되는 것이죠.
이렇게 인터페이스와 클래스를 타입관계에서 완전히 떨어뜨린다음에, 인터페이스끼리는 상속을 하고, 클래스끼리는 클로닝을 하는 것이 프로그래밍을 더 편하게 만들어주지 않을까 상상해봅니다.
제가 하고 싶은 말을 그대로 해 주시는군요. 언제나 semmal님의 놀라운 식견에 감탄하게 되네요. :)
개인적으로는 (이번에는 아쉽게도 보류되었지만) C++0x에 추가 예정이었던 concept 개념과 유사한 것을 생각하고 있었습니다. 예를 들어, a, c, e라는 메서드를 정의한 인터페이스 I가 있으면, a, c, e라는 메서드를 가지고만 있으면 모두 I를 구현한 객체로 인정되는 거죠.
concept는 이름이 정확하게 맞아야 되는 걸로 알고 있습니다. 그런데 프로그래밍 하다보면 이름이 달라도 의미상 일치하는 경우가 있기도 하죠. 예를들어서 identity(x)는 id(x)와 이름만 차이가 있다고 할 때, 나머지 하나를 위해 또 인터페이스를 만들어야 합니다. 또한 f(x)가 인터페이스에서는 a(x)로 사용되고 또다른 f(x)가 존재하는 경우도 생각할 수 있습니다.
저는 사실 이름만 매치된다고 하더라도 감사히 쓰기는 하겠지만, connect(identity(x),id(x))같은 선언도 할 수 있었으면 좋겠습니다.
이름이 다르지만 형식은 같은 두 인터페이스를 항상 같은 것으로 볼 수 있을까 하는 생각도 듭니다. 이를테면
point(int x, int y)와person(int weight, int height)을 같은 것으로 봐도 맞을까요?그런 경우엔 레코드 타입을 쓰는 게 적당할 거 같은데요. person(Person p), point(Point p) 이런 식으로 호출하고 Person 에는 weight와 hight 라는 필드가 Point 에는 x와 y라는 필드가 있으면 레코드 타입이 달라서 (레코드 타입은 필드 이름도 포함하므로) 구분이 가능하게 됩니다.
여기서 말하는 인터페이스는 함수 타입이라기보다는 OO에서 클래스 레벨에 적용되는 듯 하고요, 위에서 이야기가 나온 connect 같은 건 래퍼의 일종이고 수동으로 정의하는 거니깐 point와 person을 수동으로 연결하면 그건 프로그래머가 그러면 안되죠 -_-;;;
그 생각도 해봤는데, 정확히 말하자면 오브젝트나 함수의 equality를 자동으로 비교할 수 있느냐는 문제라고 볼 수 있죠? 제가 너무 확대해석하지 않았다는 가정하에서...
비슷하게 생겼다고 의미가 같다는 것을 증명할 방법이 없다는 겁니다. 게다가 비슷한 함수라도 인자의 순서가 바뀌는 경우도 허다하구요. 여러가지로 머리가 복잡해지니 결국에 기영님 말씀처럼 강짜로 레코드화하는 거죠. 이건 결국 "똑같이 생겼으면 의미도 같은거야!"라고 말하는 셈이니까요.
결국 connect를 쓰든 레코드를 쓰든 그 이유는 "이게 같은거라니깐!"하고 우기는 수단이지요.