본문 바로가기
Book

[독서 기록] 오브젝트 14장 일관성 있는 협력

by Renechoi 2023. 1. 18.
 
오브젝트
객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 존재로 바라보는 것이다. 세번째 걸음을 내디딜 수 있는지 여부는 협력에 참여하는 객체 들에게 얼마나 적절한 역할과 책임을 부여할 수 있느냐에 달려 있다. 객체지향의 마지막 걸음은 앞에서 설명한 개념들을 여러분이 사용하는 프로그래밍 언어라는 틀에 흐트러짐 없이 담아낼 수 있는 기술을 익히는 것이다. 《객체지향의 사실과 오해》가 첫번째 걸음과 두번째 걸음인 객체와 협력에 초점을 맞췄다면 《오브젝트: 코드로 이해하는 객체지향 설계》는 세번째와 네번째 걸음인 책임의 할당과 그 구현에 초점을 맞춘다. 이 책을 읽고 나면 객체에 적절한 역할과 책임을 부여하는 방법과 유연하면서도 요구사항에 적절한 협력을 설계하는 방법을 익히게 될 것이다. 나아가 프로그래밍 언어라는 도구를 이용해 객체지향의 개념과 원칙들을 오롯이 표현할 수 있는 방법 역시 익힐 수 있을 것이다. ★ 이 책에서 다루는 내용 ★ ◎ 역할, 책임, 협력에 기반해 객체지향 프로그램을 설계하고 구현하는 방법 ◎ 응집도와 결합도를 이용해 설계를 트레이드오프하는 방법 ◎ 설계를 유연하게 만드는 다양한 의존성 관리 기법 ◎ 타입 계층을 위한 상속과 코드 재사용을 위한 합성의 개념 ◎ 다양한 설계 원칙과 디자인 패턴
저자
조영호
출판
위키북스
출판일
2019.06.17

 

 

다음은 4장에서 절차적인 방식으로 구현했던 ReservationAgency의 기본 구조를 정리한 것이다. 

 

public class ReservationAgency {
	public Reservation reserve(Screening screening, Customer customer, int audienceCount) {
    	for(DiscountCondition condition : movie.getDiscountConditions()) {
        if (condition.getType() == DiscountConditionType.Period) {
        	// 기간 조건인 경우
        } else {
        	// 회차 조건인 경우 
        }
     }
     
     if (discountable) {
     	switch(move.getMovieType()){
        	case AMOUNT_DISCOUNT :
           	 	// 금액 할인 정책인 경우 
            case PERCENT_DISCOUNT :
            	// 비율 할인 정책인 경우 
                .......

 

위 코드에는 두 개의 조건 로직이 존재한다. 하나는 할인 조건의 종류를 결정하는 부분이고 다른 하나는 할인 정책을 결정하는 부분이다. 이 설계가 나쁜 이유는 변경의 주기가 서로 다른 코드가 한 클래스 안에 뭉쳐있기 때문이다. 또한 새로운 할인 정책이나 할인 조건을 추가하기 위해서는 기존 코드의 내부를 수정해야 하기 때문에 오류가 발생할 확률이 높아진다. 

 

할인 조건과 할인 정책의 종류를 판단하는 두 개의 if 문이 존재하며 새로운 조건이 필요하면 우리는 이 if 문들에 새로운 else 절을 추가하게 될 것이다. 따라서 조건에 따라 분기되는 어떤 로직들이 있다면 이 로직들이 바로 개별적인 변경이라고 볼 수 있다. 절차지향 프로그램에서 변경을 처리하는 전통적인 방법은 이처럼 조건문의 분기를 추가하거나 개별 분기 로직을 수정하는 것이다. 

 

객체지향은 조금 다른 접근 방법을 취한다. 객체지향에서 변경을 다루는 전통적인 방법은 조건 로직을 객체 사이의 이동으로 바꾸는 것이다. 아래 코드를 보면 Movie는 현재의 할인 정책이 어떤 종류인지 확인하지 않는다. 단순히 현재의 할인 정책을 나타내는 discountPolicy에 필요한 메시지를 전송할 뿐이다. 할인 정책의 종류를 체크하던 조건문이 discountPolicy로의 객체 이동으로 대체된 것이다. 

 

public class Movie {
	private DiscountPolicy discountPolicy; 
    
    public Money calculateMovieFee(Screening screening){
    	return fee.minus(discountPolicy.calcualteDiscountAmount(screening));
    }
}

 

다형성은 바로 이런 조건 로직을 객체 사이의 이동으로 바꾸기 위해 객체지향이 제공하는 설계 기법이다. 할인 금액을 계산하는 구체적인 방법은 메시지를 수신하는 discountPolicy의 구체적인 타입에 따라 결정된다. Movie는 discountPolicy가 자신의 요청을 잘 처리해줄 것이라고 믿고 메시지를 전송할 뿐이다. 

 

DiscountPolicy와 할인 조건을 구현하는 DiscountCondition 사이의 협력 역시 마찬가지다. DiscountPolicy는 DiscountCondition을 믿고 isSatisfiedBy 메시지를 전송한다. 

 

public abstract class DiscountPolicy {
	private List<DiscountCondition> conditions = new ArrayList<>();
    
    public Money calculateDiscountAmount(Screening screening) {
    	for (DiscountCondition each : conditions ) {
        	if (each.isSatisfiedBy(screening)) {
            	return getDiscountAmount(screening);
             }
        }
        
        return screening.getMovieFee(); 
    }

 

- 488 ~ 489p

 

 

객체지향 코드는 조건을 판단하지 않는다. 단지 다음 객체로 이동할 뿐이다.

- 492p 

 

 

 

GOF의 조언에 따르면 캡슐화란 단순히 데이터를 감추는 것이 아니다. 소프트웨어 안에서 변할 수 있는 모든 '개념'을 감추는 것이다. 개념이라는 말이 다소 추상적으로 들린다면 간단히 다음처럼 생각하라. 캡슐화란 변하는 어떤 것이든 감추는 것이다[Bain08, Shalloway01] 

- 495 

 

 

다시 한번 강조하자면 캡슐화란 단순히 데이터를 감추는 것이 아니다. 소프트웨어 안에서 변할 수 있는 어떤 '개념'이라도 감추는 것이다.

-> 데이터 캡슐화 : movie 클래스의 인스턴스 변수 title의 가시성은 private 이기 때문에 외부에서 직접 접근할 수 없다. 이 속성에 접근할 수 있는 유일한 방법은 메서드를 이용하는 것 뿐이다. 다시 말해 클래스는 내부에 관리하는 데이터를 캡슐화 한다. 

 

-> 객체 캡슐화 : DiscountPolicy 클래스에서 정의돼 있는 getDiscountAmount 메서드의 가시성은 protected다. 클래스의 외부에서는 이 메서드에 직접 접근할 수 없고 클래스 내부와 서브클래스에서만 접근이 가능하다. 따라서 클래스 외부에 영향을 미치지 않고 메서드를 수정할 수 있다. 다시 말해 클래스의 내부 행동을 캡슐화하고 있는 것이다. 

 

-> 메서드 캡슐화 : movie 클래스는 discountPolicy 타입의 인스턴스 변수 discountPolicy를 포함한다. 이 인스턴스 변수는 private 가시성을 가지기 때문에 movie와 DiscountPolicy 사이의 관계를 변경하더라도 외부에는 영향을 미치지 않는다. 다시 말해서 객체와 객체 사이의 관계를 캡슐화 한다. 눈치가 빠른 사람이라면 객체 캡슐화가 합성을 의미한다는 것을 눈치챘을 것이다. 

 

-> 서브타입 캡슐화 : Movie는 DiscountPolicy에 대해서는 알고 있지만 AmountDiscountPolicy와 PercentDiscountPolicy에 대해서는 알지 못한다. 그러나 실제로 실행 시점에는 이 클래스들의 인스턴스와 협력할 수 있다. 이것은 파생 클래스인 DiscountPolicy와의 추상적인 관계가 AmountDiscountPolicy와 PercentDiscountPolicy의 존재를 감추고 있기 때문이다. 다시 말해 서브타입의 종류를 캡슐화하고 있는 것이다. 눈치가 빠른 사람이라면 서브타입 캡슐화가 다형성의 기반이 된다는 것을 알 수 있을 것이다. 

 

 

- 495p 

 

 

반응형