Search
🚟

5. 객체지향 프로그래밍

개요

좋은 아키텍처를 만드는 작업은 객체 지향 설계 원칙을 이해하고 응용하는 것에서 시작된다.
그렇다면 객체 지향이란 정확히 무엇인가?
어떤 이는 데이터와 함수의 조합이라고 말한다.
하지만 o.f()가 f(o)와 다르다는 의미를 내포하고 있어 설득력이 부족하다.
또 다른 이는 실제 세계를 모델링하는 새로운 방법이라고 한다.
하지만 실제 세계를 모델링한다는 것의 의미와 그 방향성을 추구해야 하는 이유가 불분명하다.
또 다른 이들은 캡슐화, 상속, 다형성을 중심으로 객체 지향을 설명한다.
이들은 객체 지향이 이 세 가지 개념의 적절한 조합이라고 주장한다.

캡슐화?

객체지향을 정의하는 핵심 요소 중 하나로, 객체 지향 언어는 데이터와 함수를 효과적으로 캡슐화하는 방법을 제공한다.
이를 통해 데이터와 함수가 응집력 있게 구성된 단위들을 명확하게 구분할 수 있다.
이러한 구분선 바깥에서 데이터는 은닉되며, 특정 함수만이 외부에 노출된다.
이런 개념들은 객체지향 언어에서 private, public과 같은 접근 제한자로 표현된다.
하지만 객체지향 언어는 캡슐화에 전적으로 의존하지는 않는다.
대신 프로그래머가 올바르게 행동하여 캡슐화된 데이터를 우회하지 않을 것이라는 신뢰를 기반으로 한다.

상속?

객체지향 언어는 캡슐화를 약화시켰으나, 상속 기능만큼은 확실하게 제공했다.
하지만 상속이란 본질적으로 변수와 함수를 하나의 유효 범위로 묶어 재정의하는 메커니즘에 불과하다.
이전의 프로그래밍 언어들도 상속과 유사한 기능을 제공했지만, 이는 진정한 상속이 아닌 모방에 가까웠다.
객체지향 언어는 이러한 상속 기능을 체계적이고 편리하게 제공한다.

다형성?

getchar() 함수는 STDIN에서 문자를 읽는다. 그렇다면 STDIN은 어떤 장치일까?
putchar() 함수는 STDOUT으로 문자를 쓴다. 그렇다면 STDOUT은 어떤 장치일까?
이러한 함수들은 다형적이다. 즉, STDIN과 STDOUT의 타입에 따라 그 행위가 달라진다.
함수를 가리키는 포인터를 응용한 것이 다형성이다.
객체지향 언어는 새로운 다형성을 제공하지는 않지만, 다형성을 더욱 안전하고 편리하게 사용할 수 있게 해준다.
다형성이 가진 힘 - 새로운 장치의 등장
새로운 장치의 입출력 드라이버가 5가지 표준 함수를 구현한다면 장치와 무관하게 사용할 수 있다.
즉, 입출력 드라이버가 프로그램의 플러그인이 된 것이다.
유닉스 운영체제가 입출력 장치들을 플러그인 형태로 만든 이유는 프로그램이 장치에 독립적이어야 하기 때문이다.
플러그인 아키텍처는 입출력 장치의 독립성을 지원하기 위해 만들어졌으며, 이후 대부분의 운영체제에서 구현되었다.
객체지향의 등장으로 플러그인 아키텍처를 어디서든 적용할 수 있게 되었다.
의존성 역전
위 함수에서 Main은 고수준 → 중수준 → 저수준 모듈을 차례로 호출한다. 즉, 제어 흐름은 소스 코드 의존성을 반드시 따른다.
하지만 다형성이 이 구조에 추가되면 특별한 일이 일어난다.
위 그림에서 HL1 모듈은 ML1 모듈의 F() 함수를 호출한다(점선). 소스 코드에서 HL1 모듈은 인터페이스를 통해 F() 함수를 호출한다(실선).
이 인터페이스는 런타임에는 존재하지 않으며, HL1은 단순히 ML1을 호출할 뿐이다.
하지만 여기서 ML1과 인터페이스 사이의 소스 코드 의존성이 제어 흐름과는 역전된다.
이러한 접근법을 사용하면 객체 지향 언어로 개발된 시스템의 소프트웨어 아키텍트는 시스템의 모든 소스 코드 의존성의 방향을 결정할 수 있다.
위 예시를 보면 UI와 Database는 Business Rules에 의존하도록 소스 코드 의존성이 부여되었다.
즉, UI와 Database가 Business Rules의 플러그인이 되어 각 컴포넌트가 독립적으로 동작하게 된다.
배포 독립성 - 컴포넌트의 소스 코드가 변경되면 해당 코드가 포함된 컴포넌트만 다시 배포하면 된다.
개발 독립성 - 배포 독립성이 있으면 서로 다른 팀에서 각 모듈을 독립적으로 개발할 수 있다.

결론

객체 지향이란 다형성을 통해 시스템의 모든 소스 코드 의존성을 완벽하게 제어할 수 있는 능력이다.
객체 지향 언어를 사용하면 아키텍트는 플러그인 아키텍처를 구성할 수 있으며, 이를 통해 고수준 정책을 담은 모듈이 중·저수준 모듈로부터 독립성을 확보할 수 있다.
저수준의 세부사항들은 플러그인 모듈로 분리할 수 있어, 고수준 정책 모듈과 독립적인 개발 및 배포가 가능하다.