📜 제목으로 보기

시작특이점을 아는 (동일형)prev데코객체 for 0부터 시작하는 구간처리

0부터 시작하는 연속된 구간을 받아, [미리 쪼개놓은 구간별 데코 객체들이 돌아가며 처리]하여 누적하는 계산기

  • 10 ~ 12 / 12 ~ 14의 구간을 쪼개놓았는데, 11~13을 11~12 /12 ~13을 계산하는 방식이 아니다

    • 0 ~ 14까지, 0~5, ~10, ~12, ~14 를 쪼개놓았더라도
    • 11~13을 계산하는 것이 아니라, 0~2로 계산된다.
    • 즉, 중간만 계산못하고, 처음부터 구간만큼만 계산되는데
    • 중간구간을 계산하고 싶으면, 0~13 - 0~11로 하면 된다.

    image-20220716231314849

  • SectionPriceCalc

    • SectionPriceRule : prev로 연결할 데코레이터 객체
      • SectionPriceRule#calculateFee(duration): 데코레이터 실행기
    • SectionPriceCalc#calculateFee(duration): 처리할 구간을 외부에서 받아와 구간별 계산해줄 메서드

SectionPriceRule#구간별 요금을 각자 구간만큼 처리해줄 시작특이점의 데코객체

구간처리 데코, 시작특이점을 아는 데코 객체는, 같은 형의 prev로 생성자로 받아 저장한다. 처리할 구간은 start는 없이 처음부터 ~ 끝to(처리구간전체)를 가지되, 내부에서 prev를 이용해 짜른다.

  1. 구간별 요금 처리를, 구간별로 담당해줄 데코레이터 객체(-Rule)를 만든다. 각 구간처리 데코 객체들은

    1. 자신이 처리할 구간(시간: Duration)의 (처음부터~) to를 받으며 ,중간에 짤린 시작from은 안받는다.
      • 소유할 prev객체로부터 prev의 to를 이용해서 짜를 것이다.
    2. 자신의 구간에서 계산에 필요한 정보(돈) Money을 받는다.
    3. 데코 객체 특이성으로서 자신의형의 prev(혹은 next)를 받아서 태어난다.
      • 구간은 제일 처음( 구간0 )의 특이점 객체를 알기 때문에, 다음 타자는 prev를 받는다.
        • 특이점객체는 연산을 하지 않는 꼬리재귀 종착역으로서, 그 타이밍에 현재까지 누적계산된 값을 반환한다.
        • 구간처리 데코 or 시작특이점을 아는 데코객체는 prev를 인자로 받아 생성된다.

    image-20220716165027865

    image-20220716170217741

    • 내가 처리할 구간은, 처음부터 시작되는 것 같지만, 변수명을 to로 하고 내부의 prev의 상한to를 이용해서 짤라서 처리할 예정이다.

1개 데코객체의 구간처리는, 누적될 결과값을 반환하므로, prev가 null인 시작특이점 객체 검사(invariant) + 전체구간이 처리구간보다 짧은지 검사(precondition)검사 후 부적절시 0에 해당하는 결과값 NULL객체를 반환한다.

  1. 구간처리 데코객체는, 전체구간을 받되, 각 데코객체별로 처리할 수 있는 구간을 짤라서 해당 부분만 계산해준다.

    • 처음부터 to의 끝만 정해진 처리구간을 가지고 있지만 내부에서는, 소유한 prev의 상한 ~ 내 상한큼만 계산해서 반환해준다.

    image-20220716165548249

  2. 구간처리 데코객체는, 내부에서 필수 invariant검사 및 precondition(전체구간) 검증을 해야한다.

    • 구간처리 데코객체의 1구간 처리는 전체 데코객체들을 돌고 있는 외부에 누적될 결과값을 반환할 것이다.
    1. invariant: prev는 시작특이점(연산X)가 아니여야한다. -> 맞다면, 연산하지말고, 누적될 결과값을 NULL객체를 반환한다.
    2. precondition: 들어오는 전체구간은, 내 하한인 prev의 상한보다 더 길어야한다. 같거나 더 짧으면, 내가 처리할이유가 없어서 누적될 결과값을 NULL객체를 반환한다.

    image-20220716173001348

전체구간의 상한이 내 상한 안쪽인지 -> duration이 상한 vs 내 상한보다 큰지 -> to가 상한 을 확인한다. (시작이 구간 비교 == 상한 비교)

  1. 시작이 0에서 시작하는 것으로 같으므로, 구간의 비교 == 상한 비교가 된다.

    1. duration > to : 내가 처리할 수 있는 끝인 to가 상한
    2. duration <= to: duration이 상한
  2. 상한이 결정되면, prev의 상한인 prev.to를 하한으로 하여 처리할 구간 결정한다.

    image-20220716174240929

구간을 시간(초)로 바꿔서, 초당 가격을 곱하여 구간별 요금처리를 마무리한다.

image-20220716174355742

test# duration은 LocalDateTime 2개를 between으로 구한다.

image-20220716174741401

SectionPriceCalc#협력할 데코객체를 1개만 소유 + 다음타자를 생성하고 기존은 prev에 저장해서 누적 -> 재귀형식으로 다음 객체에 저장하면서 마지막 타자만 가지고 있는 클래스

구간처리(시작특이점) 데코객체를 소유하여 협력하는 계산클래스는 시작특이점 객체를 1개를 미리 소유하고 있다. (어차피 변경될 필드이라서 final의 생성자 초기화는 불가능하다. 컬렉션처럼 빈컬렉션(시작특이점객체) + add로 받기기능 형태)

  • 생성자로 안받고, 내부에서 미리 필드 초기화해서 가지고 있다.

    • 생성자 type이 아니라 필드 type으로 초기화한다. 어차피 다음 타자 데코객체를 재할당해서 알고 있을 예정이므로

    image-20220716175400821

  • 시작특이점 객체로 초기화할 땐, 그것의 NULL객체들로 채워준다. prev는 null이어야한다.

    65f78c19-f527-40d4-9a22-71e3435c0ea8

    image-20220716175755444

시작특이점 데코객체를 이용하는 클래스는, 데코객체를 필드로 1개만 소유하되, setter받기기능 메서드 호출을 통해 내부에서 다음타자를 생성하고 필드에 재할당한다. 이 때, 다음 타자 생성에 필요한 prev제외 필드정보들을 넣어줘야한다.(다음타자 생성에 필요한 정보prev가 내부에 있으니, 그외 정보만 받아서, 내부에서 생성해야한다.)

  • 다음 타자는, prev로서 rule필드값이 필요하므로, 그외 정보만 받아와서 내부에서 생성한다.

  • 다음타자 정보들은, 내부정보를 이용하여 preconditino검증을 해야한다.

    • to(다음타자의 구간(상한)) : 예비 prev구간인 rule.getTo()보다 커야한다.
    • price : 계산 결과값을 0으로 만드는 NULL객체가 아니어야한다.

    image-20220716184627058

prev를 가지는 시작특이점 데코객체는, 다음 타자를 생성하여 후 1개 필드에 마지막 타자만 재할당해서 소유하면, 반복문을 통해, 마지막 타자 -> 저장된 prev -> 그것의 저장된 prev -> 시작특이점 객체까지 소환할 수 있다.

  • 다음타자를 생성하고, 그 전 타자(현재 rule 필드)는, 다음타자 prev에 저장되게 한다.

  • 마지막 타자만 rule필드에 소유하고 있으면 된다. 그 전에 것들은 다음 타자의 prev에 연쇄적으로 저장되어있다.

    image-20220715191930750 image-20220716184940917

데코객체 다음타자의 개별구나 추가시 구간은 start없는 처음부터이므로 인자로는 Duraton.ofSeconds()등의 0부터 시작하는 구간을 준다.

  • 사용 : 아래는 잘못되었다, 입력은 start가 없이 end로서 처음부터 상한까지의 개별구간을 입력해줘야한다.

    image-20220716190238359

    image-20220716190437846

    image-20220716231442497

마지막 타자(rule필드)부터 시작하는 순차적 데코객체 loop실행기는, 현재 타자를 지역변수로 뺀 뒤, 처리 후 prev로 업데이트 해 나가되, do-while문을 사용해서 마지막타자부터 시작특이점 객체까지 개별처리를 누적한다.

  • 외부에서 전체 구간을 주고, 누적될 결과값을 초기값을 준다고 가정한다.

    image-20220716215613117

데코객체는 반복문 속에서 prev로 업데이트될 예정이므로, 누적 결과값 처럼, 반복문 위에서 지역변수로 빼서 재할당하며 업데이트 되어야한다.

image-20220716220206426

현재 target 데코객체는, prev를 꺼내서 재할당 업데이트 한다. 그 prev(target)의 prev가 null이면, 시작특이점 객체라서, 연산없이 종료된다. (재귀, 데코객체 실행기 등의 종착역은 계산 안하는 곳이다.)

image-20220716231538565

주의) 쪼개놓은 구간의 데코객체들은, 자기구간이 아니면 default값을 반환하니, postcondition에서 zero를 검사하면 안된다.

image-20220716231636477

구간처리에서 중간구간을 계산하고싶다면, [0~중간구간 끝] - [0~중간구간 시작] 형태로 계산해야한다.

image-20220716231836044

my) 중간에 구간추가하는 로직 짜보기

image-20220717012443186

image-20220717012452357

시작을 모르지만, 끝이 정해진 (템플릿추상클래스) 구상 next데코객체 for 정해진 기능 추가

동적 기능추가를 next로 연결한 뒤, 꼬리재귀호출로 누적 적용하는 방법

서로 다른 기능을 가졌지만, 같은 형으로서 next에 받기 위해서는, 추상체아래 구상체로서 기능을 수행해야한다. 근데, next라는 공통필드를 가지므로, 추상클래스여야한다.

일단 next를 첫번째 인자로 받고 생성자체이닝하는 구상 next데코객체들을 만들자.
  1. 일단 next필드를 1번째 인자로 가질 첫번째 구상체 데코객체를 만든다.

    • 각 구상체들은 여러번 누적 적용시킬 것이므로, 직전 next데코객체의 계산 결과값을 인자로 가져야한다.
      • 첫번째 인자로 줘야, 생성자 체이닝이 매끄럽게 나온다.
      • 괄호에서부터 줄바꿈한다.
    • 그외 자신만의 계산에 필요한 정보가 필요하나, 메서드에서 실제 계산로직 수행시, 생성자에서 받도록 추가 예정이다.
      • 결과값은 메서드로 호출되어 메서드로 반환하니, 누적결과값변수는 여기서 안받는다.
    • 마지막 next데코객체라면, 연산을 하는 끝 특이점 객체로서 next로 null을 가진다.
      • 시작특이점 객체는 구간을 처리하고, 그 구간이 0으로서, 연산이 없다.
      • 끝특이점 객체는 추가기능의 마지막으로서, 마지막인 자신의 연산까지 하고 결과값을 반환해야한다.

    image-20220717133534680

  2. 생성자 첫번째 인자의 파라미터를 next필드를 공통으로 가질 추상클래스로 주고 추상체를 만들고 상속한뒤, next필드에 받아준다.

    image-20220717133650708

    image-20220717133808818

    image-20220717133846676

구상체들은 추상체의 next필드와 그 것을 받는 생성자를 공통으로 필요로 한다. -> 하지만, 좋은 부모는 생성자를 쓰지 않으므로, [생성자 대신, not final 필드 + 체이닝setter로 변경후 public 템플릿메소드]로 올린다.

공통필드(next)를 생성자에서 받던 자식들은, not final 필드 + 체이닝setter 조합으로 만들어서 public템플릿메서드로 올린다.
  • 생성자의 결과는 자기자신이다.
    • setter후에도 자기자신이 나오려면, return this의 체이닝setter로 변경해줘야한다.
  • 추상클래스의 구상체들은, 공통 필드에 대해서만, 해당필드를 애초에 생성자에서 받지말고 필드 + setter조합으로 올리자!!!

  • 체이닝 setter를 만드는 과정에서, 자동완성하면 -> return this 대신, 파라미터가 나아가버리니, 수정해줘야한다. 101b57e5-13e3-48c6-abcb-55baebe00b03

    image-20220717134611052

    image-20220717134623372

    image-20220717134634155

  • setter를 pull members up으로 올리면, not final 필드도 같이 올릴 수 있다. image-20220717134802056

    image-20220717134830467

    image-20220717134853570

  • 추상체로 메서드 등을 올리고 난 뒤에는, 좋은 부모의 조건을 확인해서 수정해준다.

    image-20220717134932044

  • 나머지 구상 next데코객체에서도 공통로직은 삭제한다

    image-20220717134949742

구상 next데코객체의 소유해서 사용하는 class는 외부에서 setter체이닝(by null)이 끝나 완성된, 시작 next데코객체만 받는다.

  • prev데코객체처럼, 해당 데코객체 소유 클래스 생성 후엔 동적으로 기능 추가는 못한다.

    • 외부에서 체이닝을 통해 동적기능추가가 완료된 시작 next데코객체만 받는다.

    image-20220717164032195

    image-20220717164103545

next데코객체 소유 클래스에서, 계산책임위임시 - 추상체에 public템플릿 공통메서드이자 모든 구상체들이 재귀호추할 [꼬리재귀함수]로서, 누적결과값변수를 포함해야하며, 초기값을 인자로 넣어서 호출되어야한다.

  • 올리는 메서드가, 단순 public 템플릿메서드가 아니라, 꼬리재귀로 호출될 public 템플릿메소드이다.
  • 계산에 필요한 정보 + 누적결과값 변수를 파라미터로 가질 것이며 -> 최초호출시 누적결과값 초기값을 넣어 호출한다.

image-20220717170310959

image-20220717170933175

next데코객체의 실행기 메서드의 2가지 선택지 -> 꼬리재귀 or 반복문
  • 둘다 돌아가면서 계산값을 누적결과값 변수를 업데이트하는 것은 동일하다
    1. 꼬리재귀 -> 누적결과값을 파라미터로 가지고 다니며, 누적 전 초기값을 인자로 주고 호출 시작한다.
      • return문에서 다음 next데코객체를 호출하며, 삼항연산자로 재귀문 첫줄처럼 nul로 터미네이팅한다.
      • 객체의 꼬리재귀는 객체내부에서 next필드의 메소드를 호출함으로써 다음타자를 부른다.
    2. 반복문 -> 누적결과값을 반복문내에서 update하며, 반복문 위에 update되는 결과값의 초기값을 지역변수로 선언한다.
      • while문에서 null로 시작특이점 or 끝특이점 객체를 찾는다.
      • 객체의 재귀 데코객체를 반복문으로 만드려면, 데코객체 소유 클래스에서 돌려야한다.

데코객체소유 클래스에서, 데코객체 실행기메서드내부에서, [훅메서드로 개별계산] 후 [업데이트된 누적결과값 변수를 null터미네이팅 + 꼬리재귀]로 호출하기

꼬리재귀를 호출할 next데코객체들의 추상클래스는, 현재 구상체 상태이며, 재귀호출로 다음 것으로 넘어온 상태임을 인지해야한다. 그래야, “나는 마지막 타자까지 넘어왔다고 생각하여 -> next필드에 null이 차있으면 터미네이팅”도 쉽게 생각이 든다.

image-20220717180702790

  • 개별 데코객체들의 계산은 내수용형태의 훅메서드로, 누적 결과값 변수를 인자로 받았다가 -> 반환받아 업데이트 한다.

image-20220717175244464

꼬리재귀 필수조건은 return문내 update된 누적결과값변수를 포함한 인자들로 다음재귀호출이며, 현재객체의 연산까지 마치고 누적결과값을 반환하고 싶다면, 연산후 return문내에서 삼항연산자로 null로 터미네이팅(종착역) 처리를 한다.
  • 꼬리재귀 필수족거return내에서 [삼항연산자]로 null터미네이팅(지금까지 누적결과값 변수를 반환) + next필드에 저장된 다음 next데코객체로 [업데이트된 누적결과값을 받아 꼬리재귀]를 호출한다

    image-20220717175449153

각각의 훅구상체들의, 개별계산로직을 넣어준다. 개별구상체들 계산에서 필요한 정보가 있다면, 자식수준에서 생성자에서 주입받도록 정의하면 된다.

image-20220717181020761

image-20220717181042943

중요! 오류수정: 꼬리재귀하는 구상next데코 객체들의 계산내부에 이미 기존 누적값 -> 여기에 추가하는 누적의 성질이 존재하기 때문에, 구상next데코객체를 소유하는, 추상클래스의 재귀호출에서는 [누적계산완료된 결과값변수를 재할당 업데이트만] 해주면 된다.

image-20220717182018534

구상next데코객체 자체 꼬리재귀 대신 객체 소유 클래스의 반복문으로 호출하도록 변경

값의 n -> n-1의 메서드재귀를 메서드루프로 바꾸는것 과 달리, 데코객체의 꼬리재귀(자신class의 method내에서 다음객체를 바로 호출 + 데코객체의 메서드에 처음부터 누적결과변수 끝까지 달고 다님) -> 데코객체의 반복문(외부에서 소유class가 데코객체를 다음타자로 업데이트 + 각 객체는 누적없이 자신의 연산만 처리함)으로 변경하는 것은 더 복잡하다.

image-20220718182305257

구상next데코객체 소유 클래스(반복문이 생기는 곳)부터 수정

01 누적결과값을 가지고 다녀 매번 누적후 종착역(특이점) 도달시 한번에 누적결과값을 반환하는 로직을, 반복문위의 누적변수에 누적하기 위해 자기것만 계산하는 로직으로 바꿔야하며 객체는 자신의 계산만 반환후 객체 바깥으로 반환되어 누적되도록해야하므로, –> 데코객체 소유 클래스에서 반복문을 형성하게 된다.
  • 기존

    • 데코객체 소유 클래스 image-20220718182606519
    • 구상 next데코객체의 추상체 image-20220718182911600
  • 그러려면, 데코객체 소유class(Calculator)는 데코객체에게 (반복문이라면 초기값으로 줬던) 꼬리재귀 최초 호출 인자의 계산위임 -> 인자에 있던 누적 초기값을 변수로 선언하고, 반복문내에서 next데코객체들도 직접 업데이트해주기 위해 업데이트변수로 초기값을 선언한다.

    • 데코객체들의 종특(다음타자를 필드로 저장)으로서 자신은 연산후, 자신의 정보를 통해 다음타자를 확인한다면, do-while문을 쓴다.

    ee57ad3a-fec7-4d13-8b58-d53a1b61fd41

    image-20220718182627631

02 꼬리재귀 정의 파라미터 -> 다음타자 꼬리재귀 호출의 인자를 보고 업데이트 로직을 가져와 반영한다. + for문이 아니므로, 다음타자로 넘어가는 업데이트 로직도 반영한다. + 더이상 데코객체가 누적을 안할 것이기 때문에, 반복문에서 누적해야한다 + 값객체라서 누적도 재할당으로 반영해야한다.
  • 오타: 아래 보라색 필드의 calculator가 아니라 업데이트될 직전 targetCalculator로 수정되어야한다.

image-20220718183816092

0420f9f3-b32a-4c56-a2a5-a8a4bee00b36

03 구상next데코객체의 반복문 while의 종착역은 특이점객체(연산x, 필드만null)가 아니라, 자신의 연산까지 끝낸 상태이므로, 다음타자를 불렀을 때 null이면 종료한다.
  • prev는 종착역이 계산을 안하지만, 필드만 null인 객체라서, 업데이트된 다음타자의 필드를 ull이면 끝내지만

    • prev는 종착역에는 시작특이점 객체가 존재하며, 필드로 null을 가지기 때문에, 다음타자의 필드 null을 검사한다.

      • 왜냐면, 시작을 null객체로 시작할 수 없기 때문에 구간0의 필드가null인 객체를 따로 잡았다.

      image-20220718184708170

  • next는 특이점객체가 없으며, 업데이트된 다음타자가 null이면 종료다.

    • next는 종착역이 끝 특이점 객체 없이, 마지막까지 연산하고, 다음타자 자체가 null객체이므로 다음타자가 null인지를 검사한다

    1f6afcc7-3c1c-4724-bd9e-5e891b743dfe

image-20220718184605361

04 누적된 결과값을 반환한다(필요시 postcondition검사)

image-20220718185126427

구상next데코객체가 더이상 누적연산이 아니라, 자신의 차례에 자신의 연산만 하도록 변경

  • 이미 밖에서 누적시키고 있으니, 구상next데코객체는 자신의 연산만 해서 반환해주면 된다.

    • 이 때, 직전까지의 누적값이 필요하면 인자로 받지만, 꼬리 재귀호출은 하지 않는다.

    image-20220718185343656

01 꼬리재귀의 흔적인 return 속 종착역 + 다음타자 부르는 처리를 삭제하고, 자신의 처리(훅메서드)만 하도록 수정한다.

f8d6ad4c-3494-4077-bcc2-cff5a904c2bd

image-20220718223219251

02 이제 [for문에서 누적 + 개별next데코객체는 자기값만 반환] vs [for문은 업데이트만 + 개별next데코객체가 기존값을 받아 유지하면서 누적]하는 방법 중 1가지를 선택한다.

  • 현재는 현재까지의 계산결과누적하며 계산하는 로직이기 때문에 for문이 개별next데코객체가 누적완료한 값으로, 누적 결과값 변수를 업데이트만하는 것으로 유지하자.

    • next데코는, 직전까지의 계산후 업데이트된 결과변수를 인자로 받아 사용해야하므로 현재까지 누적된 결과를 가져오니, for문은 업데이트만 하자.
    • prev데코는, 구간처리에 쓰였는데, 계산메서드가 누적결과값을 변수로 받지않고, 자기 일만 하므로, 바깥의 for문에서는 누적을 해줬어야했다.

    ff6d8bea-12d1-46ab-9fd9-b5ea34528ab6

    image-20220718225235493

03 구상next데코객체는 넣어준 순서대로 next로 나아가긴 하나, 생성하는 순간, 현재 존재하니 않는 nxt다음타자도 생성자에서 실시간 생성해야하며, 끝 null까지 다 한번에 넣어줘야한다. 즉, 동적 기능 추가가 어렵다. (고전적인 데코레이터도, base기능을 생성자에 넣는, prev방식으로 바깥에서 직전타자를 감싸서 넣어줬다.)

image-20220718232134842

  • 고전적인 데코레이터 패턴도 prev데코객체 형식으로서, 다음타자자 생성시, 직전객체를 넣어주고, 마지막 타자로 업데이트하여, -> 마지막 타자에 다음타자를 동적으로 추가가 가능하다

    • 실제로는 다양한 기능을 가진 구상 prev데코객체로 표현하며
    • prev필드를 추상클래스가 가지고서, 각 구상체들은 부모가 정의해놓은 prev의 기능 호출 + 현재 구상체의 현재기능추가로 메서드를 오버라이딩해서 호출한다
      • 우리는 구간처리에 있어서는 prev데코객체가 정보만 같은 형으로 구성되었었음.

    image-20220718232258550

    image-20220718233157342

    image-20220718232717159

    image-20220718232839228

prev vs next 비교

next 대신 컬렉션으로 연결 + 전략패턴으로 변경해서 사용하기