공부/스프링프레임워크

[spring] 토비의스프링 정리

ghhong 2022. 6. 22. 17:05

1장 오브젝트와 의존관계

1.1 초난감 DAO
ㅇ 자바빈이라 함은 다음 두 가지 관례를 따라 만들어진 오브젝트를 가리킨다. 디폴트 생성자(자바빈은 파라미터가 없는 디폴트 생성자를 갖고 있어야 한다. 툴이나 프레임워크에서 리플렉션을 이용해 오브젝트를 생성하기 때문이다.), 프로퍼티(자바빈이 노출하는 이름을 가진 속성을 프로퍼티라고 한다. 프로퍼티는 setter,getter를 이용해 수정, 조회할 수 있다.)

1.2 DAO의 분리
1.2.1 관심사의 분리
ㅇ 오브젝트에 대한 설계와 이를 구현한 코드는 변한다. 소프트웨어 개발에서 끝이라는 개념은 없다. 요구사항은 끊임없이 바뀌고 발전한다. 그래서 개발자가 설계할 때 가장 염두에 둬야 할 것은 미래의 변화를 어떻게 대비할 것인가이다. 
ㅇ 변화는 몇 시간 후에도 발생할 수 있다. 객체지향 설계와 프로그래밍이 이전의 절차적 프로그래밍 패러다임에 비해 초기에 좀 더 많은, 번거로운 작업을 요구하는 이유는 객체지향 기술 자체가 지니는, 변화에 효과적으로 대처할 수 있다는 기술적인 특징 때문이다. 
ㅇ 따라서 우리는 분리와 확장을 고려한 설계를 해야한다. 모든 변경과 발전은 한 번에 한 가지 관심사항에 집중해서 일어난다. 그렇기에 우리는 한 가지 관심이 한 군데에 집중되게 해야한다. 즉 관심이 같은 것끼리 모으고, 관심이 다른 것은 분리한다. 

1.2.2 커넥션 만들기의 추출
ㅇ UserDao의 관심사항을 분석하여 getConnection메서드를 분리한다.
ㅇ 기능이 추가되거나 바뀐 것은 없지만 UserDao는 깔끔해졌고 변화에 좀 더 쉽게 대응할 수 있는 코드가 됐다. 이런 작업을 리팩토링이라고 한다.

1.2.3 DB커넥션 만들기의 독립
ㅇ 다른 DB를 사용하는 상황에선 어떻게 해야할까?
ㅇ 상속을 통한 확장 : getConnection메서드를 추상메서드로 구현한다. 후에 UserDao클래스를 상속받아서 각각 getConnection메서드를 원하는 방식대로 구현하여 사용한다.
ㅇ 이렇게 슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상메서드나 오버라이딩이 가능한 protected 메소드 등으로 만든 뒤 서브클래스에서 이런 메서드를 필요에 맞게 구현해서 사용하도록 하는 방법을 디자인 패턴에서 템플릿 메소드 패턴(template method pattern)이라고 한다. 
ㅇ 추상메서드를 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것을 팩토리 메서드 패턴(factory method pattern)이라고 부르기도 한다. getConnection메서드에서 생성하는 Connection오브젝트의 구현 클래스는 제각각이겠지만 UserDao는 Connetion인터페이스 타입의 오브젝트라는 것 외에는 관심을 두지 않는다. 그저 Connetion인터페이스에 정의된 메서드를 사용할 뿐이다.
ㅇ 디자인 패턴 : 디자인 패턴은 소프트웨어 설계 시 특정 상황에서 자주 만나는 문제를 해결하기 위해 사용할 수 있는 재사용 가능한 솔루션을 말한다. 간단히 패턴 이름을 언급하는 것만으로도 설계의 의도와 해결책을 함께 설명할 수 있다는 장점이 있다. 디자인 패턴은 대부분 객체지향 설계에 관한 것이고, 대부분 객체지향적 설계 원칙을 이용해 문제를 해결한다. 패턴의 설계 구조를 보면 대부분 비슷한데, 그 이유는 객체지향적인 설계로부터 문제를 해결하기 위해 적용할 수 있는 확장성 추구 방법이 대부분 두 가지 구조로 정리되기 때문이다. 하나는 클래스 상속이고 다른 하나는 오브젝트 합성이다.
ㅇ 템플릿 메서드 패턴 : 상속을 통해 슈퍼클래스의 기능을 확장할 때 사용하는 가장 대표적인 방법이다. 변하지 않는 기능은 슈퍼클래스에 만들어두고 자주 변경되는 확장할 기능은 서브클래스에서 만들도록 한다. 슈퍼클래스에는 미리 추상 메서드 또는 오버라이드 가능한 메서드를 정의해두고 이를 활용한다.
ㅇ 팩토리 메서드 패턴 : 슈퍼클래스 코드에서는 서브클래스에서 구현할 메서드를 호출해서 필요한 타입의 오브젝트를 가져와 사용한다. 이 메서드는 주로 인터페이스 타입으로 오브젝트를 리턴하므로 서브클래스에서 정확히 어떤 클래스의 오브젝트를 만들어 리턴할지는 슈퍼클래스에서는 알지 못한다.
ㅇ 하지만 이 방법은 상속을 사용했다는 단점이 있다. 만약 이미 UserDao가 다른 목적을 위해 상속을 사용했다면??? 단지 커넥션 객체를 가녀오는 방법을 분리하기 위해 상속구조로 만들어버리면 다른 목적을 위해 상속을 적용하기 힘들다.
ㅇ 또한 상속은 여전히 두 가지 다른 관심사에 대해 긴밀한 결합을 허용한다.

1.3 DAO의 확장
1.3.1 클래스의 분리
ㅇ 관심사가 다르고 변화의 성격이 다른 이 두 가지 코드를 확실히 분리해보자. 처음에는 독립된 메서드를 만들어서 분리했고(getConnection()), 다음에는 상하위 클래스로 분리했다(상속). 이번에는 완전히 독립적인 클래스로 만들어보자.

1.3.2 인터페이스의 도입
ㅇ 클래스를 분리해도 다른 DB커넥션을 가져오게 하기 위해 자바가 추상화를 위해 제공하는 가장 유용한 도구인 인터페이스를 사용한다. 
ㅇ 인터페이스는 어떤 일을 하겠다는 기능만 정의해놓은 것이다. 따라서 인터페이스에는 어떻게 하겠다는 구현 방법은 나타나 있지 않다. 그것은 인터페이스를 구현한 클래스들이 알아서 결정할 일이다.

1.3.3 관계설정 책임의 분리
ㅇ UserDao와 ConnectionMaker라는 두 개의 관심을 인터페이스를 써가며 거의 완벽하게 분리했는데도, UserDao가 인터페이스 뿐 아니라 구체적인 클래스까지 알아야 한다(connectionMaker = new DConnetionMaker()). 
ㅇ ==> UserDao의 클라이언트 오브젝트가 바로 제 3의 관심사항인 UserDao와 ConnectionMaker구현 클래스의 관계를 결정해주는 기능을 분리해서 두기에 적합한 곳이다.
ㅇ UserDao의 클라이언트에서 UserDao를 사용하기 전에, 먼저 UserDao가 어떤 ConnectionMaker의 구현 클래스를 사용할지를 결정하도록 만들자. 오브젝트 사이의 관계는 런타임 시에 한쪽이 다른 오브젝트의 레퍼런스를 갖고 있는 방식으로 만들어진다.
ㅇ 외부에서 만든 오브젝트를 전달받으려면 메서드 파라미터나 생성자 파라미터를 이용하면 된다. 파라미터 타입을 전달받을 오브젝트의 인터페이스로 선언해뒀다고 해보자. 이런 경우 파라미터로 전달되는 오브젝트의 클래스는 해당 인터페이스를 구현하기만 했다면 어떤 것이든지 상관없다. 단지 해당 인터페이스 타입의 오브젝트라면 파라미터로 전달 가능하고, 파라미터로 제공받은 오브젝트는 인터페이스에 정의된 메서드만 이용한다면 그 오브젝트가 어떤 클래스로부터 만들어졌는지 신경쓰지 않아도 된다.
ㅇ 이는 단지 오브젝트 사이에 다이내믹한 관계가 만들어지는 것이다. 클래스 사이의 관계 vs 오브젝트 사이의 관계 . 이 둘의 차이를 잘 구분해야 한다. 코드에서 특정 클래스를 전혀 알지 못하더라도 해당 클래스가 구현한 인터페이스를 사용했다면, 그 클래스의 오브젝트를 인터페이스 타입으로 받아서 사용할 수 있다. 바로 객체지향 프로그램에는 다형성이라는 특징이 있는 덕분이다.
ㅇ 모델링 시점의 클래스 다이어그램 , 런타임 시점의 오브젝트 다이어그램( 클래스 이름 앞에 :이 붙어있음). 런타임 시점에 클라이언트인 main메서드에서 책임을 갖고 UserDao에 connectionMaker오브젝트를 전달한다. 즉 책임을 클라이언트로 넘긴다.
ㅇ 이렇게 인터페이스를 도입하고 클라이언트의 도움을 얻는 방법은 상속을 사용해 비슷한 시도를 했을 경우에 비해서 훨씬 유연하다.

1.3.4 원칙과 패턴
ㅇ 개방 폐쇄 원칙(OCP, open closed principle) : 클래스나 모듈은 확장에는 열려 있어야 하고(개방), 변경에는 닫혀 있어야 한다(폐쇄).
ㅇ 높은 응집도와 낮은 결합도 : 응집도가 높다는 것은 하나의 모듈, 클래스가 하나의 책임 또는 관심사에만 집중되어 있다는 뜻이다. 낮은 결합도는 높은 응집도보다 더 민감한 원칙이다. 책임과 관심사가 다른 오브젝트 또는 모듈과는 낮은 결합도, 즉 느슨하게 연결된 형태를 유지하는 것이 바람직하다. 
ㅇ 전략 패턴(strategy pettern) : 전략 패턴은 디자인 패턴의 꽃이라고 불릴 만큼 다양하게 자주 사용되는 패턴이다. 개발 폐쇄 원칙의 실현에도 가장 잘 들어맞는 패턴이라고 볼 수 있다. 전략 패턴은 자신의 기능 맥락(context)에서, 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴이다. 여기서 알고리즘이란 독립적인 책임으로 분리가 가능한 기능을 뜻한다.

1.4 제어의 역전(IoC, Inversion Of Control)
1.4.1 오브젝트 팩토리
ㅇ UserDaoTest는 기존에 UserDao가 담당했던 어떤 ConnectionMaker를 사용할지를 결정하는 기능을 엉겁결에 떠맡았다. UserDaoTest는 테스트가 관심사이므로 이를 분리하자.
ㅇ 팩토리 : 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 돌려주는 오브젝트를 팩토리라고 부른다. 이는 오브젝트를 생성하는 쪽과 생성된 오브젝트를 사용하는 쪽의 역할과 책임을 깔끔하게 분리하려는 목적으로 사용한다.
ㅇ 설계도로서의 팩토리 : UserDao와 ConnectionMaker는 실질적인 로직을 담당하는 컴포넌트라면, DaoFactory는 애플리케이션을 구성하는 컴포넌트의 구조와 관계를 정의한 설계도 같은 역할을 한다고 볼 수 있다. 

1.4.2 오브젝트 팩토리의 활용
ㅇ 팩토리 내에 다른 DAO생성 기능을 추가했을 때 ConnectionMaker구현 클래스의 인스턴스가 반복돼서 나타나지 않게끔 팩토리 내부에 ConnectionMaker생성용 메서드를 이용하도록 수정한다.

1.4.3 제어권의 이전을 통한 제어관계 역전
ㅇ 기존의 프로그램 구조에서 각 오브젝트는 프로그램 흐름을 결정하거나 사용할 오브젝트를 구성하는 작업에 능동적으로 참여했다. 모든 오브젝트가 능동적으로 자신이 사용할 클래스를 결정하고, 언제 어떻게 그 오브젝트를 만들지를 스스로 관장한다. 모든 종류의 작업을 사용하는 쪽에서 제어하는 구조다.
ㅇ 제어의 역전이란 이런 제어 흐름의 개념을 거꾸로 뒤집는 것이다. 제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다. 당연히 생성하지도 않는다. 또 자신도 어떻게 만들어지고 어디서 사용되는지를 알 수 없다. 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문이다.
ㅇ 프로그램의 시작을 담당하는 main()과 같은 엔트리 포인트를 제외하면 모든 오브젝트는 이렇게 위임받은 제어 권한을 갖는 특별한 오브젝트에 의해 결정되고 만들어진다.
ㅇ 라이브러리를 사용하는 애플리케이션 코드는 애플리케이션 흐름을 직접 제어한다. 단지 동작하는 중에 필요한 기능이 있을 때 능동적으로 라이브러리를 사용할 뿐이다.
ㅇ 반면에 프레임워크는 거꾸로 애플리케이션 코드가 프레임워크에 의해 사용된다. 보통 프레임워크 위에 개발한 클래스를 등록해두고, 프레임워크가 흐름을 주도하는 중에 개발자가 만든 애플리케이션 코드를 사용하도록 만드는 방식이다.
ㅇ 제어의 역전에서는 프레임워크 또는 컨테이너와 같이 애플리케이션 컴포넌트의 생성과 관계설정, 사용, 생명주기 관리 등을 관장하는 존재가 필요하다. IoC를 애플리케이션 전반에 걸쳐 본격적으로 적용하려면 스프링과 같은 IoC프레임워크의 도움을 받는 편이 훨씬 유리하다.

1.5 스프링의 IoC