프로그래밍 언어/Spring

<Spring>LMS 프로젝트 Member OCP, DIP 준수한 코드로 변경

창조적생각 2021. 10. 25. 21:10

*본 프로젝트는 JSP 팀 프로젝트로 만들었던 Learning Management System을 Spring으로 이식하는 과정입니다.

**본 프로젝트는 김영한 선생님의 인프런 강의 스프링 핵심원리 - 기본편

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 개발자가 되어보세요! 📣 확인해주

www.inflearn.com

을 바탕으로 실습하는 과정입니다. 스프링에 대해서 배우고 싶으시다면 이 강의를 들어보시는 것도 좋을 것입니다.

 

개발 환경

IDK : intelliJ

JDK : 자바 11

의존성은 김영한 선생님의 강의 순서대로 하기 위해 아무것도 넣지 않고 시작한다.

 

2. OCP,DIP 준수하는 코드로 변경

 

문제부분은 파란부분이다.

 

*좋은 객체 지향 설계의 원칙(SOLID)

1.SRP: 단일 책임 원칙(Single Responsibility Principle)

2.OCP: 개방 - 폐쇄 원칙(Open/Closed principle)

3.LSP: 리스코프 치환 원칙(Liskov Substitution Principle)

4.ISP: 인터페이스 분리 원칙(Interface Segregation Principle)

5.DIP: 의존관계 역전 원칙(Dependency Inversion Principle)

 

1. SRP 단일책임 원칙

 - 한 클래스는 하나의 책임만을 가진다. 

 -- 문맥과 상황에 따라 다르다. 변경이 있을 때 파급효과가 작아야 한다. 

 ---ex) UI변경, 객체의 생성과 사용을 분리

 

2. OCP 개방-폐쇄원칙

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

--구현체에 의존하지 않고 인터페이스에 의존한다.

--- 역할과 구현의 분리

----객체를 생성하고 연관관계를 맺어주는 별도의 조립,설정자가 필요하다.

 

3.LSP 리스코프 치환 원칙

-프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀수 있어야 한다.

--하위 클래스는 인터페이스 규약을 다지텨야 한다는 것과 다형성을 지원하기 위한 원칙, 인터페이를 구현한 구현체를 믿고 사용하려면, 이 원칙이 필요하다.

 

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

- 특정 클라이언트를 위한 하나의 인터페이스보다는 나누어서 여러 개의 인터페이스를 사용하는 것이 낫다.

--ex)

 

5. DIP 의존관계 역전 원칙

- 프로그래머는 '추상화에 의존해야지, 구체화에 의존하면 안된다.' 의존성 주입은 이 원칙을 따르는 방법 중 하나이다.

--구현 클래스에 의존하지 말고 인터페이스에 의존해야 한다.

--- 역할에 의존하게 해야 한다.

 

2.문제 되는 클래스 및 코드

문제되는 부분은 MemberService인터페이스를 구현하는 MemberServiceImpl클래스에서 MemberRepository 인터페이스를 구현하는 MemoryMemberService()를 클래스 안에서 변경함이다.

 

구현체가 이 클래스 안으로 들어와버리면 종 전에 기대했던 의존관계에서 실제로 MemberServiceImpl은 인터페이스 뿐만이 아니라 구현체인 MemoryMemberRepository에 까지 의존을 하면서 2.OCP와 5.DIP를 위배하게 된다. 

기존의 예상 의존관계와 실제 의존관계

3. OCP, DIP 원칙을 준수하게 수정

 

문제는 구현체가 각각의 인터페이스를 구현하는 구현체안으로 들어가서 발생한다.

그렇기에 클래스 안에는 온전히 구현체가 아니라 인터페이스에 의존할 수 있도록 바깥에서 인터페이스의 구현체를

연결 시켜주기로 한다.

 

4. Appconfig

애플리케이션의 전체 동작 방식을 구성하기 위해 구현 객체를 생성하고 연결하는 책임을 지는 별도의 설정 클래스인 AppConfig를 작성한다.

 

우선 문제가 된 MemberServiceImpl에서 오로지 인터페이스만을 의존하도록 코드를 변경해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package eleven.lms.member;
 
public class MemberServiceImpl implements MemberService{
 
    //private final MemberRepository memberRepository = new MemoryMemberRepository;
 
    private final MemberRepository memberRepository; //인터페이스만을 선언해준다.
 
    public MemberServiceImpl(MemberRepository memberRepository) {//생성자를 생성해준다. 매개변수는 MemberRepository를 선택하여 밖에서 주입할수 있게한다.
        this.memberRepository = memberRepository;//생성자주입방식
    }
 
    @Override
    public void join(Member member) {
        memberRepository.save(member);
    }
 
    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}
cs

이렇게 코드를 수정하면 이제 MemberServiceImpl은 오로지 인터페이스에만 의존한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package eleven.lms;
 
import eleven.lms.member.MemberRepository;
import eleven.lms.member.MemberService;
import eleven.lms.member.MemberServiceImpl;
import eleven.lms.member.MemoryMemberRepository;
 
public class AppConfig {
    public MemberService memberService(){//MemberService의 구현체 선택
        return new MemberServiceImpl(memberRepository());
    }
    private MemberRepository memberRepository(){// 위에서 사용될 memberRepository의 구현체를 선책해준다.
        return new MemoryMemberRepository();
    }
}
 
cs

AppConfig에서 이제 각각의 인터페이스들의 구현체를 지정해주기로 한다.

이렇게 되면 실행파일에서 방식을 달리해야 한다.

Test 파일에서보면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package eleven.lms.memberTest;
 
import eleven.lms.AppConfig;
import eleven.lms.member.Member;
import eleven.lms.member.MemberService;
import eleven.lms.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
 
public class MemberTest {
    MemberService memberService;
 
    @BeforeEach
            public void beforeEach(){
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
    }
 
 
    //MemberService memberService = new MemberServiceImpl(memberRepository); 이전의 테스트를 위해 구현했던 MemberService
 
    @Test
    void join(){
        //given
        Member member = new Member(1L,"studentA");
        //when
        memberService.join(member);
        Member findMember=memberService.findMember(1L);
        //then
        Assertions.assertThat(member).isEqualTo(findMember);
    }
}
cs

기존의 //MemberService memberService = new MemberServiceImpl(memberRepository);

부분에서 memberService를 구현할때와 달리 AppConfig.memberService()를 가져와서 구현해주었다.

 

728x90