📜 제목으로 보기

  • 참고 유튜브 : https://www.youtube.com/watch?v=navJTjZlUGk
  • 정리본: https://github.com/LenKIM/object-book
  • 코드: https://github.com/eternity-oop/object
  • 책(목차) : https://wikibook.co.kr/object/

구상체들이 다음 타자를 가지는 방법(next 외부체이닝 받기 -> 내부체이닝 메서드 호출)

달라진 부분 2가지 확인하기

image-20220707171010562

  • result라는 재료를 받았지만, 전략객체는 여러개의 call을 돌면서 result에 누적하여 파라미터 result는 값이 아닌 상태 변화하여 다음 caclulator한테 가고 반환되는, 부수효과가 있는 context객체라는 증거를 확보하여 연결되는 합성객체임을 확인할 수 있다.
    • 인자가 똑같은 값이 아니라 변하는 인자는 delegate(위임) = 부수효과가 있는 context가 공유되는 것
  • context인 result는 현재 전략객체에서 다음 전략객체 next에게 넘아갈 수도 있다.
    • 다음 할인이 있다면, 생성자에 다음전략객체가
    • 다음 할인이 없다면, 생성자에null이 올 수 있다고 생각한다.
    • 현재 전략객체의 calls돌면서 전략 적용후 next == null? result(리턴) 아니라면 : 다음 전략객체.전략메서드( calls, result)로 바통 넘겨주기

전략객체가 동일형 다음 타자를 받고 꼬리호출로 다음타자의 전략메서드를 호출할 수 있다.

PricePerTime

image-20220707171845735

  • 재귀함수처럼, 마지막에 다음 n에서 내일 을 이어받을 함수를 호출한다. 같은 전략메서드 = 주체가 같은형.반환이 같은 전략메서드()꼬리 호출인 마지막 return에서 종착역(null터미네이팅) && 반환값이 같은 전략메서드()로 내부 체이닝하는 것이다.
    • 외부에서 체이닝할라면, VO처럼 반환값과 다음 주체가 같은형이어야함
    • 내부 체이닝은, return문에 재귀함수처럼 종착역(null터미네이팅) + 같은 반환값의 메서드를 호출해야한다.

NightDiscount

image-20220707172027460

  • 마찬가지로, 다음 타자를 가지고 있으면 전략메서드로 넘겨주고, 아니면 결과값을 return

  • Plan은 1개의 Calculator만 알고 있지만, 발동만 시키면, 시작 Calculator(전략객체)가 옆 친구전략객체를 알고있는 것 유무에 따라 더 전진될지 안될지가 결정된다.

    • 동적으로 연결된다고 한다.
    • 객체지향은 linked List밖에 없다.
      • next node가있으면 가고 없으면 스탑이다.

Tex (마찬가지)

image-20220707172256605

  • calls 전체에 영향을 안받아서, result context만 갱신시킨다.

AmountDiscount (책: LateDiscountPolicy)

image-20220707172346485

Main

image-20220707172440201

  • plan에 맨 처음 적용될 전략객체는 PricePerTime이고 나머지들이 이어진 다음 전략객체들이 생성자 next자리로 들어온다.
    • 재귀의 마지막 부분을 터미네이팅시키는 null을 생성자에 넣어줘야 끝난다.

데코레이터 패턴은 이상한 모양

image-20220707173025499

  • 생성자에서 다음 타자를 받는 이상은 장풍모양이 된다.

중복제거 및 고도화

image-20220707173430634

  • 모든 전략객체들이 next를 받아서 저장하고, 관리하는 코드가 똑같다.
    • 부모로 묶어야한다.
  • 부모로 묶을 때 조심해야할 부분: 공통 상태(next)를 가지고 있으면 얄쨜없이 abstract로 묶어야 한다.

abstract Calculator

image-20220707174221254

  • 부모 -> 전략 인터페이스에서 -> 추상클래스로 추출해야한다.

image-20220707174311364

  • 구상체(전략객체)들은 다음타자를 생성자로 받았으나 좋은 부모는 생성자로 받질 않는다 -> 초기화없는 필드에 setter받기기능으로 받아야한다.

  • null터미네이팅이 존재하는 필드기 때문에, 다음 타자가 없으면 안넣어주고 그대로 null을 유지하면 된다.

  • 또한, 외부 체이닝을 통한 여러개 받기기능을 위해 받기기능 주체자와 동일형의 context인 this를 반환하여 외부에서 받기기능을 체이닝 할 수 있다.

    • 내부 체이닝: 재귀함수 -> return에 반환형이 똑같은 메서드로 호출
      • **(구상체)매번 다음 타자를 받거나, (추상체)외부체이닝을 통해 여러개를 받은 다음타자들을 -> null터미네이팅과 함께 return꼬리호출부에 다음타자들을 호출시킴. **
    • 외부 체이닝: 메서드내 상태 변화 후 메서드를 호출한 주체와 동일한 형(아직 안생긴 객체면 new 생성자() , 이미 존재하는 객체면this)을 반환
      • 내부체이닝을 위해, 외부재료를 외부체이닝(return this)하면서 여러개 받기기능

    image-20220707175619380

  • my) 추상체에서 this는 구상체가 사용하며 구상체 자기자신이다.

    • next구상체를 받는 메서드 내부라서 헤깔릴 수 있지만, 추상체는 구상체가 쓸 내용만 제공하므로, 추상체 속 this는 호출하는 순간은 구상체 자신이다.

image-20220707180315501

  • 전략메서드 구현의 다른부분은 다시 훅으로 뿌려주고, 전략 메서드 및 내부 공통 부분을 템플릿 메서드로서 담는다.

좋은 부모인지 확인

  1. 자식들을 위해 생성자가 없다
  2. public, final 아니면 abstract proteced 메서드만 있다.

Plan(전략 적용객체)는 코드가 바뀐게 없음.

image-20220707180549838

  • 전략객체 받기 -> 템플릿 구상체들 받기

중복제거된 구상체들 확인

PricePerTime

  • 기존

image-20220707171845735

  • 중복제거 by 추상클래스

image-20220707180731239

  • 다음 타자받는 로직이 부모로 넘어간 덕분에, calls + result 계산기능 외에는 본인의 책임만 가진다.

좋은 자식인지 확인

  1. 상태들 모두 자기것 (부모 것X) -> 부모변화와 무관
  2. 훅메서드구현으로 부모에게 정보 제공만 함. (부모를 몰름) -> 부모 변화와 무관
    • result만 갱신해서 넘겨주는 service 제공

NightDiscount

image-20220707181029894

Tex, AmountDiscount

image-20220707181045438

Main 사용법이 바뀜

image-20220707181127081

  • Plan은 확장(상속) 가능성 있는 class로서 받기기능으로 첫번째 템플릿구상체(구 전략객체)를 받기기능으로 받아들이게 된다.
  • 첫번째 들어가는 Calculator템플릿구상체외부 체이닝 받기기능 in 추상클래스 받기기능에서 return this(메서드 주체를 반환)을 통해 체이닝으로 2번째 Calculator구상체를 받을 수 있다.
    • this가 반환되면 또 Calculator이며, Calculator는 원래 setNext(체이닝 받기기능)이 존재한다.

상속을 다시 합성으로

  • 상속이 나오면 무조건 합성으로 다시 바꾸기

image-20220707184431755

  • 좋은 상속은 기계적으로 합성으로 바꿀 수 있다.

  • Calculator자체가 구상클래스화 -> 훅메서드 부분의 전략객체화

합성(전략)으로 변경되는 Calculator

image-20220707184603502

  • 추상클래스의 this반환을 활용해서 다음 타자를 외부체이닝으로 구상체별 다음타자 받기를 구현했지만, 합성으로 변환하는 순간 추상클래스 -> 구상클래스가 되므로 추상체this를 이용한 구상체별 외부체이닝으로 다음타자 받기가 불가능해진다.
  • 추상클래스에서 다음타자next를 소유하는 방법 정리
    1. 자식이 물려받아서 쓸 next 필드는 초기화없이 null상태로 선언
    2. 받기기능으로 받되 없으면 null그대로. 있든 없든 추상체this를 return해서 사용 구상체 자기자신 반환
    3. 사용처에서는 null터미네이팅이 적용된 내부체이닝으로 연쇄호출
      • 개별 다음타자 소유 -> 재귀함수 호출
  • next로 소유하는 방법을 버림 -> 위 3가지 다 버림

    • 다음타자의 전략메서드 호출을 내부 체이닝 - 재귀함수처럼 호출했지만, 재귀함수는 forloop로 바꿀 수 있다.
    • 개별로 다음타자 소유 -> 재귀함수처럼 호출이 무의미하다
  • 상속-재귀로 다음타자 호출 -> 합성-forloop로 모든 타자 컬렉션을 호출 image-20220707185806033

    • 개별로 다음타자를 가짐 -> 구상클래스가 모든 타자를 컬렉션으로 가짐으로 변경
      • cf) HashSet은 순서가 보장된다.

    image-20220707185813234

    • 여러개 받기기능이라도, 구상클래스이므로 필수 1개를 받도록 생성자 주입후 add
      • 하나는 필수이며, 나머지는 list로 받아들이는 경우가 많기 때문에, 이렇게 sample로서 구현
    • 이후 여러개 받기기능처럼 setter받기기능으로 add하되, return 구상체this개별 외부체이닝이 아닌 자신 상태를 외부체이닝

    image-20220707185828887

    • 개별 다음타자 재귀호출이 아니라
      • 복잡한 객체간 통신
    • 모든 타자 forloop돌면서, result상태 업데이트

훅메서드 대신 태어난 Calc전략인터페이스

image-20220707185958809

image-20220707190030505

image-20220707190109659

또 달라진 Main 사용법

  • 상속 -> 개별로 다음타자 가지기 -> 재귀호출

    • 첫번째 구상체객체부터 시작하여, 개별로 다음타자 체이닝해서 받기

    image-20220707181127081

  • 합성 -> 모든 타자 받기(체이닝 유지) -> forloop호출

    • 구상class 1곳에서, 모든 다음타자 체이닝해서 받기

    image-20220707190216766

  • 상속 모델의 호스팅 -> 구상클래스가 전략객체들을 소유모델로 소유
  • 원래 Plan도 Calculator들을 전략객체들을 소유모델이었음.
    • 하지만 전략객체들이 중복코드로 상속모델이 됨
      • 다시 한번 상속모델을 구상클래스 + 전략객체들 소유모델로 변경
  • 기존: Plan <- 전략Calculator들 소유
    • 변경1: Plan <- Calculator부모 <- 자식들
      • 변경2: Plan <- Calculator <- 전략Calc들
  • 그럼 Plan도 단일객체고, Calculator도 단일객체니 2개를 합치면 안되나?

    • 합성과 상속을 반복해보면, 처음 설계한 역할이 명확하지 않다는 것을 알게 된다.
    • DB상 1:1상태 -> 합치면 되는 비정규화 상태

    image-20220707191248293

  • 의사결정 image-20220707192915623
    • Plan이 또다른 계산방식 추가(멤버등급별 요금계산)이 없이, 오로지 Calculator에 의해서만 요금계산이 끝나면, 합쳐도 된다.
      • Calculator는 정확하게는 CallsBasedCalculator
        • 전략객체로 개별 계산해주되, 큰 로직은 계산의 재료를 calls만 받아서 처리하기 때문이다.
      • **개별계산방법이 오로지 전략객체의 전략메서드 특성상 1개의 로직 재료 calls에 대해서만 개별 처리해주는 것이기 때문에 **
    • 하지만, calls이외에 다른 로직에 의해 요금계산도 추가된다면?
      • Plan과 Calculator(calls로 요금계산)을 1:1이라고 합치는 순간 Call에 기반한 요금계산만 Plan에서 이루어지게 된다.
        • 전략객체들을 직접적으로 Plan에 주입하면, 카테고리 1개의 개별처리밖에 못한다.
      • Plan는 calls이외에 회원등급기반 요금계산도 전략을 통한 개별처리로 처리되어야한다
  • 개별처리를 위한 적용대상(Plan) + 구상클래스 + 전략객체들형태를 유지하면 다른 카테고리의 개별처리도 적용대상에게 구상클래스로 주입할 수 있다.
    • 다른 카테고리의 개별요금처리를 위해서라도, 적용대상 <-> 전략구상클래스를 합치지말자.
      • 요금계산에는 멤버쉽, 국가유공자, 나이별, VIP(충성고객) 여러 카테고리가 남아있다.
  • 의사결정하는 가운데, 실제이름도 알게 된다. Calculator는 포괄적인 이름이다. 전략패턴이 담당하는 카테고리가 있다. ~Based 전략메서드가 처리담당하는 메서드 인자로 판단하여 ~기반의 개별처리밖에 안된다.
    • Plan과 Calculator가 단일class라고 해서 동급의 레벨이 아니였다.
      • Plan
        • 한참 아래의 Call기반의 Calculator: **Plan이 골라야하는 메뉴중 1개 **
          • calc

image-20220707221311884

  • 다시 메인을 보면
    • plan에 들어가는 Calculator는 일반명사처럼 생겼지만, 콜 기반의 요금 계산만 해주는 콜 카테고리 계산기로서 1개의 계산기일 뿐이다.
    • Calculator는 plan입장에서는 여러가지 요금계산 계획중에 1개의 요금계산을 위임한 녀석이다.
      • CalculatorBasedOnCalls
        • call에 따른 3가지 전략(개별처리)들을 처리해주고 있다.
    • new Calculator(콜기반요금계산) 외에
    • Plan은 new CalculateBasedMemebr()와도 결합할 수 있을 것이다.
  • Plan - Calculator - 전략Calculator의 3단이 맞냐?
    • 이름에 현혹되지마라. 3단이 맞다.
      • Plan + Call기반 구상클래스 + 주입되는 전략객체들
    • Calculator라는 추상화된 좋은 이름을 가질 수있냐 의심해봐야한다. 계산하되 call기반으로만 계산하고 있으며 다른 카테고리의 CalculateFee도 할 수 있다.
    • 일반명사로 만들었다면, 의심해라 나는 자격이 없다.
      • Calculator정도의 이름은 팀장아니면 실장만 달 수 있다.

합성(구상클래스 + 전략객체들)했을 때의 유혹

image-20220707222106743

  • Plan이 Calculator와 직접적은 결합은 하지 않지만, 내부결합의 유혹을 많이 받는다.
    • 왜냐면, 다른 카테고리의 Calculator가 나오기 전까지 plan만들 때마다 new Calculator()가 반복된다.
    • 어차피 plan만들면, setCalculator( new Calcuator())를 할 것이니 내부에 감추고 싶다. + 나머지 추가 전략객체들을 받는 것도 뻔하게 나머지 전략객체들을 setNext하겠지의 유혹
      • 성급한 최적화다.
성급한 최적화(전략객체를 받아주는 구상클래스를 내부로 통합)

image-20220707222852050

  • 일반명사를 가지며, 바뀌지 않을 것 같고 반복되는 -> 전략주입용 구상클래스 Calculator를 변하지 않는 것으로 간주하며, 반복된다고 생각하여 Plan내부로 넣는 성급한 최적화를 하고 싶은 유혹에 빠져든다.
    • 하지만, Calculator는 사실 CalculatorBasedCalls이며, 전략주입 구상클래스는 다른 전략으로 바뀔 수 있는 클래스임을 망각한 것이다.
  1. setCalculator가 아니라, 전략객체들을 가변배열로 받는 setCalculators()로 주입한다. image-20220707223518297
    • new Calculator()부분을 client에서 제거함.
  2. 전략객체들을 for문으로 돌되, 첫번째 요소를 지나면 flag를 false로 놓고 작동안하게 하는 isFirst플래그를 만들어놓고 for문을 돌면서 image-20220707224050633

    • 첫번째 전략객체만 -> 전략주입 구상클래스의 필수 1개 생성자로 주입되고
    • 2번째부터는 구상클래스.setNext( 다음 전략 ) 외부체이닝으로 받아준다.
  • 그러나 전략패턴이 구상클래스 + 주입전략객체들 + 전략인페의 구성이 이루어졌다면
    • 보통 같으면 구상클래스 없이 전략인페 + 주입전략 객체들이지만
    • 구상클래스를 둠으로서 여러 전략객체들을 여러개 적용할 수 있는 구조를 마련했다면
      • 그 구상클래스도 전략의 일부분일 뿐이며, 일반명사로 쓰고 있다고 해서 바뀌지 않는 부분이 아니다. 전략적용 객체에 주입될 때, 언제든지 다른 카테고리의 전략주입 구상클래스로 대체될 수 있음을 생각하자.