본문 바로가기
Book

[독서 기록] 오브젝트 8장 의존성 관리하기 9장 유연한 설계

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

 

 

 

의존성 

 

a - > b 

 

a가 b에 의존한다 

 

 

런타임 의존성과 컴파일 의존성 

 

movie -> 컴파일에는 DiscountPolicy에만 의존 

실제 런타임 때 Amount, Percent에 의존 

 

 

컴파일 의존성은 구체적인 런타임 의존성으로 대체돼야 한다.

- 261p 

 

- 객체를 생성하는 시점에 생성자를 통해 의존성 해결

- 객체 생성 후 setter 메서드를 통해 의존성 해결

- 메서드 실행 시 인자를 이용해 의존성 해결 

 

 

의존성이 높으면 결합도가 높다 

 

더 많이 알수록 더 많이 결합된다. 

 

추상화에 의존하라

 

 

public class Movie {
	private DiscountPolicy discountPolicy;
    
    public Movie(String tittle, Duration runningTime, Money fee) {
    	this.discountPoliy = new AmountDiscountPolicy(...) { ...

구체 클래스인 AmountDiscountPolicy의 인스턴스를 직접 생성해서 대입 -> 결합도가 불필요하게 높음 

 

- 269p 

 

 

의존성은 명시적으로 표현돼야 한다. 의존성을 구현 내부에 숨겨두지 마라. 유연하고 재사용 가능한 설계란 퍼블릭 인터페이스를 통해 의존성이 명시적으로 드러나는 설게다.

- 271p 

 

 

결합도 측면에서 new가 해로운 이유는 크게 두 가지다.

- new 연산자를 사용하기 위해서는 구체 클래스의 이름을 직접 기술해야 한다. 따라서 new를 사용하는 클라이언트는 추상화가 아닌 구체 클래스에 의존할 수밖에 없기 때문에 결합도가 높아진다. 

- new 연산자는 생성하려는 구체 클래스뿐만 아니라 어떤 인자를 이용해 클래스의 생성자를 호출해야 하는지도 알아야 한다. 따라서 new를 사용하면 클라이언트가 알아야 하는 지식의 양이 늘어나기 때문에 결합도가 높아진다. 

- 271p 

 

 

 

this.discountPolicy = new AmountDiscountPolicy(Money.wons(800),
					new SequenceCondition(1),
                    new SequenceCondition(10),
                    ...

Movie 클래스가 AmountDiscountPolicy의 인스턴스를 생성하기 위해서는 생성자에 전달되는 인자를 알고 있어야 한다.

- 272p 

 

 

Movie avater = new Movie("아바타", Duration.ofMinutes(120), Money.wons(10000), new AmountDiscountPolicy(Money.Wons.(....

 

사용과 생성의 책임을 분리해서 Movie의 결합도를 낮추기 -> Movie의 생성자가 구체클래스인 AmountDiscountPolicy가 아니라 추상 클래스인 DiscountPolicy를 인자로 받아들이도록 선언돼 있다는 점에 주목 

- 274p 

 

 

클래스 안에서 객체의 인스턴스를 직접 생성하는 방식이 유용한 경우도 있다. 주로 협력하는 기본 객체를 설정하고 싶은 경우가 여기에 속한다. 예를 들어, Movie가 대부분의 경우에는 AmountDiscountPolicy의 인스턴스와 협력하고 가끔씩만 PercentDiscountPolicy의 인스턴스와 협력한다고 가정해보자. 이런 상황에서 모든 경우에 인스턴스를 생성하는 책임을 클라이언트로 옮긴다면 클라이언트들 사이에 중복 코드가 늘어나고 movie의 사용성도 나빠질 것이다. 

 

이 문제를 해결하는 방법은 기본 객체를 생성하는 생성자를 추가하고 이 생성자에서 DiscountPolicy의 인스턴스를 인자로 받는 생성자를 체이닝하는 것이다. 

- 274p 

 

 

 

movie에 특별한 if 문을 추가하지 않아도 할인 혜택을 제공하지 않는 영화를 구현할 수 있다. 간단한 NoneDiscountPolicy의 인스턴스를 movie의 생성자에 전달하면 되는 것이다.

Movie avatar = new Movie("아바타", Duration.ofMinutes(120), Money.wons(10000), new NoneDiscountPolicy());

-278p 

 

 

중복을 갖게 하기 

 

public class OverlappedDiscountPolicy extends DiscountPolicy {
	priavate List<DiscountPolicy> discountPolicies = new ArrayList<>(): 
    
    public OverlappedDiscountPolicy(DiscountPolicy... discountPolicies) {
    	this.discountPolicies = Arrays.asList(discountPolicies);
    }

 

이제 OverlappedDiscountPolicy의 인스턴스를 생성해서 movie에 전달하는 것만으로도 중복 할인을 쉽게 적용할 수 있다. 

 

Movie avatar = new Movie("아바타" , Duration.ofMinutes(120), Money.wons(10000), new OverlappedDiscountPolicy( new AmountDiscountPolicy(...), new PercentDiscountPolicy(...) );

- 279p 

 

 

유연하고 재사용 가능한 설계는 객체가 어떻게(how) 하는지를 장황하게 나열하지 않고도 객체들의 조합을 통해 무엇을 하는지를 표현하는 클래스들로 구성된다. 따라서 클래스의 인스턴스를 생성하는 코드를 보는 것만으로 객체가 어떤 일을 하는지를 쉽게 파악할 수 있다. 코드에 드러난 로직을 해석할 필요 없이 객체가 어떤 객체와 연결됐는지를 보는 것만으로도 객체의 행동을 쉽게 예상하고 이해할 수 있다. 다시 말해 선언적으로 객체의 행동을 정의 할 수 있는 것이다. 

- 280p 

 

 

 


 

 

개방-폐쇄 원칙 

 

학장과 수정 -> 동작과 코드의 관점 

기존의 코드를 수정하지 않고도 애플리케이션의 동작을 추가하거나 변경할 수 있다. 

 

moive -> 컴파일 시 discountPolicy에만 의존 

 

런타임 의존 -> amount, overlapped, percent 

 

 

개방 폐쇄 원칙의 핵심은 추상화에 의존하는 것이다. 

- 284p 

 

 

 

생성 사용 분리 

 

 

생성과 사용을 분리하기 위해 객체 생성에 특화된 객체를 Factory라고 부른다. 

- 289p 

 

public class Factory {
	public Movie createAvatarMovie() {
    	return new Movie("아바타" , 	Duration.ofMinutes(120), 
        			Money.wons(10000),
                    new AmountDiscountPolicy(...));
        }
   }

 

public class Client {
	private Factory factory; 
    public Client(Factory factory) {
    	this.factory = factory;
    }
    
    public Money getAvaterFee() {
    	Movie avater = factory.createMadMaxMovie();
        return avater.getFee();
    }
    
}

 

-290 

 

 

Factory는 도메인 모델에 속하지 않는다는 사실을 알아챘을 것이다. FACTORY를 추가한 이유는 순수하게 기술적인 결정이다. 전체적으로 결합도를 낮추고 재사용성을 높이기 위해 도메인 개념에게 할당돼 있던 객체 생성 책임을 도메인 개념과는 아무런 상관이 없는 가공의 객체로 이동시킨 것이다.

- 291p 

 

 

어떤 행동을 추가하려고 하는데 이 행동을 책임질 마땅한 도메인 개념이 존재하지 않는다면 PURE FABRICATION을 추가하고 이 객체에게 책임을 할당하라. 그 결과로 추가된 PURE FABRCATION은 보통 특정한 행동을 표현하는 것이 일반적이다. 

- 291p 

 

 

의존성 주입 

 

생성과 사용의 분리 -> 다른 객체가 movie에게 생성된 인스턴스를 전달 -> 이처럼 사용하는 객체가 아닌 외부의 독립적인 객체가 인스턴스를 생성한 후 이를 전달해서 의존성을 해결하는 방법을 의존성 주입이라고 부른다. 

 

-> 생성자 주입 

-> setter 주입

-> 메서드 주입 

 

- 293p 

 

 

객체 사이의 협력이 존재할 때 그 협력의 본질을 담고 있는 것은 상위 수준의 정책이다. Movie와 AmountDiscountPolicy 사이의 협력이 가지는 본지은 영화의 가격을 계산하는 것이다. 어떻게 할인 금액을 계산할 것인지는 협력의 본질이 아니다. 다시 말해서 어떤 협력에서 중요한 정책이나 의사결정, 비즈니스의 본질을 담고 있는 상위 수준의 클래스다. 

- 300p 

 

 

 

상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 된다. 둘 모두 추상화에 의존해야 한다.

추상화는 구체적인 사항에 의존해서는 안 된다. 구체적인 사항은 추상화에 의존해야 한다. 

- 302p 

 

 

 

 

 

반응형