본문 바로가기
NC University/Design Pattern with C++

1일차 - 상속(inheritance)

by 날쑤 2016. 5. 24.

Review - protected constructor

  • 자신(추상적인 개념)은 객체를 생성할 수 없지만 파생 클래스(구체적인 개념)는 객체를 생성할 수 있게 하는 클래스 디자인 기법
  • 최상위 추상(abstract) 클래스의 생성자는 protected로 만드는 것이 좋음.

  위의 코드에서 주석을 친 부분을 활성화시키면 컴파일 에러가 발생한다. 이는 객체 생성 시에 외부로 노출되는 함수가 아닌 protected 생성자를 호출하려 하기 때문이다. 반면에 파생 클래스인 Dog 내부에서는 protected 생성자의 호출이 가능하기 때문에 Dog 타입의 객체 생성에는 아무런 문제가 없다.

Review - access modifier

  • 상속 시에 기본 클래스에 대한 접근권한을 '축소'하기 위해 사용함. (단, 권한 확장은 불가)
  • 기본 클래스의 멤버들 중 접근 변경자에 명시된 범위보다 넓은 접근 권한을 가진 것들을 명시한 범위로 줄여서 상속

  위의 코드에서 Base의 멤버 변수 a는 public이지만, Derived는 Base를 private으로 상속받기 때문에 Derived에서는 권한이 private으로 축소된다. 그러므로 외부에서 Derived 객체의 멤버 변수 a를 접근하면 컴파일 에러가 발생한다.

private 상속을 사용한 어댑터(adapter) 패턴

  이미 링크드 리스트가 구현되어 있는 상태에서 사용자의 요구로 스택을 구현해야 하는 상황을 가정해보자. 이 경우에 구현자의 선택지는 스택 클래스를 새롭게 작성하는 것과 구현되어 있는 리스트를 재사용하는 것(리스트를 한쪽 방향으로만 사용)이 있을 것이다. 전자를 선택할 수도 있겠지만 이 경우에는 새로 만드는 것도 귀찮은 일이거니와 추가적인 자료구조로 인해 메모리 사용량도 증가하게 될 것이다. 그러므로 여기서는 리스트를 재사용하는 쪽으로 방향을 잡도록 하자.

  이처럼 기존 클래스의 인터페이스(혹은 함수 이름)만 변경해서 클라이언트가 요구하는 다른 클래스를 만드는 패턴을 어댑터 패턴(adapter pattern)이라고 한다.

  한편 Stack 클래스의 멤버 함수들은 인라인으로 선언되어 있는데, 이로 인해 실제로 함수를 호출하는 부분의 어셈블리 코드에서는 Stack 클래스가 완전히 사라지게 된다. 이처럼 C++에서는 인라인 치환 등의 기술로써 성능저하가 없는 어댑터를 만들 수 있다.

  위의 코드는 일단 표면적으로는 문제가 없어보인다. 하지만 Stack 클래스의 사용자가 (아마도 실수로) push_front 함수를 호출한다면 어떻게 될까? Stack 클래스는 list를 public으로 상속받았기 때문에 부모인 list의 push_front 함수를 호출하는 것은 문법 상으로는 전혀 문제가 없으며 컴파일도 잘될 것이다. 하지만 Stack 클래스의 관점에서는 이는 노출되어서는 안되는 인터페이스이며, 결과적으로도 Stack을 망가뜨리는 결과를 가져올 것이다. 이런 경우에 사용하는 것이 바로 private 상속이다.

  private 상속의 취지는 기본 클래스로부터 구현은 물려받지만(파생 클래스 내부에서는 기본 클래스의 함수를 사용), 인터페이스는 물려받지 않겠다(기본 클래스의 함수를 외부에 노출하지 않음)는 것이다. 이는 앞의 코드에서와 같이 상속을 통해 어댑터를 만들 때 의도하지 않은 인터페이스가 외부로 노출되는 것을 방지해줄 수 있다. 하지만 상속을 통한 어댑터 구현은 기존 클래스와 어댑터 클래스 사이에 강한 결합(tight coupling)이 생기게 해서 프로그램의 변경에 많은 제약을 가져올 수 있다. 그러므로 위의 구현은 아주 좋은 디자인이라고 보기는 어렵다.

포함을 사용한 Stack 어댑터의 구현

  SW의 재사용은 일반적으로 상속과 포함(composite)이 기본이 된다. 그런데 상속은 위에서 언급했듯이 일반적으로는 유연성이 떨어지는 디자인이므로, 이번에는 포함을 이용해서 Stack 클래스를 만들어보자.

  우선 stack 어댑터의 기반이 되는 클래스를 상속이 아닌 Stack 클래스의 멤버 변수로 집어넣자. 더 나아가 기반이 되는 클래스 타입을 템플릿 타입 인자로 선언함으로써 무조건 list 기반이 아닌 상황에 따라 사용자가 선택할 수 있도록 해주자. 이러한 디자인은 우리의 stack adapter를 좀 더 유연하게 만든다. 그리고 이제는 상속 기반이 아니기 때문에 IDE에서 더 이상 불필요한 인터페이스의 노출이 일어나지 않는다.

  마지막으로 C++ 표준에는 stack 클래스가 이미 존재한다. <stack> 헤더에 있는 std::stack 클래스의 구현을 살펴보면 위의 코드와 거의 유사한 형태로(즉, 기반이 되는 클래스를 포함 관계로 가지는) 구현되어 있으며, 이러한 이유로 이를 stack container adapter라고도 부른다. 이와 같이 많은 경우에서는 상속보다는 포함이 유연한 클래스 디자인을 위한 더 나은 선택이다. 단, 기반이 되는 클래스의 인터페이스를 재정의해야 하는 경우에는 앞의 예제에서 사용한 private 상속이 대안이 될 수있다.

'NC University > Design Pattern with C++' 카테고리의 다른 글

1일차 - 추상 클래스(Abstract class)  (0) 2021.06.11
1일차 - thiscall  (0) 2016.05.19