📜 제목으로 보기

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

설계의 일관성

  • 14장의 내용은 학습비용이다

  • 설계의 일관성을 강조한다.

  • 코드의 배치를 일관성 있게 했다.

    • 코드를 짜는 이유: 알고리즘을 만들기 위해
      • 알고리즘: 어떤 문제를 변수와 제어를 통해서 순차적으로 해결해나가는 과정
    • 설계: 알고리즘을 적절하게 배치하는 것
    • 일관성 있는 설계: 알고리즘 여러갠데 배치는 비슷하게 만드는 것
    • 서로 다른 알고리즘이지만, 1개의 함수에 담아버리면, 일관성 있게 된다.
      • 둘다 함수에 감춰버리면, 바깥에서는 함수만 호출하는 똑같은 코드가 나온다.
  • 다양한 요금제에 대해 다양한 알고리즘으로 구현한다

    • 하지만, 다양한 알고리즘은 calculateFee라는 인터페이스를 지키고 있다
      • 지키고 있는데 뭐가 문제일까?
    • 각각 구현되어있는 구상클래스의 알고리즘이 제각각인 것이 문제라고 생각한다
      • 안에 있는 클래스가 어떻게 짜여져있는지 왜 관심을 가져야할까?
      • 다르게 구현되어있어도 메서드의 시그니처를 인터페이스로 지키고 있어서 호출되는 모양은 같은데
  • 왜 코드안에 있는 구현까지도 일관성 있게 해야할까? 그 이유는 학습비용 때문이다.

    • 다 공부하고 고쳐야하니 비용이 많이 든다.
      • 개발 세계에서는 시간만 비용에 관여한다
    • 고인물 효과가 일어난다.
      • 회사에서 오래본 사람만 쉽게 잘만들고, 처음 들어온 사람은 출중할지라도 잘몰라서 비숙련자가 된다. 조직 전체에 대한 폐쇄효과가 나타나서 문제를 일으킨다.
      • 학습자체가 어려운게 아니라 학습하는데 시간을 많이 써야하기 때문에 비용이 증가한다
    • 잘 안건들이려는 탄성이 생겨 그 부분을 폐쇄조치하고 격리하고 다른 것을 만들려고 한다.
      • if로 막아버리고 작동은 하게 두되, 새로운 사람들은 다른 코드를 짜서 거기서 논다.
    • 제품의 수명이 짧아진다.
      • 유지보수가 안되서 폐쇄되서 해마다 차세대 제품을 만든다.
  • 학습비용을 줄이는 방법은 설계일관성 밖이다.

    • 하나의 규칙을 가지고 있기 때문에, 전체를 이해하는 데 1개만 배우면 된다.
    • 학습비용이 늘어나지 않게 비슷한 형태의 설계로 반복해서 사용한다.
      • 일관성: 어떤 context에서 지속되는 것
      • 일반성: 아무 context에서 똑같이 사용할 수 있는 것
    • 여러 상황에서 똑같이 사용할 수 있도록, 제네릭(일반성 있는)한 설계를 해야한다.
      • 제네릭: 형에 상관없이 알고리즘에 사용할 수 있는 것
  • 일반성 있게 짜면, 거꾸로 일반성에 대한 예외가 발생한다.

    • 일반성 있게 짜면, 적용범위가 줄어들고, 많은 기능들이 잃어버린다.
      • +,-.x 기능은 정수라는 적용범위내에서 사용가능하지만, /기능은 적용범위에 대해 예외가 되어서 사용못하고 잃어버리는 기능이 된다.
    • 일반성의 도출은 case 수에 달려있다.
  • 우리가 해야할 것: 알고리즘들을 보고, 일반성을 추출해서 설계로 바꾸는 것이 목표
    • 설계모듈간에 일반성을 추출하면 컴포넌트화가 된다.
    • 컴포넌트화간에 일반성을 추출하면 시스템화가 된다.
  • 책에서는 4가지 요금제별 새로나오는 요금제별 알고리즘을 각각 다르게 한 뒤 -> 일반성을 획득 -> 설계에 반영하는 예제가 나온다.

새로운 정책1: 시간대별 요금부과 정책(TimeOfDayCalc) 추가

  1. 날짜구간 관련 DateTimeInterval객체를 구현한다.

  2. 1번에 따른 Call을 재구현한다.

  3. PricePerTimeCalc할인정책을 복사해서 TimeOfDayCalc를 만든다.

  4. 책에서는 4개의 list가 순서를 맞춰서 쌍으로 들어온다고 가정하고 이빨이 맞는지 검증 없이 사용하기만 한다.

    • 새벽0~아침11시, 아침11시~오후4시, 오후4~밤10시까지, 밤10시~밤12시까지

    • 우리가 이빨을 맞춰서 입력한다고 가정하는데, 검증없이 사람이 입력하도록 두면 당연히 이빨이 안맞는 4개의 list가 된다

    image-20220714152728250

  5. call속에 있는 날짜들DateTimeInterval list로 쪼갠 뒤, starts~ends에 대해 구간 vs 구간에서 시작 vs 시작 ~ 마지막 vs 마지막을 비교할 때 image-20220714184523912

    1. 겹치는 시간이 존재한다면, 더 안쪽만 골라내면, 양 구간의 교집합으로 잡혀서, start~end구간내에서 걸리는 부분만 집어낼 수 있다.
    2. 하지만, 겹치는 시간이 존재하지 않는다면, 더 안쪽만 골라내면, 크로스 안겹치는 구간이 잡힌다.
      • 애초에 내가 넣어준 구간만 for문으로 돌아가므로, 넣어준 구간이 아닌 interval의 구간은, 계산도 하지 않는다.
        • 내가 구간을 누락했다면, 추가적으로 interval중에 그부분만 빼서 base기본초당/기본요금으로 계산하도록 해야한다.
        • 하지만, 빠진 구간을 일일히 찾아서 처리하기 힘들다.
    3. 하루 전체를 구간별로 쪼개서, 계산을 누적해나간다.
      1. 겹치는 구간이 아니라면, 해당 구간은basePrice, baseDuration으로 계산하도록 하는 로직이 빠져있다.
    • 커밋
    • 아래 그림에서 sum = sum.plus(tempResult)로 값객체 누적변수 sum에 재할당하는 부분이 빠졌음. 추가해야함.

    image-20220714181901315

  6. 하지만, 시간 구간에서 빠지는 놈들을 수거할 수있는 장치를 필드로 만들어놓는다.

    • 구간에 대한 예외를 처리하는 방법이다.
    • 필드명을 base를 붙여서 만든다.

    image-20220714152924503

  • 왜 4개의 list로 짰을까??
    • index 0, 1, 2로 index로 묶여 이빨을 맞췄다고 생각해서 이렇게 l값context인 list로 짠 것이다.
    • 하지만, index로 묶여있다는 것은 코드로부터 알 수 없다
    • 묶고싶은 것을 구조적으로 묶지않고, 암묵적으로 index로 묶일꺼야~라고 생각하고 있는 것이다.
      • 이렇게 암묵적으로 묶으면, 일주일뒤에 짠 사람도 모른다. 코드를 보는 사람은 더더욱 모른다.

list index로 암묵적인 묶음을 사용하지말고 형을 만들어서 구조체로 묶자. -> 다음 정책에 반영 현재는 틀린 코드를 유지

  • 교훈: list index로 암묵적으로 묶지말자. 묶음으로 가려면, 구조체로서 형으로 따로 만들어서 묶어야한다.

새로운 정책2: 요일별 요금부과 정책(DayOfWeekCalc)

DayPrice 행위하는 데이터 객체 생성

  • 구상체용 데이터객체는, 구상체pacakge내부에 같이 넣고, internal로 접근제한자를 준다?!
  • DayOfWeekCalc에 사용될 모든 필드들을 모아서 만든 구조체 클래스(데이터 객체)
    • DayOfWeekCalc는 TimeOfDayCalc처럼 index로 이빨맞춘 List필드들이 아니라 객체로 묶인 Set<데이터객체>을 알고 있으면 된다.
  • 책에서 요일당 요금은 1개만

    • 화요일은 60초에 18원 1개
    • 일요일은 60초에 12원 1개
      • 변수도 price, duration 세트의 1개씩만 있으면, 요일별 n초당 가격1개씩을 인스턴스로 생성할 수 있다.
      • 나중에는 Set<DayPrice> = 형태로 여러개를 가지도록 사용된다.
  • 하지만 요일에 대해서는, 주중 vs 주말 여러개씩의 묶음으로 가지고 있어야, 들어오는 interval이 무슨요일인지 판단하기 편하기 때문에, 요일은 Set<DayOfWeek>으로 갖는다.

    • list로 하면 안된다.

    image-20220714211948396

  • 객체지향에서 데이터객체라도, 데이터를 가지고 있는 이유는 행위를 하기 위해서이다.

    • 요일별 요금을 가져서 -> 하나의 Call에 대해서는 스스로 계산을 할 줄 안다.

      • 기존 계산 구상체들의 계산메서드 시그니쳐를 복사해온다.

      image-20220714212532581

    • Call은 여러 일에 걸쳐 이루어질 수 있기 때문에, call이 아닌 call이 가진 1일당 데이터인 DateTimeInterval을 받아야한다.

      • 책에서 하나의 call이 여러일에 걸쳐져있을 때는, 날짜별로 짤라서 start, end가 있는 interval이 나오며, 배열로 가져온다.
  • 요일별로 계산해주는 것이 DayOfWeekCalc전용 데이터객체 DayPrice의 목적이다.

    • 객체지향 세상이므로, 얘한테 물어보는 것이 아니라 스스로 행위를 한다.

    • override와 proteced는 지우고, interval배열[]을 받는 시그니쳐로 변경한다.

      image-20220714213142439

DayPrice를 이용하는 녀석 만들기

  • PricePerTimeCalc를 복사해서 DayOfWeekCalc를 만든다.

  • 필드라고는 Set<DayPrice> prices만 알면 된다.

    image-20220714214939444

  • 받은 calls를 돌면서 1개의 call은 날짜별로 intervals를 만드는데, 각 요일별 요금 Set을 돌리면서

    • 개별 요일별요금DayPrice가 intervals를 받아, 각 interval이 어느요일에 해당하는지 확인되면, 계산해서 누적해서 반환해준다.

    image-20220714220756241

  • list4개가 줄 지어서 있는 것에 대신에 도입한 데이터객체DayPrice에 훨씬 더 위임되어 있다.

    • 요일별 + 요금 Set필요 -> 각 요일별 + 요금 = DayPrice 객체가 돌면서 요일별 요금 계산행위까지 다 처리한다.

    • 즉, 요일별요금 계산행위 필요 -> 요일별요금 객체를 만들고 요일별요금 계산행위정의(필요한 재료받아서) -> 존재하는 요일만큼 요일별요금 객체를 컬렉션으로 보유 -> 컬렉션을 돌리면서 요일별요금객체가 요일별요금 계산행위를 하게 함.

TimeOfDayCalc vs DayOfWeekCalc 비교해보기

  • 난잡하게 list 4개를 가지고 -> index로 암묵적으로 연결
  • 미리 묶인, 데이터를 가진 형(데이터객체)를 만들고 -> 행위까지 위임

새로운 정책3: (통화)구간별 요금부과 정책 (DurationPriceCalc)

  • 통화를 10분만 했는데, 얼마일까? 구간 정책, duration정책
    • 0~1분: 15원
    • 1분~3분: 20원
    • 3분~나머지: 30원
    • 15분을 통화해도, 각 구간별로 다른 요금계산이 합쳐서 반환된다.
  • 시간대별 처럼, 구간별은 중간에 비는 구간이 생기는데, 이것은 어떻게 처리할까?

    • 62초부터 ~ 3분: 2초가 비게 된다.
      • TimeOfWeekCalc에서는 basePrice, baseDuration 필드를 마련했다.
  • 애당초 구멍은 왜 생길까?

    • if를 넣어도, if도 사람이 짜는 것이기 때문에 구멍이 생긴다.

    • 구멍의 근원은 start와 end 2개를 모두 같고 있기 때문이다.

      image-20220714225514396

    • 둘중에 하나만 가지면 구멍이 절대 생길 수 없다

      • start를 없앴다면, 초기값으로 0초부터 시작하는데, end에 따라 구간만 나뉠뿐이지 구멍은 생기지 않는다.
      • baseDuration을 주면, 구멍을 근원적으로 막는 알고리즘으로 바꾼 것이다.
        • 아무리 if를 걸어도 101번째에는 못막는 버그가 생긴다.
    • start, end를 포함한 list 4개 있는 코드는, 보는 순간 확정버그다

      • start를 빼는 것이 정답이다.
  • 구간처리를 구멍안생기도록 연쇄되는 처리가 되도록 하려면, “얘 다음에는 얘가 처리할 거야. 얘 다음엔 얘가 처리할 거야”의 linked List처럼, 첫빠따는 얘가 먹고, 다음빠따는 연결된 노드가 처리하도록 짜야한다.

    • 빈틈이 없어지도록 다음타자를 가지게 하려면, 자료구조로 치면, LinkedList
    • 패턴으로 보면 데코레이터 패턴이, 다음타자가 다음처리를 하게 하는 것
      • 데코레이터가 첫빠따를 받고, 다음 데코레이터한테 넘겨야한다.

데코레이터 객체(DurationPriceRule, 구간별 할인가격)를 만들어 빈틈없는 구간처리 하기

  • 구간처리를 빈틈없이 처리하려면, cost는 start없는 다음타자를 가지는 데코레이터처리해줄 객체로서 1개만을 갖는 것이다.
    • 데코레이터의 특징은, 처리할 구간에 대해 구간별 처리해줄 객체 1개만 가지면 되는 것이다. 데코레이터 객체 안에 나머지가 연결되어있고, 최종에는 마지막에 소유할 은 1개의 객체만 가지면 된다.
  • 데코레이터에는 prev를 연결하는 타입next를 연결하는 타입 2가지 종류가 있다.
    • prev를 연결하는 것은, 생성자에서 이미 완성된 앞타자 prev를 받아서 넣으면 된고
      • next를 연결하는 것은, 생성자에서 다음 것을 생성해서 next자리에 넣어줘야한다.
    • 시간에 관한 것이므로, duration에 관한 것들은 무조건 뒤에다가 다음타자next를 연결해야한다. start없이. 왜냐면 우리는 세상의 끝을 모르기 때문
      • 확실히 아는 값인 0을 기준으로 첫번째 duration 데코레이터 객체 등장 -> 시간을 연결해준다.
        • 중간에 시간을 0부터 3분까지, 5분까지, 10분까지 다음타자로 연결해주면 된다.
      • 이렇게 하면 빈틈이 안생긴다.
  1. 받와아야할 정보는 x초부터 ~ 내가원하는 초 까지 인데, 나는 from을 안가질 것이므로 0초부터 원하는 기간인to만 가지면 된다.

    1. from(start)은 안가지고 자신이 처리할 수 있는 구간처음부터 end인 Duration to만 가지게 한다.
    2. 해당구간의 가격 price를 가진다.
  2. 또한 현재 데코레이터객체는 이전 데코 객체값인 prev를 알고 있다.

    image-20220715125509188

    • 최초에 데코레이터패턴을 소유할 DurationPriceCalc정책시간에 관련된 정책으로서 to가 0특이점 데코레이터 객체를 알고 있을 것이기 때문이다.
      • 끝을 모르고 최초객체(시작)를 아는 데코레이터패턴은, 객체 소유클래스에서 시작객체 존재+ (다음타자한테는)prev(로 끼어주는 조합)으로
      • 시작을 모르고 끝이 정해진 데코레이터패턴은, 객체 소유 클래스에서 시작생성 + next + null터미네이팅조합으로
        • 생성자에서 끝날때까지 next의 새로운 데코객체를 넣어서 체이닝해줘야하고, 마지막에 null을 집어넣어 터미네이팅한다.
    • 새로운 타자를 prev로 끼워줘야한다.
      • 반대로 이야기한다.
      • 최초의 것(데코객체, null or null객체)을 만들었기 때문에, 새로만든 것을 prev로 끼워넣어줘야한다. 다음타자는, 직전타자(시작타자)를 안고서 태어나야하므로
      • 시작을 알아서 이미 확정된 데코객체가 존재하는 상황이라면, 확정된 객체에 next를 다시 추가할 순 없다. 다음객체에게 이미 확정된 객체를 prev로 추가해줘야한다.
        • **next+null터미네이팅+생성자체이닝의 조합이 아니라 **
시간과 같이 end가 0인 최초 데코객체를 안고 태어나야한다면, 다음타자는 이미 만들어진 직전객체, null이 아닌 prev로 안고 태어나야한다.
  • 만약, 최초객체를 만들었는데, 그 객체의 next에다가 setter해주게 되는 것은 말이 안된다.

  • 그에 비해 , 최초객체가 있으면, 다음타자null이 아닌 prev를 안고 태어나는 형태가 되어야한다.

    image-20220715122724206

  • 데코 레이터패턴의 행위시, 자신의 상태(price, to)로 처리하고, next or prev의 연쇄처리까지 해줘야하는데, 연쇄처리 행위에 있어서 특이점이 존재하며 null처리 or 특이점객체로 해결한다.

    • 바깥에서 체인 끊을 수 없기 때문에, 데코객체의 행위에서 터미네이팅을 해줘야한다.
      • 만약 terminal코드를 없애줄려면, 그 형 객체(특이점 객체)를 따로 만들어줘야한다.
  • 최초데코객체를 가지는 구간처리의 경우, prev를 가져 전방으로 연결해시켜주며, 최초데코객체의 prev는 없으므로 null을 안고서 태어나도록 미리 정해놔야하며, 이 prev == null의 최초데코객체는 처리해야할 구간의 end가 0이므로, 초기값만 반환하고 끝내야한다.

    image-20220715130445634

  • 이제 나의 구간만 처리하도록 해야한다.

    1. 애초에 직전타자prev의 상한선to보다 작거나 같으면, 내가 할일은 없다.
    2. 이제 나의 시작점(prev.to)보다 큰 구간에 대해서 상한을 먼저 정해야, 내가 처리할 구간 prev.to ~ 상한만 골라낼 수 있다.
      1. 처리구간duration이 나의 상한 to보다 크면? to를 상한으로
      2. 처리구간duration이 나의 상한to보다 작으면?” duration을 상한으로
    3. 이제 정해진 상한 - 하한(prev.to)을 잘라낸 뒤, getSeconds()로 몇초인지 변환하고, 부가할 요금을 반영한다

    image-20220715173158962

01 DurationPriceCalc 정책을 만들고 최초시작 데코객체(특이점객체) 소유하기

  1. 데코객체를 이용해 구간처리해줄 구상체는, 구간 처리를 연결해서 해주는 데코객체 DurationPriceRule 필드를 1개만 속성으로 가지면 다 처리된다.

    • 시작이 정해져있는 시간등 구간처리의 prev로 연결되는 데코객체라면, 최초시작 데코객체 = 특이점객체를 소유해야한다.
    • 생성자 주입할 수도 있지만, [이미 정해져있는 것]은 내부에서 NULL객체 or 시작객체로 or [컬렉션으로 여러개 받는다면] 내부에서 빈컬렉션으로 필드초기화한다.

    image-20220715173538626

  2. 특이점 데코객체(최초시작 데코객체)자신의 생성자 시그니쳐를 복사해와서 그 정보들의 base정보(NULL객체, 초기값들)을 채워서 우리가 선택한 정보들로 초기화해서 가지고 있어야한다.

    image-20220715175326439

    • base정보들을 가지고 있는 최초객체를, 다음타자의 prev로 추가해줄 예정이다.
    • 이 때, prev의 초기정보는 null으로서 이것이 특이점이다.
      • 앞으로 연결하는 type은 prev자리에 제일 처음이 특이점 =null or 특이점객체을 넣어줘야한다.
        • next로 연결한다면, next자리에 null을 넣어준다
        • 진입점이기 때문에 어쩔수 없다, linkedlist는 다 똑같다.

    b36f4a26-56a9-4f57-8e79-40d465dea76d

    image-20220715175306912

02 DurationPriceCalc 다음 데코객체를 만들어줄 정보(prev제외)를 외부에서 받아와 -> prev를 저장하는 다음타자 생성 -> 재할당으로 다음타자를 소유하도록 업데이트해주는 메서드

  • 현재 데코객체의 필드는, 구간별요금price + 내가처리할구간duration + prev이므로,

    • 다음 타자를 받아들일 때는, prev를 제외한 필드들price + duration을 add받기기능으로 받아와야한다.
    • prev는 현재 데코객체 이용클래스(정책)이 소유하고 있는 상태다.
      • 외부에서 받기기능은 public void로 받아온다.

    image-20220715181030522

  • 다음 구간을 처리할 데코객체의 상한 to현재 소유데코객체 = 예비prev보다 구간이 더 커야지 의미가 있다.

    image-20220715183521524

  • 또한, 우리는 최초시작객체 = 구간끝이0인 객체 -> 아무것도 구간처리 안할 때는 Money.ZERO의 NULL객체를 반환하고 있으므로 다음객체가 어떤일을 해야한다면, 구간별요금price는 [특이점객체의 반환값]인 Money.ZERO를 가지고 있을 수 없다로 설정해야한다.

    • 데코객체 내부에서는 최초객체라면, 구간to가 0이므로, 아무일도 안하여서 ZERO(NULL객체)를 반환했다.

      image-20220715183935278

    • 계산로직이 price * seconds인데, price가 zero가 되어버리면, 구간에 대한 계산값 자체가 zero가 되어버리니.. 아무일도 안하는 것과 마찬가지다?

      image-20220715184319845

    image-20220715184456247

  • addRule은 다음 데코레이터객체를 생성해서 데코이용 정책클래스의 소유필드를 업데이트한다.

    • 이 때, 기존에 소유하고 있던 것다음 타자의 prev필드에 저장하고
    • 생성한 다음타자를 소유하도록 재할당으로 업데이트한다.
      • 기존 것은 다음것의 prev로 저장하고 -> 다음것을 소유한다.

    image-20220715191930750

    image-20220715215434495

    다음타자 데코객체를 만든다. prev는 현재소유한 데코객체인 rule을 집어넣어 만든다.
    -> 데코객체이용클래스의 데코객체 변수는 값객체처럼 재할당이 운명이다. 동적으로 기능을 추가한 뒤 사용변수에 재할당해서 업데이트된 기능을 보유하게 한다.
    --> 여기서는, 새로운 기능을 동적으로 추가하는 것이 아니라, 현재 소유 데코객체(prev)를 동적으로 다음타자로 업데이트한 것을 변수에 할당해준다.
    ---> 동적으로 새로운 기능을 업데이트해주고, 그것만 호출하는 것이 아니라.
    prev(rule) 기능 사용 -> [다음타자로 업데이트]addRule-> [다음타자 사용]으로
    사용 -> 동적 업데이트 -> 사용 -> 동적 업데이트의 [실행기 분리후, 실행후 업데이트]를 가지는 것 같다
    동적으로 소유객체를 prev속성으로 next객체로 업데이트하는 것도 데코레이터 패턴이다.
    이 메서드에서는 나는 새로운 다음타자 rule만 만들어서 소유하면 된다.
    -> 기존의 prev였던 rule은, 다음타자의 속성으로 들어가서 저장되어있을 것이기 때문이다.
    --> 특이점데코객체(시작데코객체)를 소유했지만,
    현재 메소드를 통해, [다음번 타자를 만들어서 필드로 소유] + [이전것은 속성으로 저장해놓기]의 형태가 반복된다.
    맨 끝만 소유하게 될 것이고 -> prev로 저장된 직전객체는, 소유객체 계산시 반복문을 통해 최초객체까지 불러오면서 계산시킬 것이다.
    여기서 데코레이터는 메소드에서 직접 다음번 데코레이터를 부르진 않고,
    단지 값을 계산할 때, 현재객체로 해당구간 처리후 prev를 불러 값을 처리한다.
    왜냐면, 실행기가 따로 있기 때문이다.
    실행기가 합쳐지면, next와 prev를 불러서 한꺼번에 취합을 할텐데, 이 경우, aggregate 객체로 분리해놓고
    각각의 객체가 맡은 구간만 해결하도록 만들어놔서 -> 나중에 한꺼번에 불러서 처리할테니, 현재는 다음타자만 가지고, 직전은 prev에 저장만 한다.
      
    코드가 자유로워지면,
    (1) 데코레이터가 순환적인 재귀루프를 돌게 할 수도 있고 -> 끝이 제한되는 경우, 스택오버플로우 위험 없을 때
    *(2) 데코레이터를 재귀안돌리고, 자기일만 하고 빠져나오게 반복문을 돌린다.-> 끝을 모르는 경우, 스택오버플로우 위험이 있을 때
      
    데코레이터패턴도,
    *(1)loop로 처리하도록 실행기 메서드를 따로 빼고 -> 메서드로 직전타자를 저장하며 다음타자로 업데이트하여 마지막타자만 소유하는 방법
    (2) 데코레이터가 직접 다음루프로 가는 재귀의 방법 -> 생성자에서 원하는 수만큼의 다음타자를 직접 생성하여 저장해놓고, 처음타자를 소유하되, 처리후 재귀로 다음타자를 부른다
    -> 만약, 데코레이터 타자들이 1000개가 넘는, 세밀한 구간처리 요금제를 만들었다고 가정하자
    끝을 모르는..경우로서,  addRule로 구간을 잘게쪼게해서 만들어넣을 수 있음.
    다음타자를 재귀로호출하면, 스택오버플로우가 걸린다.
    --> 기업코드들은, 모두 실행기를 불리해놓고 반복문으로 처리한다.
    

03 DurationPriceCalc 소유한 rule처리 -> prev 속 저장된 rule로 처리하도록, 소유된 마지막타자가 앞에 것들을 do-while문으로 순환하면서 call 속 duration구간을 나눠서 처리한다.

  • 현재의 소유한 마지막타자 rule를 시작점으로 do-> 처리 + 처리자업데이트(prev를 처리자로) -> while( 처리객체 != null)로 로직을 짜야한다.

    • 반복문속 처리자는 rule을 재할당하지말고, 반복문(dowhile)위에 지역변수를 만들고, 초기값으로 rule을 배정한 뒤, 업데이트 해준다.

    image-20220715220600202

    • rule -> 반복마다 prev로 업데이트될 처리자target이라고 변수를 지어준다.

    9db090d9-a6da-490c-a902-30c8c6be97da

    • target처리자가, 현재 처리를 마치면, 저장한 prev를 꺼내서 그놈으로 업데이트시켜서 반복한다.
      • 저장소 prev로 업데이트 시켰는데 그 target이 null이라면, 현재 최초객체이므로, 특이점 객체는 연산안하는 객체로서 종료되게 한다

    image-20220715221343508

  • 따로 외부실행기 + loop데코레이터객체를 전진시켰으니, 스택오버플로우에 안걸린다.
  • 데코레이터객체 본인이 재귀로 전진해도 되지만, 스택오버플로우에 걸릴 수도 있다.
    • 반복문을 제거하고,return 문에 꼬리재귀형식으로target.calculate( )내부에 prev가 존재한다면, 재귀호출에서 전진해나가도 된다.
    • 그러나 안에 재귀를 두면, 망하는 경우가 많아 외부실행기 + loop로 분리한다.

새 정책 3개에서 추출할 수 있는 일관성(공통점)을 찾아볼 수 있을까?

  • 설계에서 공통점을 찾는 요령

    • 설계: 객체간 협력모델
  • 알고리즘에 휘둘리지말고, 각각의 요금제가 어떠한 협력모델을 가지고 있는지 살펴보자.

    • DurationPriceCalc이 할일은 calculate인데, 일을 직접하지 않고, 데이터를 가지고 구간별 처리해줄 외부에서 공급받은 데코레이터 객체와 협력해서 일을 처리하고 있다.
      • 협력 객체(rule, 1개 같지만 n개)를 이용해서, 요금을 처리하는 객체라고 요약할 수 있다.
    • DayOfWeek도 협력 객체(prices, n개)를 이용해서, 내 할일을 처리하고 있다.
    • TimeOfDay도 n개의 start, ends, durations, prices 객체들을 이용해서, 내 할일을 처리하고 있다.
  • 각 요금제는 N개의 rule를 소유하고 있으며, 그것을 이용해서 계산하는 구조를 가진다.

    • 요금제는 껍데기인 것이고, 무슨 rule을 탑재하느냐에 따라서 요금제가 달라진다.
      • 다른애들이 도와줘서 나의 일을 해줬어~
      • 추상화를 이정도로 봐야한다.
  • 그렇다면, Calculator를 구상한 클래스를 새롭게 추가할 필요 없이, 협력객체공급 + 그 객체사용 코드만 새롭게 구상하면 된다는 사실을 알게 된다.

    • 3개의 역할을 도출했다. -> 상속구조 or 소유모델로 만들 수 있다.
      • 노예상은 다 똑같은 일들만 하고 있다. -> 더이상 다형성을 가지지 않(더이상 자식X)으므로, Calc에게 직접 단일객체 합성으로 일을 위임하고, 필요가 없어진다.
        • 상속이 전략객체로 바뀌는 과정과 같다.
        • **노예들 ListorSet을 가지게 한다. **
      • 노예들: 종류별로 달라진다.
      • 프로듀서: 노예 종류별로 부리는 법이 달라진다.

    image-20220715224621177

    • 책들은 전부, 디자인패턴처럼, 상속을 합성으로 바꾸는 것이 목표이다.
      • 상속은 조합폭발을 해결할 방법이 없기 때문
      • 상속되어있는 자식들간에, 설계상 공통을 찾아서, 빼내게 되면, 소유모델로 바뀌게 된다.
        • 상속의 자식들 -> 더 세분화된 역할을 소유하는 객체로 일반화
    • 여기서는
      1. 자식클래스들의 공통점(3개 역할의 일반화) 추출로 인해, Calc자식들을 삭제
        1. 3개의 객체(자식들)를 1개로 봐라보게 됨.
        2. 역할들은 각각의 특성을 가진 객체로 나눔
          1. 노예 -> 노예 인터페이스의 구상체로 감
          2. 부리는놈 -> 프로듀서 인터페이스의 구상체로 감
            • 코드는 어디 가지 않는다
            • if의 제거방법은, 그 case만큼 객체를 만들고, 밖에서 주입하는 수 밖에 없다.
            • 2중if는 전략 인터페이스 2개를 만들어야한다.
      2. 만든 class3개의 일반성을 획득하지 못한다면, 학습비용이 3배로 나간다.
      3. 일반화에 성공했따면?
        1. new Calc()만 new때려서 만들고, 그 안에 노예, 프로듀서만 선택해서 넣어주면 된다.
  • 덩어리진 3개의 Calc구상체들을, 노예 + 프로듀서 + 일반화되어 상속할필요없는 Calc로 바꾼 바람에, 학습비용이 확 준다.

    • 역할을 통해 쪼개놨다면, 덩어리보다 더 학습비용이 났다.

상속의 자식들을 일반화해서, 템플릿메소드패턴의 추상클래스로 만들고, 전략패턴으로 바꾸자..?!

  • 자식들을 공통구조를 일반화해서 템플릿메소드로 올리는데 성공하면
    • 전략패턴으로 변경가능해져서, 추상클래스가 전략주입 일반클래스가 되고, 덩어리졌던 훅메서드 자식클래스들전략메서드만 처리하는 전략객체가 되지만
    • 이 경우, 전략메서드에 사용되는 협력객체또한 추가로 받아야한다.??