2장 : 객체지향 프로그래밍

가벼운 마음가짐

1. 영화 예매 시스템

  • 할인 조건

    • 가격의 할인 여부를 결정하며 ‘순서 조건’과 ‘기간 조건’의 두 종류로 나눌 수 있다.

    • ‘순서(sequence condition)’은 상영 순번을 이용해 할인 여부를 결정하는 규칙이다.

    • ‘기간(period condition)’은 영화 상영 시작 시간을 이용해 할인 여부를 결정한다.

  • 할인 정책

    • 할인 정책은 할인 요금을 결정하며 ‘금액 할인 정책(amount discount policy)’와 ‘비율 할인 정책(percen discount policy)’이 있다.

    • ‘금액 할인 정책’은 예매 요금에서 일정 금액을 할인해주는 방식이다.

    • ‘비율 할인 정책’은 정가에서 일정 비율의 요금을 할인해 주는 방식이다.

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

✔️ 협력, 객체, 클래스

  • 객체를 지향하는 것

    1. 어떤 클래스가 필요한지를 고민하기 전에 어떤 객체들이 필요한지 고민하라. 클래스는 공통적인 상태와 행동을 공유하는 개체들을 추상화한 것이다.

    2. 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 봐야 한다. 객체들의 모양과 윤곽이 잡히면 공통된 특성과 상태를 가진 객체들을 타입으로 분류하고 이 타입을 기반으로 클래스를 구현하라.

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

  • 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야를 도메일(domain)이라고 부른다.

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

✔️ 클래스 구현하기

public class Screening {
	private Movie movie;
	private int sequence;
	private LocalDateTime whenScreeded;

	public Screening(Movie movie, int sequence, LocalDateTime shenScreened) {
		this.movie = movie;
		this.sequence = sequence;
		this.whenScreened = shenScreened;
	}

	public LocalDateTime getStartTime() {
		return whenScreended;
	}

	public boolean isSequence(int sequence) {
		return this.sequence == sequence;
	}

	public Money getMovieFee() {
		return movie.getFee();
	}
}

클래스는 내부와 외부로 구분되며 훌륭한 클래스를 설계하기 위한 핵심은 어떤 부분을 외부에 공개하고 어떤 부분을 감출지를 결정하는 것이다. 클래스의 내부 외부 구분이 중요한 이유는 경계의 명확성이 객체의 자율성을 보장하기 때문이다.

  • 자율적인 객체

    • 객체는 상태(state)와 행동(behavior)을 함께 가지는 복합적인 존재다.이처럼 데이터와 기능을 객체 내부로 함께 묶는 것을 캡슐화라고 부른다.

    • 대부분의 객체지향 프로그래밍 언어들은 외부에서의 접근을 통제할 수 있는 접근 제어(access control) 메커니즘도 함께 제공하며, 접근 제어를 위해 public, protected, private과 같은 접근 수정자(access modifier)를 제공한다.

    • 캡술화와 접근 데어는 객체를 두 부분으로 나눈다. 하나는 외부에서 접근 가능한 부분으로 이를 퍼블릭 인터페이스(public interface)라고 부른다. 다른 하나는 외부에서 접근 불가능하고 오직 내부에서만 접근 가능한 부분으로 이를 구현(implementation)이라고 부른다.

  • 프로그래머의 자유

    • 프로그래머의 역할을 클래스 작성자와 클라이언트 프로그래머로 구분하는 것이 유용하다. 클라이언트 프로그래머에 대한 영향을 걱정하지 않고 내부 구현을 마음대로 변경할 수 있는 것을 구현 은닉(implementation hiding)이라고 부른다.

    • 접근 제어 메커니즘은 프로그래밍 언어 차원에서 클래스의 내부와 외부를 명확하게 경계 지을 수 있게 하는 동시에 클래스 작성자가 내부 구현을 은닉할 수 있게 해준다. 객체의 변경을 관리할 수 있는 기법 중에서 가장 대표적인 것이 바로 접근 제어다.

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

  • 시스템의 어떤 기능을 구현하기 위해 객체들 사이에 이뤄지는 상호작용을 협력(Collaboration)이라고 부른다.

✔️ 협력에 관한 짧은 이야기

  • 객체는 다른 객체의 인터페이스에 공개된 행동을 수행하도록 요청(request)할 수 있다. 요청을 받은 객체는 자율적인 방법에 따라 요청을 처리한 후 응답(response)한다.

  • 객체가 다른 객체와 상호작용할 수 있는 유일한 방법은 메시지를 전송(send a message)하는 것뿐이다. 메시지를 수신한 객체는 스스로의 결정에 따라 자율적으로 메시지를 처리할 방법을 결정한다. 이처럼 수신된 메시지를 처리하기 위한 자신만의 방법을 메서드(method)라고 부른다.

  • 메시지와 메서드의 구분에서부터 다형성(polymorphism)의 개념이 출발한다.

3. 할인 요금 구하기

  • 추상 클래스

  • 상속, 다형성, 추상화

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

  • 💡 오버라이딩과 오버로딩

    • 오버라이딩

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

    • 오버 로딩

      • 메서드의 이름은 같지만 제공되는 파라미터의 목록이 다른 경우

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

4. 상속과 다형성

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

  • 어떤 클래스가 다른 클래스에 접근할 수 있는 경로를 가지거나 해당 클래스의 객체의 메서드를 호출할 경우 두 클래스 사이에 의존성이 존재한다고 말한다.

  • 코드의 의존성과 실행 시점의 의존성이 서로 다를 수 있다. 다시 말해 클래스 사이의 의존성과 객체 사이의 의존성을 동일하지 않을 수 있다.

✔️ 차이에 의한 프로그래밍

  • 상속은 기존 클래스를 기반으로 새로운 클래스를 쉽고 빠르게 추가할 수 있는 간편한 방법을 제공한다. 또한 상속을 이용하면 부모 클래스의 구현은 공유하면서도 행동이 다른 자식 클래스를 쉽게 추가할 수 있다.

✔️ 상속과 인터페이스

  • 인터페이스는 객체가 이해할 수 있는 메시지의 목록을 정의한다. 상속을 통해 자식 클래스는 자신의 인터페이스에 부모 클래스의 인터페이스를 포함하게 된다. 결과적으로 자식 클래스는 부모 클래스가 수신할 수 있는 모든 메시지를 수신할 수 있기 때문에 외부 객체는 자식 클래스를 부모 클래스와 동일한 타입으로 간주할 수 있다.

  • 자식 클래스가 부모 클래스를 대신하는 것을 업캐스팅(upcasting)이라고 부른다.

✔️ 다형성

  • 동일한 메시지를 전송하지만 실제로 어떤 메서드가 실행될 것인지는 메시지를 수신하는 객체의 클래스가 무엇이냐에 따라 달라지는데, 이를 다형성이라고 부른다. 즉 다형성이란 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력을 의미한다.

  • 다형성은 컴파일 시간 의존성과 실행 시간 의존성을 다르게 만들 수 있는 객체지향의 특성을 이용해 서로 다른 메서드를 실행할 수 있게 한다. 메시지와 메서드를 실행 시점에 바인딩하며 이를 지연 바인딩(lazy binding) 또는 동적 바인딩(dynamic binding)이라고 부른다. 그에 반해 전통적인 함수 호출처럼 컴파일 시점에 실행될 함수나 프로시저를 결정하는 것을 초기 바인딩(early binding) 또는 정적 바인딩(static binding)이라고 부른다.

  • 💡 구현 상속과 인터페이스 상속

    흔히 구현상속을 서브클래싱이라고 부르고 인터페이스 상속을 서브타이핑이라고 부른다.

    순수하게 코드를 재사용하기 위한 목적으로 상속을 사용하는 것을 구현 상속이라고 부르며, 다형적인 협력을 위해 부모 클래스와 자식 클래스가 인터페이스를 공유할 수 있도록 상속을 이용하는 것을 인터페이스 상속이라고 부른다.

✔️ 인터페이스와 다형성

  • 앞에서는 추상 클래스로 자식 클래스들이 인터페이스와 내부 구현을 함께 상속받도록 만들었다. 종종 구현은 공유할 필요가 없고 순수하게 인터페이스만 공유하고 싶을 때는 인터페이스라는 프로그래밍 요소를 사용하면 된다.

5. 추상화와 유연성

✔️ 추상화의 장점

  • 추상화의 계층만 따로 뗴어 놓고 살펴보면 요구사항의 정책을 높은 수준에서 서술할 수 있다.

    • 자식 클래스들은 추상화를 이용해서 정의한 상위의 협력 흐름을 그대로 따르게 된다. 이 개념은 매우 중요한데, 재사용 가능한 걸계의 기본을 이루는 디자인 패턴(Design Pattern)이나 프레임워크(Framework) 모두 추상화를 이용해 상위 정책을 정의하는 객체지향의 메커니즘을 활용하고 있기 때문이다.

  • 추상화를 이용하면 설계가 좀 더 유연해진다. 결론은 간단하다. 유연성이 필요한 곳에 추상화를 사용하라.

✔️ 유연한 설계

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

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

  • 작성하는 모든 코드에는 합당한 이유가 있어야 한다. 고민하고 트레이드오프하라.

✔️ 코드 재사용

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

✔️ 상속

  • 상속은 객체지향에서 코드를 재사용하기 위해 널리 사용되는 기법이지만, 상속은 캡슐화를 위반하고 설계를 유연하지 못하게 만든다.

✔️ 합성

  • 인터페이스에 정의된 메시지를 통해서만 코드를 재사용하는 방법을 합성이라고 부른다.

  • 상속은 클래스를 통해 강하게 결합되는 데 비해 합성은 메시지를 통해 느슨하게 결합된다. 따라서 코드 재사용을 위해서는 상속보다는 합성을 선호하는 것이 더 좋은 방법이다.

Last updated