객체 지향 원리 적용

- 애자일 소프트웨어에 인상적인 글귀 - "계획에 따르기 보다는 변화에 대응하기를"
- Mac 기준으로 Method쪽에 CMD+SHIFT+T 를 누르면 Test 코드를 작성할 수 있다.
- Test 코드 작성 시 꿀팁은 given, when, then
- given 에 객체를 만들어주고
- when 에 테스트 로직을 추가하고
- then 에서 마지막으로 검증해준다.
- Test 코드에서 고려해야할 것은 성공, 실패 코드 두가지 이다.
-
class RateDiscountPolicyTest { RateDiscountPolicy discountPolicy = new RateDiscountPolicy(); @Test @DisplayName("VIP는 10% 할인이 적용되어야 한다.") void vip_o() { //given Member member = new Member(1L, "memberVIP", Grade.VIP); //when int discount = discountPolicy.discount(member, 10000); //then assertThat(discount).isEqualTo(1000); } @Test @DisplayName("VIP가 아니면 할인이 적용되지 않아야 한다.") void vip_x() { //given Member member = new Member(2L, "memberBASIC", Grade.BASIC); //when int discount = discountPolicy.discount(member, 10000); //then assertThat(discount).isEqualTo(0); } } - 일반적으로 Java 코드로 작성하여 다형성을 이용해 의존관계를 구현하다보면 OCP, DIP 를 위반하는 케이스를 마주하게 된다.
- OCP - 클라이언트의 코드 변경이 이루어지는것
- DIP - 역할과 구현을 나누지 못하고 클라이언트가 역할과 구현 모두 의존하고 있는것 (추상화에 의존해야지 구체화에 의존하면 안된다.)
- 단순히, 다형성으로 구현해주지 않고 인터페이스만 구현해주면 NullPointException이 발생한다.
-
public class OrderServiceImpl implements OrderService { //private final DiscountPolicy discountPolicy = new RateDiscountPolicy(); private DiscountPolicy discountPolicy; } - 이 문제를 해결하기 위해서 클라이언트에 DiscountPolicy의 구현체를 주입해줘야 하는데 그것이 Spring Container 역할이 된다.
- 여기서는 Spring Container 대신 AppConfig 라고 하는 설정자를 지정해보자.
- AppConfig 역할은 구현 객체를 책임지고 연결한다. DIP 를 지켜주기 위함이다.
- 기존에 어떻게 구현되었는지 조금 더 생각해보자.
- 아래 코드를 보았을 때 OderServiceImpl은 DiscountPolicy, RateDiscountPolicy 두개 다 모두 의존하게 된다. (OCP, DIP 위반)
-
public class OrderServiceImpl implements OrderService { private final DiscountPolicy discountPolicy = new RateDiscountPolicy(); } - 이 문제를 해결하는 방법으로 설정자를 지정한다. 즉, 관심사를 분리하는 방법이다.
- 아래 코드처럼 설정자에 구현객체를 연결하는 책임만 전달하면 된다.
- 실제 MemberServiceImpl에서는 인터페이스만 의존하게 되므로 기존의 객체지향 설계 OCR, DIP 를 만족하게 된다.
- 클라이언트 입장(MemberServiceImpl)에서 보면 외부에서 의존관계를 다형성으로 주입하고 이러한 상태를 DI(Dependency Injection) 이라고 한다. 더이상 구현 클래스는 클라이언트의 관심사가 아니다.
-
public class AppConfig { public MemberService memberService() { return new MemberServiceImpl(new MemoryMemberRepository()); } } -
//생성자로 넣어준다. 여기서는 더이상 MemoryMemberRepository는 찾아볼 수 없다. //생성자 주입, injection 이라고 한다. public class MemberServiceImpl implements MemberService { private final MemberRepository memberRepository; public MemberServiceImpl(MemberRepository memberRepository) { this.memberRepository = memberRepository; } } - 관련된 테스트 코드를 작성해보자.
-
MemberService memberService; @BeforeEach public void beforeEach() { AppConfig appConfig = new AppConfig(); memberService = appConfig.memberService(); }
AppConfig 리팩토링
- AppConfig 에서 역할과 구현 클래스가 보이도록 구성하는것이 좋다. (중복을 제거하는 의미이다. 따로 함수로 extract 하여 구현 클래스로 둔다.)
- 확장성에 좋아진다.
- AppConfig 영역을 구성영역 그리고 일반적인 로직을 담당하는 클라이언트 관련되서는 사용영역으로 나눌 수 있다. 구성영역만 변경하면 사용영역을 변경하지 않아도 된다. 서로 코드적인 영향을 미치지 않는다.
- SRP 관심사 분리
-
// 한줄만 바꿔주면 기존 사용영역은 변경 필요가 없다. public DiscountPolicy discountPolicy() { // return new FixDiscountPolicy(); return new RateDiscountPolicy(); } - 아래 사진 처럼!

IoC / DI
- 프레임워크 vs 라이브러리
- 프레임워크
- 작성한 코드를 대신 작성하는것을 대신 실행시켜준다. JUnit도 프레임워크라고 볼 수 있다.
- 라이브러리
- 내가 작성한 코드를 직접 제어의 흐름을 담당한다면 라이브러리이다.
- 프레임워크
- 의존관계는 정적인 클래스 의존관계 그리고 Run Time에 결정되는 동적인 객체(인스턴스) 의존 관계를 볼 수 있다.
- 아래 코드에서 실제 어떤 객체가 OrderServiceImpl 에 주입 될지 알 수 없다.
- 애플리케이션 실행 시점에 생성된 객체 인스턴스의 참조가 연결된 의존 관계이다.
-
private final MemberRepository memberRepository; private final DiscountPolicy discountPolicy; //memberRepository에 JDBC 디비 객체일지 H2일지 뭐가 들어올지 모른다. public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; }
- 그래서 정적인 코드는 일반적으로 애플리케이션 레벨의 코드로 표현할 수 있다. 반대로 동적인 객체의 인스턴스 의존관계는 IoC 를 통해 제어권을 가져가게 된다. 마치 설정자에 의해 책임이 분리된것이다.
- 그래서, AppConfig 처럼 객체를 생성하고 관리하여 의존관계를 연결해주는 것을 IoC 컨테이너 (or DI 컨테이너) 라고 한다.
Spring으로 전환
- Spring Container 를 기반으로 소스를 관리해보자.
- @Configuration 을 어노테이션으로 붙이면 설정 파일이 된다.
- @Bean 을 메서드 별로 처리하면 Spring Container 에 등록된다. 여기서 의미하는 Spring Container 는 ApplicationContext 이다.
- Spring Container 를 등록하는 방법은 아래 코드 처럼 사용한다.
- 주목해서 보아야 할 부분은 아래 부분인데 @Bean 처리되어 Spring Container에 관리되어 getBean을 통해 꺼내주게 된다.
MemberService memberService =
applicationContext.getBean("memberService", MemberService.class); //메서드이름, 타입
OrderService orderService = applicationContext.getBean("orderService",
OrderService.class); -
public class OrderApp { public static void main(String[] args) { // AppConfig appConfig = new AppConfig(); // MemberService memberService = appConfig.memberService(); // OrderService orderService = appConfig.orderService(); ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); MemberService memberService = applicationContext.getBean("memberService", MemberService.class); //메서드이름, 타입 OrderService orderService = applicationContext.getBean("orderService", OrderService.class); long memberId = 1L; Member member = new Member(memberId, "memberA", Grade.VIP); memberService.join(member); Order order = orderService.createOrder(memberId, "itemA", 10000); System.out.println("order = " + order); } }
'Spring' 카테고리의 다른 글
컴포넌트 스캔 (0) | 2021.12.12 |
---|---|
싱글톤 컨테이너 (0) | 2021.12.12 |
Spring 예제 (0) | 2021.12.10 |
객체지향 설계와 스프링 (1) | 2021.12.09 |
Spring 프로젝트 설정 & 테스트 코드 작성하기 (0) | 2021.05.25 |
댓글
이 글 공유하기
다른 글
-
컴포넌트 스캔
컴포넌트 스캔
2021.12.12컴포넌트 스캔 등록해야 하는 @Bean이 많아지면 프로그래머는 굉장히 힘들것이다. 누락될 수도 있고 자동으로 @Bean을 등록해주는 @ComponentScan 기능이 있다. 이런 경우 보통 클래스를 @Component으로 등록하고 각 메소드에 대한 의존관계를 위해 @Autowired를 등록해준다. @ComponentScan을 사용하면 @Configuration 이 붙은 설정 정보도 자동으로 등록된다. 그래서 이러한 의존관계 중 일부를 제외하고 싶다면 excludFilters를 사용해서 컴포넌트 스캔 대상에서 제외시킨다. @ComponentScan 은 @Component 가 붙은 모든 클래스를 스프링 빈으로 등록한다 package hello.core; import org.springframework.con… -
싱글톤 컨테이너
싱글톤 컨테이너
2021.12.12웹 애플리케이션과 싱글톤 트래픽이 들어왔을 때 같은 리소스를 여러 트래픽에서 호출하면 각각에 대해 객체가 생성될것이다. TPS가 커질 수록 문제가 발생한다. 순수 자바 코드의 경우 DI 컨테이너인 AppConfig는 요청할 때마다 객체를 새로 생성한다. 이런 경우 메모리의 낭비가 굉장히 심해진다. 그래서 고안된것이 싱글톤 패턴이다. private static final SingletonService instance = new SingletonService(); 그런데, 위와 같이 코드를 작성하면 어떤 문제가 발생할까? 보통 생성된 싱글톤을 호출하게 되면 .getInstance()를 쓰게 된다. getInstance()를 쓰게 되면 클라이언트는 구체 클래스 getInstance()에 의존하게 된다. 이는 … -
Spring 예제
Spring 예제
2021.12.10Spring Spring build 할 때 프로퍼티에서 gradle보다 intellij를 쓰는것이 더 빠르다. Client에서 회원서비스를 통해 인터페이스에서 회원 저장소오아 연동될 DB를 구현한다. 예를들어서, MemberService 인터페이스를 구성하고 구현체로 MemberServiceImpl을 구현한다. 개발자가 구체적인 클래스 다이어그램을 구현한다. Spring에서 프로그래밍 처리하다보면 동시성 이슈가 생길 수 있기 때문에 Concurrent 를 고려해야한다. 아래 코드를 확인해보면 MemberRepository는 인터페이스이고 실제 구현은 MemoryMemberRepository에서 수행한다. 여기서 구현체가 존재하지 않는다면 NullPointException이 발생한다. private fin… -
객체지향 설계와 스프링
객체지향 설계와 스프링
2021.12.09스프링 생태계 스프링 데이터 JPA, 세션, 시큐리티, 배치 등 웹기술 스프링 MVC, Webflux 데이터 접근 기술 트랜잭션, JDBC, ORM 스프링 부트의 장점 스프링과 3rd party 라이브러리 자동 구성을 지원한다. (외부 라이브러리 버전을 일일이 맞춰주지 않아도 Spring boot 그 역할을 수행한다.) 그래서 스프링이 무엇일까? 자바 언어 기반의 프레임워크이고, 객체 지향 프로그래밍이다. 즉, 객체지향을 기반으로 개발할 수 있도록 도와주는 프레임워크라고 볼 수 있다. 좋은 객체지향 프로그래밍이란? 객체들의 모임을 파악한다. 프로그램을 유연하고 변경 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 활용된다. 마치, 레고 블록 조립하듯이 사용할 수 있다. 객체 지향의 꽃이라 볼 수 있…
댓글을 사용할 수 없습니다.