캡슐화, 상속, 다형성에 대해 설명해보세요.

  • 캡슐화

보통 캡슐화만 이야기 할 때는 '은닉'과 '캡슐화'를 하나로 묶어 이야기 하는 것이다. 하지만 엄밀히 이야기 하자면 이 둘은 구분된다.

우선 '캡슐화'는 데이터와 연산을 한데 묶는 것을 의미한다. 프로그램에서 흘러 다니는 데이터와 결합도는 보통 비례 관계에 있다. C와 같은 절차 지향 언어에서는 함수가 상태를 저장할 수 없기 때문에 데이터가 OO 언어에 비해 많이 흘러 다니게 되고 결합도가 높게 된다. 캡슐화는 이와 같은 데이터 흐름으로 인한 결합도 증가를 막아주는 장치이다.

은닉이란 내부 데이터, 내부 연산을 외부에서 접근하지 못하도록 은닉(hiding) 혹은 격리(isolation)시키는 것을 의미한다. 객체는 '무엇을 하는가'하는 연산으로 정의되어야 한다. 객체는 '서비스 제공자(Service Provider)'이어야 하는 것이다. '어떻게' 연산을 수행하는가는 철저히 은닉되어야 한다. 외부로 공개된 인터페이스 혹은 계약을 통해서만 프로그래밍 해야 하는 것이다.

캡슐화와 은닉이 잘 되려면 객체에 책임을 적절히 배분해 주어야 한다. '객체는 하나의 책임만을 맡아야 한다.'는 단일 책임의 원칙(SRP, Single Responsibility Principle)은 좋은 기준이 되어준다.

getter/setter는 가능한 사용을 자제해야 한다. 이는 '데이터를 요청하지 말고 도움을 요청하라'라는 OO 금언과 관련 있다. 구현이 잘 은닉되어 있고, 책임이 제대로 분배되어 있다면 getter/setter는 그리 필요치 않다. 데이터를 이곳저곳에서 가져와 조합하는 대신 필요로 하는 대부분의 데이터를 갖고 있는 객체에게 일부 데이터를 넘겨주고 일을 해달라는 요청(위임)하자.

인터페이스는 은닉과 관련 있다. 인터페이스를 사용하면 클라이언트는 구체 클래스에 대해 알지 못해도 된다. 이는 디자인 패턴의 내면에 흐르는 철학이자 OO의 철학이기도 하다. 구체 클래스는 인터페이스보다 변하기 쉽다. 그러므로 인터페이스를 이용하라는 의존관계 역전의 원칙(DIP, Dependency Inversion Principle)을 생각하자. 인터페이스를 통해 구체 클래스를 은닉하도록 하자. 인터페이스에 대해서는 상속과 다형성에서도 언급할 것이다.

하나의 클래스가 전체적으로는 하나의 역할만을 맡고 있지만 관점에 따라서는 2개 이상의 인터페이스를 구현하고 있을 수 있다. 이런 경우에는 하나의 클래스가 여러 타입이 된다. 그리고 이러한 클래스를 이용하는 클라이언트는 자신이 필요로 하는 인터페이스를 사용하면 된다. 하나의 거대한 인터페이스보다는 작은 여러 개의 인터페이스가 좋다는 인터페이스 분리의 원칙(ISP, Interface Segregation Principle)을 생각하자.

  • 상속

우선 한 가지를 분명히 짚고 넘어가도록 하자. 상속은 그 자체가 OO의 핵심이라기보다는 다형성을 위한 것이다. 상속에는 구현 상속(extends)과 인터페이스 상속(implements)이 있다. 구현 상속은 재사용과 다형성 획득이라는 두 가지 기능이 있다.

인터페이스 상속은 다형성 획득과 인터페이스를 통한 은닉이라는 기능이 있다. 구현 상속에는 '깨지기 쉬운 기반 클래스 문제'가 있기 때문에 다형성 획득이라는 측면에서 보자면 인터페이스 상속이 안전하고 낫다. 재사용 측면에서 보자면 보통 상속 보다는 합성이 좋다. 상속은 불필요한 결합도를 증가시키기 때문이다.

그러므로 다형성을 위해서는 인터페이스 상속을 사용하고, 재사용을 위해서는 합성을 사용하자. 구현 상속은 여러 클래스가 공통의 연산 집합을 공유하고 있을 때 정규화를 위해 사용하면 된다. 물론 깨지기 쉬운 기반 클래스 문제가 있다는 것은 알고 사용해야 한다. 하지만 자바에서 모든 객체가 Object를 구현상속하고 있듯 적절히 사용하면 도움이 된다.

상속을 할 때는 상속한 메소드에서 예외를 던지면 안 된다. 예외를 던지면 다형성을 획득하기 어렵다. 상속이란 다형성을 위해 있는 것인데 상속이 다형성을 해치면 어떻게 하는가? LSP(Liskov Substitution Principle)를 지키자.

  • 다형성

캡슐화와 은닉은 '무엇을'과 '어떻게'를 분리시켜 준다. 상속은 '어떻게'를 다양하게 정의하도록 해준다. 다형성은 이 둘을 조합하여 런타임에 '무엇을' '어떻게' 실행시킬 것인지를 동적으로 정하게 된다. 이는 확장에는 열려있고 수정에는 닫혀있다는 OCP(Open Closed Principle)를 지킬 수 있도록 해준다.

예를 들어 Logging을 한다면 '어디에', '어떤 포맷으로' 저장할 지가 바뀌게 된다. 이 곳이 바로 다형성이 등장할 곳이다. '어디에' 저장할지는 '저장(무엇)'을 '어디에(어떻게)'할지를 동적으로 바꾸고 확장할 수 있도록 하는 것이다. '어떤 포맷으로'도 마찬가지이다. 이 부분을 적절히 인터페이스를 통해 추상화한 뒤 상속을 통해 구체 클래스를 구현하고 다형성을 이용하면 계약을 지키는 한 기존 프로그램은 '어떻게' 저장할지에 대한 수정에 닫혀있고, 새로운 저장 방식에 대한 확장에 열려있게 된다. 즉 OCP를 만족시키는 구조가 되는 것이다.

프로그래밍

class와 object에 대해 설명해보세요.

프로그래밍

스택과 큐의 차이점을 설명해보세요.

커뮤니티 Q&A

이론과 관련된 게시글이에요.

이해가 안 되거나 궁금한 점이 있다면 커뮤니티에 질문해 보세요!

게시글 작성하기