현대 컴 언어에서 객체지향은 매우 중요한 부분입니다. 최근의 대부분 개발론이 이 객체지향을 바탕으로 한다고 해도 과언이 아닙니다. 닷넷, 자바, 델파이, 파워빌더 등 최근 모든 개발툴들은 이 객체지향을 기본으로 채택하고 있습니다.
그러나, 여러 정황으로 볼때 상당수의 국내 델 개발자들의 객체지향에 대한 이해도는 너무 부족한것 같습니다. 오늘은 객체지향 설계에서 매우 중요하게 여겨지는 loose coupling 에 대해서 논해 보려 합니다.
loose coupling이란 간단히 말해서, 코딩의 한 부분이 다른 부분에 지나치게 의존적이 되도록 코딩하지 말라는 것입니다. 또 달리 말하면 반드시 특정 클래스 객체만 다루도록 코드를 만들지 말라는 것입니다. 전역변수를 많이 사용하지 말라는 것도 이 개념에 포함된다 할수 있습니다. 전역변수에 의존하면 서로 의존적인 코드가 많아져서 어떤 부분을 바꿀 때 여러 부분을 동시에 수정해야 하는 상황이 발생하기 때문입니다.
델파이를 사용해본 사람들은 모두 알겠지만, 델파이에서 가장 기본적인 이벤트 메서드 타입인, TNotifyEvent의 첫번째 인수 Sender의 타입은 TObject입니다.
type TNotifyEvent = procedure (Sender: TObject) of object;
델파이에서 TObject는 모든 클래스의 최상위 클래스이므로, TNotifyEvent는 어떤 객체든지 인수로 전달 받을 수 있습니다. 이런 개념이 loose coupling라고 할 수 있습니다. 만일 위와 같이 TNotifyEvent를 정의하지 않고, 다음과 같이 TNotifyEvent를 정했다고 가정해 봅시다.
type TNotifyEvent = procedure (Sender: TForm) of object;
이렇게하면 폼(TForm) 콤펀넌트 이외에는 사용할 수 없는 아주 제한적인 이벤트 메서드가 되어 버릴 것입니다.
loose coupling는 객체지향에서 주로 등장하지만, 예전 컴 언어들도 나름대로 이 비슷한 개념을 사용했습니다. 그런 대표적인 예가 사칙연산자 입니다. +, -, *, / 는 일반적 산술 연산 코딩에서 매우 빈번하게 사용합니다. C++의 연산자 중복정의(operator overloding)개념을 공부해본 사람이라면, 모든 연산자는 실제로는 어떤 함수를 내부적으로 호출한다는 것을 알 것입니다.
I := I + 1;
위와 같은 문장은 실제로는, 델파이 컴파일러에 의해 대략 다음과 같은 함수 호출로 대체됩니다.(실제 호출되는 함수명은 나도 알길이 없습니다)
IntAssign( I, IntAdd(I, 1) );
그렇다면 만일 I는 정수 타입이고 R은 실수 타입 변수라면, 다음 문장은 대체 어떤 함수 호출도 대체될까요?
R := I + R; // 정수와 실수를 더하므로 IntAssign( R, IntAdd(I, R) )을 호출할 수는 없음
전산 개론을 배워본 사람은 알겠지만, 실수와 정수는 그 저장하는 비트모양이 전혀 다르므로 그냥 더할수는 없습니다. 이경우, 대부분의 컴파일러들은 형상승 원칙에 의해서 큰 값을 보관하는 쪽으로 자동 형변환을 일으켜 연산합니다. 따라서 R := I + R 은 대략 다음과 같은 함수 호출로 변경됩니다.
RealAssign( R, RealAdd( Real(I), R ) );
델 개발자들은 덧셈 연산자를 사용하면서 이런 복잡한 내부 함수 호출에 전혀 신경쓸 필요는 없습니다. 델파이 컴파일러가 알아서 이런 형변환을 자동 수행하기 때문입니다. 결론인즉, 사칙 연산자는 이미 특정 숫자형에 구애받지 않도록 loose coupling개념을 적용하여 만들어져 있다는 것입니다. 만일 개발자인 우리들이 이런 형변환을 일일이 해주면서 사칙연산을 해야 한다면 엄청 고통스러울 것입니다.
연산자가 그러하듯이, 만일 이러한 loose coupling 개념을 적용하여 함수를 만들 수 있다면, 하나의 함수로 여러 데이타형을 처리할 수 있다는 결론이 됩니다.
델파이 객체지향에서 가장 고민스런 코딩 중의 하나가 min과 max 함수 정의 방법론일 것입니다. 두 값을 인수로 전달받아서 최소값을 구하는 min함수를 만든다고 가정합시다. 델파이는 모든 타입을 수용하는 타입은 없으므로 다음과 같이 무수한 min함수를 만들어야 할 것입니다.
function min(I, J : Integer) : integer; overload; function min(I, J : Double) : Double; overload; function min(I, J : String) : String; overload; function min(I, J : TDateTime) : TDateTime; overload;
문제는 이 min 함수들의 내부 코드는 다음과 같이 모두 동일할 것이란 점입니다.
begin result := i; if i > j then result := j; end;
단지 인수들의 타입만 다를 뿐, 실제 구현 코드는 거의 같은, 이런 반복코드를 줄이고자 하는 기법이 loose coupling라고 이해하면 됩니다. C++은 이런 경우 템플리트라는 문법으로 해결합니다. 불행히도 델파이는 이런 min, max 함수 경우 해결할 방법이 없습니다. 그러나 loose coupling 개념은 여러 코딩에서 적용할 수 있습니다.
loose coupling 개념을 적용하기 위해, 델파이에서 사용하는 대표적 문법이 상속, polymorphism(virtual메서드)과 interface 등 입니다. 이 문법들은 특정 클래스 객체에만 한정되는 함수나 메서드를 만들지 말고, 여러 클래스 타입을 인수로 전달받을 수 있는 범용적 함수나 메서드를 만들 수 있게 하는 것입니다.
loose coupling의 또다른 적용방법으로, 동적 패키지가 있습니다. 구체적인 클래스명을 참조할 수 없기 때문에 동적 패키지는 별로 효용성이 없다고 생각할수도 있으나, 사실은 그 반대입니다. 구체적 클래스명을 참조하지 않아도 된다는 점이 동적 패키지의 진정한 장점입니다. 구체적 클래스명을 몰라도 된다는 것은 uses문에 그 클래스를 정의한 유닛명을 나열하지 않아도 된다는 것이고, 이는 동적 패키지에 포함되는 모든 유닛에 대해서 메인 실행파일은 완전히 무시해버려도 무방하다는 야그가 됩니다.
C++의 #include도 그렇지만, 델파이의 uses문도 엄청 짜증나는 문법 중의 하나입니다. 동적 패키지는 그 자체가 단독 모듈로 존재하기 때문에 uses문이 엄청 간결해집니다. 이는 대형 프라젝트에서 여러모로 유리합니다. 팀별끼리 별도 모듈을 만들고 병합할때, 다른 팀이 만드는 패키지에 포함되는 유닛명에 대해 별로 신경쓸 필요가 없기 때문입니다. 이말인즉 여러팀이 동시에 개발할 때 동일한 클래스명을 두팀이 사용하는 경우가 발행해도, 동적 패키지는 전혀 문제가 없다는 야그이며, 대형 프라젝트에서 심각한 문제중 하나인 이름 중복 때문에 골치가 훨씬 덜 아프다는 것입니다.
동적 패키지의 장점은 이외에도 여럿이 있지만, 동적 패키지는 강력한 만큼 엄청난 객체지향적 내공이 있어야 사용가능하므로, 이를 사용하고 말고는 지극히 주관적이므로, 각자의 판단에 맡겨둡시다.
그러나, 지금까지 반드시 특정 클래스명을 참조해야 가능한 식으로 코딩을 해왔다면 그 발상을 뒤집어 보십시요. 특정 클래스만이 아니라 여러 클래스 객체를 수용할 수 있는 loose coupling 방식으로 바꾸십시요. 반복 코드를 줄이며 아주 유연한 함수를 만들 수 있을 것입니다. 물론 이 기법이 하루아침에 체득되는 것은 분명 아닙니다. 이정도의 개념을 파악하기 위해 나도 꽤나 오랜 시간이 걸렸습니다. 그러나 여러분들은 내가 걸린 시간보다 훨씬 단축할 수 있을 것입니다. 이제는 많은 자료와 책이 있기 때문입니다.
절차지향이 동일 반복 코드를 재사용하는 방법이라면, 객체지향은 조금씩은 다른 부분이 있을지라도 동일한 로직도 재사용해야 한다는 것입니다.
나도 그러했지만, 대부분의 개발자들이 처음 배울 때는 개발팁만을 구하려 합니다. 그러나 지금부터라도 개발 방법론에 관심을 가지십시요
첫댓글 가슴에 와 닿는 말씀입니다.