프로그래밍 노트/아키텍처

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

깡냉쓰 2023. 9. 4. 19:07
728x90
반응형

객체 지향 프로그래밍

  • 1966년 등장
  • 알골(ALGOL)언어의 함수 호출 스택 프레임(stack frame)을 힙(heap)으로 옮기면, 함수 호출이 반환된 이후에도 함수에서 선언된 지역 변수가 오랫동안 유지될 수 있음을 발견
  • 이러한 함수가 클래스의 생성자, 지역 변수는 인스턴스 변수, 중첩 함수는 메소드가 되었음

객체 지향(Object-Oriented)란 무엇일까?

데이터와 함수의 조합?

  • 그다지 만족스러운 대답은 아님, OOP 등장 이전에도 프로그래머는 데이터 구조를 함수에 전달해 왔다.

실제 세계를 모델링하는 새로운 방법?

  • "실제 세계를 모델링한다"라는 말은 무엇을 의미하는 걸까? 왜 우리는 그 방향을 추구해야하는가?
  • OO는 현실 세계와 의미적으로 가깝기 때문에 OO를 사용하면 소프트웨어를 좀 더 쉽게 이해할 수 있다.
  • 하지만 의도가 불분명하며 정의가 모호하다.

캡슐화, 상속, 다형성 제공?

  • 객체 지향 본질을 설명하기 위해 세 가지 특징 요소에 기대는 부류
  • 일단, 이해를 위해 밑에서 이 세가지 개념을 간략히 살펴보자

캡슐화(encapsulation)

  • 데이터와 함수를 쉽고 효과적으로 캡슐화하는 방법을 객체지향 언어가 제공해 준다.
  • 이를 통해서 데이터와 함수가 응집력있게 집단을 서로 구분할 수 있는 기준이 될 수 있다.
    • 기준 밖에서 데이터는 은닉되고, 일부 함수만이 외부에 노출 된다. (private / public)
  • 실제로 c언어에서 캡슐화를 더 완벽하게 구현할 수 있다. 객체 지향으로 넘어오면서 오히려 캡슐화가 약화됬다. 따라서 객체지향이 강력한 캡슐화에 의존한다는 정의는 받아들이기 힘듬

상속(inheritance)

  • 객체 지향 언어가 더 나은 캡슐화를 제공하지는 못했지만 상속만큼은 확실히 제공하였다.
  • 상속이란 단순히 어떤 변수와 함수를 하나의 유효 범위로 묶어서 재정의하는 일에 불과
    • 사실상 C 프로그래머도 언어의 도움 없이 순수 이러한 방식으로 상속 비슷하게 구현이 가능. but, 흉내내는 요령은 있지만 편리한 방식은 아님
      • NamedPoint인자를 Point 타입으로 강제로 변환해야함 (-> OOP에서는 업케스팅(upcasting)이 암묵적으로 이루어짐)
  • 객체 지향 언어가 완전히 새로운 개념을 만들지는 못했지만, 데이터 구조에 가면을 씌우는 일을 상당히 편리한 방식으로 제공했다고 볼 수는 있음

다형성(polymorphism)

  • 함수를 가리키는 포인터를 응용한 것이 다형성이며, 프로그래머는 다형적 행위를 수행하기 위해 함수를 가리키는 포인터를 예전부터 사용해 왔다.
  • 객체 지향이 새롭게 만든 것은 전혀 없지만 다형성을 좀 더 안전하고 더욱 편리하게 사용할 수 있게 해준다.
    • 함수 포인터를 직접 사용하여 다형적 행위를 만드는 것은 위험함 (프로그래머가 특정 관례를 수동으로 따르는 방식 -> 버그 발생 가능성 높음)
    • 객체 지향 언어는 이러한 관례를 없애주며, 따라서 실수할 위험이 없다. 객체 지향 언어를 사용하면 다형성은 대수롭지 않은 일이 된다.

다형성이 가진 힘

1950년대 후반에 프로그램이 장치 독립적(device independent)이어야 한다는 사실을 배웠다.
당시 장치에 의존적인(dependent)수많은 프로그램을 만들고 나서야, 이 프로그램이 다른 장치에서도 동일하게 동작할 수 있도록 만드는 것이 진정 바랐던 일임을 깨닫게 된 것이다.

  1. 천공카드 더미에서 입력을 읽어서 새로운 천공카드 더미로 출력을 생성하는 프로그램 작성
  2. 시간이 흘러 천공 카드 대신 자기 테이프(magnetic tape)를 사용하기 시작
  3. 기존 프로그램에서 상당히 많은 부분을 재작성해야 했음

천공카드냐 테이프냐에 관계없이 동일한 프로그램을 사용할 수 있다면 매우 유용할 것이다.
입출력 인터페이스를 천공카드/테이프가 구현하게 된다면 입력을 읽어서 출력을 생성하는 프로그램은 별도로 컴파일을 다시 할 필요없이 사용이 가능하다.
입출력 방식이 추가되면 해당 인터페이스를 구현만 하면 된다.
해당 인터페이스는 입력을 읽어서 출력을 생성하는 프로그램의 플러그인(plugin)이 된다.
객체지향의 등장으로 어디서든 플러그인 아키텍처를 적용할 수 있게 됬따.


의존성 역전(DI, Depedency Inversion)

다형성이 존재하기 전 소프트웨어는 어떤 모습이었을까
전형적인 호출 트리의 경우 main 함수가 고수준 함수를 호출하고, 고수준 함수는 다시 중간 수준 함수를 호출, 중간 수준 함수는 다시 저수준 함수를 호출한다. 이러한 호출 트리에서 소스 코드 의존성의 방향은 제어흐름(flow of controle)을 따르게 된다.

main 호출 트리

여기에 다형성을 추가하면 의존성의 방향을 제어흐름과 다르게 가져갈 수 있다.

의존성 역전

  • HL1 모듈은 ML1 모듈의 F()함수를 호출한다.
  • 소스코드에서는 HL1 모듈은 인터페이스를 통해 F()함수를 호출한다.
    • 이 인터페이스는 런타임에는 존재하지 않는다. HL1은 단순히 ML1모듈의 함수 F()를 호출할 뿐이다.

ML1과 I 인터페이스 사이의 소스 코드 의존성(상속 관계)이 제어흐름과는 반대인 점을 주목해야 한다.

  • 고수준 모듈을 구현하려면 저수준 모듈을 사용해야 하는데, 반대로 저수준 모듈이 고수준 모듈에 의존하는 상황이 됨
  • HL1은 ML1을 추상화한 I인터페이스에 의존할 뿐, ML1에 의존하지 않는다.

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

위의 호출 트리를 보면 수 많은 소스 코드 의존성이 존재하는데, 소스 코드 사이에 인터페이스를 추가함으로써 방향을 역전시킬 수 있다.
아키텍트는 시스템의 소스 코드 의존성 전부에 대해 방향을 결정할 수 있는 절대적 권한을 갖는다. 즉 소스 코드 의존성을 원하는 방향으로 설정할 수 있다.

이런 권한으로 어떤 것을 할 수 있는가?

  • 업무 규칙이 데이터베이스, UI에 의존하는 대신에, 시스템의 소스 코드 의존성을 반대로 배치하여 데이터베이스와 UI가 업무 규칙에 의존하게 만들 수 있다.
  • UI와 데이터베이스가 업무 규칙의 플러그인이 되게할 수 있다. 즉, 업무 규칙의 소스 코드에서 UI나 데이터베이스를 호출하지 않는다.
  • 업무 규칙, UI, 데이터베이스는 세 가지로 분리된 컴포넌트 또는 배포 가능한 단위(ex. jar)로 컴파일할 수 있고, 이 배포 단위들의 의존성 역시 소스 코드 사이의 의존성과 같다.
  • 따라서 업무 규칙은 UI, 데이터베이스와는 독립적으로 배포할 수 있다. 이들 컴포넌트는 서로 개별적이며 독립적 배포가 가능하다.
    • 배포 독립성(independent deployabilility)

결론

객체 지향이란 무엇인가? 이라는 질문에 소프트웨어 아키텍처 관점에서의 정답은 명확하다.

  • 객체지향이란 다형성을 이용하여 전체 시스템의 모든 소스 코드 의존성에 대한 절대적인 제어 권한을 획득할 수 있는 능력이다.
  • 아키텍트는 플러그인 아키텍처를 구성할 수 있고, 이를 통해 고수준의 정책을 포함하는 모듈은 저수준의 세부사항을 포함하는 모듈에 대해 독립성을 보장할 수 있다.
  • 저수준의 세부사항은 중요도가 낮은 플러그인 모듈로 만들 수 있고, 고수준의 정책을 포함하는 모듈과는 독립적으로 개발하고 배포할 수 있다.

Reference

  • 클린 아키텍처 : 소프트웨어 구조와 설계의 원칙
728x90
반응형