본문 바로가기

JAVA/Design Patterns

인터페이스로 프로그래밍하기 #깨지기 쉬운 기반 클래스 문제2

반응형

다중상속

다중(구현)상속을 지원하는 언어는 클래스 정의에서 여러 개의 extends 관계를 허용한다.

만약 구현 상속이 나쁘다면 분명 다중 구현 상속은 더 나쁘다.

하지만 다중 상속의 '개념'이 유용할 때가 있다.

예를 들어 메뉴바를 가진 프레임 윈도인 '메뉴 사이트'를 소개하는데 애플리케이션의 메인 윈도는 프레임윈도(JFrame)이면서 또한 MenuSite이다.

메뉴 사이튼 두기반 클래스의 모든 속성을 지니고 있으며, 이런 상황에서의 다중 상속은 합당하다.


다중 상속'개념'이 유용할 경우가 있다.


JFrame을 확장하고 MenuSite 인터페이스를 구현하며, 모든 MenuSite 연산을 기본 구현 객체에 위임한다.

개념적으로 이는 다중 상속과 같은 기능을 한다.

이런 위임 기반의 해결책은 경험있는 개발자들은 흔히 사용하는 것이기 때문에, 이를 '다중상속(Multiple Inheritance)' 디자인 패턴이라 부를 수 있을 것이다.

다중 상속 패턴의 일반적인 형태


public interface Base {

void f();


public static class Implementation implements Base {

@Override

public void f() {

// TODO Auto-generated method stub


}

}

}


public class Derived extends Something implements Base {


Base delegate = new Base.Implementation();

@Override

public void f() {

// TODO Auto-generated method stub

delegate.f();

}


}


public class Something {

}


'구현/위임' 이디엄은 상속과 같은 기반 클래스 코드를 한번 이상 작성하지 않아도  된다는 이점이 있다.

같은 목적을 위해 기반 클래스의 구현 상속하는 대신 캡슐화하여 사용하는 것이 다를 뿐이다.

물론 앞에 f() 메소드와 같이 기본 구현을 자잘한 접근 메소드를 통해 접근해야 한다는 불편함이 있다.

마찬가지로 MonitorableStack.push() 메소드에서도 SimpleStack의 메소드를 호출하는 접근 메소드들이 있다.

조금 불편할지 모르겠지만, 깨지기 쉬운 기반 클래스를 제거하는 비용으로 오히려 저렴한 것 같다.

C++ 프로그래머라면 구현/위임 이디엄이 구현모호와 같은 다중 상속과 관련 문제를 해겨해줄 수 있다는 사실을 숙지하기 바란다.


구현/위임 이디엄을 통해 다중 상속을 안전하게 구현할 수 있다.


프레임워크

깨지기 쉬운 기반 클래스 문제를 프레임워크 기반 프로그래밍에 대한 언급없이 마칠수 없다.

MFC(Microsoft's Foundation Class) 라이브러리와 같은 프레임워크는 클래스 라이브러리를 만드는 인기 있는 방법이 되었다.

MFC는 사라져 가지만 MFC의 구조는 "마이크로소프트가 그런 방식을 했다면 옳은 방법일꺼야"라고 생각하는 많은 프로그래머들의 코드 속에 여전히 살아 있다.


마이크로소프트의 문제 해결 방식이 항상 올바른 것은 아니다.


프레임워크는 모든 기능이 아닌 핵심 기능 혹은 플로우만을 제공하는 반쯤 구워진 클래스 라이브러리라 할 수 있다.

그리고 이를 이용하는 개발자가 기반 클래스를 상속해 핵심 기능을 제공하여 기반 클래스가 적절히 작동하도록 한다.

자바의 AWT 컴포넌트 클래스가 제공하는 paint() 메소드가 좋은 예가 될것이다.

컴포넌트는 paint()를 정의하지만 스크린에 무언가를 실제로 그리기 위해서는 클래스를 상속해 paint() 메소드를 오버라이딩해야 한다.

paint() 메소드는 무언가를 그리는 코드를 위한 장소이며, 실제로 스크린에 무언가를 그리는 기능은 파생 클래스가 제공해야 하는 것이다.

대부분의 프레임워크는 이와 같이 부분적으로 구현된 기반클래스들로 구성되어 있다.


많은 프레임워크가 기반 클래스를 확장하여(extends) 사용하도록 하는 파생 기반 아키텍처를 택하고 있다.


파생 기반의 아키텍처는 여러 이유로 불쾌하다.

깨지기 쉬운 기반 클래스 이슈도 그 중 하나의 이유가 되겠지만 프레임워크가 작동하게 하기 위해 너무 많은 클래스를 구현해야한다는 것이 더 큰 문제이다.

윈도를 그리기 위해 paint() 메소드를 오버라이딩해야 하기 때문에 , 각각의 윈도는 자신을 그리기 위해 paint() 메소드를 오버라이딩한 파생 글래스를 필요로 한다.

그러므로 독특한방식으로 자신을 그리는 모든 윈도는 모두 Component를 상속해 각각의 클래스를 만들어야 한다.

15개의 다양한 윈도를 가진 프로그램은 15개의 Component 파생 클래스를 가져야 하는 것이다.


구현 상속 기반 아키텍처는 깨지기 쉬운 기반 클래스 문제 외에도 너무 많은 클래스를 구현해 주어야 하는 문제를 갖고 있다.


OO에 대한 법칙중 하나는 하나의 클래스가 충분히 안정적으로 구현(문서화와 디버깅을 포함)되려면 대략 2~3주가 걸린다.

C++의 경우에는 좀 더 길다.

그러므로 클래스가 많아질수록 프로그램을 작성하는 기간이 길어진다.


클래스가 많아 질수록 개발 기간이 길어진다.


클래스 숫자 증가의 문제를 다른 측면에서 살펴보자

파생을 통한 커스터마이제이션 전략은 계층이 깊어지면서 제대로 동작하지 않는다.

Editor 클래스는 기본적으로 텍스트 조작을 다루며, 키스트로크(keystroke)를 받아 이에 반응해 내부 버퍼를 수정한다.

그리고 실제 버퍼의 갱신은 키스트로크를 인자로 받은 (propected) updateBuffer() 메소드에서 일어난다.

이론상으로 이 메소드를 오버라이딩하여 특정 키스트로크가 해석되는 방식을 바꿀 수 있다.


하지만 불행히도 새로운 행동 CustomerEditor를 구현 상속하는 클래스만 이용할 수 있으며, Editor를 상속하는 기존 클래스들은 이용하지 못한다.

그리고 새로운 키 매핑이 모든 에디터에서 지원되게 하려면 Editor, EditableTextControl, StandardEditor, CustomerEditor를 구현  상속하는 각각의 클래스를 만들어 주어야 한다.

이는 클래스 계층 구조의 크기를 두 배로 증가시킨다.

그리고 Editor와 이를 구현 상속하는 클래스들 사이에 새로운 클래스를 삽입해야 하는 경우가 생 길수도 있다.

이를 위해서 소스 코드를 변경해야 하며, 소스 코드를 갖고 있지 않을 수도 있다.

Strategy 패턴은 이런 문제를 해결해준다.


Editor           CustomEditor

   #updateBuffer    <-----------------     #updateBuffer


EditorTextControl StandaloneEditor



반응형