package hello.core.member;

public enum Grade {
    BASIC,
    VIP
}

hello.core/member 패키지를 생성 후 enum으로 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 void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Grade getGrade() {
        return grade;
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }
}

멤버 엔티티를 만들어준다! 아이디,이름,등급 3가지 속성을 가지고 

생성자와 getter setter를 만들어준다!

 

package hello.core.member;

public interface MemberRepository {

    void save(Member member);

    Member findById(Long memberId);
}

멤버 저장소 인터페이스를 만들어준다.

저장하는 기능 , 회원을 찾는 기능!

 

package hello.core.member;

import java.util.HashMap;
import java.util.Map;

public class MemoryMemberRepository implements MemberRepository{

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

MemberRepository의 구현체를 만들어 준다!

 

값을 저장하기 위한 Map자료구조를 사용한다. <Long, Member>

저장하는 기능과, 찾는기능을 구현한다.

 

package hello.core.member;

public interface MemberService {

    void join(Member member);

    Member findMember(Long memeberId);
}

회원  서비스 인터페이스를 만든다.

두가지 기능!

-회원가입

-회원조회

 

package hello.core.member;

public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository = new MemoryMemberRepository();

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

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

이제 서비스 인터페이스에 대한 구현체를 만들었다.

 

가입을 하고 찾기 위해선 MemberRepository가 필요하다.

구현객체로는 MemoryMemberRepository를 사용한다.

 

join에서 memberRepository의 save를 호출하면

다형성에 의해 MemoryMemberRepository에 있는

save가 호출된다.

회원 도메인 요구사항

회원을 가입하고 조회할 수 있다.

회원은 일반과 VIP 두 가지 등급이 있다.

회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55335?tab=note&speed=0.75

 

회원 도메인을 먼저 설계해본다!

클라이언트가 회원 서비스를 호출하는데 회원가입과 회원조회 두가지 기능을 제공한다.

회원 저장소라는 것을 별도로 만드는 이유는 회원 데이터를 어떻게 할지 정해지지 않았기 때문이다.

 

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55335?tab=note&speed=0.75

 

실제 구현레벨로 내려오면 클래스 다이어그램으로 그려진다.

MemberService를 인터페이스로 만들고 MemberServiceImpl 구현체를 만든다.

MemberRepository는 회원 저장소 역할을 한다.(인터페이스)

 

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55335?tab=note&speed=0.75

회원 객체 다이어그램이다.

실제 서버에 올라오면 객체간에 참조들이 어떻게 되는지

그린 다이어그램이다. 클라이언트는 회원서비스 구현체를 바라보고

회원서비스 구현체는 메모리 회원 저장소를 바라본다.

 

클래스 다이어그램은 실제 서버를 실행하지 않고 클래스들을 분석해서 볼 수 있다.

동적으로 결정되는 (서버가 뜰때) 클라이언트가 실제사용하는 것을

객체다이어 그램으로 표현한다.

먼저 회원, 주문과 할인정책

 

회원

 

회원을 가입하고 조회할 수 있다.

회원은 일반과  VIP 두 가지 등급이 있다.

회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.(미확정)

 

주문과 할인 정책

 

회원은 상품을 주문할 수 있다.

회원등급에 따라 할인 정책을 적용할 수 있다.

할인 정책은 모든  VIP는 1000원을 할인해주는 고정 금액 할인을 적용(나중에 변경 될지도?)

할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을

미루고 싶다. 최악의 경우 할인을 적용하지 않을 수도 있다.(???미확정)

 


 

요구사항을 보면 회원 데이터, 할인 정책 같은 부분은 지금 결정하기 어려운 부분이다. 그렇다고 이런

정책이 결정될 때 까지 개발을 무기한 기다릴 수 도 있다. 앞에서 포스팅한 객체 지향 설계 방법을 이용!!

 

인터페이스를 만들고 구현체를 언제든지 갈아끼울 수 있도록 설계하면 된다. 

 

순수한 자바로만 진행할 예정이다.

먼저 https://start.spring.io/ 이 사이트에서 

자바 버전 11로 해서 Generate 버튼을 눌러서 

프로젝트를 생성 하였다!

 

이번에는 핵심 원리에 대한 시간이기에 

순수 자바코드로 진행 후 스프링의 기능들을 덫붙일 예정이다!

 

build.gradle파일에 아무 의존성 추가도 해주지 않고

일단 동작하는지 확인 후 다음 포스팅에 요구사항들을 정리할 예정이다.

 

컴파일 성공!

 

class Object

 

클래스 Object는 Java에서 모든 클래스의 superclass이다.

 

class Object의 멤버 메서드

    boolean equals(Object obj)

    int hashCode()

    String toString()

    Class<?> getclass()

 

Java의 모든클래스는 내가 만들어 주지 않아도 이미 위의 메서드를 가지고 있다.

다만 내 의도대로 작동하지는 않을 것이다.

(오버라이딩을 해서 써야함) 

그럼 왜 만들어놨을까???

 

toString()

 

만약 toString메서드를 따로 만들어주지 않는다면

예 : PhoneBook@db12340

이런식으로 반환됨

 

equals(Object)

 

Object클래스의 equals 메서드의 매개변수는 Object 타입이다.

매개변수로 제공된 객체와 자기 자신의 동일성을 검사한다.

이 메서드를 의도대로 사용하려면 오버라이딩 해야한다.

 

Class Class

 

모든 클래스는 하나의 Class 객체를 가진다.

이 객체는 각각의 클래스에 대해서 유일하다.

메서드 getClass()는 Object 클래스가 제공하는 메서드이며, 이 유일한 Class 객체 반환한다.

 

 

Wrapper class

 

Java에서 primitive type데이터와 non-primitive type데이터,

즉 객체는 근본적으로 다르게 처리된다.

 

가령 Object 타입의 배열에는 다형성의 원리에 의해서 모든 종류의 객체를 저장할 수 있다.

하지만 int double, char 등의 primitive type 데이터는 저장할 수 없다. 객체가 아니므로

 

때때로 primitive type 데이터를 객체로 만들어야할 경우가 있다.

이럴 때 Integer, Double , Character 등의 wrapper class를 이용한다.

 

기본 타입의 데이터를 하나의 객체로 포장해주는 클래스

예)

  int a = 20;

  Integer age = new Integer(a);

  int b = age.intValue(); // b는 20이됨

 

데이터 타입간의 변환 기능을 제공한다.

예)

   String str ="1234";

   int d = Integer.parseInt(str);

'알고리즘 with 자바 > 자료구조' 카테고리의 다른 글

추상클래스와 인터페이스 2  (0) 2021.07.07
추상클래스와 인터페이스 1  (0) 2021.07.07
스케줄러 프로그램  (0) 2021.07.01
상속 3  (0) 2021.06.30
상속 2  (0) 2021.06.29

스프링은 다음 기술로 다형성 + OCP,DIP를 가능하게 지원

  DI : 의존관계, 의존성 주입

  DI 컨테이너 제공

 

클라이언트 코드의 변경 없이 기능 확장

쉽게 부품 교체하듯이 개발

 

어떤 개발자가 좋은 객체 지향 개발을 하려고 OCP,DIP원칙을 지키면서 개발을 

해보니, 너무 할일이 많았다. 그래서 프레임워크로 만들어버렸다.

 

순수하게 자바로 OCP,DIP 원칙들을 지키면서 개발을 해보면, 결국 스프링

프레임워크를 만들게 된다.(더 정확히는 DI컨테이너)

 

정리

 

모든 설계에 역할과 구현을 분리하자.

애플리케이션 설계도 공연을 설계 하듯이 배역만 만들어두고, 배우는 언제든지 유연하게

변경할 수 있도록 만드는 것이 좋은 객체 지향 설계이다.

 

인터페이스를 도입하면 추상화라는 비용이 발생한다.

기능을 확장할 가능성이 없다면, 구체 클래스를 직접 사용하고, 향후 꼭 필요할 때

리팩터링해서 인터페이스를 도입하는 것도 방법이다.

SOLID

클린 코드로 유명한 로버트 마틴이 좋은 객체 지향 설계의 5가지 원칙을 정리한 것!

 

 

 

SRP 단일 책임 원칙

 

한 클래스는 하나의 책임만 가져야 한다.

하나의 책임이라는 것은 모호하다.

중요한 기준은 변경이다. 변경이 있을때 파급 효과가 적으면 단일 책임 원칙을 잘 따르는것

 

OCP  개방 폐쇄 원칙

 

소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.

이런 거짓말 같은 말이? 확장을 하려면, 당연히 기존 코드를 변경해야하지 않나?

다형성을 활용해보자

인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현

 

LSP 리스코프 치환 원칙

 

프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로

바꿀 수 있어야 한다.

다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야한다는 것, 다형성을 지원하기 위한

원칙, 인터페이스를 구현한 구현체는 믿고 사용하려면, 이 원칙이 필요하다.

 

ISP 인터페이스 분리 원칙

 

특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

자동차 인터페이스 -> 운전 인터페이스, 정비 인터페이스로 분리

사용자 클라이언트 -> 운전자 클라이언트, 정비사 클라이언트로 분리

분리하면 정비 인터페이스 자체가 변해도 운전자 클라이언트에 영향을 주지 않음

인터페이스가 명확해지고, 대체 가능성이 높아진다.

 

DIP 의존관계 역전 원칙

 

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

방법 중 하나다.

쉽게 이야기해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻

 

정리

 

객체 지향의 핵심은 다형성

다형성 만으로는 쉽게 부품을 갈아 끼우듯이 개발 할 수 없다.

다형성 만으로는 구현 객체를 변경할 때 클라이언트 코드도 함께 변경된다.

다형성 만으로는  OCP,DIP를 지킬 수 없다

뭔가 더 필요하다.

 

객체 지향 프로그래밍 

 

객체지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위,

즉 객체들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다.

 

객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이

사용된다.

 

유연하고 변경에 용이?

 

레고 블럭 조립하듯이

키보드, 마우스 갈아 끼우듯이

컴포넌트를 쉽고 유연하게 변경하면서 개발할 수 있는 방법

 

다형성

 

다형성의 실세계 비유

 

실세계와 객체 지향을 1 : 1로 매칭하지 않음

역할과 구현으로 세상을 구분

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55329?tab=note

 

운전자는 자동차 기종을 바꿔도 운전을 할 수있다.

자동차가 바뀌어도 운전자에게 영향을 줄 수없다.

 

자동차 인터페이스를 따라 모든 기종들을 구현했기때문

운전자를 위해 자동차 역할이라는 것이 존재

 

자동차 세상을 무한히 확장가능하다.

 

클라이언트의 영향을 주지않고 새로운 기능을 제공할 수 있다.

역할과 구현으로 구분하였기 때문에 가능하다.

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55329?tab=note

 

로미오 , 줄리엣 역할에 배우는 대체가 가능하다.

로미오가 클라이언트고

줄리엣이 서버라면

줄리엣이라는 구현이 바뀐다고 로미오에 영향을 주지않는다.

이것이 바로 유연하고 변경에 용이하다는 의미.

 

역할과 구현으로 구분하면 세상이 단순해지고, 유연해지며 변경도 편리해진다.

 

클라이언트는 대상의 역할(인터페이스)만 알면 된다.

클라이언트는 구현 대상의 내부 구조를 몰라도 된다.

클라이언트는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않는다.

클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다.

 

자바 언어

 

자바 언어의 다형성을 활용

 

역할 = 인터페이스

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

객체를 설계할 때 역할과 구현을 명확히 분리

객체 설계 시 역할을 먼저 부여하고, 그 역할을 수행하는 구현 객체 만들기

 

객체의 협력이라는 관계부터 생각

 

혼자있는 객체는 없다.

클라이언트 : 요청, 서버 : 응답

수 많은 객체 클라이언트와 객체 서버는 서로 협력 관계를 가진다.

 

자바 언어의 다형성

 

오버라이딩을 떠올려보자!

오버라이딩은 자바 기본 문법

오버라이딩 된 메서드가 실행 

다형성으로 인터페이스를 구현한 객체를 실행 시점에

유연하게 변경할 수 있다.

물론 클래스 상속 관계도 다형성, 오버라이딩 적용가능

 

다형성의 본질

 

인터페이스를 구현한 객체 인스턴스를 실행 시점에서 유연하게 변경할 수 있다.

클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다.

 

스프링과 객체지향

 

다형성이 가장 중요하다.

스프링은 다형성을 극대화해서 이용할 수 있게 도와준다.

스프링에서 이야기하는 제어의역전,의존관계 주입은 다형성을 활용해서 역할과

구현을 편리하게 다룰 수 있도록 지원한다.

AOP : Aspect Oriented Programming

공통 관심사항 , 핵심 관심사항 분리하는 것!

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/lecture/49601?tab=note&speed=0.75

 

모든 메서드에 시간 측정 로직을 붙이는 것이아니라

한 곳에 모으고 적용하는것이다!

package hello.hellospring.AOP;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class TimeTraceAop {
    @Around("execution(* hello.hellospring..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("START: " + joinPoint.toString());
        try {
            return joinPoint.proceed();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
        }
    }
}

TimeTraceAop 클래스를 생성한다! 

AOP는 @Aspect를 해줘야한다.

그리고 스프링 빈으로 등록해야한다.

 

@Component 를 쓰거나 SpringBean에 등록을 해서 쓸 수 있다.

나는 @Component를 써서 진행하였다!

 

한가지를 더해주어야하는데

@Around 라는 것으로 공통 관심 사항을 어디에 적용할 건지

타게팅을 해줄 수 있다 따로 문법이 있다! 매뉴얼 대로 하면 

쓰기 쉬웠다!

 

콘솔에 찍힌 문구이다.

START: execution(String hello.hellospring.controller.HomeController.home())
END: execution(String hello.hellospring.controller.HomeController.home()) 7ms
START: execution(String hello.hellospring.controller.MemberController.list(Model))
START: execution(List hello.hellospring.service.MemberService.findMembers())
START: execution(List org.springframework.data.jpa.repository.JpaRepository.findAll())
Hibernate: select member0_.id as id1_0_, member0_.name as name2_0_ from member member0_
END: execution(List org.springframework.data.jpa.repository.JpaRepository.findAll()) 192ms
END: execution(List hello.hellospring.service.MemberService.findMembers()) 204ms
END: execution(String hello.hellospring.controller.MemberController.list(Model)) 230ms

 

 

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/lecture/49601?tab=note&speed=0.75

 

스프링의 AOP 동작 방식 

 

AOP를 적용 하면 가짜 멤버 서비스를 만들어낸다(프록시)

가짜 스프링빈이 끝나면 그때 진짜를 호출해준다.

컨트롤러가 호출하는 것은 프록시라는 가짜 서비스를 호출한다.

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/lecture/49601?tab=note&speed=0.75

AOP 적용 전 후 비교그림이다.

 

MemberController에서 

MemberService.getClass()로 콘솔에 찍어보면

 

memberService = class hello.hellospring.service.MemberService$$EnhancerBySpringCGLIB$$8c469d23

 

실제가 아닌 가짜를 확인할 수 있었다!!

 

이 모든 것이 DI를 통해 이루어지기 때문에 가능한 일이다.

 

'웹프로그래밍 > Spring 입문' 카테고리의 다른 글

21. AOP  (0) 2021.07.01
20. 스프링 데이터 JPA  (0) 2021.06.30
19. JPA  (0) 2021.06.30
18. 스프링 JDBC Template  (0) 2021.06.29
17. 스프링 통합 테스트  (0) 2021.06.29

AOP가 필요한 상황

 

모든 메서드의 호출 시간을 측정하고 싶다면?

공통 관심 사항 vs 핵심 관심 사항

회원 가입 시간, 회원 조회 시간을 측정하고 싶다면?

 


 

문제

 

회원가입, 회원 조회에 시간을 측정하는 기능은 핵심 관심 사항이 아니다.

시간을 측정하는 로직은 공통 관심 사항이다.

시간을 측정하는 로직과 핵심 비즈니스 로직이 섞여서 유지보수가 어렵다.

시간을 측정하는 로직을 별도의 공통 로직으로 만들기 매우 어렵다.

시간을 측정하는 로직을 변경할때 모든 로직을 찾아가면서 변경해야 한다.

 

 

'웹프로그래밍 > Spring 입문' 카테고리의 다른 글

22. AOP 적용  (0) 2021.07.01
20. 스프링 데이터 JPA  (0) 2021.06.30
19. JPA  (0) 2021.06.30
18. 스프링 JDBC Template  (0) 2021.06.29
17. 스프링 통합 테스트  (0) 2021.06.29

+ Recent posts