오브젝트 - 2장 객체지향 프로그래밍

Seyeon_CHOI·2022년 10월 20일
0

오브젝트 - 코드로 이해하는 객체지향 설계 / 2장 객체지향 프로그래밍

단순 책 내용 정리가 아닌 개인 생각과 중요 내용 정리본임

01. 영화 예매 시스템


📌 요구사항 살펴보기

온라인 영화 예매 시스템

  • 영화 - 영화에 대한 기본 정보
  • 상영 - 실제로 관객들이 영화를 관람하는 사건
  • 하나의 영화는 하루 중 다양한 시간대에 걸쳐 한 번 이상 상영될 수 있다.

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

  1. 할인 조건 - 가격의 할인 여부 결정
    • 순서 조건과 기간 조건으로 나뉨
  2. 할인 정책 - 할인 요금을 결정
    • 금액 할인 정책과 비율 할인 정책으로 나뉨

영화별로 하나의 할인 정책만 할당 가능

(대략적으로만 설명하고 넘어가겠다.. 조건이 너무 많다..)



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


📌 협력, 객체, 클래스

객체지향 언어에 익숙한 사람은 가장 먼저 어떤 클래스가 필요한 지 결정한 후에 해당 클래스에 어떤 속성과 메서드가 필요한지 고민한다.

하지만, 이것은 객체지향의 본질과는 거리가 멀다.

진정한 객체지향 패러다임으로의 전환은 클래스가 아닌 객체에 초점을 맞출 때 얻을 수 있다.

첫째, 어떤 클래스가 필요한지를 고민하기 전에 어떤 객체들이 필요한지 고민하라.

  • 어떤 객체들이 어떤 상태와 행동을 가지는지를 먼저 결정해야한다.

둘째, 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 봐야한다.

  • 객체는 홀로 존재 X -> 다른 객체에게 도움을 주거나 의존하면서 살아가는 협력적인 존재



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

도메인?
-> 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야

요구사항을 분석하는 초기 단계부터 프로그램을 구현하는 마지막 단계까지 객체라는 동일한 추상화 기법을 사용

요구사항과 프로그램을 객체라는 동일한 관점에서 바라볼 수 있기 때문에
도메인을 구성하는 개념들이 프로그램의 객체와 클래스로 매끄럽게 연결될 수 있다 !

📌 클래스 구현하기

인스턴스 변수의 가시성은 private, 메서드의 가시성은 public

클래스를 구현할 때, 가장 중요한 점은 클래스의 경계를 구분 짓는 것이다.
-> 클래스는 외부와 내부로 구분되며 어떤 부분을 외부에 공개하고 감출지를 결정하는 것

WHY?

  • 경계의 명확성이 객체의 자율성을 보장해줌
  • 프로그래머에게 구현의 자유를 제공

📍 자율적인 객체

먼저 두가지 중요한 사실을 알아야한다.

  1. 객체가 상태와 행동을 함께 가지는 복합적인 존재
  2. 객체가 스스로 판단하고 행동하는 자율적인 존재

두가지 사실은 서로 깊이 연관돼 있다.

데이터와 기능을 객체 내부로 함께 묶는 것 -> 캡슐화
한 걸음 더 나아가
외부에서의 접근을 통제할 수 있는 접근 제어
많은 프로그래밍 언어들은 접근 제어를 위해 public, protected, private와 같은 접근 수정자 제공

객체가 자율적인 존재로 있으려면, 최대한 외부의 간섭을 최소화 해야 한다.


캡슐화와 접근 제어는 객체를 두 부분으로 나눔

외부에서 접근 가능한 부분 --> 퍼블릭 인터페이스(public interface)
외부에서 접근 불가능하고 오직 내부에서만 접근 가능한 부분 --> 구현(implementation)

인터페이스와 구현의 분리 원칙은 훌륭한 객체지향 프로그램을 만들기 위해 따라야 하는 핵심 원칙



📍 프로그래머의 자유

프로그래머의 역할

  • 클래스 작성자: 필요한 클래스들을 엮어서 애플리케이션을 빠르고 안정적으로 구축
  • 클라이언트 프로그래머: 숨겨 놓은 부분에 마음대로 접근할 수 없도록 방지 --> 구현 은닉

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

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

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


객체가 다른 객체와 상호작용할 수 있는 유일한 방법은 메시지를 전송하는 것
다른 객체에게 요청이 도착할 때 해당 객체가 메시지를 수신했다고 이야기함.

메시지를 수신한 객체는 스스로의 결정에 따라 자율적으로 메시지를 처리할 방법을 결정한다.
이처럼 수신된 메시지를 처리하기 위한 자신만의 방법을 메서드



03. 할인 요금 구하기


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

속성들은 생성자를 통해 전달받음

public class Movie {
    private String title;
    private Duration runningTime;
    private Money fee;
    private DiscountPolicy discountPolicy;

    public Movie(String title, Duration runningTime, Money fee, DiscountPolicy discountPolicy) {
        this.title = title;
        this.runningTime = runningTime;
        this.fee = fee;
        this.discountPolicy = discountPolicy;
    }

    public Money getFee() {
        return fee;
    }

    public Money calculateMovieFee(Screening screening) {
        return fee.minus(discountPolicy.calculateDiscountAmount(screening));
    }

코드 상에 어떤 할인 정책을 사용할 것인지 결정하는 코드가 없다.
단지 discountPolicy에게 메시지를 전송할 뿐이다.

이 속에는 사실 상속, 다형성, 추상화가 숨겨져 있다.


📌 할인 정책과 할인 조건

public abstract class DiscountPolicy {
    private List<DiscountCondition> conditions = new ArrayList<>();

    public DiscountPolicy(DiscountCondition ... conditions) {
        this.conditions = Arrays.asList(conditions);
    }

    public Money calculateDiscountAmount(Screening screening) {
        for(DiscountCondition each : conditions) {
            if (each.isSatisfiedBy(screening)) {
                return getDiscountAmount(screening);
            }
        }

        return Money.ZERO;
    }

    abstract protected Money getDiscountAmount(Screening Screening);
}

여기서는 부모 클래스인 DiscountPoLicy 안에 중복 코드를 두고 AmountDiscountPoLicy와 PercentDiscount Policy가 이 클래스를 상속받게 할 것이다.

실제 애플리케이션에서는 DiscountPolLicy의 인스턴스를 생성 할 필요가 없기 때문에
추상 클래스(abstract class)로 구현했다.

할인 조건을 만족하는 DiscountCondition이 하나라도 존재하는 경우, 추상메서드인 getDiscountAmount 메서드를 호출해 할인 요금을 계산한다.

04. 상속과 다형성


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

코드의 의존성과 실행 시점의 의존성이 서로 다를 수 있음
-> 클래스 사이의 의존성과 객체 사이의 의존성은 동일하지 않을 수 있음

유연하고, 쉽게 재사용할 수 있으며,
확장 가능한 객체지향 설계가 가지는 특징은 코드의 의존성과 실행 시점의 의존성이 다르다는 것이다.


📌 차이에 의한 프로그래밍

상속 - 객체지향에서 코드를 재사용하기 위해 가장 널리 사용되는 방법

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

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


📌 상속과 인터페이스

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

인터페이스는 객체가 이해할 수 있는 메시지의 목록을 정의한다는 것을 기억해야한다.
상속을 통해 자식 클래스는 자신의 인터페이스에 부모 클래스의 인터페이스를 포함하게 된다.

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

자식 클래스가 부모 클래스를 대신하는 것을 업캐스팅

📌 다형성

메시지와 메서드는 다른 개념

Movie는 동일한 메시지를 전송하지만 실제로 어떤 메서드가 실행될 것인지는 메시지를 수신하는 객체의 클래스가 무엇이냐에 따라 달라진다. --> 다형성

메시지와 메서드를 실행 시점에 바인딩하는 것 -> 지연 바인딩 또는 동적 바인딩
전통적인 함수 호출처럼 컴파일 시점에 실행될 함수나 프로시저를 결정하는 것 --> 초기 바인딩 또는 정적 바인딩

05. 추상화와 유연성

유연성이 필요한 곳에 추상화를 사용하라.

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

상속의 단점

  • 캡슐화 위반
  • 설계를 유연하지 못하게 만듬

해결하기 위해서는

인터페이스에 정의된 메시지를 통해서만 코드를 재사용하는 방법 --> 합성

합성은 상속이 가지는 문제점 해결

  • 효과적으로 캡슐화
  • 설계를 유연하게 만듬

코드 재사용을 위해서는 상속보다는 합성이 더 좋은 방법

profile
오물쪼물 코딩생활 ๑•‿•๑

0개의 댓글