스파르타 내일배움캠프/Today I Learned

Today I Learned - Day 67 [상속과 구현, 그 미묘해 보이는 극렬한 차이에 대하여]

불면증 도사 2024. 12. 17. 21:42

일전에, 3번에 걸쳐서 객체 지향에 대해서 서술한 적이 있다.

[참고 URL]

더보기

 

그렇다면 '추상화'의 예시인 인터페이스와 '상속'에 대해서 용도나 기타 활용이 어떻게 다를 지 궁금할 수도 있을 것이다.

오늘은 그것을 중점으로 살펴볼까 한다.

다만, 기능 상의 차이점에 대한 것 보다는 개념 상의 차이점에 대해서 더 서술해 볼 까 한다.

당연하지만, 활용 방식에 따라서 다르게 사용하는 경우도 있을 것이고, 본인의 방식이 정답인 것도 아니니, 개인의 코딩 스타일에 참고하는 정도로만 활용해도 충분하리라 생각한다.


상속과 구현의 차이점?

부모 클래스의 구성 요소를 이어받아서 활용하는 것은 상속, 인터페이스의 내용을 상세화 하는 것을 구현이라고 한다.

이렇게 한 줄으로만 설명하는 것도 가능하지만, 이렇게 해 놓으면 각자의 특성이 무엇인지 알기가 힘들다.

기능 상으로도 변수 활용이 가능하냐 부터 시작해서 이래저래 다르기도 하지만, 가장 중요한 '개념'에 대한 설명이 불충분하기 때문.

그러니까 이 둘을 떼어내서 설명한 뒤에 다시 둘을 비교하며 설명해 보고자 한다.


상속에 대하여

상속은 '부모 클래스의 구성 요소를 이어받아서 활용하는 것'을 의미한다.

변수 부터 시작해서 함수, 이외의 기타 구조들 까지도 다 이어받기 때문에 기능 분화를 만들고자 하는 경우에 활용하기가 좋은 기능.

다만, 부모 클래스의 구조 까지 그대로 이어받기 때문에 복수의 부모를 상속받을 경우 모호함의 문제가 발생할 수 있고, 이를 예방하기 위해서 C#에서는 다중 상속을 금지하고 있다.

 

사실 상속은 추상화와는 관계가 없는 기능으로, '부모 클래스의 함수를 호출하여 사용'하는 것이 가능하긴 하지만, 그 함수의 기본형태는 부모에서 가져오기 때문에 오히려 추상화의 정 반대에 있는 기능이라고 보는 것이 옳다.

물론, 활용 여하에 따라서 부모의 기능과 완전히 동떨어진 기능을 만드는 것도 가능은 하지만, 그렇게 만드는 것은 스파게티 코드로 볼 수도 있다는 점을 주의하도록 하자.


구현에 대하여

구현은 '인터페이스의 구성요소를 받아와서 각 클래스에서 완성하는 것'을 의미한다.

때문에 기본 형태고 뭐고 그저 '호출부' 밖에 없고, 그 호출부에 맞춘 기능 작성을 해야하는 구조다.

즉, 같은 이름의 함수라도 클래스가 다르다면 기능의 연관성은 1도 없다.

 

이러한 특성 덕분에 추상화의 대표적인 예시로 활용되고 있다.

비슷한 예시로 추상 클래스가 있기는 하나, 이 또한 '클래스의 기본 형태는 추상 클래스에서 이어받는다'는 점 때문에 완전한 추상화라고 하기는 힘들다.

 

추상 클래스에 대해서는 이 정도로 끝내고, 인터페이스의 특징에 대해서 더 서술해 보자.

인터페이스에는 함수 만이 서술 가능하고, 변수는 프로퍼티로 활용해야만 사용이 가능하다.

동시에 모든 함수는 public으로만 서술해야 한다.

즉, 전체적으로 외부에서 호출되는 것을 가정한 구성이라고 볼 수 있다.

 

실제로 인터페이스 자체의 의미도 '서로 다른 시스템이나 장치 간에 정보나 신호를 교환할 수 있는 공유 경계'다.

즉, '다른 클래스에서 어떻게 불릴까'를 정의하는 기능으로, 그렇기 때문에 '기능이 같을 필요는 전혀 없다.'

[물론, 어지간하면 비슷한 용법으로 사용되는 클래스들 끼리 묶는 것이 가독성 측면도 그렇고, '기능 한정' 측면에서도 좋다.]

 

때문에 활용할 수 있는 가짓수도 많다.

상술했듯이 같은 방식으로 호출되는 요소를 만들기 위해서 사용할 수도 있고, 호출 방식에 따라서 기능을 제한하기 위해서도 활용할 수 있다. 그게 아니더라도 '같은 부류로 묶고 싶은 요소들 끼리 묶는' 용도로도 사용이 가능하다.


상속과 구현의 차이점!

여기 까지 봤으면, 상속과 구현의 차이점에 대해서 정리할 수 있을 것이다.

기본적인 정보로 정리하면 상속은 '기본 형태가 정해진 상태로 기능을 추가하는 것'이고, 구현은 '형태가 정해지지 않은 것을 상세화 시키는 것'이다.

좀 더 깊게 들어가면 상속은 명백한 상하관계 형성을 위해서 사용하는 기능으로, '해당 객체에 무엇이 고정되는가'가 중요한 요소다. 즉, '틀을 정해놓기 위해서 사용하는 기능'인 셈.

반대로 구현은 그냥 함수를 사용하는 구문 만 정해놓고 전체적으로 자유롭게 풀어놓고 있는 구성이다. 때문에 중요한 것은 '해당 객체에 어떻게 접촉하는 가'이지, 객체의 구성과 형태는 크게 중요하지가 않다. 오히려 이에 대한 틀을 만들지 않기 위해서 사용하는 기능이라고 보는 것이 더 옳을 것이다.

 

사용 방식이 유사하다고 헷깔리기 쉬운데, 이렇게 특징이 서로 정 반대에 해당하는 기능이다.

이렇게 보면 어떤 것을 언제 써야하는지 어느 정도 감을 잡을 수 있을 것이다.


여담이지만, 객체 지향 프로그래밍을 개발한 인원들 중에 한 명은 '상속을 만든 것을 후회했다'라는 이야기를 들은 적이 있다.

물론 양쪽 모두 활용도가 높고 유용한 기능인 것은 맞지만, 상속의 경우에는 너무 많이 활용하면 오히려 코드 복잡도가 늘어난다. 특히 '이전에 선언했던 기능을 사용하기 위해서 다른 클래스를 확인해야 하는' 구조이기 때문에 과용하면 본인 밖에 못 보는 코드가 만들어질 수도 있기도 하다.

실제로 라이노에서 클래스 구조도를 만들어 보면 상속 관계도 전부 의존 관계로 작성한다.

즉, 상속 구조를 적극적으로 활용하고 있었다고 한다면 이에 대해서 고민 해 볼 필요가 있다고 볼 수 있겠다.

[실제로 디자인 패턴을 활용하다 보면, 기능 분화는 모듈을 통해서 하는 경우가 많고, 상속 구조로 하는 경우는 점점 적어진다.]

 

물론, 그렇다고 상속이 중요하지 않은 기능인 것은 아니다. 어찌되었건 '기반 객체에서 분화되는' 구조를 만들려면 상속을 쓸 수 밖에 없고, 한 번 작성한 요소를 추가로 작성하지 않아도 된다는 점에서 사용자 편의적인 측면에서도 유용한 기능이다.

그러니, 이번 글을 계기로 상속과 구현에 대해서 다시 한번 고려해 보면 어떨까.

그렇게 한걸음 더 내딪는 것이다.