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

KIM KYUBIN·2022년 9월 13일
0

OOP

목록 보기
6/7

오브젝트 : 코드로 이해하는 객체지향 설계 - 조영호 저

1. 영화 예매 시스템

1.1 요구사항 살펴보기

  • 사용자는 영화 예매 시스템을 이용해 쉽고 빠르게 보고 싶은 영화를 예매할 수 있다.

  • 단어 정의

    • 영화 : 영화에 대한 기본 정보
    • 상영 : 실제로 관객들이 영화를 관람하는 사건
  • 사용자가 실제로 예매하는 대상은 영화가 아니라 상영이다.

  • 특정한 조건을 만족하는 예매자는 요금을 할인받을 수 있다.

  • 할인액을 결정하는 두 가지 규칙

    • 할인 조건 : 가격의 할인 여부 결정

      • 순서 조건 : 상영 순번을 이용해 할인 여부를 결정하는 규칙
      • 기간 조건 : 영화 상영 시작 시간을 이용해 할인 여부를 결정
    • 할인 정책

      • 금액 할인 정책 : 예매 요금에서 일정 금액을 할인해주는 방식
      • 비율 할인 정책 : 정가에서 일정 비율의 요금을 할인해 주는 방식
  • 영화별로 하나의 할인 정책만 할당할 수 있다.

    • 이와 달리 할인 조건은 다수의 할인 조건을 함께 지정할 수 있다.
  • 할인을 적용하기 위해서는 할인 조건과 할인 정책을 함께 조합해서 사용한다.

    • 먼저 사용자의 예매 정보가 할인 조건 중 하나라도 만족하는지 검사한다.

      • 할인 조건을 만족할 경우 할인 정책을 이용해 할인 요금을 계산한다.
      • 할인 정책은 적용돼 있지만 할인 조건을 만족하지 못하는 경우나 아예 할인 정책이 적용돼 있지 않은 경우에는 요금을 할인하지 않는다.

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

2.1 협력, 객체, 클래스

  • 어떤 클래스가 필요한지를 고민하기 전에 어떤 객체들이 필요한지 고민하라.
    • 클래스의 윤곽을 잡기 위해서는 어떤 객체들이 어떤 상태와 행동을 가지는지를 먼저 결정해야 한다.
  • 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 봐야 한다.
    • 객체들의 모양과 윤곽이 잡히면 공통된 특성과 상태를 가진 객체들을 타입으로 분류하고 이 타입을 기반으로 클래스를 구현하라.

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

  • 도메인(domain) : 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야

  • 객체지향 패러다임이 강력한 이유는 요구사항을 분석하는 초기 단계부터 프로그램을 구현하는 마지막 단계까지 객체라는 동일한 추상화 기법을 사용할 수 있기 때문이다.

  • 일반적으로 클래스의 이름은 대응되는 도메인 개념의 이름과 동일하거나 적어도 유사하게 지어야 한다.

    • 클래스 사이의 관계도 최대한 도메인 개념 사이에 맺어진 관계와 유사하게 만들어서 프로그램의 구조를 이해하고 예상하기 쉽게 만들어야 한다.

2.3 클래스 구현하기

  • 인스턴스 변수의 가시성은 private이고 메서드의 가시성은 public
    • 외부에서는 객체의 속성에 접근할 수 없도록 막고 적절한 public 메서드를 통해서만 내부 상태를 변경할 수 있게 해야 한다.
  • 클래스의 내부와 외부를 구분해야 하는 이유는 무엇일까?
    • 경계의 명확성이 객체의 자율성을 보장하기 때문이다.
    • 프로그래머에게 구현의 자유를 제공하기 때문이다.

2.3.1 자율적인 객체

  • 객체가 상태(state)행동(behavior)을 함께 가지는 복합적인 존재라는 것이다.

  • 객체가 스스로 판단하고 행동하는 자율적인 존재라는 것이다.

  • 캡슐화 : 데이터와 기능을 객체 내부로 함께 묶는 것

  • 대부분의 객체지향 프로그래밍 언어들은 외부에서 접근을 통제할 수 있는 접근 제어(access control) 메커니즘을 함께 제공한다.

    • 접근 제어를 위해 접근 수정자(access modifier)를 제공한다.
  • 캡슐화와 접근 제어는 객체를 두 부분으로 나눈다.

    • 퍼블릭 인터페이스(public interface) : 외부에서 접근 가능한 부분
    • 구현(implememtation) : 외부에서는 접근 불가능하고 오직 내부에서만 접근 가능한 부분
  • 인터페이스와 구현의 분리(separation of interface and implementation) 원칙은 훌륭한 객체지향 프로그램을 만들기 위해 따라야 하는 핵심 원칙이다.

2.3.2 프로그래머의 자유

  • 프로그래머의 역할을 클래스 작성자(class creator)클라이언트 프로그래머(client programmer)로 구분하는 것이 유용하다.

    • 클래스 작성자는 새로운 데이터 타입을 프로그램에 추가하고, 클라이언트 프로그래머는 클래스 작성자가 추가한 데이터 타입을 사용한다.
  • 클라이언트 프로그래머의 목표는 필요한 클래스들을 엮어서 애플리케이션을 빠르고 안정적으로 구축하는 것이다.

    • 클래스 작성자는 클라이언트 프로그래머에게 필요한 부분만 공개하고 나머지는 꽁꽁 숨겨야 한다.
      => 구현 은닉(implememtation hiding)
  • 클라이언트 프로그래머는 내부의 구현은 무시한 채 인터페이스만 알고 있어서 클래스를 사용할 수 있기 때문에 머릿속에 담아둬야 하는 지식의 양을 줄일 수 있다.

  • 클래스 작성자는 인터페이스를 바꾸지 않는 한 외부에 미치는 영향을 걱정하지 않고도 내부 구현을 마음대로 변경할 수 있다.

  • 클래스를 개발할 때마다 인터페이스와 구현을 깔끔하게 분리하기 위해 노력해야 한다.

  • 설계가 필요한 이유는 변경을 관리하기 위해서라는 것을 기억하라.

2.4 협력하는 객체들의 공동체

  • 의미를 좀 더 명시적이고 분명하게 표현할 수 있다면 객체를 사용해서 해당 개념을 구현하라.

    • 그 개념이 비록 하나의 인스턴스 변수만 포함하더라도 개념을 명시적으로 표현하는 것은 전체적인 설계의 명확성과 유연성을 높이는 첫걸음이다.
  • 협력(Collaboration) : 시스템의 어떤 기능을 구현하기 위해 객체들 사이에 이뤄지는 상호작용

  • 객체지향 프로그램을 작성할 때는 먼저 협력의 관점에서 어떤 객체가 필요한지를 결정하고, 객체들의 공통 상태와 행위를 구현하기 위해 클래스를 작성한다.

2.5 협력에 관한 짧은 이야기

  • 객체는 다른 객체의 인터페이스에 공개된 행동을 수행하도록 요청(request)할 수 있다.

  • 요청을 받은 객체는 자율적인 방법에 따라 요청을 처리한 후 응답(response)한다.

  • 객체가 다른 객체와 상호작용할 수 있는 유일한 방법은 메시지를 전송(send a message)하는 것뿐이다.

  • 다른 객체에게 요청이 도착할 때 해당 객체가 메시지를 수신(receive a message)했다고 이야기한다.

  • 메서드(method) : 수신된 메시지를 처리하기 위한 자신만의 방법


3. 할인 요금 구하기

3.2 할인 정책과 할인 조건

  • TEMPLATE METHOD 패턴 : 부모 클래스에 기본적인 알고리즘의 흐름을 구현하고 중간에 필요한 처리를 자식 클래스에게 위임하는 디자인 패턴

  • 오버라이딩과 오버로딩

    • 오버라이딩(overriding) : 부모 클래스에 정의된 같은 이름, 같은 파라미터 목록을 가진 메서드를 자식 클래스에서 재정의하는 경우

      • 자식 클래스의 메서드는 오버라이딩한 부모 클래스의 메서드를 가리기 때문에 외부에서는 부모 클래스의 메서드가 보이지 않는다.
    • 오버로딩(overloading) : 이름은 같지만 제공되는 파라미터의 목록이 다르다.

      • 오버로딩한 메서드는 원래의 메서드를 가리지 않기 때문에 이 메서드들은 사이 좋게 공존한다.

3.3 할인 정책 구성하기

  • 생성자의 파라미터 목록을 이용해 초기화에 필요한 정보를 전달하도록 강제하면 올바른 상태를 가진 객체의 생성을 보장할 수 있다.

4. 상속과 다형성

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

  • 코드의 의존성과 실행 시점의 의존성이 서로 다를 수 있다.
    • 다르면 다를수록 코드를 이해하기 어려워진다.
    • 반면, 다르면 다를수록 코드는 더 유연해지고 확장 가능해진다.

4.2 차이에 의한 프로그래밍

  • 상속을 이용하면 클래스 사이에 관계를 설정하는 것만으로 기존 클래스가 가지고 있는 모든 속성과 행동을 새로운 클래스에 포함시킬 수 있다.

  • 차이에 의한 프로그래밍(programming by difference) : 부모 클래스와 다른 부분만을 추가해서 새로운 클래스를 쉽고 빠르게 만드는 방법

4.3 상속과 인터페이스

  • 상속이 가치 있는 이유는 부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려받을 수 있기 때문이다.

  • 인터페이스는 객체가 이해할 수 있는 메시지의 목록을 정의한다는 것을 기억하라.

  • 결과적으로 자식 클래스는 부모 클래스가 수신할 수 있는 모든 메시지를 수신할 수 있기 때문에 외부 객체는 자식 클래스를 부모 클래스와 동일한 타입으로 간주할 수 있다.

  • 업캐스팅(upcasting) : 자식 클래스가 부모 클래스를 대신하는 것

4.4 다형성

  • 다형성 : 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력

    • 다형적인 협력에 참여하는 객체들은 모두 인터페이스가 동일해야 한다.
  • 지연 바인딩(lazy binding) 또는 동적 바인딩(dynamic binding) : 메시지와 메서드를 실행 시점에 바인딩한다.

4.5 인터페이스와 다형성

  • 자바의 인터페이스는 말 그대로 구현에 대한 고려 없이 다형적인 협력에 참여하는 클래스들이 공유 가능한 외부 인터페이스를 정의한 것이다.

5. 추상화와 유연성

5.1 추상화의 힘

  • 추상화를 사용할 경우 두 가지 장점
    • 추상화의 계층만 따로 떼어 놓고 살펴보면 요구사항의 정책을 높은 수준에서 서술할 수 있다.

      • 세부적인 내용을 무시한 채 상위 정책을 쉽고 간단하게 표현할 수 있다.
      • 추상화를 이용해 상위 정책을 기술한다는 것은 기본적인 애플리케이션의 협력 흐름을 기술한다는 것을 의미한다.
    • 설계가 좀 더 유연해진다.

      • 추상화를 이용해 상위 정책을 표현하면 기존 구조를 수정하지 않고도 새로운 기능을 쉽게 추가하고 확장할 수 있다.

5.2 유연한 설계

  • 책임의 위치를 결정하기 위해 조건문을 사용하는 것은 협력의 설계 측면에서 대부분의 경우 좋지 않은 선택이다.

  • 추상화가 유연한 설계를 가능하게 하는 이유는 설계가 구체적인 상황에 결합되는 것을 방지하지 때문이다.

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

  • 구현과 관련된 모든 것들이 트레이드오프의 대상이 될 수 있다.

  • 작성하는 모든 코드에는 합당한 이유가 있어야 한다.

5.4 코드 재사용

  • 합성(composition) : 다른 객체의 인스턴스를 자신의 인스턴스 변수로 포함해서 재사용하는 방법

5.5 상속

  • 상속의 가장 큰 문제점은 캡슐화를 위반한다는 것이다.
    • 부모 클래스의 구현이 자식 클래스에게 노출되기 때문에 캡슐화가 약화된다.

      • 캡슐화의 약화는 자식 클래스가 부모 클래스에 강하게 결합되도록 만들기 때문에 부모 클래스를 변경할 때 자식 클래스도 함께 변경될 확률을 높인다.
  • 상속의 두 번째 단점은 설계가 유연하지 않다는 것이다.
    • 상속은 부모 클래스와 자식 클래스 사이의 관계를 컴파일 시점에 결정된다.

      • 따라서 실행 시점에 객체의 종류를 변경하는 것이 불가능하다.

5.6 합성

  • 합성은 상속이 가지는 두 가지 문제점을 모두 해결한다.

    • 인터페이스에 정의된 메시지를 통해서만 재사용이 가능하기 때문에 구현을 효과적으로 캡슐화할 수 있다.
    • 의존하는 인스턴스를 교체하는 것이 비교적 쉽기 때문에 설계를 유연하게 만든다.
  • 대부분의 설계에서는 상속과 합성을 함께 사용해야 한다.

profile
상상을 현실로 만들기 위해 노력하는 개발자

0개의 댓글