-
[spring] 회원 도메인 설계하기spring 2023. 9. 24. 23:36
회원 요구사항
- 회원 가입, 회원 조회
- 회원은 일반,vip 두 등급이 있다.
- 회원 데이터는 어떻게 처리할지 미정이다. 자체 db를 구축할 수도 있고, 외부 시스템과 연동할 수도 있다.
어떻게 요구사항을 충족할까??
1. 회원가입, 회원조회 -> MemberService 인터페이스를 만들어 큰 틀을 잡고, 이를 상속하는 구현객체를 만든다.
2. 두 등급 -> enum클래스를 사용해 두 등급을 한번에 상수로 선언한다.
3. 데이터는 어떻게 처리할지 미정이므로, DB설정되기 전까지는 MemberRepositroy라는 인터페이스와 이를 상속한 구현객체를 통해 임시적인 회원 저장소를 만든다. 이는 IDE를 종료하면 다 없어진다.
클래스 다이어그램과 객체 다이어그램
class diagram 위에서 설명하였듯, 회원 조회와 회원가입을 담당하는 MemberService 인터페이스를 만들고, 이를 상속하는 MemberServiceImpl 구현객체를 만든다. 임시 회원정보 저장소인 MemberRepository를 만들고 이를 구현하는 MemmoryMemberRepository를 만든다.
객체 diagram 위 클래스 다이어그램을 객체 다이어그램으로 표현하면 다음과 같다. client는 회원 조회나, 회원 가입을 하기 위해 회원 서비스인 MemberService를 구현하는 인스턴스인 MemberServiceImpl을 참조할 것이다. MemberServiceImpl 에서는 메모리 회원저장소인MemoryMemberRepository에 의존하면서 join, findMember등의 메소드를 사용한다.
interface와 구현객체는 서로 어떤 관계인가?
interface는 기능의 큰 틀이라고 볼 수 있다. 우리는 쉽게 꼈다 뺐다 할 수 있게 역할과 구현으로 분리해서 설계를 해야한다. interface는 기능을 직접적으로 구현했다고 볼 순 없다. 다만 어떤 기능이 있는지 대략적인 틀을 제공한다.
그럼 실질적인 기능을 구현하는 것은 어디일까? 그것이 바로 구현객체이다. 구현객체는 인터페이스에서 선언한 메소드들이 구체적으로 어떻게 동작할 것인지, 메소드 오버라이딩을 통해 구현한다.enum열거형을 사용하여, 일반과 vip 등급으로 나눔
package hello.core.member; // enum 열거형 :서로 관련있는 상수들끼리 모아 상수들을 정희하는 것 // 특징 1. 지정된 상수들은 모두 대문자로 선언한다. // 특징 2. 열거형으로 선언된 순서에 따라 0부터 Index 값을 갖는다. public enum Grade { BASIC, VIP }
회원 객체 Member이다.
회원 객체 Member에는 ID, name, grade 총 세 가지의 변수를 저장할 수 있다.
package hello.core.member; public class Member { private Long id; private String name; private Grade grade; //생성자 public Member(Long id, String name, Grade grade) { this.id = id; this.name = name; this.grade = grade; } public Long getId() { return id; } public String getName() { return name; } public Grade getGrade() { return grade; } public void setId(Long id) { this.id = id; } public void setName(String name) { this.name = name; } public void setGrade(Grade grade) { this.grade = grade; } }
회원 저장소 인터페이스 MemberRepository
svae() - 회원 저장 , findById() - id를 매개변수로 받아 이에 대응하는 객체를 반환
package hello.core.member; public interface MemberRepository { //회원을 저장한다. void save(Member member); //회원 id로 회원을 찾는다. - 이때 멤버의 id값을 매개변수로 취한다. //반환값은 id값에 대응하는 해당 객체이다. Member findById(Long memberId); }
회원 저장소 인터페이스를 상속하여, 실질적인 기능을 담당하는 MemoryMemberRepository
Map자료형을 통해, key-value 형태로 매핑하여 하나로 저장하는 변수인 store을 선언하였다.
회원 가입 -> 오버라이딩된 save()를 실행하게 되면, 매개변수로 들어가는 member객체의 id와 member객체가 하나의 key-value형태로 저장된다.
회원 조회 -> 오버라이딩된 findById()를 실행하게 되면, 매개변수로 들어가는 member객체의 id값을 가지고, 저장되어있는 회원들 중 일치하는 회원 객체를 찾아서 이를 반환한다.
package hello.core.member; //회원 저장소 구현체 public class MemoryMemberRepository implements MemberRepository { // 멤버 ID를 Member객체와 key-value형태로 매핑하는 변수 : store private static Map<Long,Member> store = new HashMap<>(); @Override public void save(Member member) { store.put(member.getId(), member); } @Override public Member findById(Long memberId) { return store.get(memberId); } }
회원 가입, 회원 조회 라는 기능의 큰틀을 가진 회원 서비스 인터페이스 MemberService
package hello.core.member; public interface MemberService { //회원가입 void join(Member member); //회원조회 Member findMember(Long memberId); }
MemberService인터페이스를 구현한 구현객체 MemberServiceImpl
MemberRepository를 구현한 MemoryMemberRepository객체를 만들어서 , memberRepository 변수에 저장한다.
오버라이딩된 join을 호출하면 , 다형성에 의해 MemoryMemberRepository의 save()가 호출된다. ( client가 MemberServiceImpl 의 join메소드 호출 -> MemberServiceImpl은 MemoryMemberRepository의 save()메소드 호출 | 위 객체 다이어그램 동작)
package hello.core.member; public class MemberServiceImpl implements MemberService{ //추상화에도 의존하고 구체화에도 의존하는 상황 ,DIP 위반 //아래 코드만을 보면 memberRepository는 MemberRepository, MemoryMemberRepository 둘을 모두 의존한다 private final MemberRepository memberRepository = new MemoryMemberRepository(); //join에서 save를 호출하면,,다형성에 의해서,, MemberRepository의 save()가 호출되는것이 아니라 //인터페이스를 구현한 MemoryMemberReposiotry의 save()가 호출된다. @Override public void join(Member member) { memberRepository.save(member); } @Override public Member findMember(Long memberId) { return memberRepository.findById(memberId); } }
테스트 해본다.
먼저 MemberService를 구현하는 MemberServiceImpl 객체를 생성하여 memberService 변수에 저장한다.
이제 memberService변수는 MemberServiceImpl과 MemberService의 기능을 모두 사용할 수 있다.
memberService 변수는 join메소드를 사용하여 객체를 전달한다. ( 회원 가입하기)
이때, MemberService의 join메소드가 호출되는것이 아닌, MemberServiceImpl의 join메소드가 호출된다.( 다형성 )
MemberServiceImpl이 다시 MemoryMemberRepository를 호출하고,, 어떻게 동작하는지는 위에 서술해놨으니 생략하겠다. 결국
(1L, "memberA", Grade.VIP);
인 객체가 하나 생성될 것이다.
memberService는 위와 똑같은 동작으로 MemberServiceImpl의 findMember메소드를 호출할것이고, 리턴값은 memberA일 것이다.
결론적으로 1L라는 id로 찾은 객체와, 방금 회원가입한 객체는 동일하게 memberA라는 출력값을 가질 것이다.
package hello.core; public class MemberApp { public static void main(String[] args) { MemberService memberService = new MemberServiceImpl(); Member member = new Member(1L, "memberA", Grade.VIP); memberService.join(member); Member findMember = memberService.findMember(1L); System.out.println("new member= "+member.getName()); System.out.println("findMember= "+findMember.getName()); } }
위는 아날로그한 테스트방식이고, 스프링에서는 test프로젝트에서 junit의 Test 모듈을 사용해 아래와 같은 방식으로 테스트한다.
package hello.core.member; public class MemberServiceTest { MemberService memberService = new MemberServiceImpl(); @Test void join() { //given :주어진거 Member member = new Member(1L, "memberA", Grade.VIP); //when :언제 memberService.join(member); Member findMember = memberService.findMember(1L); //then :이렇게된다. Assertions.assertThat(member).isEqualTo(findMember); } }
이렇게, 회원가입과 회원조회가 가능환 회원 도메인 설계를 성공하였다.
추가
MemberService memberService = new MemberServiceImpl();
이 코드는 다음과 같이 해석할 수 있습니다.
이 코드는 MemberService 인터페이스를 구현한 MemberServiceImpl 클래스의 객체를 생성하고,
그 객체를 memberService 변수에 할당하는 것을 의미합니다.
즉, MemberServiceImpl 클래스의 인스턴스를 memberService 변수로 참조할 수 있도록 만듭니다.
이것은 객체 지향 프로그래밍에서 인터페이스를 활용한 중요한 개념 중 하나인 "다형성(polymorphism)"을 나타냅니다.
이렇게 하면 memberService 변수를 통해 MemberService 인터페이스에 정의된 메서드를 호출할 수 있으며,
해당 메서드는 MemberServiceImpl 클래스에서 구현된 내용을 실행(오버라이딩)하게 됩니다.
이러한 접근 방식은 코드의 유연성을 높이고,
나중에 다른 구현체로 변경할 때도 기존 코드를 수정할 필요가 없도록 해줍니다.
'spring' 카테고리의 다른 글
[spring] spring이 뭔데? (3) 2023.11.20