[클린 아키텍처] 5. 객체 지향 프로그래밍

햄도·2021년 7월 11일
0

Clean Architecture

목록 보기
5/11

출처

클린 아키텍처를 읽으며 정리한 내용입니다.

5. 객체 지향 프로그래밍

좋은 아키텍처를 만드는 일은 객체 지향 설계 원칙을 이해하고 응용하는 데에서 출발한다.

그럼 대체 객체 지향이란 무엇인가?

객체 지향이 데이터와 함수의 조합이라기엔, 객체지향이 발견되기 훨씬 이전부터 데이터 구조는 함수에 전달되었다.
실제 세계를 모델링하는 새로운 방법이라는 답변은 얼버무리는 수준의 답변이다.
캡슐화, 상속, 다형성의 조합이라는 답변도 있다. 과연 그럴까?

캡슐화?

객체지향 언어는 데이터와 함수를 쉽고 효과적으로 캡슐화하는 방법을 제공한다.
이를 통해 데이터와 함수가 응집력 있게 구성된 집단을 서로 구분지을 수 있다.
이 집단에서 데이터는 은닉되며, 일부 함수들만이 외부에 노출된다. (private 멤버 데이터와 public 멤버 함수)

하지만 이런 개념이 객체지향에만 국한된 것은 아니고, C언어에서도 캡슐화는 가능하다.
오히려 C언어에서는 데이터 구조와 함수의 선언과 구현이 완전히 다른 파일로 분리되어있어 완전한 캡슐화가 가능하다.

이후에 C++라는 형태로 객체지향이 등장했고, 이로 인해 C의 완전한 캡슐화가 깨지게 되었다.
C++ 컴파일러는 클래스의 인스턴스 크기를 알 수 있어야 하기 때문에, 클래스의 멤버 변수를 해당 클래스의 헤더 파일에 선언해야 했다.

자바와 C#은 헤더와 구현체를 분리하는 방식을 모두 버렸고, 이로 인해 캡슐화는 더 심하게 훼손되었다. 객체지향을 제공한다고 주장한 언어들이 실제로는 C의 완벽한 캡슐화를 약화시킨 셈이다.

따라서 객체지향은 캡슐화에 의존하지 않는다고 볼 수 있고, 많은 객체지향 언어가 캡슐화를 거의 강제하지 않는다.

상속?

상속만큼은 객체지향 언어가 확실히 제공하지만, 상속은 단순히 변수와 함수를 묶어 재정의하는 일에 불과하다.

객체지향 이전에도 C 프로그래머는 두 struct의 변수를 동일하게 선언해 손수 상속을 구현할 수 있었다.
눈속임처럼 보이지만, 이런 기법은 객체지향 이전부터 프로그래머가 흔히 사용하던 기법이었고 C++에서도 이 방법을 사용해서 단일 상속을 구현했다.

하지만 이러한 방법이 상속만큼 편리한 방식은 아니다. 따라서 객체지향이 데이터 구조에 가면을 씌우는 일을 편리한 방식으로 제공했다고 볼 수는 있다.
객체지향 기여 점수를 준다면 0.5점 정도라고 할 수 있겠다.

다형성?

함수를 가리키는 포인터를 응용한 것이 다형성이기 때문에, 객체지향 이전에도 C 등의 언어로 다형성을 표현할 수는 있었다.

함수 포인터는 폰 노이만 아키텍처가 구현된 이후 계속해서 사용되어왔기 때문에, 객체지향이 다형성을 새롭게 만든 것은 아니다. 하지만 객체지향은 다형성을 더 안전하고 편리하게 사용할 수 있게 해준다.

무엇보다 함수에 대한 포인터를 직접 사용하여 다형적 행위를 만드는 방식은 위험하다. 이 포인터를 초기화하고, 호출하는 모든 관례를 기억하고 지켜야 하기 때문이다.

객체지향은 이런 관례를 없애 실수할 위험이 없다. 객체지향 언어를 사용하면 다형성은 대수롭지 않은 일이 된다.
따라서 객체지향은 제어흐름을 간접적으로 전환하는 규칙을 부과한다고 결론지을 수 있다.

다형성이 가진 힘

그렇다면 다형성이 왜 좋을까?

본문의 복사 프로그램 예시에서, 새로운 입출력 장치가 생긴다고 가정해보자.

다형성을 이용하면 복사 프로그램의 소스 코드가 입출력 드라이버의 소스 코드에 의존하지 않기 때문에, 새로운 장비에서도 복사 프로그램이 동작하도록 만들기 위해 아무 변경도 필요하지 않고, 복사 프로그램을 컴파일할 필요조차도 없다.

입출력 드라이버가 FILE에 정의된 다섯 가지 표준 함수만 구현하면, 복사 프로그램에서는 이 입출력 드라이버를 사용할 수 있다. → 복사 프로그램의 플러그인처럼 됨

이러한 플러그인 아키텍처는 입출력 장치 독립성을 지원하기 위해 만들어졌고, 등장 이후 거의 모든 운영체제에서 구현되었다.

하지만 객체지향 전까지 프로그래머들은 이러한 개념을 직접 작성하는 프로그램에서 적용하지 않았는데, 함수 포인터가 위험하기 때문이었다. 객체지향의 등장으로 어디서든 플러그인 아키텍처를 적용할 수 있게 되었다.

의존성 역전

다형성을 안전하고 편리하게 적용할 수 있는 매커니즘(객체지향)이 등장하기 전 소프트웨어는 고수준 함수가 저수준 함수를 호출하는 구조였다.

이러한 호출 트리에서 소스 코드 의존성의 방향은 반드시 제어흐름을 따르게 되며, 이렇게 상위의 함수가 하위의 함수를 호출하기 위해서는 하위 함수가 포함된 모듈의 이름을 지정해야 한다.

이러한 구조에서는 제어흐름이 시스템의 행위에 따라 결정되었고, 상위의 함수는 하위의 함수에 의존할 수밖에 없었다. 하지만 다형성을 이용하면, 이 의존성을 제어흐름과 반대로 역전시킬 수 있다.

상위 모듈 HL1에서 하위 모듈 ML1의 F() 함수를 호출한다고 하면, ML1은 인터페이스 I를 구현하도록 하고 HL1이 ML1 대신 I에 의존하도록 할 수 있다.

이 인터페이스는 런타임에는 존재하지 않고, HL1은 단순히 ML1 모듈의 F()를 호출할 뿐이지만 ML1과 I 인터페이스 사이의 의존성은 제어흐름과 반대가 된다. → 의존성 역전

객체지향 언어가 다형성을 안전하고 편리하게 제공한다는 사실은 소스 코드 의존성을 어디서든 역전시킬 수 있다는 뜻이기도 하다.

이러한 접근법을 사용해 소프트웨어 아키텍트는 모든 소스 코드 의존성의 방향을 결정할 수 있는 권한을 갖는다. 이것이 객체지향의 힘이자 지향점이다.

이 힘을 이용해, 업무 규칙이 데이터베이스와 UI에 의존하는 대신, 데이터베이스와 UI가 업무 규칙에 의존하게 만들 수 있다. 즉, UI와 데이터베이스가 업무 규칙의 플러그인이 되며, 결과적으로 업무 규칙, UI, 데이터베이스는 각 컴포넌트로 분리되어 독립적으로 배포할 수 있다.

이렇게 의존성을 역전시키고, 컴포넌트를 독립적으로 분리하면 소스 코드가 변경된 컴포넌트만 독립적으로 배포할 수 있으며(배포 독립성) 배포 독립성이 보장되면 서로 다른 팀에서 각 모듈을 독립적으로 개발할 수 있게 된다.(개발 독립성)

결론

소프트웨어 아키텍트 관점에서, 객체지향이란 다형성을 이용하여 전체 시스템의 모든 소스 코드 의존성에 대한 절대적인 제어 권한을 획득할 수 있는 능력이다.

이를 통해 고수준의 정책을 포함하는 모듈은 저수준의 세부사항을 포함하는 모듈에 대해 독립성을 보장할 수 있고, 저수준의 세부사항은 중요도가 낮은 플러그인 모듈로 만들어 독립적으로 개발/배포할 수 있게 된다.

profile
developer hamdoe

0개의 댓글