카테고리 보관물: 2. 프로그래밍

Spring 입문4

  1. 컴포넌트 스캔
    1-1 컴포넌트 스캔과 의존관계 자동 주입 시작하기
    – 기존의 @Bean 방식을 @Component 방식으로 변경
    – AppConfig에 @Configuration과 @ComponentScan 애노테이션을 설정
    – 이 때 @ComponentScan은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록
    – 각 클래스가 컴포넌트 스캔의 대상이 되도록 @Component 애노테이션을 각각 붙임
    – ServiceImpl등 생성자로 Repository 따위를 전달 받는 곳에 @Autowired를 통해 의존관계를 자동으로 주입 받음 (스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입)

    1-2 기본 스캔 대상
    – 컴포넌트 스캔은 @Component 뿐만 아니라 아래 내용도 추가로 대상에 포함
    — @Component : 컴포넌트 스캔에서 사용
    — @Controlller : 스프링 MVC 컨트롤러에서 사용 (스프링 MVC 컨트롤러로 인식)
    — @Service : 스프링 비즈니스 로직에서 사용 (스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환)
    — @Repository : 스프링 데이터 접근 계층에서 사용 (스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리)
    — @Configuration : 스프링 설정 정보에서 사용

    1-3 필터
    – includeFilters : 컴포넌트 스캔 대상을 추가로 지정
    – excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정
    – FilterType 옵션
    — ANNOTATION: 기본값, 애노테이션을 인식해서 동작한다.
    ex) org.example.SomeAnnotation
    — ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작한다.
    ex) org.example.SomeClass
    — ASPECTJ: AspectJ 패턴 사용
    ex) org.example..Service+ REGEX: 정규 표현식 ex) org.example.Default.
    — CUSTOM: TypeFilter 이라는 인터페이스를 구현해서 처리
    ex) org.example.MyTypeFilter

    1-4 중복 등록과 충돌
    – 자동빈 등록 vs 자동 빈 등록
    — 컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록
    — 이름이 같은 경우 스프링은 오류를 발생 (ConflictingBeanDefinitionException)

    – 수동 빈 등록 vs 자동 빈 등록
    — 수동 빈 등록이 우선권을 가짐 ( 수동 빈이 자동 빈을 오버라이딩)


  2. 의존관계 자동 주입
    2-1 다양한 의존 과계 주입 방법
    – 생성자 주입 (권장)
    — 생성자 호출시점에서 딱 1 번만 호출되는 것이 보장
    — 불변, 필수 의존관계에 사용

    – 수정자 주입 (setter 주입)
    — setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해 의존관계를 주입
    — 선택, 변경 가능성이 있는 의존관계에 사용
    — 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용

    – 필드 주입
    — 코드가 간결
    — 외부에서 변경이 불가능해서 테스트 하기 힘들다는 단점
    — DI 프레임워크 없이 불가
    — 사용 자제

    – 일반 메서드 주입
    — 한번에 여러 필드를 주입 가능
    — 일반적으로 사용하지 않음

    2-2 옵션 처리
    – 주입할 스프링 빈이 없어도 동작을 필요로 할 때가 있음
    – @Autowired만 사용하면 required 옵션의 기본값이 true로 되어 있어 자동 주입 대상이 없으면 오류를 발생
    – 자동 주입 대상을 옵션으로 처리하는 방법
    — @Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안 됨
    — org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력
    — Optional<> : 자동 주입할 대상이 없으면 Optional.empty 가 입력

    2-3 생성자 주입 선택 권장 (결론)
    – 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없음
    – 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안 됨 (불변)
    – 수정자 주입을 사용하면, setXxx 메서드를 public으로 열어두어야 함
    – 누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아님
    – 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없음, 따라서 불변하게 설계할 수 있음
    – final 키워드
    — 생성자 주입을 사용하면 필드에 final 키워드 사용 가능
    — 생성자에 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에서 막아줌

    2-4 롬복 라이브러리
    – @RequiredArgsConstructor : final이 붙은 필드를 모아서 생성자를 자동으로 만들어 줌 (롬복이 자바의 애노테이션 프로세서라는 기능을 이용)


  3. 빈 생명주기 콜백
    3-1 빈 생명주기 콜백 시작
    데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요

    – 스프링 빈의 라이프사이클 (간략화) : 객체 생성 -> 의존관계 주입
    — 스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 수 있는 준비가 완료
    — 따라서 초기화 작업은 의존관계 주입이 모두 완료되고 난 다음에 호출해야 함
    — 스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주는 다양한 기능을 제공
    — 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 줌
    — 따라서 안전하게 종료 작업 진행 가능

    스프링 빈의 이벤트 라이프사이클
    ( 스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 종료 )
    – 초기화 콜백 : 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
    – 소멸전 콜백 : 빈이 소멸되기 직전에 호출
    ( 참고 : 생성자는 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다. 반면에 초기화는 이렇게 생성된 값들을 활용해서 외부 커넥션을 연결하는등 무거운 동작을 수행한다. 따라서 생성자 안에서 무거운 초기화 작업을 함께 하는 것 보다는 객체를 생성하는 부분과 초기화 하는 부분을 명확하게 나누는 것이 유지보수 관점에서 좋다. 물론 초기화 작업이 내부 값들만 약간 변경하는 정도로 단순한 경우에는 생성자에서 한번에 다 처리하는게 더 나을 수 있다. )

    – 스프링 빈 생명주기 콜백 방법 
    — 1. 인터페이스 InitializingBean, DisposableBean
    — InitializingBean, DisposableBean를 implements하여 클래스 생성
    — InitializingBean 은 afterPropertiesSet() 메서드로 초기화를 지원
    — DisposableBean 은 destroy() 메서드로 소멸을 지원
    — 초기화, 소멸 인터페이스 단점 : 각 인터페이스는 스프링 전용 인터페이스이므로 해당 코드가 의존 ( 메서드 이름 변경 불가, 내가 코드를 고칠 수 없는 외부 라이브러리에 적용 불가 – 거의 사용하지 않음 )
    — 2. 빈 등록 초기화, 소멸 메서드 지정
    — 설정 정보에 @Bean(initMethod = “init”, destroyMethod = “close”) 처럼 초기화, 소멸 메서드를
    지정 가능
    — 자유로운 메서드 이름 수정
    — 스프링 빈이 스프링 코드에 의존하지 않음
    — 코드가 아니라 설정 정보를 사용하기 떄문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드를 적용할 수 있음
    — 3. 애노테이션 @PostConstruct, @PreDestroy (권장)
    — 최신 스프링에 가장 권장되는 방법
    — 애노테이션 하나만 붙이면 되므로 매우 편리
    — 스프링 종속적 기술이 아닌 JSR-250라는 자바 표준 ( 패키지 javax.annotation.PostConstruct )
    — 단점 : 외부 라이브러리에 적용 불가, 외부 라이브러리를 초기화, 종료 해야 하면 @Bean의 기능을 사용

◆ 그 외 사항

포스팅 계획 –  4월 : MVC 모델링, 정처기 실기(+중간) / 5월 : 스프링부트, MVC 모델링 / 6월 : 졸업 작품(+기말) / 7월 : 스프링부트 / 8월 ~ : 알고리즘과 CS, JPA, 스프링부트를 포함한 미정

  • 아직 해당 작성글에 틀린 부분이 많은 것으로 예상됩니다. 언제나 댓글 환영입니다
  • 그리고 이 글은 인프런 김영한 선생님의 Spring 로드맵 과정입니다

 

Kotlin 안드로이드 앱 프로그래밍 (5)

Char, Str (문자와 문자열)
Char는 문자를 표현하는 타입입니다. 코틀린 코드에서 Char 타입의 데이터는 문자를 작은따옴표 (‘ex) Char = ‘a’ ‘) 로 감싸서 표현합니다. 단 Number 타입으로는 표현할 수 없습니다.

* 문자 표현

val a: Char = 'a'
if (a==1) { // 오류 발생
}

Str는 문자열을 표현하는 타입입니다. String 타입의 데이터는 문자열을 큰 따옴표(“ex) “a” “)나 삼중 따옴표 (“””) 로 감싸서 표현합니다. 큰따옴표로 표현한 문자열에서 Enter나 Tab에 의한 줄 바꿈이나 들여쓰기 등을 그대로 유지하려면 역슬래시로 시작하는 이스케이프 시퀀스를 입력해야 합니다. 그러나 삼중 따옴표로 표현할 때는 키보드로 입력한 줄 바꿈이나 들여쓰기 등이 데이터에 그대로 반영됩니다. 다음 코드와 실행 결과를 참고해보면 됩니다.

* 문자열 표현

fun main(){
    val str1 = "Hi \n Hi"
    val str2 = """
        Hi
        Hi
    """
    println("str1 : $str1")
    println("str2 : $str2")
}
---------------------------------[실행결과]
str1 : Hi (\n 반영됨 줄바꿈)
 Hi
Str2 : (""" 키보드로 입력한 줄내용 반영)
       Hi
       Hi

안드로이드 스튜디오에서 삼중 따옴표를 사용하면 닫는 따옴표 뒤에 .trimIndent() 함수가 자동으로 추가됩니다. 이 함수는 문자열 앞에 공백을 없애 줍니다.
Str 타입의 데이터에 변숫값이나 어떤 연산식의 결괏값을 포함해야 할 때는 $ 기호를 이용합니다. 이를 문자열 템플릿 이라고 합니다.

* 문자열 템플릿

fun main(){
    fun sum(no: Int):Int{
      var sum = 0
    for (i in 1..no){
          sum += i
    }
    return sum
}

val name: String = "KDIDI"
println("name : $name, sum : ${sum(10)}, plus : ${10+20}")
-----------------------------------------------------------[실행결과]
name : KDIDI, sum : 55(1부터 10까지의 sum+=i에 넣은 값), plus : 30($10+20 결과)

Any – 모든 타입
Any는 코틀린에서 최상위 클래스입니다. 모든 코틀린의 클래스는 Any의 하위 클래스 입니다. 따라서 Any 타입으로 선언한 변수에는 모든 타입의 데이터를 할당할 수 있습니다.
(최상위 클래스)
모든 타입의 데이터를 할당할 수 있음

Any 타입 사용 예

val data1: Any = 10   -> int형
val data2: Any = "Hi" -> Str형

class User
val data3: Any = User() -> Class형

Unit – 반환문 없는 함수
Unit은 다른 타입과 다르게 데이터의 형식이 아닌 특수한 상황을 표현하려는 목적으로 사용합니다.
Unit 타입으로 선언한 변수에는 Unit 객체만 대입할 수 있습니다. 따라서 Unit 타입으로 변수를 선언할 수는 있지만 의미가 없습니다. 이런 Unit 타입은 주로 함수의 변환 타입으로 사용합니다. 함수에서 반환문이 없음을 명시적으로 나타날 때 Unit 타입을 사용합니다.

* Unit 타입 사용

val data1: Unit = Unit
* Unit 타입 사용 (반환문이 없는 함수)

fun some() : Unit{
    println(10+20)
}

함수를 선언할 때 반환 타입을 생략하면 자동으로 Unit이 적용됩니다. 즉, 오른쪽 소스는 위의 소스와 같습니다.

* 반환 타입을 생략한 예

fun some(){
    println(10+20)
}
--자동으로 Unit 적용됨--

Nothing – null이나 예외를 반환하는 함수
Nothing도 Unit과 마찬가지로 의미 있는 데이터가 아니라 특수한 상황을 표현합니다. Nothing 으로 선언한 변수에는 null만 대입할 수 있습니다. 즉, Nothing 으로 선언한 변수는 데이터로서는 의미가 없습니다.

* Nothing 사용

val data1: Nothing? = null

Nothing은 주로 함수의 반환 타입에 사용합니다. 어떤 함수의 반환 타입이 Nothing이면 반환은 하지만 의미 있는 값은 아니라는 의미 입니다. 항상 null만 반환하는 함수라든가 예외를 던지는 함수의 반환 타입을 Nothing 으로 선언합니다. (의미있는 값은 아니다.)

null 반환 함수와 예외를 던지는 함수

fun some1(): Nothing?{
    return null
}
fun some2(): Nothing{
    throw Exception()
}

널 허용과 불허용
코틀린의 모든 타입은 객체이므로 변수에 null을 대입할 수 있습니다. null은 값이 할당되지 않은 상황을 의미합니다. 코틀린에서는 변수를 선언할 때 null을 대입할 수 있는 변수인지, null을 대입할 수 없는 변수인지 명확하게 구분해서 선언해야 합니다.
(null 허용 nullable) (null 불허용 not null)
이러한 구분은 변수를 선언할 때 타입 뒤에 물음표(?)로 표시합니다. 타입 뒤에 물음표를 추가 하면 널 허용으로 선언하지만 반대로 물음표를 추가하지 않으면 불허용으로 선언합니다.

널 허용과 불허용

val data1: Int = 10
data1 = null // 오류!

val data2: Int? = 10
data2 = null // 성공!
-----------------------
? 유무는 있을때는 널 허용으로 선언
없을때는 널 불허용으로 선언 따라서 data1은 오류를 출력하게 되고
data2는 null을 대입하여도 정상적으로 출력하게 된다.

함수 선언하기
코틀린에서 함수를 선언하는 방법은 fun 이라는 키워드를 이용합니다.

* 함수 선언 방식

fun 함수명(매개변수명: 타입): 반환 타입 {...}

함수에는 반환 타입을 선언할 수 있으며 생략하면 자동으로 Unit 타입이 적용됩니다.

* 반환 타입이 있는 함수 선언

fun some(data1: Int): Int {
    return data1 * 10
}

함수의 매개변수에는 var나 val 키워드를 사용할 수 없습니다. val이 자동으로 적용되며 함수 안에서 매개변숫값을 변경할 수 없습니다.

* 매개변숫값 변경 오류

fun some(data1: Int){
    data1 = 20 // 오류
}

Spring 입문 3

AppConfig 리팩터링

현재 AppConfig를 보면 중복이 있고, 역할에 따른 구현이 잘 안보인다.


기대하는 그림

  • 각 메서드의 반환타입을 구현체가 아닌 인터페이스로 반환 타입으로 반환해야함을 잊지 말자
  • 모든 메서드는 public으로 작성한다.
    AppConfig 를 보면 역할과 구현 클래스가 한눈에 들어온다. 애플리케이션 전체 구성이 어떻게 되어있는지 빠르게 파악할 수 있다.

새로운 구조와 할인 정책 적용

처음으로 돌아가서 정액 할인 정책을 정률% 할인 정책으로 변경해보자.
FixDiscountPolicy RateDiscountPolicy 어떤 부분만 변경하면 되겠는가?
AppConfig의 등장으로 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성(Configuration)하는 영역으로 분리되었다


FixDiscountPolicy -> RateDiscountPolicy 로 변경해도 구성 영역만 영향을 받고, 사용 영역은 전혀 영향을 받지 않는다.
AppConfig 에서 할인 정책 역할을 담당하는 구현을 FixDiscountPolicy -> RateDiscountPolicy
객체로 변경했다.
이제 할인 정책을 변경해도, 애플리케이션의 구성 역할을 담당하는 AppConfig만 변경하면 된다.
클라이언트 코드인 OrderServiceImpl 를 포함해서 사용 영역의 어떤 코드도 변경할 필요가 없다.
구성 영역은 당연히 변경된다. 구성 역할을 담당하는 AppConfig를 애플리케이션이라는 공연의 기획자로 생각하자. 공연 기획자는 공연 참여자인 구현 객체들을 모두 알아야 한다.

좋은 객체 지향 설계의 5가지 원칙의 적용 (여기서 3가지 SRP, DIP, OCP 적용)

SRP 단일 책임 원칙
한 클래스는 하나의 책임만 가져야 한다.


클라이언트 객체는 직접 구현 객체를 생성하고, 연결하고, 실행하는 다양한 책임을 가지고 있음
SRP 단일 책임 원칙을 따르면서 관심사를 분리함
구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당
클라이언트 객체는 실행하는 책임만 담당


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


새로운 할인 정책을 개발하고, 적용하려고 하니 클라이언트 코드도 함께 변경해야 했다. 왜냐하면 기존
클라이언트 코드( OrderServiceImpl )는 DIP를 지키며 DiscountPolicy 추상화 인터페이스에
의존하는 것 같았지만, FixDiscountPolicy 구체화 구현 클래스에도 함께 의존했다.
클라이언트 코드가 DiscountPolicy 추상화 인터페이스에만 의존하도록 코드를 변경했다.
하지만 클라이언트 코드는 인터페이스만으로는 아무것도 실행할 수 없다.
AppConfig가 FixDiscountPolicy 객체 인스턴스를 클라이언트 코드 대신 생성해서 클라이언트
코드에 의존관계를 주입했다. 이렇게해서 DIP 원칙을 따르면서 문제도 해결했다.


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


다형성 사용하고 클라이언트가 DIP를 지킴
애플리케이션을 사용 영역과 구성 영역으로 나눔
AppConfig가 의존관계를 FixDiscountPolicy RateDiscountPolicy 로 변경해서 클라이언트
코드에 주입하므로 클라이언트 코드는 변경하지 않아도 됨
소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀 있다!

IoC, DI, 그리고 컨테이너
제어의 역전 IoC(Inversion of Control)
기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고, 실행했다.
한마디로 구현 객체가 프로그램의 제어 흐름을 스스로 조종했다. 개발자 입장에서는 자연스러운 흐름이다.
반면에 AppConfig가 등장한 이후에 구현 객체는 자신의 로직을 실행하는 역할만 담당한다. 프로그램의 제어 흐름은 이제 AppConfig가 가져간다. 예를 들어서 OrderServiceImpl 은 필요한 인터페이스들을 호출하지만 어떤 구현 객체들이 실행될지 모른다.
프로그램에 대한 제어 흐름에 대한 권한은 모두 AppConfig가 가지고 있다. 심지어 OrderServiceImpl
도 AppConfig가 생성한다. 그리고 AppConfig는 OrderServiceImpl 이 아닌 OrderService
인터페이스의 다른 구현 객체를 생성하고 실행할 수 도 있다. 그런 사실도 모른체 OrderServiceImpl 은 묵묵히 자신의 로직을 실행할 뿐이다.
이렇듯 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)이라 한다.


프레임워크 vs 라이브러리


프레임워크가 내가 작성한 코드를 제어하고, 대신 실행하면 그것은 프레임워크가 맞다. (JUnit) 반면에 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아니라 라이브러리다.


의존관계 주입 DI(Dependency Injection)
OrderServiceImpl 은 DiscountPolicy 인터페이스에 의존한다. 실제 어떤 구현 객체가 사용될지는 모른다.
의존관계는 정적인 클래스 의존 관계와, 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계 둘을 분리해서 생각해야 한다.


정적인 클래스 의존관계
클래스가 사용하는 import 코드만 보고 의존관계를 쉽게 판단할 수 있다. 정적인 의존관계는
애플리케이션을 실행하지 않아도 분석할 수 있다. 클래스 다이어그램을 보자
OrderServiceImpl 은 MemberRepository , DiscountPolicy 에 의존한다는 것을 알 수 있다.
그런데 이러한 클래스 의존관계 만으로는 실제 어떤 객체가 OrderServiceImpl 에 주입 될지 알 수 없다.

동적인 객체 인스턴스 의존 관계
애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계다.

객체 다이어그램

  • 애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결 되는 것을 의존관계 주입이라 한다.
  • 객체 인스턴스를 생성하고, 그 참조값을 전달해서 연결된다.
  • 의존관계 주입을 사용하면 클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입인스턴스를 변경할 수 있다.
  • 의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.

IoC 컨테이너, DI 컨테이너

  • AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IoC 컨테이너 또는 DI 컨테이너라 한다.
  • 의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다.
  • 또는 어샘블러, 오브젝트 팩토리 등으로 불리기도 한다

스프링으로 전환하기

AppConfig를 스프링 기반으로 변경

  • AppConfig에 설정을 구성한다는 뜻의 @Configuration 을 붙여준다.
  • 각 메서드에 @Bean 을 붙여준다. 이렇게 하면 스프링 컨테이너에 스프링 빈으로 등록한다

MemberApp에 스프링 컨테이너 적용
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
– 작성한 AppConfig의 빈들을 스프링 컨테이너에 등록한 후 생성
MemberService memberService = applicationContext.getBean(“memberService”, MemberService.class);
– 컨테이너에서 등록된 스프링 빈들 중 AppCofing.memberService를 가져옴 (“가져올 메서드 이름”, “반환타입”)

스프링 컨테이너

  • ApplicationContext 를 스프링 컨테이너라 한다.
  • 기존에는 개발자가 AppConfig 를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제부터는 스프링 컨테이너를 통해서 사용한다.
  • 스프링 컨테이너는 @Configuration 이 붙은 AppConfig 를 설정(구성) 정보로 사용한다. 여기서 @Bean 이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다.
  • 스프링 빈은 @Bean 이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다. ( memberService , orderService )
  • 이전에는 개발자가 필요한 객체를 AppConfig 를 사용해서 직접 조회했지만, 이제부터는 스프링 컨테이너를 통해서 필요한 스프링 빈(객체)를 찾아야 한다. 스프링 빈은 applicationContext.getBean() 메서드를 사용해서 찾을 수 있다.
  • 기존에는 개발자가 직접 자바코드로 모든 것을 했다면 이제부터는 스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용하도록 변경되었다

컨테이너에 등록된 모든 빈 조회

모든 빈 출력하기

  • 실행하면 스프링에 등록된 모든 빈 정보를 출력할 수 있다.
  • ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회한다.
  • ac.getBean() : 빈 이름으로 빈 객체(인스턴스)를 조회한다.
    애플리케이션 빈 출력하기
  • 스프링이 내부에서 사용하는 빈은 제외하고, 내가 등록한 빈만 출력해보자.
  • 스프링이 내부에서 사용하는 빈은 getRole() 로 구분할 수 있다.
    — ROLE_APPLICATION : 일반적으로 사용자가 정의한 빈
    — ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈

스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 조회 방법
ac.getBean(빈이름, 타입)
ac.getBean(타입)
조회 대상 스프링 빈이 없으면 예외 발생
NoSuchBeanDefinitionException: No bean named ‘xxxxx’ available
(타입으로 조회시 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생한다. 이때는 빈 이름을 지정)

스프링 빈 조회 – 상속 관계
부모 타입으로 조회하면, 자식 타입도 함께 조회한다.
그래서 모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회한다.

BeanFactory vs ApplicationContext
BeanFactory

  • 스프링 컨테이너의 최상위 인터페이스다.
  • 스프링 빈을 관리하고 조회하는 역할을 담당한다.
  • getBean() 을 제공한다.
  • 지금까지 우리가 사용했던 대부분의 기능은 BeanFactory가 제공하는 기능이다.
    ApplicationContext
  • BeanFactory 기능을 모두 상속받아서 제공한다.
  • 빈을 관리하고 검색하는 기능을 BeanFactory가 제공해주는데, 그러면 둘의 차이가 뭘까?
  • 애플리케이션을 개발할 때는 빈은 관리하고 조회하는 기능은 물론이고, 수 많은 부가기능이 필요하다.
  • 정리
  • ApplicationContext는 BeanFactory의 기능을 상속받는다.
  • ApplicationContext는 빈 관리기능 + 편리한 부가 기능을 제공한다.
  • BeanFactory를 직접 사용할 일은 거의 없다. 부가기능이 포함된 ApplicationContext를 사용한다.
  • BeanFactory나 ApplicationContext를 스프링 컨테이너라 한다.

싱글톤 컨테이너

웹 애플리케이션과 싱글톤

  • 스프링은 태생이 기업용 온라인 서비스 기술을 지원하기 위해 탄생했다.
  • 대부분의 스프링 애플리케이션은 웹 애플리케이션이다. 물론 웹이 아닌 애플리케이션 개발도 얼마든지 개발할 수 있다.
  • 웹 애플리케이션은 보통 여러 고객이 동시에 요청을 한다.
  • 기존에 만들었던 스프링 없는 순수한 DI 컨테이너인 AppConfig는 요청을 할 때 마다 객체를 새로 생성한다.
  • 고객 트래픽이 초당 100이 나오면 초당 100개 객체가 생성되고 소멸된다! ->메모리 낭비가 심하다.
    해결방안은 해당 객체가 딱 1개만 생성되고, 공유하도록 설계하면 된다. -> 싱글톤 패턴

싱글톤 패턴
클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.
그래서 객체 인스턴스를 2개 이상 생성하지 못하도록 막아야 한다.
private 생성자를 사용해서 외부에서 임의로 new 키워드를 사용하지 못하도록 막아야 한다
참고: 싱글톤 패턴을 구현하는 방법은 여러가지가 있다. 여기서는 객체를 미리 생성해두는 가장 단순하고 안전한 방법을 선택했다

싱글톤 패턴을 적용하면 고객의 요청이 올 때 마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 효율적으로 사용할 수 있다. 하지만 싱글톤 패턴은 다음과 같은 수 많은 문제점들을 가지고 있다.

싱글톤 패턴 문제점

  • 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
  • 의존관계상 클라이언트가 구체 클래스에 의존한다. DIP를 위반한다.
  • 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
  • 테스트하기 어렵다.
  • 내부 속성을 변경하거나 초기화 하기 어렵다.
  • private 생성자로 자식 클래스를 만들기 어렵다.
  • 결론적으로 유연성이 떨어진다.
    안티패턴으로 불리기도 한다.

싱글톤 방식의 주의점

  • 싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지(stateful)하게 설계하면 안된다.
  • 무상태(stateless)로 설계해야 한다!
    — 특정 클라이언트에 의존적인 필드가 있으면 안된다.
    — 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다!
    — 가급적 읽기만 가능해야 한다.
    — 필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.
  • 스프링 빈의 필드에 공유 값을 설정하면 정말 큰 장애가 발생할 수 있다!!

금주의 IntelliJ 단축키

  • Ctrl + Alt + M : 리팩토링
  • iter : 집합의 각 요소들을 하나씩 꺼내주는 for문 생성
  • soupv : 변수 print 자동 작성
  • Ctrl + D : 드래그 범위 코드를 복사하여 밑에 붙임 (복제)
  • Ctrl + E : 최근 열었던 파일 목록 ( Enter 입력 시, 바로 전 파일 열람)
  • Ctrl + Shift + Enter : 현재 입력바가 어디 있든 다음 라인으로 이동
  • Shift x2 : 메서드, 클래스 등 검색 (Ctrl + N)

◆ 그 외 사항

포스팅 계획 – 3월 : 스프링 / 4월 : 정처기 실기(+중간) / 5월 : 스프링부트, MVC 모델링 / 6월 : 졸업 작품(+기말) / 7월 : 스프링부트 / 8월 ~ : 알고리즘과 CS, JPA, 스프링부트를 포함한 미정

  • 아직 해당 작성글에 틀린 부분이 많은 것으로 예상됩니다. 언제나 댓글 환영입니다
  • 그리고 이 글은 인프런 김영한 선생님의 Spring 로드맵 과정입니다

Kotlin 안드로이드 앱 프로그래밍 (4)

변수 선언
코틀린 내에서 변수 선언은 val , var 키워드로 선언합니다. val은 value의 줄임말로 초깃값이 할당되면 바꿀 수 없는 변수를 선언할 때 사용하고, var은 variable의 줄임말로 초깃값이 할당된 후에도 값을 바꿀 수 있는 변수를 선언할 때 사용합니다.
val(value) = 값 할당 시 바꿀 수 없는 변수 선언할 때 사용
var(variable) = 값 할당된 후에도 바꿀 수 있는 변수 선언할 때 사용

val(혹은 var) 변수명 : 타입 = 값 
val과 var 변수의 차이

val data1 = 10
var data2 = 10

fun main(){
    data1 = 20 //오류
    data2 = 20 //성공
}

data1과 data2에 변수를 선언하였고, data1은 val 키워드로 선언했으며 초깃값을 할당한 후 main() 함수에서 값을 변경하려고 시도합니다. 여기서 컴파일 오류가 발생합니다. val 키워드로 선언한 변수는 처음 할당한 값으로만 이용할 수 있습니다. 그런데 data2 변수는 var 키워드로 선언했으므로 값을 변경할 수 있습니다.
val = 초깃값 할당 후 변경불가
var = 초깃값 할당 후 변경가능 [위 내용 참고]

타입 지정과 타입 추론
변수명 뒤에는 콜론을 추가하여 타입을 명시할 수 있으며, 대입하는 값에 따라서 타입을 유추 할 수 있을 때는 생략 가능합니다.

val data1: Int = 10
val data2 = 10     -- (val data2: Int = 10) 유추 가능하면 생략가능

data1은 명시적으로 Int 타입을 선언한 것이며 data2는 대입한 값이 10 이므로 타입을 명시하지 않아도 자동으로 Int 타입이 됩니다. 즉 val data2: Int = 10이라고 선언한 것과 똑같습니다.

초깃값 할당
최상위에 선언한 변수나 클래스의 멤버 변수는 선언과 동시에 초깃값을 할당해야 하며, 함수 내부에 선언한 변수는 선언과 동시에 초깃값을 할당하지 않아도 됩니다. 물론 변수를 이용하려면 값을 할당하고 이용해야 합니다.

변수-클래스 멤버변수 선언과 동시에 초깃값을 할당
함수 내부에 선언한 변수 선언과 동시에 초깃값 할당하지 않아도 됨

val data1: Int //오류
val data2 = 10 //성공

fun someFun(){
    val data3: Int
    println("data3 : $data3")  //오류
    data3 = 10
    println("Data3 : $data3") //성공
}

class User{
    val data4: Int //오류
    val data5: Int = 10 //성공
}

**초깃값 할당하기**

초기화 미루기
변수를 선언할 시 초깃값을 할당할 수 없는 경우가 있습니다. 이때는 값을 이후에 할당할 것이라고 컴파일러에게 알려 주어야 합니다. lateinit나 lazy 키워드를 이용하여야 합니다.
lateinit 키워드는 이후에 초깃값을 할당할 것임을 명시적으로 선언합니다.

lateinit var data1: Int    //오류
lateinit val data2: String //오류
lateinit var data3: String //성공

lateinit 2가지 규칙
1. lateinit은 var 키워드로만 변수 선언 사용 가능합니다.
2. Int,Long,Short,Double,Float,Boolean,Byte 타입에는 사용할 수 없습니다.

lazy 키워드는 변수 선언문 뒤에 by lazy {} 형식으로 선언하며, 소스에서 변수가 최초로 이용되는 순간 중괄호로 묶은 부분이 자동으로 실행되어 그 결괏값이 변수의 초깃값으로 할당됩니다. lazy 문의 중괄호 부분을 여러 줄로 작성한다면 마지막 줄의 실행 결과가 변수의 초깃값이 됩니다.

val data4: Int by lazy{
    println("in lazy.....")
    10
}

fun main(){
    println("in main.....")
    println(data4 + 10)
    println(data4 + 10)
}

결과값
in main.....
in lazy.....
20
20

데이터 타입
코틀린이 제공하는 데이터 타입
코틀린의 모든 변수는 객체, 따라서 코틀린의 모든 타입은 객체 타입입니다. 정수를 다루는 타입이 Int인데 Int는 기초 데이터 타입이 아니라 클래스 입니다.

fun someFun(){
    var data1: Int = 10    
    var data2: Int? = null  // null 대입 가능

    data1 = data1 + 10
    data1 = data1.plus(10)  // 객체의 메서드 이용 가능
}

data1과 data2 변수를 Int 타입으로 선언했습니다. 만약 Int 타입이 기초 데이터 타입이라면 변수에 null을 대입할 수 없으며 메서드를 호출할 수도 없습니다. 하지만 코틀린의 모든 타입은 객체이므로 Int 타입의 변수에 10이라는 정수 뿐만 아니라 null을 대입할 수도 있습니다. 또 한 객체의 메서드도 호출할 수 있습니다.
** null은 객체가 선언만 되고 메모리 할당이 되지 않았음을 의미합니다. **

기초 타입 객체
(Int Short Long Double Float Byte Boolean)
기초 데이터를 객체로 표현하는 타입입니다. 정수를 표현하는 Int, Short, Long과 실수를 표현하는 Double, Float, 2진수를 표현하는 Byte, 그리고 true나 false를 표현하는 Boolean 타입이 있습니다.

val a1: Byte = 0b00001011

val a2: Int = 123
val a3: Short = 123
val a4: Long = 10L
val a5: Double = 10.0
val a6: Float = 10.0f

val a7: Boolean = true

파이썬 라이브러리 소개: diskcache

diskcache 는 말 그대로 disk 에 cache 데이터를 저장시켜주는 라이브러리 입니다.

일반 캐시와 deque 등 다양한 자료구조를 지원합니다.

기본적인 캐시 선언입니다.

from diskcache import Cache
cache = Cache()

다음과 같이 사용할 수 있습니다. 자료를 get/set 하고 만료기한을 설정할 수 있습니다.

cache.pop('alice')
# 1
cache.pop('dave', default='does not exist')
# 'does not exist'
cache.set('dave', 0, expire=None, tag='admin')
# True
result = cache.pop('dave', expire_time=True, tag=True)
value, timestamp, tag = result
value
# 0
print(timestamp)
# None
print(tag)
# admin

데이터베이스를 샤딩하는 fanout cache 도 지원합니다. 샤딩이 되기 때문에 쓰기가 겹칠 확률이 줄어듭니다. (읽기는 동시에 해도 상관 없음) 아래 예시는 샤드를 4개 하고 타임아웃이 1초인 경우입니다.

from diskcache import FanoutCache
cache = FanoutCache(shards=4, timeout=1)

함수 리턴값을 캐시할 수 있습니다.

from diskcache import FanoutCache
cache = FanoutCache()

@cache.memoize(typed=True, expire=1, tag='fib')
def fibonacci(number):
    if number == 0:
        return 0
    elif number == 1:
        return 1
    else:
        return fibonacci(number - 1) + fibonacci(number - 2)
print(sum(fibonacci(value) for value in range(100)))

Kotlin 안드로이드 앱 프로그래밍 (3)

코틀린의 등장 배경
코틀린은 젯브레인스(JetBrains)에서 오픈소스 그룹을 만들어 개발한 프로그래밍 언어입니다. 코틀린은 2011년에 처음 공개되었으며 2017년 구글에서 안드로이드 공식 언어로 채택함에 따라 알려지게 되었습니다. 코틀린은 러시아의 섬에서 이름을 따와 명명되었습니다.
코틀린으로 안드로이드 앱개발을 할수있는 이유는 자바의 JVM에 기반을 둔 언어이기 때문입니다.

코틀린은 자바와 유사하지만 다른 언어입니다. 확장자도 (.java / .kt) 로 다릅니다. 하지만 코틀린 컴파일러가 .kt 파일을 컴파일하면 자바 바이트 코드가 만들어집니다. 즉, 개발자는 자바와 다른 코틀린으로 코드를 작성하지만 컴파일하면 자바 클래스가 만들어지고 이를 JVM이 실행합니다. 이 이유로 코틀린이 자바를 대체할 목적으로 만든 언어라고 소개되고 있다.

코틀린으로 안드로이드 앱 개발을 하게 된다면 장점은?
1) 표현력과 간결함
2) 안전한 코드
3) 상호 운용성
4) 구조화 동시성

코틀린의 파일 구성
코틀린의 파일 확장자는 .kt 입니다. 다음 아래의 예시는 코틀린의 대체적인 파일 구성입니다.
(User.kt)

package com.example.test3 (패키지)

import java.text.SimpleDateFormat (임포트)
import java.util.*

var data = 10 (변수)

fun formatData(date: Date): String{ (함수)
    val sdformat = SimpleDateFormat("yyyy-mm-dd")
    return sdformat.format(date)
}

class User{ (클래스)
      var name = "hello"
      
      fun sayHello(){
          println("name : $name")
      }
}

package 구문은 이 파일을 처음 컴파일했을 때 만들어지는 클래스 파일의 위치를 나타냅니다. 소스파일에서는 맨 처음 한 줄로 선언합니다. 이 파일은 com/example/test3 폴더에 생성됩니다. 그런데 package 이름은 kt 파일의 위치와 상관없는 별도의 이름으로도 선언할 수 있습니다. 예를 들어 위의 User.kt 파일이 com/example/test3에 있더라도 package ch3 처럼 선언할 수 있습니다. 물론 이렇게 선언했을 때 컴파일된 클래스 파일은 ch3 폴더에 생성됩니다.

import 구문은 package 구문 아래에 여러 줄 작성할 수 있습니다. 그리고 import 문 아래에 변수, 함수, 클래스를 선언할 수 있습니다. 변수와 함수는 클래스 안 뿐만 아니라 클래스 밖에도 선언할 수 있습니다. 그리고 어떤 파일에 선언한 변수, 함수, 클래스를 다른 코틀린 파일에서 참조할 때 두 파일을 같은 package로 선언했다면 import 없이 사용할 수 있습니다.

[코틀린 파일명을 클래스명과 다르게 선언해도 되는 이유?]
코틀린 파일명과 파일 내에 선언한 클래스명과는 상관이 없습니다. 코틀린은 객체지향만을 목적으로 한 언어가 아니라서 그렇습니다. 객체지향만 지원하는 언어라면 모든 것이 클래스로 묶여야 하지만 코틀린은 변수, 함수 등을 클래스로 묶지 않고 최상위에 선언할 수 있으므로 파일명과 클래스명은 아무런 상관이 없습니다.

[코틀린의 변수, 함수를 클래스로 묶지 않고 선언한다면 자바에서 이용할 때 문제는?]
아무런 문제 없습니다. 코틀린에서는 변수, 함수를 최상위에 선언할 수 있으므로 하나의 코틀린 파일에 선언한 멤버를 다른 코틀린 파일에서 이용할 때는 변수, 함수, 클래스에 각각 접근하면 됩니다. 그런데 자바에서는 모든 것을 클래스로 묶어야 합니다. 즉, 자바에서는 최상위에 클래스만 선언할 수 있으며 변수나 함수는 최상위에 선언할 수 없습니다. 따라서 코틀린에서 최상위로 선언한 변수나 함수를 자바에서 이용할 수 없을 것 처럼 보입니다. 하지만 코틀린 컴파일러가 가능하게 해줍니다.

코틀린 소스를 테스트하는 방법
코틀린 소스 파일의 main()함수를 실행하는 코드입니다. 안드로이드 스튜디오에서 실행하는 방법입니다.
프로젝트 실행 -> 탐색 창에서 java 디렉터리 아래 패키지명으로 된 경로를 마우스 오른쪽으로 클릭하고 [New -> Kotiln Class/File -> File]을 선택한 후 적당한 이름으로 코틀린 파일을 만듭니다. 테스트할 코틀린 소스 파일에는 다음처럼 main() 함수가 있어야 하며, 이 소스 파일을 실행하면 main() 함수가 자동으로 실행됐다가 끝나면 프로그램이 종료 됩니다.

fun main(){                                                       [Test.Kt]
    println("hello world")
}

안드로이드 스튜디오에서 탐색 창에서 main() 함수가 있는 코틀린 파일을 마우스 오른쪽으로 선택한 후 [Run ‘TestKt’] 메뉴를 누릅니다. 메뉴 이름에서 ‘TestKt’는 파일명에 ‘Kt’자가 붙은 것 입니다.

실행 메뉴를 선택하면 안드로이드 스튜디오 아래쪽 [Run] 창에 실행 결과가 출력됩니다.

Spring 입문 2

스프링 입문 공부이지만 스프링을 사용하지 않은 순수 자바로만 구현하는 것을 시작으로 한다.
그 이후 하나씩 스프링을 활용한 코드를 추가하고, 환경이 변경되는 과정을 밟으며 다형성과 DI 등을 공부함에 목적이 있다.

◆ 작업 환경

  • JAVA 11
  • IDE : Intelli J
  • Web Framework : Spring
  • OS : Windows

◆ 프로젝트생성 (환경설정)

  1. start.spring.io에서 Gradle Project, Vers = 안정적인 최신판, Artifact = core, Packaging = Jar, Java = 11 사용
  2. 압축풀고 build.gradle을 open (build 코드 변경 시 reload 필수)
  3. setttings에서 build Tools의 Gradle의 run, test를 IntelliJ로 변경 (직접 실행으로 빠른 속도)

◆ 비즈니스 요구사항과 설계

  • 회원
    회원을 가입하고 조회할 수 있다.
    회원은 일반과 VIP 두 가지 등급이 있다.
    회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다. (미확정)
  • 주문과 할인 정책
    회원은 상품을 주문할 수 있다.
    회원 등급에 따라 할인 정책을 적용할 수 있다.
    할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경 될 수 있다.)
    할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)
    ( 도메인 협력관계, 클래스 다이어그램, 객체 다이어그램 등을 프로그램 설계 시, 필수적으로 작성)

저장소 구현 방법 1. 내부 메모리 사용 (DB의 변경 가능성을 열어둠)

  1. domain 생성
    -1. member 패키지 생성
    -2. Grade를 enum을 생성 (BASIC, VIP)
    -3. Member 엔티티 생성 (Long id, String name, Grade grade)
  2. repository 생성
    -1. repository 패키지 생성
    -2. MemberRepository 인터페이스 생성
    void save(Member), Member findById(Long)
    -3. 상속받아 MemoryMemberRepository 구현체 생성
    private static HashMap store = new HashMap<>();
    (내부 메모리를 사용하므로 HashMap형태의 store 생성 – 동시성 이슈로 인해 concurrent HashMap을 사용해야 하나 테스트용이므로 생략)
    save() : store.put(member.getId(), member)
    findById() : return store.get(memberId)
  3. service 생성
    -1. service 패키지 생성
    -2. MemberService 인터페이스 생성
    void join(Member), Meber findMember(Long)
    -3. 상속받아 MemberServiceImpl 구현체 생성
    MemberRepository Type으로 MemoryMemberRepository 객체 생성 ( m = new)
    join() : m.save()
    findMember() : return m.findById(id)
  4. Member join JUnit Test
    -1. test 파일에 member 패키지 생성
    -2. MemberServiceTest 작성
    new MemberServiceImpl
    @Test
    void join(){
    // given : new Member()
    // when : mService.join() mService.find()
    // then : Assertions.assertThat(member).isEqualTo(findMember) – 두 객체가 같은지 비교
    (Assertions는 org.asserj.core… 을 사용) << 주문도메인 사진>>
  5. 할인 정책 인터페이스와 정액 정책 구현체
    -1. discount 패키지 생성
    -2. DiscountPolicy 인터페이스 작성
    int discount(Member, Int) : return 할인 금액
    -3. 상속받아 FixDiscountPolicy 구현체 작성
    discountFixAmount = 1000 // 반환할 고정 할인 금액
    discount() : if(member.getGrade() == Grade.VIP) {1000} else {0} // enum은 ==을 통해 동일 비교
  6. 주문 엔티티
    -1. order 패키지 생성
    -2. Order 엔티티 작성
    private memberId, itemName, itemPrice, discountPrice
    생성자 + Getter,Setter
  7. 주문 서비스 인터페이스와 구현체
    -1. order 패키지에 OrderService 인터페이스 작성
    Order createOrder(Long memberId, String itemName, int itmePrice)
    -2. 상속받아 OrderServiceImpl 구현체 작성
    미리 만들어둔 memberRepository와 discountPolicy의 구현체를 new로 생성
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
    Member member = memberRepository.findById(memberId); // member 찾기
    int discountPrice = discountPolicy.discount(member,itemPrice); // member 등급에 맞는 할인 금액 반환
    return new Order(memberId,itemName,itemPrice,discountPrice); // 주문 return
    }
    // 참고사항 : 여기서는 discout 정책과 관련되어 구현된 것이 없어, 차후 정책 변경이 유리 (SOLID 중요성)
  8. createOrder Test
    MemberService memberService = new MemberServiceImpl();
    OrderService orderService = new OrderServiceImpl();
    @Test
    void createOrder(){
    //given : new Member(), join() // VIP
    //when : createOrder()
    //then : Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000)
    (VIP로 생성된 Member의 할인 금액이 1000원인지 비교)


객체지향 설계 원칙을 잘 준수했는지 확인하기위해 정액 할인 정책에서 정률 할인 정책으로 변경
(기존의 OrderServiceImpl에서 FixDiscountPolicy를 RateDiscountPolicy로 생성 – 다른 코드는 수정 X)

  1. 정률 할인 정책 구현체 작성
    -1. 기존의 DiscountPolicy 인터페이스를 상속하여 RateDiscountPolicy 생성
    -2. private int discountPercent = 10 : 할인률 10%
    -3. 정액 할인과 똑같이 작성
    if(member.grade==Grade.VIP){} else {}
    -4. Ctrl+Shift+T로 Test Class 생성
  2. Test
    DiscountPolicy discountPolicy = new RateDiscountPolicy();
    @Test // 정상 범위 테스트
    @DisplayName(“VIP는 10% 할인이 적용되어야 한다.”) // Test 결과 출력은 한글로 확인 가능
    void vip_o(){
    //given : new Member
    //when : discount
    //then : Assertions.assertThat(discount).isEqualTo(1000);
    (10% 할인된 금액인 1000원이 맞는지 비교)
    // 참고사항 : Grade를 BASIC으로 설정한 Member가 할인이 적용되지는 않았는지 Test 또한 필요함
  3. 기존 시스템에 할인 정책 교체 적용
    -1. OrderServiceImpl에서 할인 정책을 가져올 때, 단순히 변경
    new FixDiscountPolicy() –> new RateDiscountPolicy();

@ 문제점 발견
우리는 역할과 구현을 충실하게 분리했다. OK
다형성도 활용하고, 인터페이스와 구현 객체를 분리했다. OK
OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수했다

  • 그렇게 보이지만 사실은 아니다.

DIP: 주문서비스 클라이언트( OrderServiceImpl )는 DiscountPolicy 인터페이스에 의존하면서 DIP를지킨 것 같은데?

  • 클래스 의존관계를 분석해 보자. 추상(인터페이스) 뿐만 아니라 구체(구현) 클래스에도 의존하고있다.
    — 추상(인터페이스) 의존: DiscountPolicy
    — 구체(구현) 클래스: FixDiscountPolicy , RateDiscountPolicy
  • 지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다! 따라서 OCP를 위반한다.

왜 클라이언트 코드를 변경해야 할까?
클래스 다이어그램으로 의존관계를 분석해보자.


지금까지 단순히 DiscountPolicy 인터페이스만 의존한다고 생각했다.


잘보면 클라이언트인 OrderServiceImpl 이 DiscountPolicy 인터페이스 뿐만 아니라
FixDiscountPolicy 인 구체 클래스도 함께 의존하고 있다. 실제 코드를 보면 의존하고 있다! DIP 위반


중요!: 그래서 FixDiscountPolicy 를 RateDiscountPolicy 로 변경하는 순간 OrderServiceImpl 의 소스 코드도 함께 변경해야 한다! OCP 위반

어떻게 문제를 해결할 수 있을가?
클라이언트 코드인 OrderServiceImpl 은 DiscountPolicy 의 인터페이스 뿐만 아니라 구체 클래스도함께 의존한다.
그래서 구체 클래스를 변경할 때 클라이언트 코드도 함께 변경해야 한다.
DIP 위반 추상에만 의존하도록 변경(인터페이스에만 의존)
DIP를 위반하지 않도록 인터페이스에만 의존하도록 의존관계를 변경하면 된다.
인터페이스에만 의존하도록 설계를 변경하자


인터페이스에만 의존하도록 코드 변경
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
인터페이스에만 의존하도록 설계와 코드를 변경했다.
그런데 구현체가 없는데 어떻게 코드를 실행할 수 있을까?
실제 실행을 해보면 NPE(null pointer exception)가 발생한다.


해결방안
—이 문제를 해결하려면 누군가가 클라이언트인 OrderServiceImpl 에 DiscountPolicy 의 구현 객체를 대신 생성하고 주입해주어야 한다.—

◆ 관심사의 분리

AppConfig 등장
애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스를 만들자

  1. Appconfig 작성 (애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는
    별도의 설정 클래스)
  • AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
    MemberServiceImpl
    MemoryMemberRepository
    OrderServiceImpl FixDiscountPolicy
  • AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)해준다.
    MemberServiceImpl MemoryMemberRepository
    OrderServiceImpl MemoryMemberRepository , FixDiscountPolicy

설계 변경으로 MemberServiceImpl 은 MemoryMemberRepository 를 의존하지 않는다!
단지 MemberRepository 인터페이스만 의존한다.
MemberServiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.
MemberServiceImpl 의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부( AppConfig )에서결정 된다.
MemberServiceImpl 은 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다


객체의 생성과 연결은 AppConfig 가 담당한다.
DIP 완성: MemberServiceImpl 은 MemberRepository 인 추상에만 의존하면 된다. 이제 구체 클래스를 몰라도 된다.
관심사의 분리: 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리되었다.

◆ 정리
AppConfig를 통해서 관심사를 확실하게 분리했다.
AppConfig는 공연 기획자다.
AppConfig는 구체 클래스를 선택한다. 배역에 맞는 담당 배우를 선택한다. 애플리케이션이 어떻게 동작해야 할지 전체 구성을 책임진다.
이제 각 배우들은 담당 기능을 실행하는 책임만 지면 된다.
OrderServiceImpl 은 기능을 실행하는 책임만 지면 된다.

◆ 금주의 IntelliJ 단축키

  • java파일에서 psvm을 치면 자동으로 작성
    public static void main(String[] args) { }
  • sout : System.out.println()
  • Alit + Insert : 생성자와 Getter,Setter는 물론 toString() 또한 자동 생성 가능
    (구현체 자체를 출력 시, 작성된 toString()이 호출하여 print()가능
  • Ctrl + Shift + T : 해단 메서드 Test Class 자동 생성
  • Ctrl + E : 프로젝트 내 파일이나 메서드 검색을 통한 이동

◆ 그 외 사항

포스팅 계획 – 3월 : 스프링 / 4월 : 정처기 실기(+중간) / 5월 : 스프링 / 6월 : 졸업 작품(+기말) / 7월 : 스프링 / 8월 ~ : 알고리즘과 CS, JPA, 스프링부트를 포함한 미정

  • 아직 해당 작성글에 틀린 부분이 많은 것으로 예상됩니다. 언제나 댓글 환영입니다
  • 그리고 이 글은 인프런 김영한 선생님의 Spring 로드맵 과정입니다

Kotlin 안드로이드 앱 프로그래밍 (2)

안드로이드 소개
안드로이드(Android)는 리눅스를 기반으로 구글에서 제작된 모바일 운영체제입니다. 흔히
우리나라 휴대폰 시장에서는 삼성 – 애플 – 화웨이 등 독자적인 os인 ios를 탑재중인 애플
외에는 안드로이드를 채택하여 제작 출시후 판매중입니다. 2008년 처음 출시 후 계속 새 버전
을 내놓아 전 세계 모바일 플랫폼 시장에서 많은 비율로 차지하고 있습니다.

안드로이드의 특징
1. 안드로이드는 공개 운영체제인 리눅스를 기반으로 합니다.
2. 안드로이드 앱은 Kotlin or Java를 이용해 개발합니다.
3. 안드로이드 운영체제의 주요 부분과 라이브러리, 구글에서 만든 앱 등의 코드는 대부분
공개 되어 있습니다.
4. 안드로이드 스마트폰은 구글 뿐만 아니라 여러 제조업체에서 만들수 있습니다.
(위에 안드로이드 소개 문단 참조)
5. 안드로이드 앱은 다양한 방법으로 사용자에게 배포가 가능합니다.

안드로이드 운영체제의 구조

[리눅스 커널] 안드로이드는 리눅스에 기반을 둔 오픈소스 소프트웨어 스택입니다.
[하드웨어 추상화 레이어] 하드웨어의 추상화 계층으로, 상위의 자바 API 프레임워크에서
                       하드웨어 기능을 이용할 수 있게 표준 인터페이스를 제공합니다.
[안드로이드 런타임] 흔히 ART라고 하며 앱을 실행하는 역할을 합니다.
[네이티브 C/C++ 라이브러리] 안드로이드 앱은 대부분 자바 프레임워크로 개발하지만 네이
                           티브 C/C++ 라이브러리를 이용할수 있는데 이를 안드로이드
                           NDK라고 합니다.
[자바 API 프레임워크] 앱을 개발할때 사용하는 자바 API 입니다.

앱 개발 언어
원래 안드로이드가 초기에 나왔을때 부터는 Java로 앱 개발을 해왔었는데 2017년 구글 IO 행사에서 Kotlin을 안드로이드 공식 언어로 지정하면서 이제 안드로이드 개발 언어로는 Java와 Kotlin을 사용합니다.

다양한 디바이스
ios os를 탑재한 아이폰은 애플에서만 제작할수 있지만 안드로이드폰은 다양한 제조업체에서 만들 수 있습니다. (삼성전자, 화웨이 등) 사용자는 다양한 휴대폰을 접할수 있어서 장점이지만 개발자는 주의해야할점이 있습니다.
다양한 제조업체에서 출시된 휴대폰에서 앱이 똑같이 보이도록 호환성을 고려해야 합니다. 또한 제조업체에서 휴대폰을 생산할 때는 구글에서 만든 운영체제 API와 주소록 같은 기본앱 등을 조금씩 바꾸므로 자신이 만든 앱이 여러 장치에서 제대로 동작하는지를 주의 및 점검해야 합니다.

안드로이드 버전
구글은 2008년 안드로이드 1.0 버전을 출시한 이후 새로운 버전을 출시하고 있습니다. 새 버전을 출시하면서 새로운 기능이 추가되거나, 기존 API가 변경 또는 제거되는 등 앱 개발에 영향을 미치는 변화가 뒤따랐습니다. 따라서 새로운 안드로이드 버전이 나오면 개발자는 변경사항을 파악해서 앱에 적용해야 합니다.
또, 안드로이드 버전은 11.0 12.0처럼 운영체제 버전을 가리키지만 앱을 개발할 때 사용하는 버전은 API 레벨(SDK 버전)입니다. 운영체제 버전별로 API 레벨이 지정돼 있어서 소스 코드에서는 대부분 API 레벨을 이용합니다. 따라서 개발자는 운영체제 버전과 API 레벨을 함께 알고 있어야 합니다.
코드명은 안드로이드 버전의 별칭으로 사용하는데, 2012′ Android Jelly Bean , 2014′ Android Lollipop 등 코드명으로 부릅니다. 그런데 구글에서는 안드로이드 10.0 버전부터 코드명을 사용하지 않겠다고 선언했습니다.

-> Q) 새로운 버전이 나오더라도 이전 버전과 호환이 될텐데 새로운 내용을 앱에 적용할 필요
가 있는가?
-> A) 이전 버전에서 사용한 API가 변경될수 있기때문에 새로 추가된 API를 적용하지 않으면
새버전을 탑재한 휴대폰에서 앱이 동작하지 않을수도 있기 때문입니다.

안드로이드 앱 개발의 특징
컴포넌트가 제일 중요합니다. 안드로이드 앱 개발의 구조를 이해하려면 컴포넌트가 무엇이고 어떻게 동작하는지 반드시 알아야 합니다.

컴포넌트는 애플리케이션의 구성요소
컴포넌트를 쉽게 정의하자면 애플리케이션의 구성요소라고 할수 있습니다.
컴포넌트는 애플리케이션을 구성하는 단위입니다. 즉, 하나의 애플리케이션은 여러 컴포넌트로 구성됩니다. 컴포넌트가 어떤 형태인지는 상황에 따라 달라지는데 JAR 파일이나 DLL로도 개발합니다.
안드로이드 앱의 기본구조도 컴포넌트에 기반을 두므로 하나의 앱은 여러 컴포넌트로 구성됩니다. 그리고 안드로이드에서는 클래스로 컴포넌트를 개발합니다. 즉, 하나의 클래스가 하나의 컴포넌트가 되는것입니다.

안드로이드 컴포넌트 종류

[액티비티] 화면을 구성하는 컴포넌트
[서비스] 백그라운드 작업을 하는 컴포넌트
[콘텐츠 프로바이더] 앱의 데이터를 공유하는 컴포넌트
[브로드캐스트 리시버] 시스템 이벤트가 발생할 때 실행되게 하는 컴포넌트

구분방법
컴포넌트는 앱이 실행될 때 안드로이드 시스템에서 생명주기를 관리하는 클래스지만 개발자가 만들어야하는 클래스입니다. 개발자가 컴포넌트 클래스를 만들 때는 지정된 클래스를 상속받아야 하는데 이 상위 클래스를 보고 구분할 수 있습니다.
액티비티는 Activity
서비스는 Service
콘텐츠 프로바이더는 ContentProvider
브로드캐스트 리시버는 BroadcastReceiver 클래스를 상속받아서 만듭니다.

앱 개발할 때 컴포넌트 구성
컴포넌트는 개발자가 만들고자 하는 앱의 기능과 화면 등을 고려해 필요한 만큼 구성하면 된다. 앱을 개발할 때 어떤 컴포넌트를 어떻게 구성하는지는 설계에 따라 달라지며 정해진 규칙은 없습니다. 심지어 액티비티가 없는, 그래서 사용자에게 화면을 제공하지 않는 앱도 개발할 수 있습니다.

Spring 입문 1

◆ 작업 환경

  • JAVA 11
  • IDE : Intelli J
  • Web Framework : Spring
  • OS : Windows

◆ 참고 사항

Controller, Service, Repository 패턴

Controller, Service, Repository 패턴은 정형화 되어있음

Controller = 외부 요청을 받음

Service = 비즈니스 로직을 만듦

Repository = 데이터 저장

Test 시, given, when, then 패턴을 사용하는 것에 익숙해지자

◆ Simple Member Manage System

컴포넌트 스캔, 자동 의존관계 설정 방식 사용

Controller

HomeController

MemberController : 회원 가입 / 조회 기능 제공

MemberForm : 회원 가입 관련 Form 제공

Service

MemberService : 회원 정보 등록 / 조회

Repository

MemberRepository : Interface (회원 등록, 이름으로 조회, ID로 조회, 전체 조회)

MemoryMemberRepository

Domain

Member : 회원 ID, Name 저장

Application

SpringApplication

Templates

home.html

/members/createMemberForm.html : 회원 가입

/members/memberList.html : 회원 조회

TestCase

Repository TestCase

Service TestCase

Application TestCase

◆ 처음부터 다시 만들어본 과정 브리핑

controller, domain, repository, service 디렉토리 만듦

1. Home.html과 HomeController 작성

(@Controller, @GetMapping(“/”), @Autowired)

2. Member domain 작성

– 저장할 회원 정보 private로 멤버 생성 (Getter, Setter)

3. MemberRepository interface 작성

( save, findById, findByName, findAll )

4. 위 interface를 implements한 MemoryMemberRepository 작성

– @Override 자동 생성

– <Long, Member>를 저장할 HashMap type의 store 객체 생성

– Id는 단순 Long ++sequence로 대체

– save(Member) : 받아온 객체에 Id를 ++sequence로 부여, HashMap에 .put( , )

– findById(Id) : return Optional.ofNullable(store.get(id))

– findByName(Name) : return store.values().stream()

.filter(member -> member.getName().equals(name))

.findAny();

– findAll() : return new ArrayList<>(store.values())

– clearStore() : store.clear(); // test를 위한 store 비우기

5. MemberService 작성 (controller에서 repository의 메서드에 직접 접근 할 수 없도록 중간자 역할)

– 생성자로 memberRepository와 연결 (@Autowired)

– findByName()을 통한 중복 회원 검증 메서드 작성 (IllegalStateException)

– join(Member) : 회원가입 (중복 검증 메서드 포함)

– findMembers() : 전체 회원 조회

– findOne(Id) : 해당 Id의 회원 조회

6. MemberController 작성

– 생성자로 memberService와 연결 (@Autowired)

* 회원 가입

– @GetMapping createForm() : 회원가입 html로 이동

– html에서 사용자가 작성한 정보를 받아올 MemberForm 작성 (private String name + Getter/Setter)

– @PostMapping create(Member) : new Member – setName – join – “redirect:/”(Home 화면으로)

* 회원 조회

– @GetMapping list(Model) : service의 findMembers()를 통해 repository의 전체 회원 조회

– service의 findMembers()를 통해 List<Member>를 받아옴

– model에 addAttribute( , )를 통해 받아온 list<>를 추가

– thymeleaf의 th:each문 : for문과 유사하게 받아온 model의 list 인덱스를 반복

– 회원을 조회할 수 있는 html문 작성

◆ 이번주 IntelliJ 단축키

– Extract Variable = Crtl + Alt + V (저장 객체 자동 작성)

– Extract Method = Crtl + Alt + M (드래그를 범위 method로 작성)

– Declaration or Usage = Crtl + B (정의된 method로 이동)

– Creat Test = Ctrl + Shift + T (Test Case 자동 생성)

– 주석 처리 = Ctrl + /

– ReRun = Shift + F10

– Constructor = Alt + Insert (생성자 등 여러가지 자동 생성)

◆ 그 외 사항

포스팅 계획 – 3월 : 스프링 / 4월 : 정처기 실기(+중간) / 5월 : 스프링 / 6월 : 졸업 작품(+기말) / 7월 : 스프링 / 8월 ~ : 알고리즘과 CS를 포함한 미정

Effective Java – Consider a builder when faced with many constructor parameters

정적 팩토리와 생성자에는 동일한 제약 조건이 있다. -> 선택적 매개변수가 많을 때 적절히 대응하기 어렵다.

매개 변수가 많아질 경우 사용할 수 있는 세 가지를 고려해 볼 수 있다.

  • 텔레스코핑 생성자 패턴
  • 자바빈즈 패턴
  • 빌더 패턴

(1) Telescoping constructor pattern

필수 매개변수와 선택 매개변수를 갖는 생성자의 형태를 띤다. 아래에 예시를 나타내겠다.

필수 매개변수만 갖는 생성자
필수 매개변수 + 하나의 선택 매개변수 생성자
필수 매개변수 + 두 개의 선택 매개변수 생성자
...
...

위와 같이 필수 매개변수만 갖는 생성자를 생성할 수 있고, n개의 선택 매개변수를 생성하는 생성자를 함께 갖는 경우의 방식이다.

예제) 식품의 영양 정보를 표현하는 클래스를 생각해보자, 식품의 양, 개수, 칼로리, 총 지방, 포화 지방 등 20개 이상의 선택 필드를 가질 수 있다. 몇 가지만 값을 가지고 대부분은 0의 값을 가진다. 생성자를 어떻게 만들까?

public class NutritionFacts{
  private final int servingSize; //required
  private final int servings; //required
  private final int calories; //optional
  private final int fat; //optional
  private final int sodium; //optional
  private final int carbohydrate; //optional

public NutritionFacts(int servingSize, int servings){
  this(servingSize, servings, 0);
}

public NutritionFacts(int servingSize, int servings, int calories){
  this(servingSize, servings, calories, 0);
}

public NutritionFacts(int servingSize, int servings, int calories, int fat){
  this(servingSize, servings, calories, fat, 0);
}

public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium){
  this(servingSize, servings, calories, fat, sodium, 0);
}

public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate){
  this.servingSize = servingSize;
  this.servings = servings;
  this.calories = calories;
  this.fat = fat;
  this.sodium = sodium;
  this.carbohydrate = carbohydrate;
}
}

NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);

위와 같이 오버로딩을 사용하여 나타내는 방식이 점층적 생성자 패턴이라고 한다.

대신 위와 같은 방법을 사용하면 원하지 않은 변수에도 초기값을 설정해야 한다.

오버 로딩 -> 같은 이름이지만 변수의 개수 또는 타입이 달라 여러 개를 정의할 수 있다. 

단점

위처럼 점층적 생성자 패턴도 쓸 수 있지만, 매개변수의 개수가 많아지면 클라이언트 코드 작성이 힘들고 가독성이 떨어진다.

저 생성자 코드를 보면 내가 무엇에게 값을 주는지 알기 어렵고 어떤 파라미터에 값을 입력하는지 주의해서 봐야 한다.

또한, 동일한 타입의 매개변수가 늘어져 있다면 찾기 어려운 버그로 이어질 수 있다. 

클라이언트가 실수해서 다른 생성자를 선택하거나, 알아채지 못할 수 있다.

(2) JavaBeans pattern

매개변수가 없는 생성자로 객체를 만든 후, Setter 메서드를 이용하여 원하는 값을 설정하는 방식

public class NutritionFacts{
  private int servingSize = -1; //required
  private int servings = -1; //required
  private int calories = 0; //optional
  private int fat = 0; //optional
  private int sodium = 0; //optional
  private int carbohydrate = 0; //optional

  public NutritionFacts(){}

  public void setservingSize (int val) { servingSize = val };
  public void setservings (int val) { servings = val };
  public void setcalories (int val) { calories = val };
  public void setfat (int val) { fat = val };
  public void setsodium (int val) { sodium = val };
  public void setcarbohydrate (int val) { carbohydrate = val };

}

위처럼 아까 Telescoping constructor pattern 보다는 생성자 코드가 길지만 보기는 쉽게 변한다는 장점이 있다.

어떻게 매개변수에 setter  메서드가 존재하기에

cocaCola.setServingSize(240); 과 같이 값을 지정해주면 된다.

단점

1. 여러 메소드 호출로 나누어져 인스턴스가 생성되므로, 생성 과정이 이뤄지는 동안 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓기에 된다.

2. 클래스를 불변으로 만들 수 없어서 프로그래머가 추가 작업을 해주어야 한다.

(3) Builder pattern

-> 점층적 생성자 패턴의 안전성 + 자바 빈즈의 가독성

장점

1. 작성이 쉽고 가독성이 좋다. -> 롬복을 이용하면 더 쉽게 된다.

2. 불변 규칙을 이용할 수 있고, 검사 또한 가능하다. -> IllegalStateException을 통해 예외 처리 가능하다.

단점

1. 어떤 객체를 생성하기 위해 빌더를 만들어야 가능하기에 성능상 문제가 될 수 있다.

2. 매개 변수가 4개 이상이 될 경우 사용하는 것이 좋다. Telescoping 보다 더 긴 코드가 생성될 수 있다.

-> 그래도 Builder pattern을 염두에 두고 코드를 구현해라.

public class NutritionFacts {
	private final int servingSize;
	private final int servings;
	private final int calories;
	private final int fat;
	private final int sodium;
	private final int carbohydrate;

	public static class Builder {
		private final int servingSize;  // 필수
		private final int servings;     // 필수
		private int calories = 0;
		private int fat = 0;
		private int sodium = 0;
		private int carbohydrate = 0;

		public Builder(int servingSize, int servings) {
			this,servingSize = serginsSize;
			this.servings = servings;
		}

		public Builder fat(int val) {
			fat = val;
			return this;
		}

		public Builder sodium(int val) {
			sodium = val;
			return this;
		}

		public Builder carbohydrate(int val) {
			carbohydrate = val;
			return this;
		}

		public NutritionFacts build() {
			return new NutritionFacts(this);
		}
	}

	private NutirionFacts(Builder builder) {
		servingSize = builder.servingSize;
		servings = builder.servings;
		calories = builder.calories;
		fat = builder.fat;
		sodium = builder.fat;
		carbohydrate = builder.carbohydrate;
	}
}

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
	.calories(100)
	.sodium(35)
	.carbohydrate(27)
	.build();

정리

생성자나 정적 팩토리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 게 더 낫다. 매개변수 중 다수가 필수가 아니거나 같은 타입이면 특히 더 그렇다. 빌더는 점층적 생성자보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고, 자바 빈즈보다 훨씬 안전하다.