토비의 스프링 1장을 읽으며

김다혜·2020년 3월 8일
0

토비의 스프링

목록 보기
1/5
post-thumbnail

1장은 '오브젝트와 의존관계'에 대해 다루고 있다. 스프링이 관심을 갖는 대상오브젝트의 설계와 구현, 동작원리에 더 집중해보자.
(책에 대한 내용과 나의 생각과 추가적으로 공부한 내용에 대해 다루고자 한다_지극히 나를 위한 정리😎 진행되며 코드의 문제 사항이 나오는데, 문제점 앞에 👿 이모지를 붙이도록 하겠다. 그리고, 앞에 문제점을 개선한 부분에 대해서는 😇 이모지를 붙이겠다.)
1장은 초난감 DAO를 시작으로 코드를 리팩토링하면서 보다 쉽게 설명해준다. 사이사이에 나오는 추가 설명에 집중해보자.

🤦‍♀️ 초난감 DAO 리팩토링

리팩토링이란 겉으로 동작하는 기능은 그대로, 코드 구조와 구현 방법을 바꿈으로써 더 나은 코드로 만드는 것

초난감 DAO 코드
위의 코드를 보면 아래와 같은 여러 관심사항이 한 코드에 존재한다.

  • '커넥션을 어떻게 가져올지?'에 대한 관심
  • DB에 보낼 sql을 만들고 실행하는 관심
  • 리소스를 반환하는 관심

👿 이렇게 한 코드에 여러 관심사항이 존재하는 것은 많은 중복 코드를 야기하며, 훗날 변경이 일어나게 된다면 놓치는 부분이 생길 수 있으며 굉장히 지저분한 코드가 된다.

변화는 먼 미래에만 있는 게 아니다. 며칠 내에, 때론 몇 시간 후에 변화에 대한 요구가 갑자기 발생할 수 있다.

관심사의 분리

관심이 같은 것끼리는 하나의 객체 안으로 모이게 하는 것. 우리는 관심이 한 군데에 집중되게 하여, 서로 영향을 주지 않게 해야 한다. 변화도 한 곳에서 일어나게 해야 한다.

중복 코드의 메소드 추출 (a.k.a. 메소드 추출 기법)

공통의 기능을 담당하는 메소드로 중복된 코드를 뽑아내는 것
메소드 추출 기법 코드

😇 독립적인 메소드로 추출함으로 인해 한가지 관심에 대한 변경이 일어날 경우 관심이 집중되는 코드만 수정하면된다.

상속을 통한 확장

🤔 위의 코드를 여러 업체에 제공해야 하고, userDao의 코드는 업체에 제공하고 싶지 않다고 가정해보자.

상속을 통한 확장 코드

😇 DB 커넥션에 대한 관심을 추상 메소드로 제공함으로써, userDao에 대한 코드를 제공하지 않고, 커넥션이라는 관심에 대해 손쉽게 수정할 수 있게 된다.
즉, userDao어떤 기능을 사용한다는 데에만 관심이 있고, 서브클래스에서 어떤 커넥션을 제공하는지에 대한 관심을 갖는다는 것이다.

👿 상속을 사용했다는 것엔 여러 단점이 존재한다.

  • 다중 상속이 불가능하다.
  • 상위 메소드가 수정되면 하위 메소드의 수정이 불가피한 경우가 있다. (너무 밀접하다!)
  • 해당 DB 커넥션을 생성하는 코드를 다른 dao 클래스에 적용할 수 없다.

위의 코드에서는 템플릿 메소드 패턴팩토리 메소드 패턴에 대한 개념이 들어가 있으니 아래 그림과 함께 참고하자.

클래스의 분리

클래스의 분리 코드 - 독립적인 클래스로 만들어보자
😇 많은 단점이 있던 상속이 제거되었다.
👿 위의 코드에서도 여러 단점이 존재한다.

  • UserDao는 DB 커넥션을 가져오는 구체적인 방법에 종속되어 있다. (userDao가 DB 커넥션에 대한 정보를 너~무 많이 알고있다.) 즉, userDao의 수정이 불가피해졌다.
	simpleConnectionMaker = new SimpleConnectionMaker();
  • 메소드명이 변경되어서는 안된다. (메소드명이 명확히 정의되어 있지 않다. 여러 업체에서 DB 커넥션을 위해 어떤 메서드를 구현해야 할지..)

인터페이스의 도입

인터페이스의 도입 코드

추상화라는 개념이 등장하고, 추상화를 제공하는 가장 유용한 도구는 인터페이스라고 설명한다. 인터페이스를 사용함으로써, 사용하는 곳에서는 구체적인 클래스를 알지 않아도 된다.

😇 인터페이스를 통해 오브젝트에 접근하므로 구체적인 클래스 정보를 알 필요가 없다. (private ConnectionMaker connectionMaker) 또한, 인터페이스에 정의된 메소드를 사용하므로 클래스가 바뀐다고 해도 메소드 이름이 변경될 걱정은 없다.
👿 여전히 UserDao는 DB 커넥션을 가져오는 구체적인 방법에 종속되어 있다.

public UserDao() {
	connectionMaker = new DConnectionMaker();
}

관계설정 책임의 분리

관계설정 책임의 분리 코드
🤔 connectionMaker = new DConnectionMaker();라는 코드는 매우 짧고 간단하지만 그 자체로 충분히 독립적인 관심사를 담고 있다. 관계를 설정해주는 것에 대한 관심이다.
런타임 오브젝트 관계를 갖는 구조를 클라이언트의 책임으로 옮겨보자.

😇 인터페이스 도입으로 더 깔끔해고 유연해졌으며, 서로 영향을 주지 않으면서도 필요에 따라 자유롭게 확장할 수 있는 구조가 됐다.
즉, userDao는 자신의 관심사(데이터 엑세스 작업)를 실행하는 데에만 집중할 수 있게 되었다.

👿 UserDaoTest는 사실 테스트를 하려고 만든 것이다. 결국 테스트와 관계설정의 책임까지 떠맡게 되었다.

✔️ 위의 코드는 오브젝트 사이에 다이내믹한 관계가 만들어지며 추상화가 적용되었다.

✔️ UserDao는 DB 연결 방법이라는 기능을 확장하는 데는 열려 있고, 자신의 핵심 기능을 구현한 코드는 그런 변화에 영향받지 않고 유지할 수 있으므로 변경에는 닫혀있다. (개방폐쇄원칙 )

✔️ UserDao는 자신의 책임에 대한 응집도가 높고, ConnectionMaker와 인터페이스를 통해 매우 느슨하게 연결되어 있다.(높은 응집도와 낮은 결합도)

✔️ 전략 패턴도 확인할 수 있다. 컨텍스트(UserDao)를 사용하는 클라이언트 (UserDaoTest)는 컨텍스트가 사용할 전략(ConnectionMaker를 구현한 클래스, 예를 들어 DConnectionMaker)을 컨텍스트의 생성자 등을 통해 제공한다.

팩토리 메소드 적용

오브젝트 팩토리 적용 코드
👉 실질적인 로직을 담당하는 컴포넌트(데이터 로직 or 기술 로직)에서 오브젝트들의 관계를 정의하는 책임을 분리했다.

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

👿 UserDao가 아닌 다른 DAO의 생성 기능을 넣는다면 ? 많은 중복이 일어날 것이다.(new UserDao(new DConnectionMaker()) new AccountDao(new DConnectionMaker())와 같이..)

✔️ 이 코드는 단순하지만 제어의 역전이 일어난 상황이다. 덕분에 유연성과 확장성이 좋아진다.

스프링의 IoC 적용

오브젝트 팩토리스프링이 관리하는 오브젝트로 변경하여 사용해보자.

👿 앞에서 만든 DaoFactory를 만들어서 바로 사용한 것보다 좀 더 번거로운 준비 작업과 코드가 필요하다. 또한, UserDao는 구체적인 DConnectionMaker라는 구체적인 클래스의 존재를 알고 있다.

😇 하지만, 스프링을 사용하게되면, 오브젝트 팩토리로 직접 사용했을 때와 애플리케이션 컨텍스트를 사용했을 때 얻을 수 있는 장점은 많다.

  • 일관된 방식으로 원하는 오브젝트를 가져올 수 있다. (애플리케이션 컨텍스트에서 가져오는 방식)
  • 빈을 검색하는 다양한 방법을 제공한다. (타입으로도 가능)

IoC프레임워크라고 불리는 스프링 자세히 보기

의존관계 주입

의존관계 주입 자세히 보기

😇 런타입 클래스에 대한 의존관계가 나타나지 않고, 인터페이스를 통해 결합도가 낮은 코드를 만들므로, 유연성 좋은 코드가 되었다.

🙋‍♀️ 추가로 알아두자

추상화 vs 정보 은닉

추상화는 interface만 노출하고, 정보를 은닉하는 것
정보 은닉은 상태를 숨기는 것

클래스 사이의 관계가 만들어진다는 것 vs 오브젝트와 오브젝트의 관계

클래스 사이에 관계가 만들어진다는 것은 한 클래스가 인터페이스 없이 다른 클래스를 직접 사용한다는 것
오브젝트와 오브젝트의 관계런타임 시에 다른 오브젝트의 레퍼런스를 갖고 있는 방식으로 만들어지는 것

디자인 패턴

소프트웨어 설계 시 특정 상황에서 자주 만나는 문제를 해결하기 위해 사용할 수 있는 재사용 가능한 솔루션

템플릿 메소드 패턴

🙋‍♀️ 간단히 말하면 로직을 바꿔 끼우는 것이다.
🙋‍♀️ 고정된 템플릿에 변하는 메소드를 넣는 것이라고 생각하자.
슈퍼클래스에서 기본적인 로직의 흐름(변하지 않는 흐름)을 만들고, 기능의 일부(확장될 기능)추상 메소드로 만들어서 서브클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법

즉, 상속을 통해 슈퍼클래스의 기능을 확장할 때 사용하는 가장 대표적인 방법이다.

팩토리 메소드 패턴

🙋‍♀️ 간단히 말하면 생성을 바꿔 끼우는 것이다.
🙋‍♀️ 객체를 만들어내는 부분을 서브클래스에 위임하는 것이다. (서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 방법)
상속을 통해 기능을 확장하게 하는 패턴

팩토리 메소드팩토리 메소드 패턴의 팩토리 메소드는 의미가 다르다.
팩토리 메소드는 자바에서 일반적으로 오브젝트를 생성하는 기능을 가진 메소드를 부른다.
팩토리 메소드 패턴의 팩토리 메소드는 서브클래스에서 오브젝트 생성 방법과 클래스를 결정할 수 있도록 미리 정의해둔 메소드를 의미한다.
"팩토리 메소드 패턴의 팩토리 메소드는 꼭! 상속이 있어야하며, 팩토리 메소드라고 불릴 수 있고, 패토리 메소드는 더 넓은 개념이다."

전략 패턴

🙋‍♀️ 변하지 않는 것(context)에서 변하는 것(전략)을 분리하는 것
자신의 맥락(context)에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 한다.

✔️ 바뀔 수 있는 쪽의 클래스는 인터페이스를 구현하도록 하고, 다른 클래스에서는 인터페이스를 통해서만 접근

개방 폐쇄 원칙(OCP)

클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.

✔️ 자신이 사용하는 외부 오브젝트의 기능은 자유롭게 확장하거나 변경할 수 있게 만든다.

객체지향 설계의 원칙(SOLID)

다음 포스팅을 통해 한번에 정리하겠다. 링크 첨부할 예정! 😄

높은 응집도와 낮은 결합도

응집도가 높다는 것은 하나의 모듈, 클래스에 하나의 책임 또는 관심사에만 집중되어 있다는 것이고, 변화가 일어날 때 해당 모듈에서 변하는 부분이 크다는 것이다.
결합도가 낮다는 것느슨한 연결로 유지하는 것이고, 여타 모듈과 객체로 변경에 대한 요구가 전파되지 않는 것을 말한다.

✔️ 한쪽의 기능 변화가 다른 쪽의 기능 변경을 요구하지 않아도 되게 했고, 자신의 책임과 관심사에만 순수하게 집중할 수 있다.

제어의 역전(IoC)

🙋‍♀️ 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다
프로그램의 제어 흐름 구조가 뒤바뀌는 것
제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다.
제어권을 상위 템플릿 메소드에 넘기고 자신은 필요할 때 호출되어 사용되도록 한다는 개념

오브젝트 팩토리 (팩토리)

이 클래스의 역할은 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 돌려주는 것

라이브러리 vs 프레임워크

프레임워크는 제어의 역전 개념이 적용된 대표적인 기술이다.
라이브러리를 사용하는 어플리케이션 코드는 어플리케이션 흐름을 직접 제어한다. 단지 동작하는 중에 필요한 기능이 있을 때, 능동적으로 사용할 뿐이다.
프레임워크는 어플리케이션 코드가 프레임워크에 의해 사용된다. 프레임워크가 흐름을 주도하는 중에 개발자가 만든 어플리케이션 코드를 사용하도록 만드는 방식이다.

스프링의 IoC_IoC프레임워크라고 불리는 스프링

🙋‍♀️ 스프링에서는 결국 애플리케이션 컨텍스트에서 빈 생성을 맡아 해주기 때문에, 오브젝트 스스로 생성할 필요가 없다는 것 ! 제어의 역전이다 !

    • 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트 (스프링이 직접 생성과 제어를 담당)
    • 즉, 스프링이 IoC 방식으로 관리하는 오브젝트
  • 빈 팩토리
    빈을 생성하고 관계를 설정하는 IoC의 기본 기능에 초첨을 맞춘 것 (빈을 등록, 생성, 조회하고 돌려주는 기능을 담당)
  • 애플리케이션 컨텍스트
    • 빈 팩토리를 확장한 IoC 컨터에너
    • 애플리케이션 전반에 걸쳐 모든 구성요소의 제어 작업을 담당하는 IoC 엔진이라는 의미가 더 부각된 것
    • 별도의 정보를 참고해서 빈(오브젝트)의 생성, 관계설정 등의 제어 작업을 총괄한다.
    • 생성 정보와 연관관계 정보를 별도의 설정정보를 통해 얻는다. (xml)
    • 빈을 검색하는 다양한 방법을 제공한다. (빈 검색 간편)

스프링의 애플리케이션 컨텍스트오브젝트 팩토리는 중요한 차이점이 있다.
애플리케이션 컨텍스트는 매번 같은 오브젝트를 return 한다는 것이고, (스프링은 여러 번에 걸쳐 빈을 요청하더라도 매번 동일한 오브젝트를 돌려준다.) 오브젝트 팩토리는 다른 오브젝트를 return 한다는 것이다.

싱글톤 레지스트리

🙋‍♀️ 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다. 그것이 바로 싱글톤 레지스트리다. 애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리이다.
🤔 왜 스프링은 싱글톤으로 빈을 만드는 것일까?
💁‍♀️ 매번 클라이언트에서 요청이 올 때마다 각 로직을 담당하는 오브젝트를 새로 생성해서 사용한다고 생각해보자. 굉장히 많은 리소스와 시간이 소요될 것이다.

🤔 싱글톤 패턴이 무엇인가 ?
💁‍♀️ 애플리케이션 안에 제한된 수, 대개 한 개의 오브젝트만 만들어서 사용하는 것
💁‍♀️ 싱글톤 패턴 구현 방식에는 한계가 존재한다.

  • 싱글톤 클래스 자신만이 자기 오브젝트를 만들도록 제한하기 때문에, private constructor을 가진다. 따라서, 상속이 불가능 하다.
  • 테스트 하기 어렵다. 다이나믹하게 주입하기 힘들기 때문이다.

🙋‍♀️ 매번 새로운 값을 담는 인스턴스 변수는 사용하지 말자. 공유되어 사용되기 때문에 위험하다. 단순한 읽기 전용이라면 final키워드를 사용하자.

싱글톤 레지스트리는 private 생성자를 사용하지 않아도 평범한 자바 클래스를 싱글톤으로 활용하게 해준다 👏

DI (의존관계 주입)

DI는 오브젝트 레퍼런스를 외부로부터 제공(주입)받고 이를 통해 여타 오브젝트와 다이내믹하게 의존관계가 만들어지는 것

😇 코드 상으로는 의존관계가 나타나지 않으며, 인터페이스를 통해 결합도가 낮아지는 코드를 만들게된다. 의존관계 대상이 바뀌더라도 자신은 영향 받지 않으며, 변경을 통한 다양한 확장 방법에는 자유롭다.

단지, 외부에서 파라미터로 오브젝트를 넘겨줬다고 해서, 즉 주입했다고 해서 다 DI가 아니라는 것을 주의하자.
DI에서 말하는 주입은 다이내믹하게 구현 클래스를 결정해서 제공받을 수 있도록 인터페이스 타입의 파라미터를 통해 이루어져야 한다.

의존관계 검색 vs 의존관계 주입

의존 관계 검색 방식에서는 검색하는 오브젝트 자신은 스프링의 빈일 필요가 없다는 것, 반면 의존관계 주입 방식에서는 DI가 적용되려면 주입받는 쪽에도 반드시 빈 오브젝트여야 한다.

마치며

스프링이란 '어떻게 오브젝트가 설계되고, 만들어지고, 어떻게 관계를 맺고 사용되는지에 관심을 갖는 프레임워크'라는 사실을 꼭 기억해두자. 스프링의 관심은 오브젝트와 그 관계다.

스프링을 사용한다고 좋은 객체지향 설계와 깔끔하고 유연한 코드가 저절로 만들어질까? 그건 절대 아니다. 개발자의 책임이다. 다만, 스프링은 그런 좋은 설계와 코드를 적용하고자 할 때 좋은 동반자가 되어줄 것이다.

0개의 댓글