02 객체지향 프로그래밍
태그
비어 있음
영화 예매 시스템
특정한 조건을 만족하는 예매자는 요금을 할인 받을 수 있다.
객체지향 프로그래밍을 향해
객체지향 프로그램을 작성할 때 어떤 클래스가 필요한지를 고민하는 것은 객체지향의 본질과 거리가 있다. 클래스가 아닌 객체에 초점을 맞추어 어떤 객체가 필요할지 고민해야 한다. 그리고 객체를 독립적인 존재가 아니라 기능을 구현하기 위한 협력적인 존재로 보아야 한다.
자율적인 객체
클래스를 구현할 때 가장 중요한 것은 클래스의 경계를 구분 짓는 일이다.
캡슐화와 접근 제어는 객체를 퍼블릭 인터페이스(외부에서 접근 가능)와 구현(내부에서만 접근 가능)으로 나눈다.
인터페이스와 구현의 분리 원칙은 훌륭한 객체지향 프로그램을 만들기 위해 따라야 하는 핵심 원칙이다.
프로그래머의 자유
클래스 작성자 : 새로운 데이터 타입을 프로그램에 추가한다.
클라이언트 프로그래머 : 클래스 작성자가 추가한 데이터 타입을 사용한다.
접근 제어 메커니즘은 내부와 외부를 명확히 경계 짓고 클래스 작성자가 내부 구현을 은닉할 수 있게 해준다.
구현 은닉을 통해 클라이언트 프로그래머는 인터페이스만 알고 있어도 클래스를 사용할 수 있고, 클래스 작성자는 인터페이스를 바꾸지 않는 한 내부 구현을 자유롭게 수정할 수 있다.
따라서 클래스를 개발할 때마다 인터페이스와 구현을 깔끔하게 분리하기 위해 노력해야 한다.
할인 요금 구하기
DiscountPolicy를 상속받는 AmountDiscountPolicy, PercentDiscountPolicy
실제로 할인 요금을 계산하는 것은 getDiscountAmount를 오버라이딩한 자식클래스의 메서드
⇒ 부모클래스에 기본적인 알고리즘의 흐름을 구현하고 중간에 필요한 처리를 자식 클래스에게 위임하는 디자인 패턴을 TEMPLATE METHOD 패턴 이라 한다.
Java
복사
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);
}
상속과 다형성
컴파일 시간 의존성과 실행 시간 의존성
코드의 의존성과 실행 시점의 의존성이 서로 다를 수 있다.
→ 클래스 사이의의존성과 객체 사이의 의존성은 동일하지 않을 수 있다.
설계는 트레이드오프의 산물이다.
코드의 의존성과 실행 시점의 의존성이 다르면 다를수록
→ 코드를 이해하기 어려워진다.
→ 유연해지고 확장 가능해진다.
훌륭한 객체지향 설계자로 성장하기 위해서는 유연성과 가독성 사이에서 고민해야 한다.
차이에 의한 프로그래밍
상속을 통해 기존 클래스를 기반으로 새로운 클래스를 쉽고 빠르게 추가할 수 있다.
부모 클래스와 다른 부분만을 추가해서 새로운 클래스를 쉽고 빠르게 만드는 방법을
차이에 의한 프로그래밍이라고 부른다.
상속과 인터페이스
자식 클래스는 부모클래스 대신에 사용될 수 있다.
자식 클래스가 부모 클래스르 ㄹ대신하는 것을 업캐스팅이라고 한다.
다형성
메시지와 메서드는 다른 개념이다.
Movie는 DiscountPolicy의 calculateDiscountAmount 에게 메시지를 전송하면 ,
AmountDiscountPolicy or PercentDiscountPolicy 에서 오버라이딩한 메서드가 실행된다.
Movie는 동일한 메시지를 전송하지만 실제로 어떤 메서드가 실행될 것인지는 메시지를 수신하는 객체의 클래스가 무엇이냐에 따라 달라진다.
→ 다형성이란 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력
메시지와 메서드를 실행 시점에 바인딩 하는 것을 지연 바인딩 or 동적 바인딩이라고한다.
추상화와 유연성
추상화를 사용할 경우 장점
추상화의 계층만 따로 떼어 놓고 살펴보면 요구사항의 정책을 높은 수준에서 서술할 수 있다.
ex) 영화 예매 요금은 최대 하나의 할인 정책과 다수의 할인 조건을 이용해 계산할 수 있다.
추상화를 이용해 정의한 상위의 협력 흐름을 따르는 개념은 중요하다.
재사용 가능한 설계의 기본을 이루는 디자인 패턴, 프레임워크에서 이러한 객체지향의 매커니즘을 활용하기 때문이다.
유연한 설계
책임의 위치를 결정하기 위해 조건문을 사용하는 것은 협력의 설계 측면에서 대부분의 경우 좋지 않은 선택이다. 예외 케이스를 최소화하고 일관성을 유지할 수 있는 방법을 선택하라
영화 정책이 없을 때 할인 금액이 0원이라는사실을 결정하는 책임을 그대로 DiscountPolicy에 위치하게 하려면 NoneDiscountPolicy라는 새로운 클래스를 추가하면 된다.
추상화를 통해 설계 했기 때문에 Movie와 DiscountPolicy를 수정하지 않고 기능을 확장할 수 있다.
추상 클래스와 인터페이스 트레이드 오프
앞서 나왔던 코드를 보면 NoneDiscountPolicy 의 메서드가 어떤 값을 반환하던 상관이 없다.
애초에 할인조건이 없을 경우 메서드를 호출하지 않기 때문이다.
하지만 이것은 부모클래스와 NoneDiscountPolicy 를 개념적으로 결합시킨다.
조건이 없을 경우 DiscountPolicy가 0을 반환할 것을 가정하기 때문에.
Java
복사
public Money calculateDiscountAmount(Screening screening) {
for(DiscountCondition each : conditions) {
if (each.isSatisfiedBy(screening)) {
return getDiscountAmount(screening);
}
}
return Money.ZERO;
}
해결방법
이렇게 구조를 바꾼다.
DiscountPolicy클래스를 인터페이스로 바꾼다.
개념적인 혼란과 결합을 제거할 수 있다.
이런 설계가 현실적으로 과하다는 생각이 들 수도 있다.
구현과 관련된 모든 것들이 트레이드오프의 대상이다.
작성하는 모든 코드에 합당한 이유가 있어야 한다.
트레이드오프를 통해 얻어진 결론과 그렇지 않은 결론 사이의 차이는 크다.
코드의 재사용
상속보다는 합성이 더 좋은 방법이라는 이야기가 많다.
상속
캡슐화 위반
유연하지 못한 설계
합성
다른 객체(B)를 자신(A)의 인스턴스 변수로 포함
A는 B의내부 구현을 모른다.
인터페이스를 통해 약한 결합
객체지향에서 가장 중요한 것은 애플리케이션의 기능을 구현하기 위해
협력에 참여하는 객체들 사이의 상호작용
느낀 점
설계를 할 때 트레이드오프에 대해 고민을 많이 해야겠다고 느꼈다.
처음 설계를 할 때 클래스를 구현하는 것보다 ,객체에 초점을 맞추고 객체 사이의 상호작용에 집중을 해야겠다고 느꼈다.
표
'책 > 오브젝트' 카테고리의 다른 글
06. 메시지와 인터페이스 (0) | 2025.03.17 |
---|---|
05. 책임 할당하기 (0) | 2025.03.17 |
04. 설계 품질과 트레이드 오프 (0) | 2025.03.17 |
03. 역할, 책임, 협력 (0) | 2025.03.17 |
01. 객체, 설계 (0) | 2025.03.17 |