스프링이 탄생하기 이전, EJB (Enterprise Java Beans, 기업 환경의 시스템을 구축하기 위한 서버측 컴포넌트 모델)가 지금의 Spring 역할을 했다. 하지만 EJB에 수많은 문제들이 있었으니, 바로
와 같은 문제점들이다.
Spring은 이러한 EJB 컨테이너를 대체하고, 단순함을 더했다.
Hibernate는 EJB Entity Bean 기술을 대체했고, 현재 JPA의 대표적인 구현체이다.
Spring은 POJO (Plain Old Java Object, 자바로 생성하는 순수한 객체)를 기반으로 한다. 이를 통해, 객체 지향적인 원리에 충실하면서, 환경과 기술에 종속되지 않고, 필요에 따라 재활용할 수 있게 됐다. 그 이외에도 BeanFactory, ApplicationContext, DI (의존관게 주입), IOC(제어의 역전), AOP (관점 지향 프로그래밍) 을 통해 개발 생산성을 높였다.
객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지 를 주고받고, 데이터를 처리할 수 있다. (협력)
객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다.
DI (Dependency Injection) 를 통해 다형성, OCP, DIP 를 가능하게 한다.
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
... 중략
}
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
... 중략
}
(강의 자료 중)
악덕 기획자: 서비스 오픈 직전에 할인 정책을 지금처럼 고정 금액 할인이 아니라 좀 더 합리적인 주문 금액당 할인하는 정률% 할인으로 변경하고 싶어요. 예를 들어서 기존 정책은 VIP가 10000원을 주문하든 20000원을 주문하든 항상 1000원을 할인했는데, 이번에 새로 나온 정책은 10%로 지정해두면 고객이 10000원 주문시 1000
원을 할인해주고, 20000원 주문시에 2000원을 할인해주는 거에요!
순진 개발자: 제가 처음부터 고정 금액 할인은 아니라고 했잖아요.
악덕 기획자: 애자일 소프트웨어 개발 선언 몰라요? “계획을 따르기보다 변화에 대응하기를”
순진 개발자: … (하지만 난 유연한 설계가 가능하도록 객체지향 설계 원칙을 준수했지 후후)
할인 정책을 변경하려면 클라이언트인 OrderServiceImpl 코드를 고쳐야 한다.
public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
위 설계는 인터페이스(DiscountPolicy)와 구현체(FixDiscountPolicy, RateDiscountPolicy)를 잘 분리했다.
하지만, 실질적으로는 주문 클라이언트(OrderServiceImpl)은 구현체 (FixDiscountPolicy)를 참조하고 있었다. -> DIP 위반
정률 할인 정책으로 기능을 확장하기 위해서는, 주문 클라이언트(OrderServiceImpl)의 코드를 수정 (FixDiscountPolicy -> RateDiscountPolicy)로 수정해야 한다. -> OCP 위반
오로지 인터페이스에만 의존하도록 코드를 수정해야 한다.
public class OrderServiceImpl implements OrderService {
private DiscountPolicy discountPolicy;
... 중략
}
구현체가 없으니 당연히 NPE (Null Pointer Exception)가 발생한다
누군가가 클라이언트인 OrderServiceImpl 에 DiscountPolicy 의 구현 객체를 대신 생성하고 주입해주어야 한다. -> DI (Dependency Injection)!
애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스를 만들자
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
// 아래 생성자에서 의존성을 주입받는다.
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
... 중략
}
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;
}
... 중략
}
public class App {
public static void main(String[] args) {
// Service를 appConfig에서 가져오는 것이 중요하다.
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
Member member = new Member(1L, "memberA", Grade.VIP);
memberService.join(member);
Member findMember = memberService.findMember(1L);
Order order = orderService.createOrder(findMember.memberId, "itemA", 10000);
}
}
memberRepository()
);memberRepository(), discountPolicy()
);이전에는, 새로운 할인 정책을 적용하기 위해서는 클라이언트 (OrderServiceImpl)의 코드를 수정해야 했다.
AppConfig 의 도입 이후로는, 클라이언트(OrderServiceImpl)은 인터페이스만 참조하며, 새로운 할인 정책을 도입하기 위해서는 AppConfig의 코드만 수정하면 된다.
public class AppConfig {
... 중략
public DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
AppConfig를 사용하기 이전에 발생했던 문제를 다시 살펴보면,
실질적으로는 주문 클라이언트(OrderServiceImpl)은 구현체 (FixDiscountPolicy)를 참조하고 있었다.
-> 클라이언트는 인터페이스(역할, DiscountPolicy)만을 참조하며, 인터페이스는 구성 영역에서 구현체를 주입해준다. (DIP 위반 해결)
정률 할인 정책으로 기능을 확장하기 위해서는, 주문 클라이언트(OrderServiceImpl)의 코드를 수정 (FixDiscountPolicy -> RateDiscountPolicy)로 수정해야 한다.
-> 사용 영역이 아닌 구성 영역에서 수정이 발생하므로, 클라이언트(OrderServiceImpl)의 수정은 발생하지 않는다. (OCP 위반 해결)
AppConfig를 사용함으로써 SRP, DIP, OCP을 지키게 됐다.
discountPolicy = new Fix/RateDiscountPolicy()
)IoC 컨테이너
또는 DI 컨테이너
라 한다.주로 DI 컨테이너
라 한다.Reference
김영한 스프링 강의
EJB : https://woongsin94.tistory.com/357
POJO : https://ittrue.tistory.com/211
LSP : https://velog.io/@harinnnnn/OOP-객체지향-5대-원칙SOLID-리스코프-치환-원칙-LSP
객체지향 특징 : https://www.codestates.com/blog/content/객체-지향-프로그래밍-특징