UMC_Back

Week 2

oyatplum 2024. 4. 6. 00:27

 

 

1. 객체 지향 설계와 스프링

2. 스프링 핵심 원리 이해 1 - 예제 만들기

3. 스프링 핵심 원리 이해 2 - 객체 지향 원리 적용

 


 

1. 객체 지향 설계와 스프링

 

스프링의 핵심

- 스프링은 자바 언어 기반의 프레임 워크

- 자바 언어의 가장 큰 특징 : 객체 지향 언어

- 스프링은 좋은 객체 지향 애플리케이션 개발할 수 있도록 도와주는 프레임워크

 

그렇다면 좋은 객체 지향이란 무엇일까?

키워드는 바로 다형성이다.

 


다형성

 

예를 들어 자동차와 운전자를 생각해보자

자동차의 역할(인터페이스)만 구현되어 있으면 운전자는 자동차의 구현(차 기종)이 어떠하든 운전을 할 수 있다.

운전자는 자동차의 내부 구조를 몰라도 되고 내부 구조가 변경되어도 영향을 받지 않는다. 또한 구현 대상 자체를 변경해도(차 기종 변경) 영향을 받지 않는다.

 

즉, 역할과 구현을 분리해야 하는 것이다.

역할 = 인터페이스

구현 = 인터페이스를 구현한 클래스, 객체

 

다형성의 본질 :

클라이언트를 변경하지 않고 서버를 변경할 수 있다.

MemberRepository repository = new MemoryMemberRepository();
MemberRepository repository = new JdbcMemberRepository();

 

위의 코드처럼 MemoryMemberRepository를 Jdbc로 변경함으로써 가능하다.

 


 

객체 지향 설계를 위한 5가지 원칙

이러한 객체 지향을 설계하기 위해서는 5가지 원칙이 있다.

1. SRP : 단일 책임 원칙

2. OCP : 개방-폐쇄 원칙

3. LSP : 리스코프 치환 원칙

4. ISP : 인터페이스 분리 원칙

5. DIP : 의존관계 역전 원칙

 

 

이 중 가장 중요한 OCP 와 DIP만 구체적으로 설명하자면

 

OCP

OCP는 확장에는 열려있으나 변경에는 닫혀있어야 한다.

이게 무슨 모순되는 소리일까? 변경하지 않고 어떻게 확장하라는 말이야~~

 

위에서 배운 다형성을 사용하면 된다.

새로운 클래스 하나 만든다고 기존 코드를 변경하는 것은 아니니까!

하지만 다형성 사용해도 아예 OCP를 지킬 수 있는 것은 아니다.

MemberRepository repository = new MemoryMemberRepository();
MemberRepository repository = new JdbcMemberRepository();

 

결국 클라이언트는 위와같이 코드를 변경해야 하기 때문이다.

이를 스프링 컨테이너가 해결해준다.

 

 

DIP

다음으로 DIP에 대해 알아보자.

프로그래머는 추상화에 의존해야지 , 구체화에 의존하면 안된다.

즉, 구현 클래스에 의존하지 말고 인터페이스를 봐라! 그래야 구현체 변경이 유연하니까~

public class MemberService{
	MemberRepository repository = new MemoryMemberRepository();
}

 

하지만 위의 코드를 보면 MemerService는 인터페이스, 구현체 모두 의존하고 있음.

이 또한 인터페이스인 MemberRepository만 의존하도록 변경해야 한다.

 


 

결론은 스프링을 사용하자!

스프링은 다형성 + OCP, DIP 가능하게 지원하니까!

 


 

 

2. 스프링 핵심 원리 이해 1 - 예제 만들기

 

회원 도메인

<interface> MemberService < MemberServiceImpl

<interface> MemberRepository < MemoryMemberRepository

의 회원 클래스 구조를 가지고 있다.

 

회원 도메인을 생성하는 과정은 입문 강의에서 했던 방식과 크게 다르지 않았다.

추가적으로 주문과 할인을 위한 Grade가 있다.

public enum Grade {
    BASIC,
    VIP
}

 

enum은 서로 연관된 상수들의 집합을 나타내는 데이터 타입이라고 한다.

 

 

주문과 할인 도메인

<interface> OrderService < OrderServiceImpl

<interface> MemberRepository < MemoryMemberRepository

<interface> DiscountPolicy < FixDiscountPolicy

 

주문과 할인 도메인을 생성하는 과정도 크게 어렵지 않다.

public class Order {

    private Long memberId;
    private String itemName;
    private int itemPrice;
    private int discountPrice;
    ...
    @Override
    public String toString() {
        return "Order{" +
                "memberId=" + memberId +
                ", itemName='" + itemName + '\'' +
                ", itemPrice=" + itemPrice +
                ", discountPrice=" + discountPrice +
                '}';
    }
}

 

위의 toString() 메소드를 새롭게 알게 됐다.

 

Java에서는 모든 클래스가 Object 클래스를 상속받는다.

Object 클래스는 여러 메서드를 가지고 있는데, 그 중에 하나가 toString() 메서드이고 이는 객체를 문자열로 표현할 때 사용된다고 한다.

 

 


 

 

3. 스프링 핵심 원리 이해 1 - 객체 지향 원리 적용

 

새로운 할인 정책

RateDiscountPolicy라는 새로운 할인 정책을 추가했다.

이렇게 새로운 기능을 추가해주면 여러 문제가 발생하는데

public class OrderServiceImpl implements OrderService{
    // private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}

 

이렇게 FixDiscountPolicy에서 RateDiscountPolicy로 변경한 경우

클라이언트인 OrderServiceImpl 코드를 수정해야한다.

 

즉 DIP(인터페이스만 의존해야함)를 위배하게 되고(DiscountPolicy 인터페이스와 RateDiscountPolicy 구현체 모두 의존하는 상황) OCP(코드 변경 불가) 또한 위배하게 되는 상황이 된다.

 

그렇다면 이 문제를 어떻게 해결해야 할까?

 


 

관심사 분리

 

바로

AppConfig를 사용해서 관심사를 분리하자!

즉, AppConfig를 통해 구현 객체를 생성하고 연결하도록 책임지게 하는 것이다.

public class AppConfig {
    public MemberService memberService(){
        return new MemberServiceImpl(new MemoryMemberRepository());
    }
}
public class MemberServiceImpl implements MemberService{
    // private final MemberRepository memberRepository = new MemoryMemberRepository();
    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

 

위와 같이 AppConfig에서 MemberService를 조회한 경우

MemberServiceImpl에 MemoryMemberRepository를 주입해주면(의존성 주입 : DI)

MemberServiceImpl에서는 생성자를 통해 MemberRepository만, 인터페이스(추상화)만 의존하게 된다.

public class MemberApp {
    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        //MemberService memberService = new MemberServiceImpl();
        MemberService memberService = appConfig.memberService();
}
public class MemberServiceTest {
    //MemberService memberService = new MemberServiceImpl();
    MemberService memberService;
    @BeforeEach
    public void beforeEach(){
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
    }
}

 

 

따라서 MemberApp과 MembeServiceTest 모두 수정해주면

 

MemberApp > AppConfig > MemberService > MemberServiceImpl 객체.

이때 MemmoryMemberRepository 객체를 주입함으로써 MemberServiceImpl에서는 인터페이스(추상화)만 의존하게 되고 DIP 원칙을 지키게 되며 OCP 역시 지킬 수 있다.

 

 

추가적으로 AppConfig는

public class AppConfig {
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }
    public MemoryMemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }
    public DiscountPolicy discountPolicy(){
        return new FixDiscountPolicy();
        //return new RateDiscountPolicy();
    }
}

 

위와 같이 MemoryMemberRepository와 DiscountPolicy 모두 역할과 구현이 잘 드러나도록 수정했다.

이러면 추후 MemoryMemberRepository를 다른 db로 변경할 때 해당 부분만 수정하면 되고

AppConfig에서 전체적인 역할과 구현이 한 눈에 들어오게 된다.

 

또한 할인 정책을 변경하는 경우 DiscountPolicy에서 주석 처리한 부분처럼 객체만 변경해주면 다른 코드는 전혀 손댈 필요가 없다.

즉, AppConfig를 사용하면 구성 영역사용 영역이 구분된다는 점!

 


 

IoC, DI 그리고 컨테이너

 

IoC는 Inversion of Control로 제어의 역전을 의미한다.

즉, AppConfig처럼 외부에서 프로그램의 흐름을 제어하는 것이다.

 

의존관계 주입 DI는 Dependency Injection으로 위에서 계속 설명한 것과 같다.

추가적으로

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;
    }
}

 

위의 코드에서 OrderServiceImpl은 DiscountPolicy 인터페이스에 의존하고 있지만

실제로 FixDiscountPolicy가 구현 객체인지, RateDiscountPolicy가 구현 객체인지 알 수 없다.

이것이 정적인 클래스 의존 관계이고

 

애플리케이션 실행 시점에 동적으로 구현 객체를 생성하여 의존 관계가 연결되는 것이 바로

의존관계 주입 DI이다.

이렇게 되면 정적인 클래스 의존 관계를 변경하지 않고, 동적인 객체 인스턴스 의존 관계를 쉽게 변경할 수 있다.

 

 

이러한 AppConfig를 IoC 컨테이너 또는 DI 컨테이너라고 부른다.

 

 


 

스프링으로 전환하기

 

지금까지 순수 자바 코드로 DI를 적용했기 때문에 스프링을 사용해보자.

@Configuration
public class AppConfig {
    @Bean
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }
    ...
}
public class MemberApp {
    public static void main(String[] args) {
//        AppConfig appConfig = new AppConfig();
//        MemberService memberService = appConfig.memberService();
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean("memberService", MemberService.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);
}

 

AppConfig 위에 @Configuration 어노테이션과 각각의 메소드에 @Bean 어노테이션을 작성해준다.

MemberApp과 OrderApp에서는 위와 같이 작성하는데

 

ApplicationContext는 스프링 컨테이너로 모든 걸 다 관리한다고 알고있자.

AnnotationConfigApplicationContext(AppConfig.class)을 해주면 AppConfig에 있는 정보를 가지고 @Bean이 붙은 객체들을 스프링 컨테이너에 등록 > 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 한다.

 

스프링 빈은 @Bean이 붙은 메서드 이름을 스프링 빈 이름으로 사용한다.

 

기존에는 memberService나 orderService를 직접 찾아왔다면 이제 스프링 컨테이너를 통해 찾아와야 한다.

applicationContext.getBean 메서드 사용하고 이때 name에는 기본적으로 메서드 이름을 넣어야 한다.

 

 


 

 

 

여기까지 해주면 순수 자바 코드로 모두 작성한 뒤 스프링으로 전환까지 해주게 되는 것이다~~

휴우...

반복적으로 같은 내용을 들으니까 이제 슬슬 감이 오는 것 같다ㅠㅠ 아마도....

다음 주차도 화이팅...

'UMC_Back' 카테고리의 다른 글

여행기 - 도시, 국가 검색 정리 (전반적인 스프링 코드 이해)  (0) 2024.08.18
Week 3_(2)  (0) 2024.04.29
Week 3_(1)  (0) 2024.04.28
Week 1  (0) 2024.03.30