본문 바로가기
SW

GoF의 디자인 패턴(Design Patterns: Elements of Reusable Object-Oriented Software) - 1장 서론(1.6)

by 라꾸스떼(YR) 2020. 4. 4.
반응형

[객체 인터페이스의 명세]

객체가 선언하는 모든 연산은 연산의 이름, 매개변수로 받아들이는 객체들, 연산의 반환 값을 명세한다. 이를 연산의 시그니처(signature)라고 한다. 인터페이스(interface)는 객체가 정의하는 연산의 모든 시그니처들을 일컫는 말로 객체의 인터페이스는 객체가 받아서 처리할 수 있는 연산의 집합이다.

타입(type)은 특정 인터페이스를 나타낼 때 사용하는 이름. 다른 인터페이스를 포함하는 인터페이스를 서브타입(subtype), 다른 인터페이스가 포함하는 인터페이스를 슈퍼타입(supertype). 서브타입은 슈퍼타입의 인터페이스를 상속한다. 서브타입이 슈퍼타입을 상속하면, 서브타입은 슈퍼타입에 정의된 연산을 포함하게 된다.

인터페이스 개념은 객체지향 시스템에서 가장 기본적인 것이다. 객체는 인터페이스로 자신을 드러낸다. 외부에서 객체를 알 수 있는 방법은 인터페이스 밖에 없기 때문에 인터페이스를 통해서만 처리를 요청할 수 있다. 어떤 요청과 그 요청을 처리할 객체를 프로그램 실행 중, 즉 런타임에 연결 짓는 것을 동적 바인딩(dynamic binding)이라고 한다.

동적 바인딩은 프로그램이 기대한느 객체를 동일한 인터페이스를 갖는 다른 객체로 대체할 수 있게 해준다. 이런 대체성을 우리는 다형성(polymorphism)이라고 하는데, 이는 객체 지향 시스템의 핵심 개념이다.

디자인 패턴은 인터페이스에 정의해야 하는 중요 요소가 무엇이고 어떤 종류의 데이터를 주고받아야 하는지 식별하여 인터페이스를 정의하도록 도와준다. 메멘토 패턴은 객체의 내부 상태를 어떻게 저장하고 캡슐화해야 하는지를 정의함으로써 객체가 나중에 그 상태로 복구할 수 있는 방법을 알려준다.

디자인 패턴은 인터페이스 간의 관련성도 정의한다. 예를 들어, 장식자 패턴과 프록시 패턴은 장식되고 중재되는 객체와 동일한 인터페이스를 갖도록 장식자 객체와 프록시 객체의 인터페이스를 요청한다. 방문자 패턴에서 방문자 인터페이스는 방문자 객체가 방문하는 객체들의 클래스 인터페이스를 그 방문자 인터페이스에 모두 반영하도록 한다.

 

[객체 구현 명세하기]

객체의 구현은 클래스(class)에서 정의(define)한다. 클래스는 객체의 내부 데이터와 표현 방법을 명세하고, 그 객체가 수행할 연산을 정의한다. 객체는 클래스를 인스턴스로 만듦으로써 생성된다. 즉, 객체는 클래스의 인스턴스이다. 클래스의 인스턴스화 과정은 객체의 내부 데이터(인스턴스 변수, instance variable)에 대한 공간을 할당하고, 이 데이터들을 연산과 관련짓는 것이다.

서브 클래스(subclass)가 부모 클래스(parent class)를 상속하면, 부모 클래스가 갖는 모든 데이터와 연산을 서브클래스가 갖게 된다.

추상 클래스(abstract class)는 모든 서브클래스 사이의 공통되는 인터페이스를 정의한다. 추상 클래스는 정의한 모든 연산이나 일부 연산의 구현을 서브 클래스에게 넘긴다. 추상 클래스는 정의한 모든 연산이나 일부 연산의 구현을 서브 클래스에게 넘긴다. 추상 클래스는 인스턴스를 생성할 수 없다. 정의만 하고 구현하지 않는 연산을 추상 연산(abstract operation)이라 하고, 추상 클래스가 아닌 클래스를 구체 클래스(concrete class)라고 한다.

서브 클래스는 부모 클래스가 정의한 행동을 재정의하거나 정제할 수 있다. 즉, 오버라이드(override)로 서브 클래스는 부모 클래스에 정의된 처리 방식을 변경할 수 있다.

믹스인 클래스(mixin class)는 다른 클래스들에게 선택적인 인터페이스 혹은 기능을 제공하려는 목적을 가진 클래스이다. 인스턴스로 만들 의도가 없다는 면에서 추상 클래스와 비슷하다. 다중 상속이 필요하다.

 

[클래스 상속 대 인터페이스 상속]

클래스는 객체의 내부 상태와 그 객체의 연산에 대한 구현 방법을 정의한다. 반면, 객체의 타입은 그 객체의 인터페이스, 즉 그 객체가 응답할 수 있는 요청의 집합을 정의한다. 하나의 객체가 여러 타입을 가질 수 있고 서로 다른 클래스의 객체들이 동일한 타입을 가질 수 있다. 즉, 객체의 구현은 다를지라도 인터페이스는 같을 수 있다는 의미이다. C++ 같은 언어에서 클래스는 객체 타입과 구현 모두를 의미한다.

클래스 상속은 객체의 구현을 정의할 때 이미 정의된 객체의 구현을 바탕으로 한다. 인터페이스 상속(서브타이핑)은 어떤 객체가 다른 객체 대신에 사용될 수 있는 경우를 지정하는 메커니즘이다. C++ 언어에서 상속은 인터페이스와 구현 상속 모두를 의미한다. C++에서 인터페이스를 상속하는 표준적인 방법은 (순수) 가상 함수를 갖는 클래스를 public으로 상속하는 것이다. public으로 상속되면 서브클래스도 부모 클래스가 갖는 가상 함수를 상속받고 서브클래스가 구현을 담당하며, 상속받은 인터페이스가 서브클래스의 사용자에게도 공개된다. C++에서 순수한 인터페이스 상속은 순수 가상 함수를 정의한 추상 클래스를 public으로 상속하면 비슷하게 구현할 수 있다. 순수 가상 함수는 전혀 구현을 정의할 수 없는 함수이기 때문에 이를 상속한다는 것은 진정한 의미의 인터페이스만을 상속받는다는 뜻이다. private로 상속하면 부모 클래스에 정의된 연산은 서브클래스의 사용자에게는 공개되지 않기 때문에 상속의 목적은 인터페이스 확장이 아닌 부모 클래스 구현의 재사용이다.

C++ 프로그래머들은 구체적인 클래스를 정의하고 요청을 보내기보다 추상 클래스의 객체에게 메시지를 보내도록 프로그래밍한다. 이렇게 하면 런타임에 구체 클래스의 인스턴스로 바꿀 수 있다. 즉, 추상 클래스를 상속한다는 것은 단순한 코드의 재사용을 위한 상속이 아니라 추상 클래스가 정의하는 인터페이스를 상속하겠다는 의미이다. 첵임 연쇄 패턴에 나오는 객체들은 반드시 동일한 타입을 가져야 하지만, 이들이 구현을 공유할 부분은 없다. 복합체 패턴에서 Component 클래스는 공통의 인터페이스를 정의하고, Composite 클래스는 공통의 구현을 정의한다. 명령, 감시자, 상태, 전략 패턴은 순수 인터페이스인 추상 클래스를 써서 구현될 때가 많다.

 

[구현에 따르지 않고, 인터페이스에 따르는 프로그래밍]

클래스 상속은 기본적으로 부모 클래스에서 정의한 구현을 재사용하여 응용프로그램의 기능성을 확장하려는 메커니즘이다. 그러나 구현의 재사용이 전부는 아니다. 상속이 가진 다른 기능들 중에는 동일한 인터페이스를 갖는 객체군을 정의하는 것이 있다. 객체군을 정의하는 것이 중요한 이유는 다형성을 끌어낼 수 있기 때문이다. 상속을 적절하게 이용하면, 모든 클래스는 추상 클래스를 상속하도록 하여 인터페이스를 공유할 수 있게 된다. 모든 서브클래스들은 추상 클래스에 정의한 인터페이스를 처리할 수 있다. 이로써 모든 서브클래스들은 부모 클래스의 서브 타입이 되는 것이다.

추상 클래스를 정의하고 인터페이스 개념으로 객체를 다룰 때 얻을 수 있는 두가지 이점.

1.사용자가 원하는 인터페이스를 그 객체가 만족하고 있는 한, 사용자는 그들이 사용하는 특정 객체 타입에 대해 알아야 할 필요는 없다.

2.사용자는 이 객체들을 구현하는 클래스를 알 필요가 없고, 단지 인터페이스를 정의하는 추상 클래스가 무엇인지만 알면 된다.

이렇게 하면 서브시스템 간의 구현 종속성이 없어진다.

즉, 구현이 아닌 인터페이스에 따라 프로그래밍해야 한다. 따라서 어떤 변수(객체)를 구체 클래스의 인스턴스로 선언하는 일은 피해야 한다. 대신 추상클래스의 인터페이스를 따르는 인스턴스 변수를 정의해야한다.

추상 팩토리, 빌더, 팩토리 메서드, 원형 패턴 및 단일체 패턴에서는 구체 클래스에서 인스턴스를 생성하도록 하고 있다. 이들 패턴에서는 객체 생성의 과정을 추상화함으로써 인스턴스화할 때 인터페이스와 구현을 연결하는 다른 방법을 제시한다.

반응형

댓글