이번 포스팅에는 스프링이 쓰이지 않지만 순수한 자바를 통해 의존관계를 왜 쓰는지 어떻게 사용되는지 알아보겠습니다.
좋은 객체지향 애플리케이션이란??
정말 많은 이야기가 있고 제가 지금 떠오르는 개념만 하더라도
SRP : 한 클래스에서는 하나의 책임만
DIP : 개발자는 추상화의 의존하며 구체화의 의존하면 안된다
OCP : 소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있다
이런 개념들이 있습니다!!
저는 개인적으로 다형성이 중요하다고 생각합니다.
바로 새로운 기능을 추가하거나 변경할 때 클라이언트를 변경하지 않고 구현기능을 유연하게 바꾸는 것입니다.
쉽게 이해하기 위해 예를 하나 들어보겠습니다!!
춘향이와 몽룡이의 이야기를 볼때 춘향과 몽룡은 역할입니다!! 그리고 이 역할을 수행하는 배우들을 구현체라고 할 수 있죠!!
위의 상황에서 배우가 누가오는지에 관계없이 춘향이의 역할과 몽룡이의 역할을 수행할 수 있습니다. 이는 다형성을 보장해주는 것을 뜻합니다.
자바로 비유하면 역할(=인터페이스), 구현(=구현 클래스) 라고 할 수 있겠습니다.
코드를 보면서 정확하게 이해해보겠습니다!!
코드로 보는 역할과 구현
할인의 역할인 DiscountPolicy 인터페이스입니다!!
package hello.core.discount;
import hello.core.Member.Member;
public interface DiscountPolicy {
/**
* @return 할인 대상 금액
*/
int discount(Member member, int price);
}
위의 인터페이스를 상속받은 고정할인정책 클래스
package hello.core.discount;
import hello.core.Member.Grade;
import hello.core.Member.Member;
public class FixDiscountPolicy implements DiscountPolicy{
private int discountFixAmount = 1000;
@Override
public int discount(Member member, int price) {
if(member.getGrade() == Grade.VIP){
return discountFixAmount;
}
return 0;
}
}
위의 인터페이스를 상속받은 비율할인정책 클래스
package hello.core.discount;
import hello.core.Member.Grade;
import hello.core.Member.Member;
public class RateDiscountPolicy implements DiscountPolicy{
private int discountPercent = 10;
@Override
public int discount(Member member, int price) {
if(member.getGrade() == Grade.VIP){
return price * discountPercent / 100;
}
return 0;
}
}
여기서 DiscountPolicy는 역할 FixDiscountPolicy와 RateDiscountPolicy는 구현체를 담당합니다.
나중에 감독님의 생각 즉, 개발을 요청에 따라 바꿔 사용하면 되는거죠!
근데 처음에 말할 때 클라이언트를 변경하지 않는다는게 무슨 뜻일까요?
-> 아까 춘향이와 몽룡이를 생각해보면 춘향이의 배역을 아이유가 맡는다고 생각해볼게요!! >< 그렇다면 춘향이가 몽룡이의 배역을 정해와야 할까요?? 이런 생각을 해보면서 코드로 가볼게요
다음 코드를 보면서 확인해보죠!!
package hello.core.order;
import hello.core.Member.Member;
import hello.core.Member.MemberRepository;
import hello.core.Member.MemoryMemberRepository;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
/*
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
*/
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId,itemName,itemPrice,discountPrice);
}
}
음... 너무 잘한거 같은데 뭐가 문제일까요??
우선 제가 위의 코드를 할인관련만 보여드렸으니 할인에 관해서만 얘기해볼게요
현재 개발 요청은 RateDiscountPolicy입니다!! 근데 어느날 갑자기 개발 요청이 FixDiscountPolicy가 된다면 어떻게 할까요?? 그냥 DiscountPolicy안에 new FixDiscountPolicy를 넣어주면 됩니다!! 쉽죠!!!
하지만 여기서 문제가 생기는거에요!
바로 아이유가 몽룡이 역으로 박서준을 섭외하는 것처럼 OrderServiceImpl이라는 클래스가 RateDiscountPolicy를 직접 섭외해온거에요!
이런 이상함을 해결하기 위해 관심사의 분리라는 것이 필요합니다!!
관심사의 분리란!! - 객체를 생성하고 연결하는 역할과 객체들을 실행하는 역할을 분리하는 것입니다.
그래서 요번에는 AppConfig라는 배우를 직접 섭외해주는 클래스를 만들어 보겠습니다.
package hello.core;
import hello.core.Member.MemberRepository;
import hello.core.Member.MemberService;
import hello.core.Member.MemberServiceImpl;
import hello.core.Member.MemoryMemberRepository;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
public class AppConfig {
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
private MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy(){
return new RateDiscountPolicy();
}
}
여기서 보시면 orderService라는 메소드를 부르면 OrderServiceImpl이라는 새로운 구현체를 생성하고 그 안에서
memberRepository와 discountPolicy메소드를 부릅니다. 그리고 각각의 메소드는 필요한 기능을 하는 새로운 구현체를 생성합니다.
OrderServiceImpl의 코드
package hello.core.order;
import hello.core.Member.Member;
import hello.core.Member.MemberRepository;
import hello.core.discount.DiscountPolicy;
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId,itemName,itemPrice,discountPrice);
}
}
OrderServiceImpl의 코드의 코드인데 아까와 달라딘 점을 아시겠나요??
바로 인터페이스를 구현만 해놓고 생성자를 통해 전달받은 값으로 구현체를 채워넣는 겁니다! 이렇게 된다면 OrderServiceImpl는 memberRepository와 discountPolicy의 구현체가 누구인지 모르고 어떤 저장소나 정책이 와도 상관이 없겠죠!!
-> 아이유가 춘향이 역할을 하는데 상대가 박서준이나 서강준이 와도 할 수 있는것과 같은 개념이죠!!
마지막으로 잘 되는지 확인해보겠습니다.
package hello.core;
import hello.core.Member.Grade;
import hello.core.Member.Member;
import hello.core.Member.MemberService;
import hello.core.Member.MemberServiceImpl;
import hello.core.order.Order;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
public class OrderApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
Long memberId = 1L;
Member member = new Member(memberId,"memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 20000);
System.out.println("order = " + order);
System.out.println(order.calculatePrice());
}
}
- Appconfig라는 섭외팀을 불러오고 memberService와 orderService에게 구현체를 전해줍니다.
만약 정책을 바꾸고 싶다면 위의 AppConfig의 discountPolicy메소드에서 구현체만 FixDiscountPolicy로 바꿔주시면 됩니다.
IoC(제어의 역전), DI(의존관계 주입)
내용이 너무 많아지는 것 같아서 아쉽지만.....
최종적으로 IoC와 DI를 정리하지 않을 수 없습니다!!!!
IoC(제어의 역전)
- 프로그램의 흐름을 직접 관리하는 것이 아니라 외부에서 관리하는 것을 뜻합니다.
- 기존에는 객체가 본인에게 필요한 객체가 있다면 스스로 필요한 객체를 생성하고 연결하고 실행했습니다. 하지만 IoC를 통해 생성, 연결을 할 필요가 없어집니다.
위의 코드에서 처음 OrderServiceImpl클래스를 보면 rateDiscountPolicy클래스를 직접 생성하고 연결하고 실행하고 있습니다.
하지만
다음에 등장하는 OrderServiceImpl클래스를 보면 AppConfig의 도움을 받아 interface만 선언하고 생성자의 매개변수도 interface로 받아들입니다.
-> OrderServiceImpl클래스가 어떤 객체가 만들어지는지 연결되는지 모른 상태로 본인의 로직만 실행할 수 있게 된것입니다.
정리하면 OrderServiceImpl이 직접 관리하던 일을 AppConfig에게 권한이 주어지면서 제어의 역전 즉, IoC가 발생한 것입니다.
DI(의존관계 주입)
- 의존관계는 두 가지로 정적인 의존관계와 동적인 의존관계가 있습니다.
- 위의 완성된 OrderServiceImpl을 보면 DiscountPolicy를 의존하는 것을 볼 수 있습니다. 그리고 이것을 정적인 의존관계 라고 합니다.
- 반면에 OrderServiceImpl이 DiscountPolicy를 통해 실제로 어떤 객체를 의존하는 지는 알 수 없습니다. 그리고 이것을 동적인 의존관계라고 합니다.
- 동적인 의존관계는 애플리케이션이 실행될 때 생성된 객체 인스턴스의 참조가 필요로 하는 객체와 연결된 것을 의미합니다.
- 의존관계 주입은 애플리케이션을 실행할 때 실제 구현 객체를 만들고 그 객체를 필요로 하는 클라이언트 객체에게 전달하는 것을 의미합니다.
-> 여기에서 클라이언트 객체는 OrderServiceImpl이 되고 실제 객체를 구현하고 구현된 객체의 의존관계를 주입하는 역할은 AppConfig가 합니다.
즉, AppConfig가 제어의 역전과 의존관계 주입을 도와준다고 보면 됩니다!! 그리고 Ioc컨테이너 혹은 DI컨테이너라고 부르며 요즘은 DI컨테이너라고 더 자주 부릅니다.
출처
'스프링' 카테고리의 다른 글
스프링 컨테이너와 스프링 빈 (0) | 2021.09.13 |
---|---|
스프링 프레임워크란 무엇일까? (0) | 2021.08.17 |
스프링으로 게시판 만들기 6 - 데이터 수정 (0) | 2021.08.05 |
스프링으로 게시판 만들기 5 - 날짜 추가 (0) | 2021.08.03 |
스프링으로 게시판 만들기 3 - controller만들기 (0) | 2021.08.02 |
댓글