Reference
토비의스프링3.1(vol1)
책을 읽으며 정리 및 개인적 이해를 돕기 위한 추가적인 말들
객체지향 설계 원칙(SOLID)
객체지향의 특징을 잘 살리 수 있는 설계의 원칙” 을 말함.
- 원칙 → 절대적 기준 x → 가이드 라인 같은 것.
- 디자인 패턴이 좀 더, 특정한 상황의 문제에 대한 “ 구체적인 솔루션” 이다
- 객체지향 설계원칙은, “좀 더 일반적 상황에 적용가능”한 설계 기준.
- 디자인 패턴들에선, 객체지향 설계 원칙을 잘 지켜 만들어져 있겠다.
개방 폐쇄 원칙( OCP : Open Closed Pattern )
- 클래스나 모듈은 “확장에는 열려” 있고, “변경에는 닫혀” 있어야 한다.
예를들어 , 앞서 만든 UserDao 는 이제, “DB 연결방법 등의 기능을 확장” 할 수 있으면서도, UserDao 의 핵심 기능 구현 코드는 “그런 변화에 영향 받지 않고” 유지 할 수 있다.
- 특히, “인터페이스를 통해 제공되는 확장포인트 + 외부에서 주입 “의 형태로 많이 나타나는 것을 본 적이 있다.
높은 응집도, 낮은 결합도
앞선 관심사의 분리, 역할(책임)의 분리 결과 → 응집도가 높고, 결합도는 낮은 결과물을 만들 수 있다.
- 응집도가 높다 = 하나의 모듈,클래스가 “하나의 책임,관심사”에만 집중
- 하나의 공통 관심사는 , 하나의 클래스에 모여있다.
- 클래스, “패키지”, 컴포넌트, “모듈” 등, 다양한 크기에서 적용될 수 있다.
- 변화에 의해 “해당모듈” 에서 “변화하는 부분이 크다!!”
- 응집도가 높은 경우에는, 어떤 변화에 의해 변화하는 부분들이 크다.
- ex) 새로운 방식의 DB 커넥션을 위해, 새로운 EConnectionMaker 라는 구현체를 만든다.
- 새로 만든, 구현체에 대한 테스트만 해 줘도 충분하다
- 오히려 “일부분만 변화” 하는 것은, 부담이 있는 상황이다. 모듈의 “어느부분에서 변화가 일어나는지 파악” && “다른 부분에는 변화를 미치진 않는지” 확인해야 하기 때문.
- 응집도가 높은 경우에는, 어떤 변화에 의해 변화하는 부분들이 크다.
- 결합도가 낮다 = 책임, 관심사가 다른 객체or모듈 과는, 낮은 결합도(느슨하게 연결된 형태) 를 유지하는 것.
- 결합도 : 하나의 오브젝트가 변경이 일어날 때, 관계를 맺고 있는 다른 객체에게 변화를 요구하는 정도.
- 앞서, 인터페이스를 도입함으로서, 관심사가 다른 객체 사이의 추상화가 가능했다. 이를 통해 두 객체 사이에 느슨한 결합을 할 수 있었다.
- 두 객체는 간접적으로만 관계를 유지, 서로를 알 필요도 없게 만들어 주자.
- 결합도가 낮아지면, 확장하기에 편리하다.
- 결합도가 높아지면, 변경에 따른 작업량이 많아지고, 변경의 증가 → 버그의 증가
앞서 생성한 코드에선, ConnectionMaker 자체가, 응집력을 유지, 확장 되었고 — ConnectionMaker와 낮은 결합도를 가진 UserDao에는 영향을 주지 않음.
전략 패턴
OCP 의 실현에 가장 잘 들어맞는 패턴.
- 자신의 기능맥락에 따라, 필요에 따라 , “변경이 필요한 알고리즘” 을 “인터페이스를 통해 통째로 외부로 분리” 시키고, 구현체를, 필요에 따라 바꿔 끼울 수 있도록 하는 디자인 패턴.
- 알고리즘 : 독립적인 책임으로, 분리가 가능한 기능.
- 대체 가능한 전략이다.
즉, UserDao는, 전략패턴에서의 Context다. 필요한 Context에 따라, ConnectionMaker 구현체를 갖다 끼우는 모습이었다.
- Context를 사용하는 클라이언트는 → “Context가 사용할 전략(구현클래스)”를 Context생성자를 통해 제공( 생성자 주입) 하는게 일반적이다.
팩토리
(주의)디자인 패턴의 추상팩토리패턴,팩토리메소드패턴 과는 다르다.
- 객체의 생성방법을 결정하고, 이를 생성하여 리턴하는 클래스
ex) DaoFactory 를 통해, 특정한 ConnectionMaker 가 주입되는 UserDao 를 생성하자
왜 필요한가??
- 객체 생성방법 결정, 및 생성하여 리턴하는 “역할”을 맡음으로서, 해당 책임을 다른 코드들로부터 분리하였으며
- UserDao 에 주입할 구체 ConnectionMaker 클래스를 결정하고 생성한다는 것은
- 객체간의 관계 ( UserDao - 특정 ConnectionMaker ) 를 정의한다는 것과 같다
즉 설계도와 같은 역할을 한다
설계도
어떤 객체가 어떤 객체를 “사용”하는지 정의해놓은 설계도
- “객체”라고 했다. ( 클래스❌)
UserDao에서 다른 ConnectionMaker가 필요하게 되면,
- ConnectionMaker의 구현체를 추가 및 정의
- DaoFactory에서 해당 구현체를 생성 및, UserDao에 주입
해 주면된다.
즉, UserDao 코드에는 아무 변화가 없어도 된다.
제어권의 이전
일반적(?)인 모습: 어떤 객체에서 “자신이 사용할 클래스를 결정” 하고, “언제 그 객체를 만들지 스스로 관장” 하는 모습 ⇒ “사용하는 측” 에서, 작업의 흐름을 관장.
제어권의 이전?
- 제어의 흐름을 뒤집는다 : “사용하는측에서, 자신이 사용할 객체를 스스로 결정 ❌ , 생성 ❌ , 결정과 생성을 알지 못한다. 모든 제어권한은, 자신이 아닌 다른대상에 위임하고 있다.“
- ex_컨테이너안에서 동작하는 것들 : 컨테이너에서, 적절한 시점에 서블릿 객체를 생성, 그 안의 메소드 (doService(). doGet() 등) 을 호출한다. - 이는 우리가 제어할 수 있는게 아님. 우린 그저 어떤 서블릿을 등록할 수는 있겠다.
- 템플릿 메소드 패턴 :
- 제어권은 상위 클래스의 템플릿 메소드에 있다
- 서브클래스에서 특정 기능을 추가 및 구현할 뿐. 이 메소드는, 상위 템플릿 메소드에 의해 “호출될 뿐”이다.
- 제어권은 상위 클래스의 템플릿 메소드에 있다
프레임워크와 라이브러리
- 라이브러리
- 라이브러리를 사용한다 : application 코드가, 흐름을 직접 제어한다. 필요한 기능은 능동적으로 라이브러리 코드를 “사용” 할 뿐이다.
- 프레임워크 : 분명한 제어권의 역전의 모습이 보임
- 내가 짜는 application 코드가 “프레임워크에 의해 사용(수동적으로 동작)” 된다.
예
UserDao 에서 구현체를 결정하고 사용하던 상황 → UserDao도 팩토리에 의해 수동적으로 생성되고, UserDao에서 사용하는 객체도 팩토리에서 결정및생성 하는 “수동적인 존재” 가되었음.
- 관심을 분리, 책임을 나누고, 확장가능한 구조를 위해 팩토리를 도입한 과정이 IoC 적용 작업이 되었음.
- IoC 에서는 프레임워크, 컨테이너처럼 “ 컴포넌트의 생성, 관계설정,사용,생명주기 관리” 를 해주는 존재가 필요하다. ( 가장 심플한 버전이 UserDao 팩토리라 볼 수 있겠다 )
스프링의 IoC 객체 ApplicationContext = 빈 팩토리 = IoC 컨테이너 = 스프링 프레임워크
- Bean : 스프링이 제어권을 갖고, 직접 만들고 관계를 부여하는 객체.
- 즉, 제어의 역전이 적용된 객체임 : 스프링 컨테이너가 “생성,관계설정,사용” 등을 제어하니.
- 스프링에서 IoC 오브젝트 —>Application Context : 빈의 생성, 관계설정,사용등을 담당한다.
- BeanFactory (최상위 클래스)
- 를 상속하는 여러 클래스
- 들을 모두 상속한 —>즉, BeanFactory를 확장한 것 ⇒ Application Context ( Bean factory에 여러 기능들을 추가로가짐)
- 를 상속하는 여러 클래스
ApplicationContext 에서는 어떻게 생성할 빈을 결정하고,생성하고, 생성할 때 사용할 빈(생성자 주입에 사용할) 을 아는것인가????
- **별도의 설정정보를 참고!**한다. ( 요즘은 Java-based 설정정보를 많이 사용 예전에 xml 파일을 통해 등록 )
- 이런면에서**, ApplicationContext가 직접 이런 정보를 담고 있진 않고, 설정정보를 가져와 활용하니, “범용적인 IoC엔진** “ 이라 볼 수도 있다.
- 별도의 설정 정보(설정 메타정보 ) : 객체를 생성하고 관계를 맺어주는 정보.
어떤식으로 ApplicationContext 객체를 생성해 줄 수 있을까?
예시) @Configuration 이 붙은 설정정보 코드를 사용해 ApplicationContext만들기 → AnnotationConfigApplicationContext 를 생성 할 때, 설정정보 “클래스를 주입”한다.
@Configuration
publioc class DaoFactory{
@Bean
public ConnectionMaker NconnectionMaker(){return new NConnectionMaker();}
}
ApplicationContext context = new AnnotationApplicationContext(DaoFactory.class);
그냥 팩토리를 사용하면 되는 거 아니야?
아니다.
팩토리 사용시에는, Client에서는 이 “팩토리들에 대해서 알고 있어야” 했다.
- 이제는 클라이언트는 구체적인 팩토리 클래스에 대해 알 필요가 없다.
- 오브젝트 팩토리가 아무리 많아져도, ApplicationContext에만 해당 클래스들을 등록해주면 된다. Client에서는 이들을 알 필요도, 직접 사용할 필요도 없다.
- ApplicationContext를 통해서만, 일관된 방식으로 원하는 객체를 가져올 수 있다.
- 단순한 빈팩토리가 아니다
- 객체 생성,관계설정 뿐만 아니라, “만들어지는 방식”, “시점”, 스코프(전략) , 인터셉팅 등 객체를 효과적으로 활용하는 기능을 제공한다.
- 빈을 검색하는 다양한 방법
- 타입으로 검색
- 빈 이름으로 검색
- 특별한 어노테이션이 설정되어있는 빈 검색
싱글톤 레지스트리와 오브젝트 스코프
위에서, 그냥 팩토리를 사용할 때와 달리, “컨테이너에서 관리-생성하는 객체(빈) 의 스코프” 와 같은 부가적인 관리 기능이 존재한다 하였음.
특히 ApplicationContext 는 기본적으로 “ 싱글톤 레지스트리” 이다.
- 다른 설정을 하지 않으면, 내부에서 생성하는 모든 빈 객체는 “ 싱글톤”으로 만들어진다.
왜?? 싱글톤으로 빈을 만들까??
스프링이 적용되는 주 대상이 “자바 엔터프라이즈 기술을 사용하는 서버환경” 이기 때문이다. 이 경우, 서버 하나는, 한 번에 수백건 이상의 요청을 처리해야 한다.
그런데 요청이 올 때 마다, 각 로직을 담당하는 객체를 “새로 만들어” 사용한다면??? 아무리 GC의 성능이 좋다한들, 감당하기 힘들다. OOME가 나고 말 것이다.
따라서 , 엔터프라이즈 분야에서는 “서비스 객체” 라는 개념을 사용해왔다.
이들을 어떤식으로 관리한다는 것일까?
- EX) 서블릿 컨테이너를 생각해 보자. 서블릿은 대부분 멀티스레드 환경에서 “싱글톤”으로 동작한다.
- 서블릿 “클래스” 당, 하나의 객체만 만들어둔다. 사용자의 요청 하나당 스레드 풀에서 하나의 스레드를 할당해준다. 그러면, 여러 스레드(요청)들에서, 하나의 서비스 를 사용하려고 하는 경우 → 매 번, 새로운 서비스 객체를 생성하지 않고, 서비스 로직을 가진 “싱글톤 서비스 객체” 를 공유해, 동시에 사용하게 된다.
서버환경에선, 서비스 싱글톤의 사용이 권장된다.
참고로, 싱글톤 패턴(디자인패턴)에 대해서는 말이 많음
고전적 싱글톤 패턴 문제점
순수 JAVA언어로 구현하는 싱글톤 패턴.
상속과 다형성의 적용이 불가능
- private 생성자 → 상속이 불가능하다
- 상속이 불가능하다는 것 → 다형성을 적용할 수 없다.
- static 필드,메소드 —> 객체지향의 특징이 적용되지 않는다.
테스트하기 힘들다
- 만들어지는 방식이 제한적 → Mock 객체로 대체하기가 힘들다.
- DI 해 주는 객체를 Mock객체로 만드는 경우가 많은데.
- 이를 못해줌.
- 엔터프라이즈 개발에서는, 테스트가 중요한데, 싱글톤으로 만들면, 테스트 를 만드는데 지장이 있다.
서버 환경에선 하나만 생성되는 것이 보장 x
클래스 로더 구성에 따라, 하나 이상의 객체가 생성되기도 한다.
여러개의 JVM에 분산되어 설치되는 경우에도, 독립적 객체가 생성되어 , 싱글톤으로서 가치가 떨어진다
- ❓❓❓ 이거 프록시로 해결하는 것으로 알고 있는데, 프록시를 쓰면, 해결이되나? → ApplicationContext 내에 이미 존재하면 생성 안하니까.,프록시에서 규제를 가하니까 해결 될 듯 하다.
전역상태는 객체지향적으로 바람직하지 못함.
싱글톤의 static 메소드(팩토리메소드) 를 이용해, 언제든 싱글톤에 접근할 수 있어, 전역상태로 사용되기 쉽다.
- 전역 상태를 갖는 것은, 객체지향 프로그래밍에서 권장되지 않는 방식.
싱글톤 레지스트리 = Application Context = IoC 컨테이너
자바의 기본적 싱글턴 패턴 구현방식은 여러 단점을 가짐.
스프링은, 직접 “싱글톤 형태” 의 객체를 생성, 관리하는 기능을 제공.
- 여기서, 싱글톤 형태의 객체들은,
- static 메소드, 필드, private 생성자가 아닌, 평범한 자바 클래스도 싱글톤으로 활용할 수 있게 해준다. ( 개발자가 직접 싱글톤 형식으로 객체를 짜줄 필요도 없다 )
- 객체 생성에 관한 모든 권한은 Application context에게 있다.
- public 생성자를 가질 수 있게 되엇으니 → 이 객체를 생성하여, Test에서 Mock객체로 주입하는 것도 가능해짐!!
싱글톤과 객체의 상태
싱글톤 객체라는 것은 여러 스레드가 동시에 접근해서 사용가능한 공유객체다.
- 따라서 항상, 동시성 상황에 주의해야하고,
- 이를 위해 “상태 관리” 가 중요하다. ⇒ Stateless 방식으로 설계해야함.
Stateless가 뭐져?
- Stateful 방식: 인스턴스 필드의 값을 변경하고 유지하는 방식
- 메소드에 의해, 인스턴스 필드의 값을 변경한다면 Stateful하다고 말하겠다.
- 인스턴스 필드가 있다고 Stateful한건 아님.
- 읽기 전용값인 경우
- 진짜 아예 상수일 수도(static final 이나 final) 있고
- MemberService meberservice; 처럼.. Interface 타입으로 두고, 싱글콘 객체를 주입 받는 경우 → 이 주입받는 객체도 “스프링이 관리하는 싱글톤 빈” 일 테니. 즉,이 객체에서 사용하려는 “다른 싱글톤 빈”을 저장하는 경우엔 인스턴스 필드를 사용하기도 한다.
- 읽기 전용값인 경우
- 어떻게 Stateless하게만들까?
- 파라미터, 로컬변수, 리턴 값을 사용하자
- 메소드 or 파라미터, 로컬변수는 매 번 이들을 저장할 독립적 공간이 만들어지기에, 싱글톤이라 해도, 여러 스레드에서 동시에 같은 메모리에 접근 할 일은 없다.
- 파라미터, 로컬변수, 리턴 값을 사용하자
빈 스코프
빈 스코프
- 빈이 생성되고 ,존재, 적용되는 범위
- 스프링 빈의 default : 싱글톤
- 스프링 컨테이너가 존재하는 동안 계속 유지된다.
- 다른 스코프?
- ex) 프로토타입 스코프 : 컨테이너는 “빈 요청을 받을 때 마다” 새로운 객체를 생성해준다.
'책 > 토비의스프링' 카테고리의 다른 글
ch02 : 테스트의 필요성 (0) | 2022.02.20 |
---|---|
DI , 왜 생성자 주입을 하라는 걸까? (0) | 2022.02.16 |
관심사의 분리 ( ~1.3.3) (0) | 2022.02.09 |