제이온

[오브젝트] 2장. 객체 지향 프로그래밍

  • 작성 언어: 한국어
  • 기준국가: 모든 국가country-flag
  • IT

작성: 2024-04-28

작성: 2024-04-28 13:46

영화 예매 시스템


요구 사항 살펴보기

  • 온라인 영화 예매 시스템을 구현하려고 한다.
  • 영화는 영화에 대한 기본 정보를 표현한다.
    • 제목, 상영 시간, 가격 정보 등
  • 상영은 실제로 관객이 영화를 관람하는 사건을 표현한다.
    • 상영 일자, 시간, 순번 등
  • 사람들은 영화를 예매한다고 하지만, 사실 특정 상영되는 영화를 예매한다고 해야 옳다.
  • 할인 조건
    • 가격의 할인 여부
    • 순서 조건: 상영 순번을 이용해 할인 여부를 결정
    • 기간 조건: 영화 상영 시작 시간을 이용해 할인 여부를 결정
  • 할인 정책
    • 할인 요금을 결정
    • 금액 할인 정책: 예매 요금에서 일정 금액을 할인
    • 비율 할인 정책: 정가에서 일정 비율의 요금을 할인
  • 영화 별로 하나의 할인 정책을 할당하거나 아예 안 할 수 있고, 할인 조건은 다수의 할인 조건을 섞을 수 있다.
  • 할인 정책이 적용되어 있지만 할인 조건을 만족하지 못하거나, 할인 정책이 적용되어 있지 않은 경우에는 요금을 할인하지 않는다.


객체 지향 프로그래밍을 향해

협력, 객체, 클래스

  • 객체 지향은 객체를 지향하는 것이다.
    • 클래스를 결정한 후에 클래스에 어떤 속성과 메서드가 필요한지 고민하지 말아야 한다.
    • 어떤 객체들이 어떤 상태와 행동을 가지는지 결정해야 한다.
    • 객체를 독립적인 존재가 아니라, 협력하는 공동체의 일원으로 봐야 한다.


도메인의 구조를 따르는 프로그램 구조

  • 도메인은 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야를 말한다.

<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fe7d22a03-4a24-40e9-8068-bd03b9fd816b%2FUntitled.png?table=block&id=3f1931fc-8ef9-470a-8189-3727146638f6&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/438;" width="2000" height="438"></span>

  • 일반적으로 클래스의 이름은 대응되는 도메인 개념의 이름과 동일하거나 적어도 유사하게 지어야 한다.
  • 클래스 사이의 관계도 최대한 도메인 개념 사이에 맺어진 관계와 유사하게 만들어서 프로그램의 구조를 이해하고 예상하기 쉽게 만들어야 한다.


클래스 구현하기

  • 클래스는 내부와 외부로 구분되며 훌륭한 클래스를 설계하기 위해서는 어떤 부분을 외부에 공개하고, 어떤 부분을 감출지 결정해야 한다.
    • 객체의 속성은 private로 막고, 내부 상태를 변경하기 위해 필요한 메서드를 public으로 열어 둔다.
  • 클래스의 내부와 외부룰 구분해야 객체의 자율성을 보장함으로써 프로그래머에게 구현의 자유를 제공한다.


자율적인 객체

  • 객체는 상태와 행동을 가진 자율적인 객체여야 한다.
  • 데이터와 기능을 객체 내부로 함께 묶는 것을 캡슐화라고 한다.
  • 접근 제어를 통해 외부의 간섭을 줄여야 객체는 스스로 자기의 행동을 결정할 수 있게 된다.
  • 인터페이스와 구현의 분리 원칙은 객체 지향 프로그래밍을 위해 따라야 하는 주요 원칙이다.
    • 퍼블릭 인터페이스: 외부에서 접근 가능한 부분
    • 구현: 내부에서만 접근 가능한 부분


프로그래머의 자유

  • 프로그래머의 역할은 클래스 작성자와 클라이언트 프로그래머로 구분된다.
    • 클래스 작성자는 데이터 타입을 새로 추가
    • 클라이언트 프로그래머는 클래스 작성자가 추가한 데이터 타입을 사용
  • 구현 은닉
    • 클래스 작성자는 필요한 부분만 클라이언트 프로그래머에게 제공하여 내부 구현을 감출 수 있다.
    • 클라이언트 프로그래머는 인터페이스만 알면 되므로 지식의 양을 줄일 수 있다.


협력하는 객체들의 공동체

  • 돈을 표현할 때 단순히 Long 타입의 변수를 선언하기 보다는, Money와 같이 객체로 포장해 주는 것이 좋다. 객체를 사용하면 의미 전달이 보다 잘 되고, 중복된 연산 처리를 한 곳에서 수행할 수 있기 때문이다.
  • 시스템의 어떤 기능을 구현하기 위해 객체들 사이에 이뤄지는 상호 작용을 협력이라고 부른다.


협력에 관한 짧은 이야기

  • 객체가 다른 객체와 상호 작용할 수 있는 유일한 방법은 메시지를 전송하거나 수신하는 것이다.
  • 수신된 메시지를 처리하기 위한 자신만의 방법을 메서드라고 부른다.
  • 메시지와 메서드를 구분하는 것은 중요하며, 여기서부터 다형성의 개념이 출발한다.


할인 요금 구하기

할인 요금 계산을 위한 협력 시작하기

  • Movie 클래스에는 할인 정책에 관한 자세한 로직이 들어 있지 않고, DiscountPolicy 인터페이스에 위임하였다. 상속과 다형성, 추상화를 이용하는 것이 정말 중요하다.


할인 정책과 할인 조건

<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F7b9b27a5-0dac-4ba7-9552-05b7f8fbdecd%2FUntitled.png?table=block&id=3264f189-6e12-4a55-94d2-75b851320c7a&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/535;" width="2000" height="535"></span>


상속과 다형성

컴파일 시간 의존성과 실행 시간 의존성

  • 코드의 의존성과 실행 시점의 의존성이 다를 수 있다.
  • 둘 간의 의존성이 다를수록 코드를 이해하기 어려워지지만, 유연하고 확장 가능한 코드가 된다.


차이에 의한 프로그래밍

  • 상속은 기존 클래스를 기반으로 새로운 클래스를 쉽고 빠르게 추가할 수 있으며, 부모 클래스의 구현을 재사용할 수 있다.
  • 부모 클래스와 다른 부분만을 추가해서 새로운 클래스를 만드는 방법을 차이에 의한 프로그래밍이라고 부른다.


상속과 인터페이스

  • 상속은 부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려 받게 해 준다.
  • 인터페이스는 객체가 이해할 수 있는 메시지의 목록을 정의한다.
  • Movie는 DiscountPolicy에게 calculateDiscountAmount 메시지를 보내고 있다. Movie 입장에서는 어떤 클래스의 인스턴스가 응답을 주는 지는 상관 없고, 응답을 성공적으로 주기만 하면 된다.
  • 따라서 AmountDiscountPolicy와 PercentDiscountPolicy 모두 DiscountPolicy를 대신해서 Movie와 협력할 수 있다.
  • 이처럼 자식 클래스가 부모 클래스를 대신하는 것을 업캐스팅이라고 부른다. 자식 클래스가 위에 위치한 부모 클래스로 자동적으로 타입 캐스팅되는 것처럼 보이기 때문이다.


다형성

  • 다형성은 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력을 뜻한다.
    • Movie는 동일한 메시지를 전송하지만, 실제로 어떤 메서드가 실행될 것인지는 메시지를 수신하는 객체의 클래스가 무엇이냐에 따라 달라진다.
  • 다형성은 프로그램의 컴파일 시간 의존성과 실행 시간 의존성이 다를 수 있다는 사실에 기반한다.
  • 다형성은 실행될 메서드를 실행 시점에 결정하므로 지연 바인딩 또는 동적 바인딩이라고 부른다.


인터페이스와 다형성

  • 구현은 공유할 필요가 없고 순수하게 인터페이스만 공유하고 싶다면 추상 클래스가 아닌 인터페이스를 사용하면 된다.


추상화와 유연성

추상화의 힘

  • 추상화의 장점
    • 추상화의 계층만 따로 떼어 놓고 살펴보면 요구 사항의 정책을 높은 수준에서 서술할 수 있다.
    • 설계가 좀 더 유연해진다.


유연한 설계

  • 추상화는 설계가 구체적인 상황에 결합되는 것을 방지하기 때문에 유연한 설계를 만들 수 있다.


추상 클래스와 인터페이스 트레이드 오프


  • 현재 NoneDiscountPolicy는 사실 없어도 DiscountPolicy의 calculateDiscountAmount() 메서드에서 할인 조건이 없으면 0을 반환하고 있어서 문제가 없다. 그러나 사용자 입장에서 Movie의 인자로 할인 정책이 없는 None 정책을 넣어 주어야 해서 추가하였다.
  • 따라서 개념적으로 NoneDiscountPolicy 클래스가 혼란스러우므로 아래와 같이 수정하면 이해하기 좋다.



  • 위와 같이 DiscountPolicy를 인터페이스로 추상화하고, 그것의 구현체인 NoneDiscountPolicy와 일반적인 할인 정책을 뜻하는 DefaultDiscountPolicy로 나누는 것이다.

<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F712983a9-3937-4265-82fe-66a144c44a0f%2FUntitled.png?table=block&id=f5ca651c-d03c-4a6c-97e9-7443ee5649a0&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/886;" width="2000" height="886"></span>

  • NoneDiscountPolicy만을 위해 인터페이스를 추가하는 것이 증가한 복잡도에 비해 효율이 떨어진다고 생각할 수도 있다.
  • 구현과 관련된 모든 것들이 트레이드 오프의 대상이 될 수 있음을 인지하고 설계해야 한다. 즉, 작성하는 모든 코드에는 합당한 이유가 있어야 한다.


코드 재사용

  • 상속은 코드를 재사용하기 위해 사용할 수 있다.
  • 하지만 코드 재사용을 위해서는 상속보다는 합성을 사용하는 것이 좋다.
  • 현재 코드에서 Movie가 DiscountPolicy의 코드를 재사용하는 방법이 바로 합성이다.
  • 만약 Movie를 상위 클래스로 두고, AmountDiscountMovie와 PercentDiscountMovie로 나눈다면 상속을 사용한 것이라 할 수 있다.


상속

  • 상속의 단점
    • 캡슐화를 위반한다.
      • 부모 클래스의 내부 구조를 잘 알고 있어야 한다.
      • 부모 클래스를 변경할 때 자식 클래스도 함께 변경될 확률이 높다.
    • 설계를 유연하지 못하게 만든다.
      • 부모 클래스와 자식 클래스의 관계를 컴파일 시점에 결정한다.
      • 실행 시점에서 객체의 종류를 변경하는 것이 불가능하다.
      • 하지만 합성은 사용하는 객체 쪽에서 chageDiscountPolicy() 와 같은 메서드를 통해 실행 시점에서 인스턴스를 교체할 수 있다.


합성

  • 인터페이스에 정의된 메시지를 통해서만 코드를 재사용하는 방법을 합성이라고 한다.
  • 캡슐화를 구현할 수 있고, 느슨한 결합을 유지할 수 있다.
  • 다형성을 위해 인터페이스를 재사용하는 경우에는 상속과 합성을 조합해서 사용해야 한다.
    • 가령, DiscountPolicy 인터페이스는 하위 구현체를 구현하기 위해 상속을 사용할 수 밖에 없다.


출처

  • 오브젝트

댓글0