3. 자바 예제 - 회원 도메인 설계 및 개발

Yuri JI·2023년 1월 8일
0
  • 먼저 스프링의 도움없이 순수하게 자바로만 비즈니스 요구 사항에 따라 코드를 작성한다.
  • 그 후 요구 사항이 변경되었을 때 유연하게 대처할 수 있는지 고민해본다.
    • 다형성, OCP, DIP가 잘 지켜지고있는지?
  • 발생하는 문제들을 객체지향원리를 적용하며 해결한다.

프로젝트 생성

  • Build, Execution, Deployment > Build Tools > Gradle

    위와 같이 IntelliJ IDEA 로 변경한다. IntelliJ 에서 자바를 바로 돌려서 Gradle로 했을 때보다 빠르다.

비즈니스 요구사항과 설계

  • 회원
    • 회원 가입, 회원 조회
    • 회원 등급은 일반, VIP 두 가지
    • 자체 DB or 외부 시스템과 연동 (미확정)
  • 주문과 할인 정책
    • 회원은 상품 주문 가능
    • 회원 등급에 따른 할인 정책 차등 적용
    • 모든 VIP는 1000원 할인 (고정 금액 할인) → 추후 변경 가능
    • 할인 정책은 변경 가능성이 높음. 회사의 기본 할인 정책은 아직 미확정 상태

🤨 요구 사항을 보면 회원 데이터와 할인 정책은 정책이 완전히 정해지지 않음
개발은 당장 시작해야한다.
⇒ 인터페이스를 만들고 구현체를 언제든지 갈아 끼울 수 있어야 한다 !
⇒ 앞에서 배운 객체 지향 설계 방법을 이용해서 비즈니스 요구 사항대로 설계해보자

❗ 지금은 순수하게 자바만을 이용해서 설계한다.

회원 도메인 개발

  1. 회원 엔티티
  2. 회원 저장소 (Repository)
    1. 회원 저장소 인터페이스

      public interface MemberRepository {
      	void save(Member member);
      	Member findById(Long memberId);
      }
    2. 메모리 회원 저장소 구현체

      public class MemoryMemberRepository implements MemberRepository{
      	private static Map<Long, Member> store = new HashMap<>();
      
      	@Override
      	public void save(Member member) { store.put() };
      
      	@Override
      	public Member findById(Long memberId) { store.get() };
      }
  3. 회원 서비스 (Service)
    1. 회원 서비스 인터페이스

      public interface MemberService {
      	void join(Member member);
      	Member findMember(Long memberId);
      }
    2. 회원 서비스 구현체

      public class MemberServiceImpl implements MemberService {
      
      	// 회원 가입/조회 하려면 MemberRepository가 필요하다.
      	private final MemberRepository memberRepository = new MemoryMemberRepository; 
      
      	@Override
        void join(Member member){ memberRepository.save(member); }
      
      	@Override
      	Member findMember(Long memberId){ return memberRepository.findById(memberId); }
      }

✍️ ConcurrnetHashMap
실무에서는 동시성 이슈때문에 HashMap이 아닌 ConcurrnetHashMap을 사용한다

✍️ MemberServiceImpl
관례상 인터페이스의 구현체가 1개만 존재할 경우에는 뒤에 Impl 을 붙여 클래스를 만든다.

  • MemberServiceImpl
public class MemberServiceImpl implements MemeberService {
		
		// 인터페이스 **MemberRepostiory**
		// 구현체     **MemoryMemberRepository**
		**private final MemberRepostiory memberRepostiory = new MemoryMemberRepository();**

		@Override
		public void join(Member member) {
			memberRepository.save(memeber);
		}
}

📌 회원 도메인 실행

  • (실행) MemberApp
public class MemberApp {
    public static void main(String[] args) {
			MemberService memberService = new MemberServcieImpl();
			Member memeber = new Member(1L, "memberA", Grade.VIP);
			memeberService.join(member); // 회원가입

			Member findMember = memberService.findMember(1L);
			System.out.println("new member = " + member.getName()); // memberA
			System.out.println("find member = " + findMember.getName()); // memberA
    }
}

이 방법은 매번 눈으로 결과를 보며 직접 확인해야한다.

📌 회원 도메인 테스트

  • (JUnit 테스트) MemberServiceTest
import org.assertj.core.api.Assertions; 

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).isEqulsTo(findMember);
	}
} 

📌 여기까지 회원 도메인 설계의 문제점

  1. 다른 저장소로 변경할 때 OCP 원칙을 준수하는지

✍️ OCP 원칙 ( Open Close Principle) : 개방폐쇄의 원칙
확장에 대해서는 Open, 변경에 대해서는 폐쇄(Close)
기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계해야한다.

  1. DIP를 잘 지키고 있는지

✍️ DIP 원칙 (Dependency Inversion Principle) : 의존 역전 원칙
자주 변경되는 구체 클래스에 의존하지마라.
ex. 자동차(인터페이스) 겨울 타이어에 의존한다. → 계절이 바뀌면 다시 변경해야한다. → DIP 위배
자동차가 구체적인 타이어(겨울, 일반, 광폭)가 아닌 추상화된 타이어 인터페이스에만 의존하게 한다.

  • 의존관계가 인터페이스 뿐만 아니라 구현까지 모두 의존하는 문제점이 있다.
    public class MemberServiceImpl implements MemeberService {
    		// 인터페이스(추상화)뿐만 아니라 구현체(구체화)에도 의존하고있다. -> DIP 위반 
    		private final MemberRepostiory memberRepostiory = new MemoryMemberRepository();
    		...
profile
안녕하세요 😄

0개의 댓글