Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Digest] 토비 스프링 3챕터에서 공부한 것 정리 #6

Open
zeroFruit opened this issue Jan 19, 2020 · 1 comment
Open

[Digest] 토비 스프링 3챕터에서 공부한 것 정리 #6

zeroFruit opened this issue Jan 19, 2020 · 1 comment
Labels
Digest This issue is about digest

Comments

@zeroFruit
Copy link
Member

zeroFruit commented Jan 19, 2020

Topic

토비 스프링 3챕터에서 개인적으로 좋았던 부분 + 배웠던 부분 정리해서 공유하고자 합니다.

OCP 원리를 좀 더 구체적이고 실용적인 예제로 잘 보여준 챕터라고 생각합니다.

성격이 다른 코드들은 가능한 분리하는 것이 낫지만 반대로 하나의 목적을 위해 서로 긴밀하게 연관되어 동작하는 응집력이 강한 코드들은 한 군데 모여있는 것이 유리하다. 구체적인 구현과 내부의 전략 패턴, 코드에 의한 DI 등 기술적인 것은 한 클래스 내에 감춰두고 외부에는 꼭 필요한 기능을 단순하게 제공한다.

저는 이 문장이 이 챕터의 핵심같아 보이네요.

Detail

메소드 추출

흔히 리팩토링을 한다고 할 때 쉽게 생각할 수 있는 방법 중 하나가 '메소드 추출'이지 않나 싶다. 책에서도 첫 번째로 시도한 방법이 '메소드 추출'인데 별 이득이 없다고 서술하고 있다.

메소드 추출 리팩토링을 적용하는 경우에는 분리시킨 메소드를 다른 곳에 재사용할 수 있어야 한다. 책에서는 추출을 하고 나니 오히려 재사용이 필요한 부분이 그대로 남아있고 변하면서 확장되어야할 부분이 추출되어서 목적이 뒤바뀌어 버렸다.

public class CafeService {
	public Coffee makeCoffee() {
		try {
			Coffee coffee = CoffeeFactory.getCoffee();
			return coffee;
		} catch (Mistake e) {
			makeRefund();
		}
	}
	
	public HoneyBread makeHoneyBread() {
		try {
			HoneyBread honeyBread = HoneyBreadFactory.getHoneyBread();
			return honeyBread;
		} catch (Mistake e) {
			makeRefund();
		}
	}
}
public class CafeService {
	public Coffee makeCoffee() {
		try {
			return getCoffee();
		} catch (Mistake e) {
			makeRefund();
		}
	}
	
	public HoneyBread makeHoneyBread() {
		try {
			return getHoneyBread();
		} catch (Mistake e) { // 이 부분이 재사용된다.
			makeRefund();
		}
	}
	// 다른 도메인 서비스에 다시 사용하기 어렵다. (1)
	private Coffee getCoffee() {
		return CoffeeFactory.getCoffee();
	}
        // 다른 도메인 서비스에 다시 사용하기 어렵다. (2)
	private HoneyBread getHoneyBread() {
		return HoneyBreadFactory.getHoneyBread();
	}
}

전략 패턴의 적용

전략 패턴을 적용하면서 확장에 해당하는 변하는 부분을 별도의 클래스로 만들고 이를 한 단계 추상화하는 인터페이스를 만들어 context 입장에서는 구체적인 strategy 클래스를 몰라도되게 설계하였다.

여기서 스스로 공부가 되었던 부분은 단순히 '전략 패턴을 적용했다'라는 사실보다 템플릿 메소드 패턴보다 유연하고 확장성이 뛰어나다 라고 말한 부분이었는데 그 이유를 책에서는:

context 입장에서는 구체적인 strategy 중 누구와 연결되는지 컴파일 시점에는 알 수 없고 런타임 때 application context에 의해 그 관계가 결정된다.

라고 말하고 있다. 잘 안 와닿을 수 있는데 위의 이유를 개발자 입장에서 생각해보면 좀 더 이해가 간다. 개발자 입장에서는 context가 구체적인 strategy에 직접적인 의존관계를 가지게 된다면 strategy가 바뀔 때마다 context를 변경시켜야 한다. 이것은 OCP 원리를 어긴 것이고 좀 더 현실적으로 말하면 고치지 않아도 될 것을 고쳐야하므로 퇴근 시간이 늦어진다.

DI

DI는 제 3자의 도움을 통해 두 오브젝트 사이에 유연한 관계가 설정되도록 하는 것이다. 일반적으로 DI는 의존 관계에 있는 두 개의 오브젝트와 이 관계를 동적으로 설정해주는 object factory (DI container) 그리고 이를 사용하는 client 사이에서 일어난다.

JdbcContext의 분리

Jdbc의 일반적인 작업의 흐름을 담고 있는 jdbcContextWithStrategy() 는 다른 클래스들에서도 사용하기 때문에 이를 클래스로 분리시켜 재사용하자는 결정을 하고 있다.

그리고 이제 UserDao가 JdbcContext 클래스에 의존하는 구조로 바뀌었는데, JdbcContext는 구체적인 클래스이다. Spring DI는 기본적으로 인터페이스를 사이에 두고 의존 클래스를 바꿔서 유연하고 확장성있는 구조를 가져가는 것이 목적이다.

그런데 JdbcContext는 그 자체로 독립적인 jdbc context를 제공해주는 service object로서 의미가 있을 뿐이고 구현 방법이 바뀔 가능성은 없다. 따라서 인터페이스를 사이에 두지않고 직접적으로 DI를 했다.

이 부분을 읽고 바로 이해가 되지 않았지만 바로 뒤에서 좀 더 구체적인 이유를 서술해주고 있다.

Spring DI의 의미를 넓게 보면 객체의 생성과 관계 설정에 대한 제어 권한을 오브젝트에서 제거하고 외부로 위임했다는 IoC 개념을 포함하고 있다.

  • JdbcContext가 Spring Container의 Singleton Registry에서 관리되는 싱글톤 빈이 되기 때문이다.
    • JdbcContext는 그 자체로 변경되는 상태 정보를 가지고 있지 않다.
    • JdbcContext는 jdbc context 메소드를 제공해주는 service object로서 의미가 있고 그렇기 때문에 싱글톤으로 등록되어서 여러 오브젝트에서 공유해 사용되는 것이 이상적이다.
  • JdbcContext가 DI를 통해 다른 빈에 의존하고 있기 때문이다.
    • JdbcContext는 DataSource에 의존하고 있다.
    • DI를 위해서는 주입되는 오브젝트와 주입 받는 오브젝트 모두 빈으로 등록되어야 한다.
    • Spring이 생성하고 관리하는 IoC 대상이어야 DI를 받을 수 있기 때문이다.

인터페이스를 사용하지 않았다는 것은 UserDao와 JdbcContext가 매우 긴밀한 관계를 가지고 강하게 결합되어 있다는 의미이다. UserDao는 항상 JdbcContext와 함께 사용되어야 한다.

비록 클래스는 구분되어 있지만 둘은 강한 응집도를 가지고 있다.

  • UserDao가 JDBC 대신 JPA와 같은 ORM을 사용해야 한다면 JdbcContext도 통째로 바뀌어야 한다.
  • JdbcContext는 1장의 DataSource와 달리 테스트에서도 다른 구현으로 대체해서 사용할 이유가 없다.

위와 같이 UserDao와 JdbcContext가 강한 결합을 가지고 있기 때문에 다음 챕터에서는 다른 방식으로 리팩토링 해봅니다.

UserDao 내부에서 직접 JdbcContext DI

DAO가 직접 JdbcContext의 생성과 관리를 담당한다. 구체적으로 풀어쓰면 DAO 내에서 JdbcContext 객체를 생성하여 사용한다. 책에서는 멋있게 풀어썼다.

오브젝트를 생성하고 그 의존 오브젝트를 주입한다. UserDao가 임시로 DI 컨테이너처럼 동작하게 만든다.

DAO를 사용하는 입장에서는 JdbcContext 존재를 모르고 편하게 사용해도 된다.

이 방법의 단점은 JdbcContext를 DAO 내에서 직접 생성하고 있기 때문에 불필요한 것을 감춘다는 의미에서 추상화는 달성했지만 JdbcContext를 생성해주는 object factory 역할의 코드들이 반복적으로 DAO 내에 작성해줘야하기 때문에 그런면에서는 object factory의 재사용성이 떨어진다고 생각한다.

@zeroFruit zeroFruit added the Share This issue is about sharing things related with study label Jan 19, 2020
@zeroFruit zeroFruit changed the title [Share] 토비 스프링 3챕터 좋았던 부분 [Share] 토비 스프링 3챕터에서 공부한 것 정리 Jan 19, 2020
@ttkmw
Copy link

ttkmw commented Jan 19, 2020

핵심이라고 생각하는 부분에 공감합니다!
프로젝트 중 이 패턴을 적용해볼 기회가 있었는데, 코드로 짤 때 정말 재밌더라고요.
(결과적으로는 템플릿/콜백이 아닌 전략패턴으로 해결하여, 예제코드는 없지만요...).

정말 재밌게 읽었던 챕터입니다!

@zeroFruit zeroFruit added Digest This issue is about digest and removed Share This issue is about sharing things related with study labels Jan 21, 2020
@zeroFruit zeroFruit changed the title [Share] 토비 스프링 3챕터에서 공부한 것 정리 [Digest] 토비 스프링 3챕터에서 공부한 것 정리 Jan 21, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Digest This issue is about digest
Projects
None yet
Development

No branches or pull requests

2 participants