프로그래밍-학습기록/객체 지향 프로그래밍

다형성과 추상 타입

leesche 2021. 1. 6. 16:38

다형성과 추상 타입

객체 지향에서 유연하게 구현을 변경할 수 있도록 하는 방법으로 추상화가 있다. 추상화는 다형성으로 가능해진다. 다형성을 알기 위해서는 상속을 알아야 한다.

상속 개요

상속(Inheritance)은 한 타입을 그대로 사용하면서 구현을 추가할 수 있도록 해주는 방법을 제공한다.

어떤 객체가 갖고 있는 기능을 확장해 새로운 기능을 구현하고 싶을 때 상속을 사용한다.

상속 대상이 되는 클래스를 상위(super) 클래스 또는 부모 클래스라고 부른다. 상속을 받는 클래스를 하위(sub) 클래스 또는 자식 클래스라고 부른다.

자식 클래스는 부모 클래스에 정의된 구현을 물려받는다.

프로그래밍 언어마다 하위 타입에서 물려받을 수 있을 것이 다를 수 있다. 다수의 언어에서 private 범위를 갖는 메서드나 필드를 제외한 나머지를 물려받을 수 있도록 하고 있다.

하위 클래스는 필요에 따라 상위 클래스에 정의된 메서드를 새롭게 구현할 수도 있는데, 이를 재정의(Overriding)라고 한다. 메서드를 재정의하면, 해당 메서드를 실행할 때 상위 타입의 메서드가 아닌 하위 타입에서 재정의한 메서드가 실행된다.

다형성과 상속

다형성(Polymorphism)은 한 객체가 여러 가지 모습을 갖는다는 것을 의미한다. 여기서 모습이란 타입을 뜻하는데, 즉, 다형성이란 한 객체가 여러 타입을 가질 수 있다는 것을 뜻한다.

인터페이스 상속과 구현 상속

자바, C++, C# 등의 언어는 다형성을 구현하기 위해 타입을 상속받는 방법을 사용하는데, 타입 상속은 크게 인터페이스 상속과 구현 상속으로 구분해 볼 수 있다. 먼저, 인터페이스 상속은 순전히 타입 정의만을 상속받는 것이다. 자바의 인터페이스나 C++에서 추상 함수만을 가진 추상 클래스를 상속 받는 경우가 이에 해당한다. 자바와 같이 클래스 다중 상속을 지원하지 않는 언어에서는 인터페이스를 이용해서 객체가 다양한 형을 갖게 된다.

구현 상속은 클래스 상속을 통해서 이뤄진다. 구현 상속은 보통 상위 클래스에 정의된 기능을 재사용하기 위한 목적으로 사용된다.

추상 타입과 유연함

추상화(abstraction)는 데이터나 프로세스 등을 의미가 비슷한 개념이나 표현으로 정의하는 과정이다.

컴퓨터 분야에서 이런 추상화는 매우 광범위하게 사용되며, 타입도 이런 추상화의 대상이 된다.

추상화된 타입은 오퍼레이션의 시그니처만 정의할 뿐 실체 구현을 제공하지는 못한다.

추상 타입은 구현을 제공할 수 없기 때문에, 보통 구현을 제공하지 않는 타입—자바의 인터페이스의 C++의 추상 메서드로만 구성된 추상 클래스—을 이용해서 추상 타입을 정의한다.

  • 추가

    상세한 구현을 제공하는 클래스에서 추상 타입을 이끌어 내는 과정은 추상화의 일부분에 지나지 않는다. 추상화를 한다고 해서 반드시 추상 타입을 만들어야 하는 것은 아니다. 다양한 영역에서 다양한 수준의 추상화가 사용된다.

추상 타입과 실제 구현의 연결

추상 타입과 실제 구현 클래스는 상속을 통해서 연결한다. 즉, 구현 클래스가 추상 타입을 상속 받는 방법으로 둘을 연결한다.

상속을 이용해서 추상 타입을 실제 구현 클래스로 연결하면 다음과 같이 추상 타입을 이용해서 코드를 작성하는 것이 가능해진다.

추상 타입을 이용한 구현 교체의 유연함

  • 콘크리트 클래스를 직접 사용해도 문제가 없는데, 왜 추상 타입을 사용하는 것일까?

    처음에는 문제가 되지 않는다. 하지만 추상화 덕분에 이후 있을 객체 구현 변경에 유연함을 얻을 수 있다.

변화되는 부분을 추상화 하기

추상화가 저절로 되는 것은 아니다. 추상화를 잘 하려면 다양한 상황에서 코드를 작성하고 이 과정에서 유연한 설계를 만들어 보는 경험을 해봐야 한다. 하지만, 모든 개발자가 다양한 환경에서 많은 경험을 할 수 있는 것은 아니기 때문에, 변화될 부분을 미리 예측해서 추상화하는 것은 쉽지 않다. (빨리 미니 프로젝트를 해보자😆)

경험하지 않은 분야라 하더라도 추상화할 수 있는 방법이 하나 있는데, 그것은 바로 변화되는 부분을 추상화하는 것이다. 요구 사항이 바뀔 때 변화되는 부분은 이후에도 변경될 소지가 많다. 이런 부분을 추상 타입으로 교체하면 향후 변경에 유연하게 대처할 수 있는 가능성이 높아진다. (→ 이런 통찰이 있으려면 일하고 있는 분야, 그 인접 분야까지 폭 넓은 지식이 있어야 겠다. '이런 부분이 필요하지 않을까?'하며 예상을 할 수 있을 것 같다.)

인터페이스에 대고 프로그래밍하기

program to interface.

여기서 말하는 인터페이스는 자바나 C#의 인터페이스가 아니라, 오퍼레이션을 정의한 인터페이스이다. 즉, 이 말은 실제 구현을 제공하는 콘크리트 클래스를 사용해서 프로그래밍하지 말고, 기능을 정의한 인터페이스를 사용해서 프로그래밍하라는 뜻이다.

그런데, 인터페이스는 최초 설계에서 바로 도출되기 보다는, 요구 사항의 변화와 함께 점진적으로 도출이 된다. 즉, 인터페이스는 새롭게 발견된 추상 개념을 통해서 도출되는 것이다.

추상 타입을 사용하면 기존 코드를 건드리지 않으면서 콘크리트 클래스로 교체할 수 있는 유연함을 얻을 수 있었는데, '인터페이스에 대고 프로그래밍하기' 규칙은 바로 추상화를 통한 유연함을 얻기 위한 규칙이다.

주의할 점유연함을 얻는 과정에서 타입(추상 타입)이 증가하고 구조도 복잡해지기 때문에 모든 곳에서 인터페이스를 사용해서는 안 된다는 것이다. 이 경우, 불필요하게 프로그램의 복잡도만 증가시킬 수 있다. 인터페이스를 사용해야 할 때는 변화 가능성이 높은 경우에 한해서 사용해야 한다. 변경 가능성이 매우 희박한 클래스에 인터페이스를 만든다면 오히려 프로그램 구조만 복잡해지고 유연함의 효과는 누릴 수 없는 그런 상황이 발생하게 된다.

인터페이스는 인터페이스 사용자 입장에서 만들기

인터페이스와 테스트

실제 콘크리트 클래스 대신에 진짜처럼 행동하는 객체를 Mock(가짜, 모의) 객체라고 부르는데, Mock 객체를 사용함으로써 실제 사용할 콘크리트 클래스의 구현 없이 테스트 할 수 있다. Mock 객체를 만드는 방법은 다양하게 존재하지만 사용할 대상을 인터페이스로 추상화하면 좀 더 쉽게 Mock 객체를 만들 수 있게 되며, 이는 사용할 코드의 완성을 기다릴 필요 없이 내가 만든 코드를 먼저 빠르게 테스트 할 수 있도록 해준다.


참고 및 출처