public Service getService() {
if (service == null) {
service = new MyServiceImpl(...);
return service;
}
이것은 초기화 지연 혹은 계산 지연이라는 기법이다. 실제로 필요할 때 까지 객체를 생성하지 않으므로 불필요한 부하가 걸리지않고, null 포인터를 반환하지 않는 장점이 있다.
하지만 getService 메서드가 MyServiceImpl 생성자 인수에 명시적으로 의존한다. 런타임 로직에서 MyServiceImpl을 사용하지 않아도 의존성을 해결하지 않으면 컴파일이 안된다.
테스트도 문제다. 일반 런타임 로직에다 객체 생성 로직을 섞어 놓은 탓에 모든 실행 경로도 테스트 해야 한다.
무엇보다 MyServiceImpl이 모든 상황에 적합한 객체인지 모른다.
시스템 생성과 시스템 사용을 분리하는 한 가지 방법으로, 생성과 관련한 코드는 모두 main이나 main이 호출하는 모듈로 옮기고, 나머지 시스템은 모든 객체가 생성되었고 모든 의존성이 연결되었다고 가정한다.
모든 화살표가 main쪽에서 애플리케이션 쪽을 향한다. 즉, 애플리케이션은 main이나 객체가 생성되는 과정을 전혀모른다는 뜻이다.
때로는 객체가 생성되는 시점을 애플리케이션이 결정할 필요도 생긴다. 이때는 ABSTRACT FACTORY 패턴을 사용한다. 여기서도 의존성이 main에서 OrderProcessing 애플리케이션으로 향한다. 즉, 애플리케이션은 구체적인 생성 방법을 모른다. 그럼에도 OrderProcessing 애플리케이션은 인스턴스가 생성되는 시점을 완벽하게 통제한다.
사용과 제작을 분리하는 강력한 메카니즘 중 하나가 의존성 주입이다. 의존성 주입은 제어 역전 기법을 의존성 관리에 적용한 메커니즘이다. 제어 역전에서는 한 객체가 맡은 보조 책임을 새로운 객체에게 전적으로 떠넘긴다. 새로운 객체는 넘겨받은 책임만 맡으므로 단일 책임 원칙을 지키게 된다. 의존성 관리 맥락에서 객체는 의존성 자체를 인스턴스로 만드는 책임은 지지 않는다. 대신에 이런 책임을 다른 '전담' 메커니즘에 넘겨여만 한다. 그렇게 함으로써 제어를 역전한다. 초기 설정은 시스템 전체에서 필요하므로 대게 '책임질' 메커니즘으로 'main' 루틴이나 특수 컨테이너를 사용한다.
DI 컨테이너는 요청이 들어오면 필요한 객체의 인스턴스를 만든 후 생성자 인수나 생성자 메서드를 사용해 의존성을 설정한다. 실제로 생성되는 개체 유형은 설정 파일에서 지정하거나 특수 생성 모듈에서 코드로 명시한다.
대다수 DI 컨테이너는 필요할 때까지는 객체를 생성하지 않고, 대부분은 계산 지연이나 비슷한 최적화에 쓸 수 있도록 팩토리를 호출하거나 프록시를 생성하는 방법을 제공한다.
AOP는 횡단 관심사에 대처해 모듈성을 확보하는 일반적인 방법론이다. AOP에서 관점이라는 모듈 구성 개념은 "특정 관심사를 지원하려면 시스템에서 특정 지점들이 동작하는 방식을 일관성있게 바꿔야 한다"라고 명시한다. 명시는 간결한 선언이나 프로그래밍 메커니즘으로 수행한다.
영속성을 예로 들면, 프로그래머는 영속적으로 저장할 객체와 속성을 선언한 후 영속성 책임을 영속성 프레임워크에 위임한다. 그러면 AOP 프레임워크는 대상 코드에 영향을 미치지 않는 상태로 동작 방식을 변경한다. 자바에서 사용하는 관점 혹은 관점과 유사한 메커니즘 세 개를 살펴보자.
자바 프록시는 단순한 상황에 적합하다. 개별 객체나 클래스에서 메서드 호출을 감싸는 경우가 좋은 예다. 하지만 JDK에서 사용하려면 CGLIB, ASM, Javassist 등과 같은 바이트 코드 처리 라이브러리가 필요하다.
르록시는 코드의 양이 많아지고 깨끗한 코드를 작성하기 어렵다.
스프링 AOP, JBoss AOP 등과 같은 여러 자바 프레임워크는 내부적으로 프록시를 사용한다.
스프링은 비즈니스 로직을 POJO로 구현한다. POJO는 순수하게 도메인에 초점을 맞춘다. POJO는 엔터프라이즈 프레임워크에 의존하지 않는다. 따라서 테스트가 개념적으로 더 쉽고 간단하다.
Bank 도메인 객체는 자료 접근자 객체 (Data Accessor Object, DAO)로 프록시 되었으며, 자료 접근자 객체는 JDBC 드라이버 자료 소스로 프록시되었다.
클라이언트는 Bank 객체에서 getAccounts()를 호출한다고 믿지만 실제로는 Bank POJO의 기본 동작을 확장한 중첩 DECORATOR 객체 집합의 가장 외곽과 통신한다. 필요하다면 트랜잭션, 캐싱 등에도 DECORATOR를 추가할 수 있다.