토비의 스프링 | 1장 오브젝트와 의존 관계 (독서메모)

주싱·2022년 9월 4일
0

토비의 스프링

목록 보기
6/30

1.2 DAO의 분리

1.2.1 관심사의 분리

  • 소프트웨어가 폐기 될 때까지 일어나는 변화에 어떻게 대응할 것인지 대비해야 한다.
  • 가장 좋은 대책은 변화의 폭을 최소한으로 줄여주는 것⭐이다.
  • 변화의 폭을 최소화하는 방법은 분리와 확장을 고려한 설계⭐를 하는 것이다.
  • 실세계에서 하나의 변화가 일어날 때 우리의 코드도 한 군데에 집중할 수 있도록 하자.
  • 한 가지 관심사는 한 군데에 집중되게 하자. 관심사가 다른 코드는 다른 곳에 있도록 분리하자.

1.2.2 커넥션 만들기의 추출

UserDao의 관심사항

  • DB와 연결과 관련된 관심사가 중복되어 흩어져 있음으로 다른 관심의 대상과 얽혀 있다.

중복 코드의 메소드 추출

  • DB 연결과 관련된 변경이 발생하면 관심 내용이 국지적으로 발생함으로 수정이 쉽고, 다른 코드에 영향은 최소화 된다.
  • 여러 메서드에 중복되어 있던 하나의 관심사항을 별도의 메서드로 분리해 내었다.
  • 이 작업은 기능에 아무런 영향을 주지 않으면서 코드의 구조만을 변경했다. 우리는 그래서 다시 동일한 테스트 코드를 실행하여 코드의 변경이 옳음을 검증했다. UserDao는 훨씬 깔끔해 졌고, 미래의 변화에 좀 더 쉽게 대응할 수 있는 코드가 되었다. 우리는 이것을 리팩토링이라고 한다.
  • 그리고 공통의 기능을 담당하는 메서드로 중복된 코드를 뽑아 냈다. 우리는 이것을 메서드 추출 기법이라고 한다.

1.2.3 DB 커넥션 만들기의 독립

  • 변화를 반기는 코드 만들기

상속을 통한 확장

  • 메서드 추출을 통해서 한 클래스 내에서 변경이 용이한 코드가 되었다면, 이제는 확장까지 용이한 코드가 되었다.
  • UserDao 는 어떤 기능을 사용한다는 데에만 관심이 있고, MyUserDao는 어떤 식으로 Connection 기능을 제공하는지에만 관심을 두고 있다. 이렇게 관심사가 분리 되었따.
  • 템플릿 메서드 패턴
    • 슈퍼클래스에 기본적인 로직의 흐름을 정의하고, 그 기능의 일부(변경이 예상되고 확장이 가능한 부분)를 추상 메서드로 정의하여 서브클래스에서 필요에 맞게 구현해서 사용할 수 있도록 하는 패턴
  • 팩토리 메서드 패턴
    • 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 패턴
  • 상속의 한계
    • 상속은 여전히 슈퍼클래스와 서브클래스의 긴밀한 결합을 허용한다. 서브클래스에서 슈퍼클래스의 기능을 직접 사용할 수 있기 때문이다. ⭐ 그래서 슈퍼클래스의 내부 변경이 있을 때 모든 서브클래스를 함께 수정하거나 다시 개발해야 할 수도 있다.

1.3 DAO의 확장

  • 변화는 이유, 시기, 주기 등이 성격이 다르다.
  • 관심사에 따라 코드를 분리하는 이유는 변화의 성격이 다른 것을 분리해서, 서로 영향을 주지 않은 채로 각각 필요한 시점에 독립적으로 변경할 수 있게 하기 위해서다.
  • 또 다시 상속의 한계
    • 다중 상속을 허용하지 않기 때문에 오직 하나의 관심사만 분리할 수 있게 된다.

1.3.1 클래스의 분리

  • 관심사가 다르고, 변화의 성격이 다른 두 코드를 좀 더 화끈하게 분리해 보자.
  • 메서드 분리 → 상하위 클래스로 분리 → 완전히 독립적인 클래스로 분리
  • 기능의 변화 없이 내부 설계를 변경해서 좀 더 나은 코드로 개선했다. 기능의 변화가 없다는 것은 리팩토링 작업의 전제이기도 하지만, 사실 검증의 내용이기도 하다. (기능의 변화가 없음 → 리팩토링 → 검증의 대상)
  • DB 연결 코드를 분리했지만 UserDao 수정하지 않고 DB 연결 기능을 확장해서 사용할 수 없습니다.
  • 자유로운 확장이 가능하도록 하려면 두 가지 문제를 해결해야 합니다.
    • UserDao가 구체적인 연결 클래스를 알고 있어야 한다.
    • UserDao에서 사용하는 DB 연결 메서드 이름을 다르게 정의한 경우 UserDao든 DB 연결 메서드든 바꾸어 주어야 한다.

1.3.2 인터페이스의 도입

  • 추상화란 공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업이다.
  • 인터페이스를 통해 접근하면 구체적인 구현은 모두 감춰지고, 구체적인 구현이 바뀌어도 코드에 영향을 받지 않는다.
  • 구현에 영향을 받는 것과 동작에 영향을 받는 것을 구분해서 이해하면 되겠다.⭐
  • 인터페이스를 사용하면 무엇을 사용할지에만 관심을 가지고, 어떻게 구현할지에는 관심을 둘 필요가 없게 된다.

1.3.3 관계설정 책임의 분리 (클라이언트 도움받기)

  • UserDao에 여전히 어떤 ConnectionMaker 구현 클래스를 사용할지 결정하는 코드가 남아있다. 이 때문에 UserDao 변경 없이 DB 연결 기능을 자유롭게 확장할 수 없다.
  • new SimpleConnectionMaker() 코드는 그 자체로 독립적인 관심사를 가지고 있다. 바로 UserDao가 어떤 DB 연결 구체 클래스를 사용할지 결정하는 것이다.
  • 다시 말하면 UserDao와 UserDao가 어떤 DB 연결 구현을 사용할지 관계를 설정해 주는 것에 대한 관심⭐⭐이다. UserDao의 클라이언트라고 한다.
  • 설계하고 코드를 작성할 때의 클래스 의존관계와 런타임의 의존 관계인 오브젝트 의존관계를 반드시 나누어 이해해야 합니다. ⭐⭐
  • UserDaoTest는 UserDao와 ConnectionMaker 구현 클래스와 런타임 오브젝트 의존 관계를 설정하는 책임을 담당한다.⭐⭐
  • 이것은 UserDao에 있으면 안되는 다른 관심사항, 책임을 클라이언트로 떠넘기는 작업을 한 것이다.
  • UserDao 변경 없이 고객사는 자신들을 위한 DB 접속 클래스를 만들어서 UserDao가 사용하게 할 수 있다.
  • 인터페이스를 도입 → 클라이언트의 도움을 받기 → ?

1.3.4 원칙과 패턴

개방 폐쇄 원칙

  • 모듈은 확장에는 열려있고, 변경에는 닫혀 있어야 한다.
  • 모듈의 클라이언트는 모듈을 수정하지 않고 모듈의 특정 기능을 확장해서 사용할 수 있다. 그리고 모듈 자신은 그러한 변경에 아무런 영향을 받지 않을 수 있다. ⭐
  • 인터페이스를 통해 제공되는 확장 포인트는 확장을 위해 활짝 개방되어 있다. 반면 인터페이스를 이용하는 클래스 자신은 변화가 불필요하게 일어나지 않도록 굳게 닫혀 있다.

객체지향 설계 원칙(SOLID)

  • 객체지향 설계 패턴들은 당영한 이야기지만 객체지향 설계 원칙을 잘 지키며 만들어 졌다.
  • SRP / OCP / LSP / ISP / DIP

높은 응집도와 낮은 결합도

  • 개발 폐쇄 원칙은 높은 응지도와 낮은 결합도라는 소프트웨어 개발의 고전적인 원리로도 설명이 가능하다.
  • 높은 응집도
    • 응집도가 높다는건 하나의 모듈이 하나의 책임 또는 관심사에 집중되어 있다는 뜻이다.
    • 메서드, 클래스, 패키지, 컴포넌트 등 다양한 수준에 적용이 가능하다.
    • 변화가 일어 날 때 해당 모듈에서 변하는 부분이 크다는 것을 의미한다.
    • 응집도가 낮다면 여러 모듈에서 변경이 일어나서 변경 자체에 대한 로드가 높을 뿐 아니라 변경에의한 영향 또한 덩달아 이중으로 늘어나는 부담을 가지게 된다.
  • 낮은 결합도
    • 관계를 유지하는데 꼭 필요한 최소한의 방법만 간접적인 형태로 제공하고, 나머지는 서로 독립적이고 알 필요가 없게 만들어 주는 것이다.
    • 결합도란, ‘하나의 오브젝트에 변경이 일어날 때 관계를 맺고 있는 다른 오브젝트에게 변화를 요구하는 정도’로 설명할 수 있다. 즉 하나의 변경이 외부로 파문이 이는 것 처럼 전파되어 가지 않음을 뜻한다.

전략 패턴 (Strategy Pattern)

  • 자신의 기능의 맥락에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴이다.
  • 독립적인 책임으로 분리하고 대체 가능한 전략이라는 의미
  • 개발 폐쇄 원칙을 실현에 가장 잘 맞는 패턴이다.
  • UserDao는 전략 패턴의 컨텍스트에 해당한다. 자신의 기능을 수행하는데 필요한 기능 중에 변경 가능한, DB 연결 방식이라는 알고리즘을 ConnectionMaker 라는 인터페이스로 정의하고, 이를 구현한 클래스, 즉 전략을 바꿔가면서 사용할 수 있게 분리했다.
  • 전략 패턴은 UserDaoTest와 같은 클라이언트의 필요성에 대해서도 잘 설명하고 있다. 컨텍스트를 사용하는 클라이언트는 컨텍스트가 사용할 전략을 컨텍스트의 생성자 등을 통해 제공해주는 게 일반적이다.

UserDao는 개발 폐쇄 원칙을 잘 따르고 있으며, 응집력이 높고 결합도는 낮으며 전략 패턴을 적용했음을 알 수 있다.

1.4 제어의 연전(IoC, Inversion of Control)

1.4.1 오브젝트 팩토리

성격이 다른 책임이나 관심사는 분리하라고 하지 않았나? UserDaoTest는 테스트를 위한 목적이 아닌가? UserDao와 UserDao가 사용할 ConnectionMaker 구현 클래스를 연결해 주는 역할을 분리하자.

팩토리

  • UserDao가 사용하는 ConnectionMaker 구현체의 생성 역할 역시 분리합니다. 그래서 UserDaoTest는 테스트 역할에 집중합니다.
  • 개선된 코드는 이전과 같이 동일하게 동작하는지 잊지 말고 테스트를 실행하자!

설계도로서의 팩토리

  • 애플리케이션의 컴포넌트 역할을 하는 오브젝트와 애플리케이션의 구조를 결정하는 오브젝트를 분리했다는 데 가장 의미가 있다.

1.4.2 오브젝트 팩토리의 활용

  • 중복 코드를 메서드 추출하기 리팩토링을 통해 개선한다. 앞에서의 예와 동일하다.

1.4.3 제어권의 이전을 통한 제어관계 역전

  • 모든 종류의 작업은 사용하는 쪽에서 제어하는 구조이다. 제어의 역전이란 이런 제어의 흐름의 개념을 꺼꾸로 뒤집는 것이다.
  • 제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다. 당연히 생성하지도 않는다. 또 자신도 어떻게 만들어지고 어디서 사용되는지를 알 수 없다. 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문이다.
  • 템플릿 메서드 패턴에서 구체적인 메서드를 정의하는 서브클래스는 자신이 어떻게 사용될지 알지 못한다. 그 사용에 대한 책임은 슈퍼클래스에게 위임한다.
  • 라이브러리는 애플리케이션 코드가 필요에 따라 라이브러리를 사용한다. 이때 애플리케이션 코드는 애플리케이션 흐름을 직접 제어한다.
  • 반면에 프레임워크는 애플리케이션 코드가 프레임워크에 의해 사용되도록 한다. 보통 프레임워크 위에 개발한 클래스를 등록해두고, 프레임워크가 흐름을 주도하는 중에 개발자가 만든 애플리케이션 코드를 사용하도록 하는 방식이다. 애플리케이션 코드는 프레임워크가 자놓은 틀에서 수동으로 동작해야 한다.

1.5 스프링의 IoC

  • 스프링은 다양한 영역과 기술에 관여하고 매우 많은 기능을 제공한다. 하지만 스프링의 핵심을 담당하는 건, 바로 빈 팩토리 또는 애플리케이션 컨텍스트라고 불리는 것이다.
  • 이 두 가지는 우리가 만든 DaoFactory가 하는 일을 좀 더 일반화한 것이라고 할 수 있겠다.

1.5.1 오브젝트 팩토리를 이용한 스프링 IoC

애플리케이션 컨텍스트와 설정정보

  • 빈(Bean) - 스프링이 제어권을 가지고 직접 생성하고 관계를 부여하는 오브젝트
  • 빈 팩토리 - 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트
  • 애플리케이션 컨텍스트 - 빈 팩토리 개념을 조금더 확장한 개념
  • 애플리케이션 컨텍스트는 별도의 정보를 참고해서 빈의 생성, 관계설정 등의 제어 작업을 총괄한다.

DaoFactory를 사용하는 애플리케이션 컨텍스트

  • @Confuguration - 스프링이 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스 임을 인식할 수 있게 해준다.
  • @Bean - 오브젝트를 만들어주는 메서드임을 알려준다.

1.5.2 애플리케이션 컨텍스트의 동작방식

ApplicationContext = IoC 컨테이너 = 스프링 컨테이너 = 빈 팩토리 = 스프링의 대표적 오브젝트 = 그냥 스프링이라고 부르기도

클라이언트는 구체적인팩토리 클래스를 알 필요가 업다

클라이언트가 필요한 오브젝트를 가져오려면 어떤 팩토리 클래스를 사용해야 하는지 알아야 하고, 필요할 때 마다 팩토리 오브젝트를 생성해야 하는 번거로움이 있다. 애플리케이션 컨텍스트를 사용하면 팩토리 클래스를 몰라도 일관된 방식으로 원하는 오브젝트를 가져올 수 있다.

애플리케이션 컨텍스트는 종합 IoC 서비스를 제공해준다

애플리케이션 컨텍스트는 단지 오브젝트의 생성과 다른 오브젝트와의 관계설정만을 담당하지 않습니다. 오브젝트가 만들어지는 방식, 시점과 전략을 다르가 가져갈 수도 있고, 부가적으로 자동생성, 오브젝트에 대한 후처리, 정보의 조합, 설정 방식의 다변화, 인터셉팅 등 오브젝트를 효과적으로 사용할 수 있는 다양한 기능을 제공합니다.

애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다.

빈의 이름뿐 아니라, 타입을 통해 빈을 찾거나 특별한 어노테이션 설정이 되어 있는 빈을 찾을 수도 있습니다.

1.5.3 스프링 IoC의 용어 정리

스프링이 IoC 방식으로 관리하는 오브젝트라는 뜻이다. 애플리케이션에서 만들어지는 모든 오브젝트가 다 빈은 아니다.

빈 팩토리

1.6 싱글톤 레지스트리와 오브젝트 스코프

오브젝트의 동일성과 동등성

  • == 메모리에 하나만 올라옴
  • equals 개별적인 객체로 존재하지만 동등성 기준에 따라 같은 정보를 담고 있음을 뜻한다.

1.6.1 싱글톤 레지스트리로서의 애플리케이션 컨텍스트

  • 애플리케이션 컨텍스트는 팩토리 역할을 함과 동시에 싱글톤을 저장하고 관리하는 싱글톤 레지스트리이기도 하다.
  • 스프링은 기본적으로 별다른 설정을 하지 않으면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다.

서버 애플리케이션과 싱글톤

  • 왜 스프링은 싱글톤으로 빈을 만드는 걸까? 스프링이 주로 적용되는 대상이 자바 엔터프라이즈 기술을 사용하는 서버환경이기 때문이다. 서버환경에서 요청이 올 때 마다 객체를 생성하면 부하가 커지기 때문에 이를 피하고자 한다.
  • 스프링의 싱글톤은 디자인 패턴에서 말하는 싱글톤 패턴과 구현이 다르다.

싱글톤 (디자인)패턴의 한계

  • 싱글톤 패턴 구현을 위해 오브젝트가 담당하는 역할 이외의 코드들이 추가되어 코드가 조금 지저분해 진다.
  • 생성자를 private로 변경하였기 때문에 객체를 생성하면서 연관된 객체를 주입해 줄 수 없다.
  • private 생성자로 인해 상속이 불가능하다.

싱글톤 레지스트리

  • 스프링은 싱글톤 (디자인)패턴 구현과 달리 정적 메서드와 private 생성자를 사용하지 않는 평범한 자바 클래스를 싱글톤으로 만들어준다. 이것을 싱글톤 레지스트리라고 한다.
  • 스프링에 의해 관리되는 빈이더라도 싱글톤으로 사용되지 않아도 된다면 테스트를 위해 직접 객체를 생성해서 사용할 수 있다. 따라서 테스트 환경에서 자유롭게 오브젝트를 만들 수 있고, 테스트를 위한 Mock 오브젝트로 대체하는 것도 간단한다.
  • 싱글톤으로 사용돼야 하는 환경이 아니라면 간단히 오브젝트를 생성할 수도 있다. 따라서 테스트 환경에서 자유롭게 오브젝트를 만들 수 있고, 목 오브젝트로 대체하는 것도 간단하다. 생성자 파라미터를 이용해서 사용할 오브젝트를 주입할 수도 있다.

싱글톤과 오브젝트의 상태

  • 싱글톤은 멀티쓰레드 환경이라면 여러 쓰레드가 동시에 접근해서 사용할 수 있다. 따라서 상태 관리에 주의를 기울어야 한다. 무상태 방식으로 만들어져야 한다.⭐⭐

1.7 의존관계 주입(DI, Dependancy Injection)

1.7.1 제어의 역전(IoC)과 의존관계 주입

  • 의존하는 객체를 주입해 주는 것이 아니라 ‘의존하는 관계’를 주입하는 것임으로 의존관계 주입이라는 용어를 이 책에서는 사용한다.

1.7.2 런타임 의존관계 설정

의존관계

  • 의존 관계에는 방향이 존재한다.
  • A가 B에 의존한다는 것은 B가 변경되면 A가 영향을 받는다는 뜻이다. A의 변화는 B에 아무런 영향을 미치지 않는다.⭐
  • 의존의 유형에는 B의 인터페이스 형태가 변경될 수도 있고 내부구현이 변경됨으로 영향을 받을 수도 있다.
  • 구현 또는 모델링 관점의 의존관계와 런타임 의존 관계를 나누어 생각할 수 있어야 한다.⭐⭐

UserDao의 의존관계

  • 코드를 구현하는 관점 또는 모델링 관점에서 UserDao는 ConnectionMaker 인터페이스가 변하면 영향을 받는다. 그러나 ConnectionMaker 를 구현한 클래스의 변화에는 아무런 영향을 받지 않는다.
  • 의존관계 주입은 구체적인 의존 오브젝트와 그것을 사용하는 클라이언트 오브젝트를 런타임 시에 연결해주는 작업을 말한다.⭐
  • 의존관계 주입이란 다음과 같은 세 가지 조건⭐⭐을 충족하는 작업을 말한다.
    • 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야 한다.
    • 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.
    • 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.
  • 의존관계 주입의 핵심은 설계 시점에는 알지 못했던 두 오브젝트의 관계를 맺도록 도와주는 제3의 존재가 있다는 것이다.

UserDao의 의존관계 주입

  • 아래 코드의 문제는 이미 런타임 시의 의존관계가 코드 속(또한 설계 시점에)에 다 미리 결정되어 있다는 점이다.
public UserDao(){
	connectionMaker = new DConnectionMaker();
}
  • 그래서 IoC 방식을 써서 UserDao로부터 런타임 의존관계를 드러내는 코드를 제거하고, 제3의 존재에 런타임 의존관계 결정 권한을 위임한다.

1.7.3 의존관계 검색과 주입

  • 의존관계 검색은 자신이 필요로 하는 의존 오브젝트를 능동적으로 찾는다. 물론 자신이 어떤 클래스의 오브젝트를 이용할지 결정하지는 않는다. 그러면 IoC라고 할 수 없을 것이다.
  • 의존관계 주입 vs 의존관계 검색
    • 일반적으로 의존관계 검색을 사용하면 (UserDao 예로) 사용자의 DB 정보를 어떻게 가져올지 집중해야할 객체에서 스프링의 애플리케이션 컨텍스트 같은 객체를 생성해야 한다.
    • 그러나 의존관계 검색 방법을 사용해야 할 때도 있다. main 함수와 같은 경우.
    • 의존관계 검색을 하면 자신이 스프링 빈일 필요가 없다. 반면에 의존관계 주입을 사용하려면 UserDao는 반드시 스프링 빈으로 관리되어야 한다. ⭐
    • DI를 원하는 오브젝트는 먼저 자기 자신이 컨테이너가 관리하는 빈이 돼야 한다는 사실을 잊지말자.

1.7.4 의존관계 주입의 응용

  • UserDao가 ConnectionMaker라는 인터페이스에만 의존하고 있다는 건, ConnectionMaker를 구현하기만 한다면 어떤 오브젝트든지 사용할 수 있다는 뜻이다. 몇 가지 응용 사례를 생각해 보자.

기능 구현의 교환

  • 개발 중, 운영 중, QA 중 단 한 줄만 바꾸어 전체 코드의 동작을 바꾸어 줄 수 있다.

부가기능 추가

1.8 XML을 이용한 설정

1.8.4 프로퍼티 값의 주입

  • 텍스트나 단순 오브젝트 등을 수정자 메서드에 넣어주는 것을 스프링에서는 ‘값을 주입한다’⭐고 말한다. 이것도 성격은 다르지만 일종의 DI라고 볼 수 있다. 사용할 오브젝트 자체를 바꾸지는 않지만 오브젝트의 특성은 외부에서 변경할 수 있기 때문이다.

1.9 정리

스프링이란 ‘어떻게 오브젝트가 설계되고, 만들어지고, 어떻게 관계를 맺고 사용되는지에 관심을 갖는 프레임워크’라는 사실을 꼭 기억해두자. 스프링의 관심은 오브젝트와 그 관계다. 하지만 오브젝트를 어떻게 설계하고, 분리하고, 개선하고, 어떤 의존관계를 가질지 결정하는 일은 스프링이 아니라 개발자의 역할이며 책임이다.⭐⭐ 스프링은 단지 원칙을 잘 따르는 설계를 적용하려고 할 때 필연적으로 등장하는 번거로운 작업을 편하게 할 수 있도록 도와주는 도구일 뿐임을 잊지 말자.

어색한 문구 정리


  • p100 - 클라이언트 필요한 오브젝트를 가져오려면 → 클라이언트가 필요한 오브젝트를 가져오려면
  • p100 - 오브젝를 → 오브젝트를
profile
소프트웨어 엔지니어, 일상

0개의 댓글