[오브젝트] 2장

LaStella·2023년 2월 11일
0
post-thumbnail

Chapter2. 객체지향 프로그래밍

📌영화 예매 시스템

이번 장에서 영화 예매 시스템을 예제로 설명한다.
영화 예매 시스템에서 실제로 사용자가 예매하는 대상은 영화가 아닌 상영이다.
예매자의 요금 할인은할인 조건할인 정책에 따라 할인액이 결정된다.
할인 조건은 상영 순번을 이용해 결정하는 순서 조건과 상영 시작 시간에 따라 결정하는 기간 조건으로 나눌 수 있다.
할인 정책은 일정 금액을 할인하는 금액 할인 정책과 일정 비율의 요금을 할인하는 비율 할인 정책으로 나눌 수 있으며, 한 영화에 하나의 할인 정책만 할당할 수 있다.

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

객체지향 프로그램을 작설할 때 어떤 클래스가 필요한가?에 대한 고민이 아니라 어떤 객체가 필요한가?에 초점을 맞추어야 한다.
1. 어떤 객체들이 어떤 상태와 행동을 가지는지 결정해야한다. (클래스는 공통적인 상태와 행동을 공유하는 객체들을 추상화한 것)
2. 객체는 다른 객체에 도움을 주거나 의존하며 살아가는 협력적인 존재다.

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

도메인이란 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야를 말한다.
영화 예매 도메인에서 영화는 여러 번 상영될 수 있으며 상영은 여러 번 예매될 수 있다.
영화할인 정책을 1개만 할당하거나 할당하지 않을 수 있다.
할인 정책에는 1개 이상의 할인 조건이 존재한다.

✔️클래스 구현하기

클래스를 구현하거나 다른 개발자의 개발된 클래스를 사용할 때 가장 중요한 것은 클래스의 경계를 구분 짓는것이다. 어떤 부분을 외부에 공개하고 어떤 부분을 감출지를 결정해야한다. private를 사용하여 객체의 속성에 대한 접근을 차단하고 public 메소드를 통해서 내부 상태를 변경할 수 있게 해야한다. 클래스의 내부와 외부를 구분하는 경계의 명확성은 객체의 자율성을 보장한다.

✔️자율적인 객체

객체는 상태행동을 가지는 존재이며 스스로 판단하고 행동하는 자율적인 존재다.
객체라는 단위 안에 데이터(상태)와 기능(행동)을 한 덩어리로 묶는 것을 캡슐화라고 부른다.
public, protedcted, private와 같은 접근 수정자를 통해 외부에서 객체의 접근 제어를 한다.

  • public
    • 어디서든(클래스 내부, 같은 패키지, 다른 패키지) 접근 가능
    • 클래스, 필드 메소드 선언에서 사용 가능
  • protected
    • 같은 패키지 안에서만 호출 가능
    • 해당 클래스의 자식 클래스이면 호출 가능 (다른 패키지에 있어도 자식 클래스는 가능)
    • 필드, 메소드, 생성자 선언에 사용 (클래스 선언에서는 사용하지 않음)
  • default
    • 접근 제한 설정을 하지 않은 기본 상태
    • 같은 패키지 안에서 호출 가능
    • 선언시 작성하는 키워드가 아님
  • private
    • 무조건 클래스 내부에서만 호출 가능
    • 필드, 메소드, 생성자 선언에 사용 (클래스 선언에는 사용하지 않음)

객체는 외부에서 접근이 가능한 퍼블릭 인터페이스와 외부에서 접근이 불가능하며 내부에서만 접근이 가능한 구현으로 나누어진다.
인터페이스와 구현의 분리원칙은 객체지향 프로그램을 만들기 위한 핵심 원칙이다.

✔️프로그래머의 자유

  • 클래스 작성자 : 새로운 데이터 타입을 프로그램에 추가한다.
  • 클라이언트 프로그래머 : 클래스 작성자가 추가한 데이터 타입을 사용한다.

클래스 작성자는 필요한 부분만 공개하고 나머지는 숨김으로써 클라이언트 프로그래머가 숨긴 부분에 대한 접근을 차단하는 것을 구현 은닉이라고 부른다.

✔️협력하는 객체들의 공동체

영화를 예매하는 기능을 구현하는 메소드는 예매 정보를 담고 있는 Reservation의 인스턴스를 생성해 반환한다.
Reservation 인스턴스는 예매자 정보, 상영 정보, 예매요금, 인원 수를 속성으로 가진다.
예매요금을 계산하기위해 calculateFee 메서드를 호출하고, 이 메서드는 다시 Movie의 calculateMovieFee메소드를 호출한다.
calculateMovieFee메서드는 1인당 예매 요금을 반환한다.
calculateFee메서드는 전체 예매 요금을 구하기 위해 인원 수를 곱한 결과를 반환한다.
Money는 금액과 관련된 다양한 계산을 구현하는 클래스다.
Long타입과 같은 정수형 타입을 사용할 수도 있지만 이 경우 구현 관점의 제약(변수 크기, 연산자 종류 등)은 표현할 수 있지만 Money타입 처럼 저장하는 값이 금액과 관련돼 있다는 의미를 전달할 수 없다. 따라서 금액과 관련된 기능이 서로 다른 곳에 중복되어 구현되는 것을 막을 수 없다. 의미를 좀 더 명시적이고 분명하게 표현(ex. Long타입 대신 Money타입)할 수 있다면 객체를 사용해서 해당 개념을 구현하는 것이 중요하다.
1. 협력의 관점에서 어떤 객체가 필요한지를 결정한다.
2. 객체가 어떤 상태와 행동을 가지는지 결정한다.
3. 객체들의 공통 상태와 행위를 구현하기 위한 클래스를 작성한다.

📌할인 요금 구하기

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

Movie의 calculateMovieFee는 discountPolicy에 calculateDiscountAmount 메시지를 전송해 할인 요금을 반환받는다.
예매 요금을 계산하기 위해서는 현재 영화에 적용돼 있는 할인 정책의 종류를 판단해야한다.

✔️할인 정책과 할인 조건

두 가지 할인 정책 AmountDiscountPolicy와 PercentDiscountPolicy는 대부분의 코드가 유사하지만 할인 요금을 계산하는 방식만 다르다.
중복 코드를 제거하기 위해 부모 클래스인 DiscountPolicy 안에 중복 코드를 두고 두 클래스가 이를 상속받게 한다.
실제 애플리케이션에서는 DiscountPolicy의 인스턴스를 생성할 필요가 없으므로 추상 클래스로 구현한다.
calculateDiscountAmount 메소드는 전체 할인 조건에 대해서 isSatisfiedBy 메소드를 호출하며, 상영정보(Screening)을 인자로 전달한다.
조건을 만족한다면 추상 메서드인 getDiscountAmount메소드에 상영정보를 전달하여 할인 요금을 계산하며 모든 조건을 만족하지 못한다면 0원을 반환한다.
이 추상 메서드 getDiscountAmount는 자식 클래스에서 오버라이딩한 메소드가 되며, 이처럼 부모 클래스에서 기본적인 알고리즘의 흐름을 구현하고 중간에 필요한 처리를 자식 클래스에게 위임하는 디자인 패턴을 TEMPLATE METHOD 패턴이라고 부른다.
DiscountCondition은 자바 인터페이스로 선언돼 있다. isSatisfiedBy 오퍼레이션은 상영정보(Screening)를 인자로 받아 할인 여부를 반환한다.
두 가지 할인 조건 SequenceCondition과 PeriodCondition은 할인 여부를 반환하는 isSatisfiedBy 오퍼레이션을 실체화한다.

  • 오퍼레이션 vs. 메소드

    오퍼레이션 : 기능의 추상적인 정의, 따라서 몸체(구현)이 존재하지 않는다.
    메소드 : 오퍼레이션의 구체적인 구현이며 메소드 구현을 포함한다.

  • 오버라이딩 vs. 오버로딩

    오버라이딩 : 부모 클래스(superclass)와 자식 클래스(child class)안에서 같은 메소드
    오버로딩 : 한 클래스(same class)에서 같은 이름의 메소드지만 다른 인자(파라미터)를 가지는 메소드

📌상속과 다형성

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

Movie클래스가 DiscountPolicy클래스와 연결되어 있으며 영화 요금을 계산하기 위해서는 추상 클래스인 DiscountPolicy가 아닌 AmountDiscountPolicy와 PercentDiscountPolicy 인스턴스가 필요하다. 하지만 코드 수준에서 Movie클래스는 두 클래스 중 어떤 것에도 의존하지 않으며 추상 클래스인 DicountPolicy에만 의존한다.
Movie인스턴스를 생성시 인자로 DiscocuntPolicy타입의 객체를 받는데 이때 AmountDiscountPolicy와 PercentDiscountPolicy의 인스턴스를 전달하여 의존하게 된다. 이와 같이 코드의 의존성과 실행 시점의 의존성은 서로 다를 수 있다.
코드의 의존성과 실행 시점의 의존성이 다르면 다를수록 코드는 더 유연해지고 확장이 가능해진다.
하지만 설계가 유연해질수록 코드를 이해하고 디버깅하기는 점점 더 어려워진다.
무조건 유연한 설계도, 무조건 읽기 쉬운 코드도 정답이 아니니 유연성과 가독성을 적절히 조절하는 것이 중요하다.

✔️상속, 인터페이스, 다형성, 추상클래스

상속 : 부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려받는 것. 상속은 구현 상속과 인터페이스 상속으로 분류할 수 있다.

  • 구현 상속 : 코드를 재사용하기 위한 목적으로 사용
  • 인터페이스 상속 : 다형적인 협력을 위해 사용

인터페이스 : 모든 기능(오퍼레이션)을 추상화로 정의만 하고 구현자히 않은 것
추상클래스 : 하나 이상의 추상 메소드를 포함하는 클래스
인터페이스 vs. 추상클래스

인터페이스와 추상클래스는 모두 상속을 통해 기능에 대한 강제 구현 규칙을 가집니다.
이 둘의 가장 큰 차이점은 다중 상속의 가능 여부지만, 이건이 핵심이 아니라 둘의 사용 목적이 다르다는 것입니다.
인터페이스는 인터페이스에 정의된 기능을 각 클래스의 목적에 맞게 구현하기 위해 사용됩니다. 따라서 구현 객체가 같은 동작을 한다는 것을 보장할 수 있습니다.
추상클래스는 부모클래스의 기능들을 하위 클래스로 확장하기 위해 사용됩니다. 상속할 각 객체들의 공통점을 찾아 추상화한 것이 추상클래스이며 하위 클래스(자식 클래스)에서 부모 클래스가 가진 기능들을 구현합니다.

아래 두 그림에서 Human 추상 클래스와 Eatable 인터페이스의 차이를 생각해보자.


💡참고글

operation vs. method - Julian Pustkuchen
Overriding vs Overloading in Java - Pankaj
인터페이스를 사용하는 이유 - Alfred’s Computing Weblog
[Java] 자바 - 인터페이스(interface)의 이해 및 사용하는 이유 - KADOSHoly
[JAVA] ☕ 인터페이스 vs 추상클래스 차이점 - 완벽 이해하기 - Inpa Dev
추상 클래스와 인터페이스의 차이 - 북항
Abstract Class vs Interface in Java – Difference Between Them - James Hartman

profile
개발자가 되어가는 중...

0개의 댓글