반응형
그래서 객체지향이 뭔데?
- 사실 나도 여기에 명확하게 대답하기가 어렵다. 객체지향 프로그래밍이 뭐냐고 물어보면, 하나의 덩어리로 된 큰 프로그램을 여러 '객체'라는 단위로 나눠서 그 객체들 간 상호작용을 통해 프로그램이 돌아가도록 프로그래밍하는 방식이라고 대답할 수는 있겠는데.
- 해당 장에서는 객체지향 프로그래밍의 4요소 중 캡슐화, 상속, 다형성을 살펴보며 객체지향이 무엇인지에 대해 대답하고자 한다.
캡슐화?
- 객체지향을 정의하는 요소 중 하나로 캡슐화를 꼽는 이유는 데이터와 함수를 쉽고 효과적으로 캡슐화하는 방법을 객체지향 언어가 제공하기 때문이다. 이를 통해 데이터와 함수가 응집력 있게 구성된 집단을 서로 구분 짓는 선을 그을 수 있다.
- 캡슐화가 객체지향 언어에만 국한된 개념은 아니다. C언어에서도 헤더 파일과 구현 파일을 구분하는 형태로 캡슐화를 완벽하게 구현한다.
- 헤더 파일을 사용하는 구현 파일에서는 헤더 파일에 접근할 수 있는 방법이 없다.
- 그러나 C++에 이르러 컴파일러의 기술적 이유로 인해 클래스의 멤버 변수를 해당 클래스의 헤더 파일에 선언하게됐다.
- C++ 컴파일러는 클래스의 인스턴스 크기를 알 수 있어야 한다.
- 이러한 변화로 인해, 헤더 파일을 사용하는 측에서는 헤더 파일의 멤버 변수를 알게 되었다. 물론 헤더 파일의 멤버 변수에 직접 접근하는 것은 여전히 불가능하지만, 만일 멤버 변수의 이름이 바뀐다면 파일을 다시 컴파일 해야한다. 캡슐화가 깨진 것이다.
- 접근 제어자를 도입함으로써 캡슐화를 어느 정도 보완했지만, 컴파일러가 헤더 파일에서 멤버 변수를 볼 수 있어야 하기 때문에 임시방편에 불과하다.
- 자바와 C#에 이르러서는 헤더와 구현체를 분리하는 방식을 버리며 캡슐화는 더욱 심하게 훼손되었다. 클래스를 선언하는 것과 정의하는 것을 구분하는 것이 불가능해졌다.
- 헤더 파일을 작성하고 이를 구현하는 구현 클래스를 만드는 것이 아니라, 클래스를 정의함과 동시에 해당 클래스의 내용을 정의한다.
- 따라서 객체지향이 강력한 캡슐화에 의존한다는 정의는 썩 납득되지 않는다. 실제로 많은 객체지향 언어가 캡슐화를 거의 강제하지 않는다.
상속?
- 객체지향 언어가 상속만큼은 확실하게 제공했지만, 상속은 단순히 어떤 변수와 함수를 하나의 유효 범위로 묶어서 재정의하는 일에 불과하다.
- 사실 객체지향 이전에도 상속'처럼' 사용하는 방법은 있었다. 그러나, 상속만큼 편리하지 않았으며 다중 상속의 구현에 어려움이 있었다.
- 따라서 객체지향 언어가 상속을 통해 완전히 새로운 개념을 제시했다고는 하기 어렵지만, 상속을 통해 데이터 구조가 다른 형태처럼 보이게 하는 기법을 편리하게 사용할 수 있게 해주었다고 할 수 있다.
다형성?
- 다형성 역시 객체지향에서 새롭게 제시된 개념은 아니다. 그러나, 객체지향은 다형성을 위험부담 없이 사용할 수 있게 해주었다.
- 기존에는 함수 포인터를 직접 사용해 다형적 행위를 수행했다. 그러나 함수 포인터를 직접 사용하는 행위는 위험하다.
- 늘 함수 포인터를 초기화해야 한다. 그렇지 않으면, 포인터가 매번 같은 함수를 가르키기 때문에 다형적 행위를 수행할 수 없다.
- 포인터를 통해 모든 함수를 호출해야한다. 그렇지 않으면, 다형적 행위를 수행할 수 없다.
- 객체지향 언어는 함수 포인터의 직접적 사용을 없애 다형성을 사용함에 뒤따르는 실수의 위험을 제거했다. 이러한 이유로 앞서 3장에서 언급한 제어흐름을 간접적으로 전환하는 규칙을 부여한다는 것이다.
- 함수 포인터를 사용해 제어 흐름을 직접적으로 전환하는 것이 아니라, 후술할 의존성 역전의 개념을 사용해 제어흐름을 간접적으로 전환한다.
- 다형성이 가진 힘
- 프로그램은 장치 독립적이어야한다. 장치가 달라질 때마다 코드를 수정해야한다고 생각하면 끔찍하다.
- 이러한 장치 독립성을 지원하기 위해 만들어진 아키텍처가 '플러그인 아키텍처'다. 그러나 함수 포인터를 직접 사용하는 방법을 사용하면 플러그인 아키텍처의 개념을 적용할 수 없었다. 이유는 위에서 설명했다.
- 그러나 객체지향 언어와 다형성의 등장으로 언제 어디서든 플러그인 아키텍처를 적용할 수 있게 되었다.
- 의존성 역전
- 전형적인 호출 트리에서 소스 코드 의존성의 방향은 반드시 제어흐름을 따르게 된다. main에서 고수준 모듈의 함수를 호출하면, 그 함수가 차츰 저수준 모듈의 함수를 호출한다.
- 그러나 다형성의 개념을 이용하면 소스 코드 의존성의 방향과 제어흐름의 방향이 반대의 형태를 띠게 할 수 있다.
- 3티어 아키텍처(컨트롤러 - 서비스 - 레파지토리)를 예시로 생각해보자. 일반적으로 컨트롤러가 서비스를, 서비스가 레파지토리를 호출한다.
- 하위 클래스의 구현체를 그대로 상위 클래스에 결합시키면, 앞서 언급한대로 의존성 방향과 제어흐름의 방향성이 같다.
- 이와 같이 구체적인 모듈에 의존할 때 발생하는 의존성을 컴파일 의존성이라 한다. 컴파일 시점부터 어떤 객체에 의존하는지 알 수 있다.
- 그러나 예를 들어, 레파지토리의 인터페이스를 만들고 서비스가 해당 인터페이스에 의존한다고 하면, 소스 코드 의존성은 컨트롤러 -> 서비스 -> 레파지토리를 향하지만, 제어흐름은 레파지토리 -> 서비스 -> 컨트롤러가 된다.
- 컨트롤러가 서비스의 메서드를 수행할 때가 되어야 어떤 구현체 레파지토리를 사용할지 알 수 있기 때문이다.
- 이와 같이 추상적인 모듈에 의존할 때 발생하는 의존성을 런타임 의존성이라 한다. 런타임 시점이 되어서야 어떤 객체에 의존하는지 알 수 있다.
- 객체지향 언어가 다형성을 안전하고 편리하게 제공한다는 말은 즉 의존성 역전을 어디서든 사용할 수 있다는 말이다.
- 이렇게 의존성 역전을 사용하면, 각 컴포넌트를 서로에게 독립되게 컴파일하고 사용할 수 있다. 즉, 서로 간에 의존하지 않게 된다.
- 다시 말해, 각 컴포넌트는 이제 개별적으로 배포될 수 있다. 특정 컴포넌트의 소스 코드가 변경되면, 해당 코드가 포함된 컴포넌트만 다시 배포하면 된다. 이를 배포 독립성이라 한다.
- 마찬가지로, 각 모듈을 독립적으로 개발할 수 있다. 이를 개발 독립성이라 한다.
결론
- 객체지향이란 무엇인가?라는 질문에 대한 답변은 여러 의견이 있을 수 있으나, 아키텍트의 관점에서 보았을 때 객체지향은 다형성을 이용하여 전체 시스템의 모든 소스 코드 의존성에 대한 절대적인 제어 권한을 획득할 수 있는 능력이다.
- 객체지향을 사용하면 플러그인 아키텍처를 구성할 수 있고, 이를 통해 각 모듈을 독립적으로 구성하고 개발, 배포할 수 있다.
반응형
'Programming > Clean Architecture' 카테고리의 다른 글
7장. SRP: 단일 책임 원칙 (1) | 2023.11.13 |
---|---|
6장. 함수형 프로그래밍 (1) | 2023.11.09 |
4장. 구조적 프로그래밍 (0) | 2023.10.23 |
3장. 패러다임 개요 (0) | 2023.10.17 |
2장. 두 가지 가치에 대한 이야기 (2) | 2023.10.11 |
댓글