스프링의 역사
그 이전의 EJB, 그게 너무 불편해서 나온게 스프링
스프링이란?
여러 스프링 프로젝트의 집합체
필수 : 스프링 프레임워크, 스프링 부트
선택 : 스프링 데이터, 스프링 세션, 스프링 시큐리티, 스프링 Rest Docs, 스프링 배치, 스프링 클라우드.. 이 외에도 많음
스프링의 진짜 핵심 : 스프링은 """좋은 객체 지향""" 어플리케이션을 개발할 수 있게 도와주는 프레임워크
객체 지향 프로그래밍 : 유연하고 변경이 용이 -> 다형성!
역할과 구현으로 세상을 구분
대체 가능성, 클라이언트쪽에 영향을 주지 않고 새로운 기능을 확장 가능. 실행 시간에 서버의 구현 기능을 유연하게 변경 가능 (이게 다형성의 본질)
핵심은 클라이언트 측면
근데 인터페이스가 변경되면 구현체도 다 바꿔야하고 클라이언트에도 영향이 가니까 클라이언트 서버 양측에 큰 변경이 발생한다. 따라서 인터페이스를 안정적으로 잘 설계할 줄 아는게 진짜 중요하다.
객체 지향의 꽃은 다형성이라고 생각함. 스프링은 이러한 다형성을 극대화해서 이용할 수 있게 도와줌.
좋은 객체 지향 설계의 5가지 원칙 (SOLID)
스프링은 다형성과 SOLID 원칙을 극대화하기 위한 도구임. SOLID 원칙에 대해 알아보자
한 클래스는 하나의 책임만 가져야 한다.
하나의 책임이라는게 좀 모호함
중요한 기준은 변경임. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 거라고 할 수 있다.
ex) UI 변경, 객체의 생성과 사용을 분리
가장 중요한 원칙
확장에는 열려있으나 변경에는 닫혀있어야한다.
확장을 하려면 변경해야하는거아닌가 이게 뭔 말??
다형성 잘 활용. 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현(확장). 이 것은 기존 코드를 변경하는 것은 아님. 이런걸 확장에 열려있지만 변경에는 닫혀있다고 함 (ex- 서비스가 레포지토리 인터페이스를 의존하고 있다면, 인터페이스 구현체를 새로 구현(확장)했다고 하자. 근데 서비스에 구현체 객체를 생성해서 인터페이스 타입형 변수에 할당하면 코드가 변경됨. 다형성을 썼지만 클라이언트 코드가 변경되네? 이걸 해결하기 위해 스프링 컨테이너의 DI와 IoC가 있는 것
예를 들면 자동차 인터페이스에 엑셀 추상 메소드가 있고, 이걸 구현할 때 속도가 증가하도록 구현해야함. 그런데 속도가 감소하도록 구현해도 컴파일적으로 오류는 안 남. 하지만 이 경우 LSP를 위반한 거임. 기능적으로 규약을 지켜야 함. 엑셀은 속도가 증가되게 구현되어야만 한다!
한마디로 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
예를 들어 자동차 인터페이스를 운전 인터페이스와 정비 인터페이스로 분리하고, 사용자 클라이언트로 운전자 클라이언트와 정비사 클라이언트로 분리하면 ISP를 지킨 것.
이렇게 분리하면 정비 인터페이스 자체가 변해도 운전자 클라이언트에 영향을 주지 않음. 인터페이스가 명확해지고, 대체 가능성이 높아짐.
얘도 제일 중요함
쉽게 이야기해서, 클라이언트 코드가 구현 클래스에 의존하지 말고 인터페이스에 의존하라는 뜻
즉 앞서 이야기한 역할에 의존하게 해야 한다는 것과 똑같은 얘기임.
역할과 구현을 철저하게 구분!
근데
private MemberRepository memberRepository = new MemoryMemberRepository();
private MemberRepository memberRepository = new JdbcMemberRepository();
이건 인터페이스도 의존하고 있고 구현체도 의존하고 있음 (의존한다는건 그냥 그걸 알고 있다는 거임). DIP 위반!
이렇듯 다형성만으로는 OCP, DIP를 지킬 수 없다. 역시나 이걸 해결하기 위해 스프링의 DI, IoC 등의 기술이 있는 것
객체 지향 설계와 스프링
이상적으로는 모든 설계에 인터페이스를 부여하자
그런데 실무적인 고민이 있음. 인터페이스를 무분별하게 막 도입하면 추상화라는 비용이 발생한다. 성능에 대한 비용을 말하는게 아니라, 다른 개발자가 딱 코드를 봤을 때 인터페이스만 보고 잘 알 수가 없고 구현체로 한 단계 더 넘어가서 봐야하는 불편함이 있음.
그래서 기능을 확장할 가능성이 없다면 구체 클래스를 직접 사용하고, 향후 꼭 필요할 때 리팩터링해서 인터페이스를 도입하는 것도 방법임. 아님 확장 가능성이 있다고 판단되면 첨부터 인터페이스로 설계해도 되고. 이런거는 경험이 쌓이면 더 잘할 수 있게 됨.
회원 도메인 개발
특정 인터페이스에 대해 구현체가 하나만 있는 경우에는, 그 구현체 이름 맨 뒤에 관례적으로 Impl을 붙임.
회원 도메인 실행과 테스트
psvm 치면 publis static void main 자동 완성 추천 뜸
soutv + tab : 변수 값 출력문 자동 완성
주문과 할인 도메인 개발
스트링과 enum을 비교할 땐 등호 두개 써주기
주문과 할인 도메인 실행과 테스트
원시 타입 long을 써도 되고 Long도 되고 큰 차이는 없는데, long의 경우 null을 넣을 수가 없어서 그 경우에는 Long을 써주면 됨
관심사의 분리
서비스 구현체 속에서, 레포지토리 구현체를 갈아끼우는 부분이 있다면, 이건 서비스 구현체가 서비스 구현체로서의 동작 이외에 레포지토리 구현체를 어떤거 쓸 지 판단도 해야하고, 다른 할인 정책 객체도 구현체를 new로 생성중이라면 이 서비스 구현체는 다양한 책임을 가지고 있는 상태이다. 이를 따로 configuration에서 주입을 맡도록 분리해보자