📜 제목으로 보기

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

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

ch4. Type과 condition

Type: 형

  • 우리가 보통 알고 있는 type은 data type이다.

    • 변수이름 = 메모리 주소의 별명이다.
      • a = 3: a라는 메모리 주소에 3을 할당했다.
    • data type = 메모리 주소로부터 시작하여 얼만큼 길이를 차지하는지 데이터의 길이
      • java의 원시형 data type들도 다 데이터의 길이다.
  • 객체지향에서 Type(형)은 dataType이 아니라 모든 것을 형으로 바꿔서 생각하는 개념이다. image-20220125210447084
    • ~형(Type): 객체지향에서 구현 가능한 유일한 개념 -> 무조건 ~형으로 구현한다.
      • Type(형)으로 만들 수 없으면, 개념을 구현할 수 없는 것이다.
  • Type(형)의 사용
    1. Role(역할): 역할은 반드시 Type으로 구현되어야한다.
      • 인터페이스가 됬든class가 됬든 enum이 됬든 Type으로 구현해야 역할을 지정한 것이다.
    2. Responsibility(책임): 책임(~할수있음을 수행)도 Type으로 드러난(로직을 구현)다.
      • 어떤 애가 예약을 받을 수 있다 -> Reservationable 인터페이스의 형(Type)
        • Reservationable이라는 형(Type)이 예약받을 수 있음을 증명한다 = 책임
      • 내가 수행해야할 책임(역할 중 특정 책임 수행시에도) = 형으로 드러난다
      • 람다로 써서 함수로 넘기면요? -> 함수의 시그니쳐도 다 형(func형)이다.
    3. **Message(메세지): 특정 역할끼리 or 특정 책임끼리 협력을 위해 -> 메세지를 주고 받을 때도 그 메세지 조차도 형(Type)으로 되어있다. **
      • 궁금한 점: 다른 객체에게 메서드 호출해서 인자를 보낼 때, 그 인자가 string or integer라면 형(type)으로 봐야할까?
        • (X) string or integer는 값이다.
        • 형과 값의 차이는 ?
          • : 불변이며, 보내도 복제된 객체를 만들어낸다.
          • 보내면 객체의 주소(참조)를 보낸다.
      • 상태관리의 책임이 없으며, (참조X ->) 캡슐라이즈를 할 수 없기 때문에, 객체지향에서 쓰지 않는다.
        • 값을 주고 받으면 -> 책임 위임이 안됨 -> 협력대상 X 동등한 대상X
      • 원하는 역할/책임(로직)/메세지를 받으면 형으로 바꿔야한다.
    4. Protocal(프로토콜): 객체 양자간 협의에 의해 서로 동의된 공통요소를 쓰자고 합의를 본 내용
      • swift: 인터페이스 -> 프로토콜이라고 씀. -> 다 형으로 표현되어야 의미가 있다.
      • 이펙티브 자바 책: 마커 인터페이스 -> 우리가 원하는 계약을 형으로 표현하기 위해, 몸체는 없지만 형을 가지게 한다.

static, enum, class: jvm 동원 가능 3가지 타입

image-20220125212644256

static: 인스턴스 단 1개만 존재시 + 동시성문제 보유
  • 싱글톤 대신 static을 쓰며, static이 적용된 field, contxext 전부 다 단 1개를 의미한다.
  • 주의점: static 초기화 -> 호출, 작동이 thread안전이 아니라는 점
    • jvm에서 동시성을 보장하는 장치가 없기 때문에, 싱글톤 보장하도록 코드를 직접짜야한다.
    • 토이프로젝트에서는 문제 x , 상용 프로젝트에서는 문제점을 보게 됨.
      • 그래서 많은 책에서는 static context를 완전히 버리기를 권유함. -> instance context만 사용
      • 왠만하면, static사용없이 instance로만 구현하자. 왜냐면 동시성 해결 잘 못할 것 같으니까
enum: 제한된 수의 인스턴스 존재시 + 제네릭 사용불가
  • 사실 class와 완전히 동일하나, jvm이 시작시 instance를 만들어주고 시작한다.
  • class는 원하는 수만큼 instance를 만들어낼 수 있으나
    • enum은 명시된 제한된 수의 instance만 만들며, jvm이 static보다 더 빨리 만들어주고 시작한다.
      • 생성에 대한 동시성 문제에 대해 안전성이 확보됨.
      • 하나의 instance를 만들더라도, 사실은 enum으로 만드는게 유리하다.
  • 일반적으로
    • 1개의 인스턴스 생성할 때 -> static context
    • 여러개의 인스턴스를 확정적으로 생성할 때 -> enum
    • 몇 개 만들지 모를 때, 무제한 인스턴스시 -> class
      • 많은 경우에 인스턴스가 무한이 아니라는 알고 있어서, class -> enum 으로 대체되는 경우가 많다.
      • enum은 값이 아니라 참조라는 점에서 좋다
        • but 제네릭에 사용할 수 없다.는 것이 단점이다. -> 형 대체가 불가능하다.
        • 그래서 마커 인터페이스를 비록한, 다양한 class, 인터페이스로 형을 지정할 수 밖에없다.
  • 정리: 동원할 수 있는 3개의 context중에 주력으로 할 것은
    • 동시성문제X + 제네릭에 사용가능 + 무제한 인스턴스 생성가능한 class(+인터페이스)를 주력으로 형(Type)으로 쓰게 된다.
    • 확장이 없는 제한된 instance -> enum을 쓸 때도
      • 값이나 type의 확정이 아닌, 일반적인 인스턴스가 확정될 수 잇는 경우에도 enum을..?
    • static은 우리의 적이다. 왠만하면 쓰지 않는다.
      • **factory(자신context(X). 하나의 instance만들어주는 공장일뿐 -> 동시성 문제 X)나 **
      • utility 함수(원래부터 상태를 갖지않음) 인 경우만 사용한다.
  • 이 책에서는 메소드의 정의를 4번을 다루지만, 간단히 유틸리티 함수인스턴스메서드를 구분하는 방법은
    • class 속 메소드에 this가 등장하지 않는다? -> 유틸리티 함수 -> class에서 빼내야한다
      • 인자와 지역변수만 가짐 or 모두 공유하는 전역context만 가지고 정의
      • class속 인스턴스메소드로 유지되면 절대 안됨
    • this 또는 유지되는 instance의 사용범위 lifecycle?을 가지고 있다면 -> class속 메서드

Condition: 조건

  • 조건: 특정 상태(인스턴스의필드or변수)에 대한 연산
    • 만약 특정상태(인스턴스의 필드 or 지역변수 or 변수)에 의존하지 않는 연산이라면, 고정되어서 분기될 일이 없어진다.
      • 안쪽이 변수, 필드라서 runtime이 바뀔 가능성이 있어야 분기의 의미가 있다.
      • 분기 = 특정 상태에 의한 분기 -> 상태(필드, 변수)가 더 중요한 말이다.

image-20220125214330681

  1. 조건 분기는 결코 제거할 수 없다.
    • 우리는 코드안의 if를 절대 제거할 수 없다.
      • 신입사원 연봉이 if 6개월 지났으면 100만원 더 오른다 -> if 제거하면 구현 못한다.
    • if는 몇단계 중첩까지 감당할 수 있을까?
      • if 2단계부터는 60%이상의 사람들은 감당 못한다 -> if 1단계까지만 감당한다.
        • 사람의 머리는 분기에 약하기 때문 -> 분기 늘어나면 경우의 수를 빼먹게 된다.
  2. 조건 분기에 대해 쓸 수 있는 방법은 2가지 뿐이다.
    1. 내부에 응집성있게 모아두기
      • switch…
        • 장점: 모든 경우의수를 한눈에 파악 (관리가 편하다)
        • 단점: 분기 늘어날 때마다 코드 전체를 변경 -> 수많은 위험과 회귀테스트
          • spring @annotation 대신 if문이라면..
    2. 외부에 분기를 위임하여 내부에선 처리기만 호출 -> 외부에서 분기를 선택하는 방식
      • 함수 내부에 있던 if + else -> 함수 바깥에 if + else를 둔다.
      • 조건 분기를 처리하는 방식은 내->외부로 위치를 바꾸는 방법 밖에 없다.
        • 장점: 분기 늘어날 때마다 외부 처리기에 if만 1개 추가 해준다. (전체코드변경X)
        • 단점: 모든 경우의 수를 내부에서 파악할 수 없다.
          • 외부에서 if분기가 끝난 객체 1개만 -> 내부에서 받는다.
            • spring에서 @annotation 방식으로 url을 지정 -> 총 몇개 url있는지 모르는 것과 마찬가지
          • 책과 디자인패턴의 대부분이 이것을 구현하는 것
        • 디자인 패턴에서는 전략패턴, 상태패턴 등 대부분의 패턴이 이 방식이 따르고 있다.
          • 전략 패턴: 전략 객체에 가서 처리 -> 전략 객체 5개면, 5개의 if문을 함수안에 넣어도 되는데, 바깥에서 5개의 경우의 수만큼 전략객체를 미리 만들고 바깥에서 전략객체를 선택해서 주면 -> 내부는 받아서 수행만
내부 cohesion응집성 있는 코드(한 눈에 잘보이는 코드)

image-20220125220548356

  • processCondition하려면, 상태인 condition을 받아서
    • if로 해당 컨디션인지 검사한 뒤
      • 거기에 맞는 처리기()를 호출해주고 있다.
분기를 외부화한 코드

image-20220125220802586

  • 바깥에서, 이미 정해진 분기마다, 처리기를 생성하도록 해놓고 image-20220125220928781
    • 생성된 처리기만 로 넘겨준다. ![image-20220125220922980](https://raw.githubusercontent.com/is3js/screenshots/main/image-20220125220922980.png)아직>
  • 내부에서는 처리기만 받아서, 호출만해주면 된다. ![image-20220125221008291](https://raw.githubusercontent.com/is3js/screenshots/main/image-20220125221008291.png)미호출>

  • **정확하게 전략패턴이다. if는 없어지지 않는다. 외부로 옮겼을 뿐 **
    • 이 분기를 외부화했을 때의 장점은?
      1. 내부 코드는 수정할 필요가 없어진다.
        • 외부코드 = client코드 = client마다 새로 작성해야할 코드 -> 어차피 원래 새로 작성/수정되는 곳에 if분기를 옮겨놨다.
          • 변화가능성 있는 분기 if코드를 <어차피 변화하는> client코드로 옮기는 것이 목적이다.
        • 내부코드 = utilies측 코드 = 수정없이 계속 재사용가능한 코드(library layer)
          • 계속 재사용 가능한 코드를 더이상 수정할 필요가 없어졌다.
          • 내부인 library layer 코드에 <변화가능성의 if분기>가 있다면 확장을 더이상 할 수 가 없다.
            • 한번 배포하면 거의 못고침.
            • 예를 들어, 내부(server 코드)의 attack메소드(재사용되는 코드)를 작성하는데
              • 무기 100종마다 if문이 attack에 작성되어있다고 치자(내부에 if분기)
              • 업데이트 때 101번째 무기가 추가된다면?
                • 내부server의 attack이라는 기본모듈을 업데이트(전체 다운로드) 하나? ㄴㄴ
              • 외부(client쪽 코드)에 무기 분기를 두면, 무기 추가시 -> 외부에서 101번째 무기 분기만 추가해주고
                • 내부 라이브러리 속 attack메소드에 101번째 무기 정보만 건네주면 된다.
              • 변화율이 적은 코드(무기사용 코드)-> 변화율 큰 if분기 등을 제외하고 배포해야한다.
                • 변화율 큰 코드(무기 추가하는 분기 코드) -> 변화율 큰 쪽에 두어 어차피 수정할때 수정하게 한다
  • IF분기(를 포함한 자주바뀌는 코드)는 외부로 옮기고, 각 경우마다 객체를 만들어서 공급해주자.그럼 내부 2단계 분기 -> 외부1단계 + 내부 1단계로 고칠 수 도 있다.
    • if의 depth를 1개까지만 주자. 2개 이상의 복잡한 코드라면, 외부로 if를 빼고, 그 경우마다 객체를 만들어서 공급해주자.
      • my) 메소드라면, 미호출 메소드 = 객체로 넘긴다.
    • if를 내부에서 제거하는 방법은 이것 밖에 없다

책임기반 개발

image-20220126214842524

  • 2003년 웨슬리에서 나온 책이 2004년 번역되어서 나왔다. 구할 수 있음 구해봐라.
    • [Evans 2004] Evans, Domain Driven. Addison Wesley,2004. or
    • [Fowler 2003] Martin Folwer 등, Patterns of Enterprise Application Architecture, Addison Wesley, 2003.
      • 책임기반의 개발, 전문가 패턴 등 패턴들을 다루게 된다.
      • 오브젝트책은 이 책을 쉽게 해설한 책이다.
책임(들) -> 역할 -> 추가 책임

image-20220126215025571

  • 책임기반의 개발에서 나오는 여러가지 개념을 쉽게 설명하도록 하자.

    1. 어떤 시스템의 가치는 무엇이냐? 사용자에게 기능을 제공함으로써 가치가 생김

    2. 사용자가 사용할 기능 = 가치 = 시스템의 책임

      • 예매사이트 들어갔는데 예매가 안된다? 싸이트의 가치는 0임.
      • 사이트가 최우선적 방어해야할 책임 = 예매(사용자 기능)
      • 책임 = (기능 =) 가치 그자체( 시스템, 객체, 역할 무엇이 됬든, 유일한 가치책임(사용자 기능)과 일치한다)
    3. (시스템의 책임에만 집중하면 된다.) 시스템 차원의 책임 -> 더 작은 단위의 책임으로 분할하자. 그리고 책임은 = 기능이며 = (알고리즘 코드가 들어있는 진짜 working하고 있는) 함수 (인터페이스, 추상클래스는 X 프로토콜만 있는지 workingX 껍데기일 뿐. 구상클래스의 메소드가 책임)

      • 예매 = 기능 = 코드가 있지지만 너무 복잡한 함수 -> depth2이상의 if 등 -> 분리시킨다.

      • 예매가 잘되어야한다? -> 방대한 책임이다 -> 머리가 나빠서 다 이해할 수 없다.
      • 딱딱 잘라지진 않는다. -> 이게 바로 실력(3~5년 걸리는 역할모델의 책임)
        • 응집성은 높으면서도(한눈에 보임) 의존성=결합도가 낮은 부분으로 쪼갤 수 있느냐
    4. 책임(쪼갠 함수)들의 공통점/공통의존기능을 추상화한 것을 -> 역할이라고 정의할 수 있다. 만약 성공했다면, 반대로 새로운 책임(케이스, 사례)을 추가할 수 도 있다.

      • 함수 분리과정에서 함수끼리 공통점 or 공통의존 기능을 발견하게 된다.

      • 역할은 여러개의 책임(=기능=함수)가 아니다.
        • **1개의 책임(기능)이라도 더 높은 수준으로 정의 -> 나중에 공통점이 모일 수도 있도록 추상화한 것 **
      • 책임 -> 더 높은 수준 형태로 추상화 -> 역할로 승격되는 것임.
        • cf) 1-1시간에 배운 추상화 - 객체지향적 추상화 - 일반화: 인터페이스, 추상클래스 이용
      • 책임과 역할을 따로 생각하는 것이 어렵다.
        • 무엇보다 다양한 책임에서 -> 공통된 점(코드, 의존기능)을 뽑아내서 -> 역할로 정의하는게 굉장히 어렵다.
        • 다양한 책임으로부터 -> 역할 정의에 성공하면, 사례들 -> 추상적인 것을 만들어낸 연역법이다.
          • 성공했으면, 추상화된 역할을 가지고 -> 다른 사례를 만들어낸 귀납법을 사용할 수 있다.
          • 일단 추상클래스를 만들어낸 순간 -> 다른 사례들을 만들어낼 수 있다.
            • if문 처리기를 더 만들어낼 수 있다.
            • if분기만큼의 객체를 -> 외부에 위임할 수 있다는 말이다.
              • depth if문 유일한 처리방법 : if문을 바깥쪽에 빼서, if문 case만큼 다른 객체들을 만드는 수 밖에 없다.
              • 만들어지는 객체들간에 공통점이 없는 것 같은데.. -> 비슷한 사례들로 찾아내서 만든 역할 -> 추상화된 역할로부터 새로운 다른객체를 생성하게 된다.
              • 어떤 사례들로 인해 객체가 생성되었어도 -> 내부에선 Runnable이 왔으니까 run()만 시켜주면 된다. image-20220126221800654
                • 여기선 Runnable이 실행되다라는 <공통기능> 역할이다.
                • 어떤 케이스로 인해 진입해서 객체를 생성했어도
                  • 생성된 객체는 일괄 Runnable == 공통점
                • 서로 다르게 구상되어있는 각 구상클래스 속 run()메소드들의 구상화 코드들이 책임 image-20220126222004509
                • **책임들의 공통점을 찾아서 추상화 성공한 것이 Runnable이라는 역할 **
                  1. 공통점1: 각각의 인자들을 인자를 안받고 return도 안한다
                  2. 공통점2: …
                • 추상화 성공한 역할 Runnable을 통해 -> d , e에 대한 케이스도 구상화해서 추가할 수 있게 된다.
                  • 추상화과정은 위로 올라가기도 하지만(책임들 -> 공통점 -> 역할 = 인터페이스 or 추상클래스 정이 성공) 아래로 내려가기도 한다(역할 -> 새로운 책임 -> 새로운 처리기(케이스) 추가도 가능하다)
                • 만약, 인자를 받는 경우라면? 내부 라이브러리 레이어들도 다 바뀌어야한다.
                  • 추상화 능력이 떨어지면, 수시로 라이브러리가 업데이트 된다.
              • 여러 케이스들을 받아들이는 처리기 -> 각 케이스들의 공통점을 찾아내야한다.
    5. 역할에 따라 협력이 정의된다.

      • 협력단계를 섵불리 책임단계에서 정의하고 싶지 않다.
        • 책임단계에는 다양한 형식이 있음. 책임이 추가되면 협력도 깨져버림
        • 보다 높은 단계인 역할단계에서 협력을 구축하고 싶다.
          • 하나하나의 lambda함수 run()수준(책임=함수)에서 프로세스를 만드는게 아니라 Runnable수준에서 프로세스를 만들어야 귀찮은 일이 발생하지 않는다.
      • 어설프게 책임(함수)단계에서 협력이 정의된다 -> 책임 추가/변경시마다 협력시스템까지 위험해진다.
  • 여기까지 정리

    • 값이 아니라 형Type을 써야한다
    • 조건분기에 대해 쓸 수 있는 방법은 2가지 밖이다.
    • 그걸이용해서 설계하는 방법은.. 책임-> 역할