📜 제목으로 보기

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

ch7. SOLID 5원칙과 GRASP 9패턴

알려진 기본 설계요령 5원칙: SOLID

  • 책 6~8장

  • 1940년대 대부분의 개발이론이 다 나왔다.
  • 1960년대 대부분의 이론 다 완성
  • 1980년대 완성된 이론을 -> 차근차근 실현단계
  • 100년의 역사지만, 처음부터 고등학문(천재들이 만들어서)
    • 평민들은 공부할 수가 없음
    • 1980년대부터 평민의 언어로 바꾸기 시작
      • 우리가 아는 문건들 -> 평민 언어로 쉬운 것만 추린 것
      • 그것이 5개 SOLID원칙
SRP
  • SRP( 단일책임원칙, Single responsibility)

  • 책임: 오브젝트 책 p117
    • srp에서의 책임: 코드의 변화하는(수정하는) 이유가 1개가 되도록 객체를 설계해라
      • 코드 수정 이유에 가장 좋은 방법: 변화율(코드의 변동 주기(시간)+이유)에 따라서 분리한다
        • “이 객체는 이 때 + 이래서 변해 -> 나눠줘야겠어 -> 변하는 이유를 <변화율:이때+이래서 변함>로 선택
        • seller와 office를 나눈(분리시킨) 이유도 마찬가지
          • 고객에게 갈취하는 역할: seller
          • 티켓을 정산하는 역할: office는 다를 거야. 이러한 이유로 서로 다르게 변할거야.
  • 쓸데 없는 책임을 벗어나게 하는 방법:
    • SRP는 spring API처리에서 많이 언급된다.
      • 똑같이 트랜잭션처리+로그처리가 어느 클래스든 공통로직으로 들어가있다.
        • 이 공통로직이 되어있다면, 공통로직 수정 -> 수많은 클래스 같이 수정해야하는 문제여러>
      • 클래스 변화 이유: 도메인이어야하는데, 일반로직까지 같이 가지고 있기 때문에 문제가 생긴다.
        • 변화하는 이유 : 도메인이어야한다. -> 여러 이유(+일반로직까지)를 가지고 있다.
        • cf) 공통로직 -> 유틸리티 클래스로 빼면 된다.
  • SRP가 제대로 지켜지 않을 때:
    • 1가지 변화 이유로 수정시작 -> 수많은 클래스를 고치게 되는 문제점(산탄총 수술, shotgun surgery)
    • 변화는 이유: trigger는 1개가 되도록 -> 클래스도 거기에 맞게 나눠서 구축하자
OCP
  • OCP(개방폐쇄원칙, Open and Close)
    • 다형성이라 생각해도 무관하다. 구현보다는 인터페이스를 참조하자.
    • open:확장이 열려있다. / close: 수정이 닫혀있다.
      • a -> b 직접 참조시
        • b의 수정 -> a에도 충격 : close원칙에 위배
        • b를 안고침 -> a에도 변화X : a의 open원칙을 위배
          • 안고쳤다 = 수정이나 확장에 열려있지 않다
      • 직접참조 = 건들이면 망가지고 안건들이면 확장이 없음
  • 직접참조를 피하면 OCP를 만족시키는 것이다.
    • 포인터의 포인터: 포인터를 직접참조하지말자.
    • a -> b의 외부 interface -> 다양한 b 구현체orClass
LSP
  • LSP(리스코프 치환 원칙, Liskov Substitusion, 업캐스팅 안전 때문에 존재)
  • **부모CLASS형 자리에는 자식Class형을 집어넣을 수 있다. **
    • -> 그렇지 않을 땐, 부모class(추상층의) 메소드를 따로 빼내서 자식들의 공통점만 가지게 수정한다.
    • 모든 case를 다 알아아야하고, 공통점만 가지도록 -> 구상화된 메소드는 인터페이스로 뺀다
    • 이게 왜 원칙일까?

image-20220204153556529

  • 추상층이 너무 구체적이면 구상층 구현에 문제가 생기므로 추상층을 어떻게 잘만들까? 어떻게 추상층이 함부러 나대지 않게 만들까?에 관한 얘기다

  • 추상층에 생물을 만들고, 메소드 2개 정의

    • 숨을 쉰다
    • 다리로 이동한다
  • 구상층으로서 생물-> 사람 / 생물-> 타조를 구현하고

    • 리스코프 치환 원칙에 따라, 부모자리에 자식을 넣어보자.
    • 지금까지는 어색하지 않다 image-20220204154155711
      • 사람이 숨을 쉰다. / 사람이 다리로 이동한다.
      • 타조가 숨을 쉰다. / 타조가 다리로 이동한다.
  • 문제는 다른 구상층들 중에서 발생한다 아메바, 독수리, 고래는 다리로 이동할 수 없다.

    image-20220204154345513

    • 이러한 일은 왜 일어났을까?
      • 추상층에 있는 다리로 이동한다 메소드
      • 앞으로 일어날 구상층의 확장에서 도메인을 반영할 수 없기 때문이다.
    • 너무 나댄 것이다. 욕심이 나지만.. 함부러 추상층에서 정의해선 안됬다.
      • 사람, 타조만 보고선 -> “어? 공통인데 추상화해서 추상층으로 빼면 좋지 않을까” 라고 욕심이 났다
  • 위의 예에 따르면 리스코프 치환 원칙 = 추상층은 구상층의 확장을 모두 포용할 수 있는 교집합만을 가지고 있어야한다.

    • my) 여러 구상층들을 미리 생각해서 -> 그것들의 교집합, 공통점만 추상층에 올리도록 해야한다. -> 머리가 좋은 사람들만 할 수 있다. 다 파악해야한다. 그래서 어렵다.
    • 발견하자마자 추상층 메소드를 수정/제거할 수 있어야한다.

    image-20220204154807594

  • 그럼 일부 구상층만 가지는 메소드는? -> 인터페이스로 따로 빼놓고, 해당하는 구상층들만 추가 impl하게 한다

    image-20220204154932059

    • 추상층 - 생물 - 숨을 쉰다() 메소드 1개만
      • 다리로 이동() -> 인터페이스로 빼내기
    • 구상층
      • 생물만 구상하다가 -> 해당하는 구상층만 다리이동()인터페이스 추가 impl
      • 나머지들은 생물만 구상
ISP
  • ISP(인터페이스 분리 원칙, Interface Segregation)

  • 구상형으로 쓰지말고, 외부호출별(?) 모듈별(?) 접근하는 객체별(?) 외부 접근을 인터페이스(형)별로 분리시켜 엉뚱한 접근을 막는다.한다.

    • 안그럴시, 밖에서 엉뚱한 메소드를 호출하더라도 못막는다?
    • 엉뚱한 호출(메소드접근권한)을 막는 방법 : 밖에 없다 -> 인터페이스별로 분리한다
      • ex> 모듈A야 너는 인터페이스A에 있는 메소드 2개만 바라봐
      • ex> 모듈B야 너는 인터페이스B에 있는 메소드 1개만 바라봐
  • 예를 들어, 어떤 객체에 메소드가 6개가 있다. 아래와 같이 짜면 안된다.

    image-20220204190242478

    • 왼쪽 2개 메소드 -> 모듈A에서 사용

    • 가운데 1개 메소드 -> 모듈B에서 사용

    • 오른쪽 1개 메소드 -> 모듈C에서 사용

    • 나머지 2개 메소드 -> 객체 자체에서 사용

      • (멍청한) 우리가 Class를 짜면 항상 이런식으로 작성된다.
        • 이렇게 하지말라는 말 -> 왜? 인터페이스 분리가 안되어있다.
        • 메소드들이 모여있으면, 모듈A가 다른 엉뚱한 메소드들을 호출해도 못막는다.
    • 첫번째 ISP 방법

      image-20220204190645061

      • hasA방법: 각각을 모듈별로 인터페이스를 가지도록 객체자체를 나눈 뒤 -> 모든 객체를 소유
    • 두번째 ISP 방법 image-20220204190814982

      • 일반적인 인터페이스 모델방법:
        1. 인터페이스별로 메소드를 분리
        2. implements A, B, C -> 각각의 메소드들 구현
        3. 자체메소드 2개 구현
    • 경우에 따라서 소유모델(hasA)인터페이스(형)모델을 섞어서 쓰기도 한다.

DIP
  • DIP(의존성 역전 원칙, Dependency Inversion, 다운캐스팅 금지)

    image-20220204191630558

    • 어려운말로 나타내면 위와 같다. 고쳐 말하면 image-20220204191654048
    • 빨간색 말만 봐라보면 -> 구상 클래스에 의존X 추상 인터페이스나 추상 클래스에 의존하자.
      • 구상화된 형을 가리키는 코드가 있다면 -> 추상화된 형으로 바꿔줘야한다.
      • 그러려면 4. 인터페이스 분리(ISP, 접근별로 인터페이스를 분리해놓고 impl하자)가 되어야하고
      • 그러기 위해선 3. 업캐스팅 안전(LSP. 모든 case를 다 알아아야하고, 공통점만 가지도록 -> 구상화된 메소드는 인터페이스로 뺀다) 도 되어야한다.
        • 그래서 어렵다.

그외 설계요령 추가 4원칙

image-20220204195428064

  • 로버티 마틴이 줄여서 말한게 5가지고 그 외에도 설계요령이 많다. 기본적으로 우린 머리가 나쁘니까 이렇게 해야해가 대부분이다. -> 머리를 덜 쓰기 위해서 지켜야할 원칙
    • DI: 의존성 주입(Dependency Injection)
      • 너흰 추론 불가능이야.
    • DRY: 중복방지(Don’t Repeat Yourself)
      • 너흰 기억력이 엉망이야
    • Hollywood Principle: 의존성 부패방지(헐리우드 원칙)
      • 나한테 연락하지마. 내가 필요시 전화할테니 너 전번만 줘
      • 묻지말고 시켜라. ex> 묻지말고 500원만 줘.
        • 물으면 많은 것들을 알게됨 ex> 지갑사정을 보고 500원만 줘. 대출사정을 보고 500원만 줘.
        • 물어보지 않은 원칙 -> 모두 시키기만 하자. 물어보지 말자.
          • 물어본놈: 간첩
      • 빨리 500원을 받아야하는데, 물어보는 순간 -> 얘네 집안과 연동됨 -> 의존성 부패
        • 물어보는 순간 일반적으로 부패된다.
        • 객체지향에서는 물어보는 순간 -> 대답용 객체가 온다(return). -> 새 의존성이 추가되어버린다.
      • 응답기반으로 프로그램을 짜면 안된다. 대답이 올지 장담할 수 가 없다. 시키는 원칙을 지키자. getter(응답객체가 return됨)형은 나쁜 것.
    • Law of demeter: 디미터의 법칙 = 최소지식만 요구한다.
      • 책 6장에서 다루는 디미터의 법칙
      • classA안에 메소드 methodA가 가질 수 있는 최대 지식은? image-20220204193354859
        • 필드 / 필드들의 형 / 자기가 만든 객체 / 인자로 넘어온 객체 / 지역변수들
        • 나머지는 알면 안된다. 1차원 관계의 것들만 이용하자. 1차원이 안되면,직거래를 유도하도록 래핑메소드를 제공해서 1차원적으로 만들자.
          • 인자로 넘어간 객체가 호출 메소드의 return값
          • 필드에 있는 객체가 호출한 메소드의 return값
            • 다 이용하지말자.
        • 직거래만 하자. 타고 가서 return되는 간접거래는 하지마. 간접적으로 아는 것들(걔의 집안사정)은 머리가 나빠서 기억못한다. 모르는게 최고
      • 어기면 열차전복 (train wreck) 사고가 난다.
        • A.B.C 다 연결되서 문제가 생기고 의존성이 생긴다.
의존성 부패방지와 최소지식의 모순

image-20220204195307676

  • 지금 나오는 것을 암기 하지 않으면 책을 읽을때 이해가 안된다.
    • 참고할정도도 아니고 9개 설계원칙은 코드짤 때 항상 머리속에서 벨리데이션 하고 있어야한다.
      • solid + DDHL
    • 헐리우드 원칙(묻지말고=응답(return객체) 바라지말고, 시키자)
    • 디미터 원칙(최소지식만 요구 = 알고 있는 1차원의 것들만 직거래)
  • 헐리우드 원칙(시키기만) + 디미터 원칙(보이는 것만 직거래) 두 원칙을 지키다 보면 모순점이 발생

    image-20220204195733419

    • 객체망에서 핵심내용은 메세지만 주고받아 객체간통신은 가능하지 내부를 들여다 보진 않는다.
      • 내장은 까지 않는다.
    • 내가 메세지를 주면서 시켰는데, 걔가 잘했는지 확신은 어떻게 하지?
      • 내가 던지기만 하면 그만인가? 객체지향은 무책임한가?
    • 우리가 던전 메세지가 제대로 작동했는지 확인하는 코드가 필요하다.
      • work를 확인하는게 아니라 메세지가 제대로 수신되었는지만 확인한다.
      • 그 메세지로부터 다른 메세지들도 잘 퍼져나갔는지가 중요
      • 실제 작동은 안궁금하다.
    • 객체망의 성립은 쉽지가 않다.
      1. 단방향이지만 알아야한다.
      2. runtime에 만들어지기도 해야한다
      3. 어떤 것은 set으로 할당되기도 하지만, 어떤 것은 생성자에서 만들어진다.
      4. 활성화되었는지 껍데기 객체인지도 모른다.
    • 메세지 전파부터 제대로 해야한다.
      • 클라코드에서 screening.reserve() 호출하는 것은 보았지만
        • 더 중요한 실제 movie의 calculateFee()를 호출하는지
          • 메세지 전파 -> 다른 메세지 제대로 전파되었는지 확인
          • 우리는 reserve() 내장을 깔 방법이 없기 때문에 들여다볼 방법이 없다.
      • 객체 생성이라는 것은 runtime에 이루어지기 때문에, 코드를 짜는 우리로서는 제대로 객체통신(메세지전달)을 확인할 방법이 없다.
        • 객체지향은 runtime context를 극한으로 = lazy하게 이용하려고 하기 때문에 생기는 문제점
        • 즉 runtime context 시점에서만 확인가능. 코드로는 정적분석 불가능

image-20220204200739653

  • 객체망 통신(메세지 전달)을 확인하려면 (가운데 객체로 확인)
    1. 회색 -> 테스트객체로 메세지를 보내고
    2. 테스트 객체 -> 파/빨강 객체로 다시 메세지를 잘 보내어지는지
    3. 테스트 객체와 (원래 내장까는) 이웃한 객체들의 메세지 수신조사해서 확인한다.
      • 회색 -> trigger객체 -> 대상객체 -> 파/빨이 다시 메세지 제대로 수신했는지 확인해서
  • 가운데 테스트 객체를 깔 방법은 없다 -> (처음부터 내장까지는 전/후 이웃) 3 객체들을 까서 조사한다
    • 대상 객체의 단위테스트는 안중요하다
      • 객체망 진짜 테스트 -> 감싸고 있는 회/빨/파 내장까지는 객체가 메세지 수신하는지?

image-20220204201353166

  • 내장까지는 객체는 어떻게 만들까?
    • Mock객체를 활용해서 검증한다.
    • 내부를 log찍는 인터페이스를 구현 -> 인터페이스를 구상하는 객체 -> 구현
mock객체 활용 검증

image-20220204210212717

  • 내부를 log찍는 인터페이스 구현도 반복적이므로 mock객체를 쓴다

  • 단위테스트를 하는데 mock을 사용하지?

    • 실환경에 존재하고 해주는 일을 하는 객체를 대신하여 사용하는 것일까?
      • 아니다.
  • 우리는 객체망을 통해 문제를 해결하는데, 해당객체를 추론해서 볼 수 없으므로 객체망검증을위해 mock객체가 필요하다

    • 회색/파란/빨강 객체 -> mock객체 -> 대상 테스트 객체가 제대로 작동하는지를 검증할 수 있다.
  • 목 객체 용어

    1. mockery(모조 객체) vs mock(목 객체)
    2. 테스트 관리 객체 vs 테스트용 모의 객체
      • mock(모의 객체)들의 우두머리 -> mockery
      • mockery는 일종의 테스트관장하는 우두머리 context라고 볼 수 있따.

image-20220204210714935

  • 일단 context인 mockery부터 배치를 한다.

image-20220204210641461

  1. 필요한 목객체 생성은 context로부터 생성한다.

    • context는 자기로부터 생성된 목객체들을 감시할 수 있다.

    • 아까 봤던 회색 / 파란 / 빨간색 객체를 생성한다.

      • mockery인 context를 안고 생성하게 한다.
      • 해당 인터페이스 구현부에 만들어진다?
      • 안에 context에 보고하는 로직으로 구현되어있다 image-20220205110407718
    • 대상 객체를 둘러싼, 객체망을 검증할 수 있는 주변 객체들을 생성했다

      • 객체 본질 = 책임 = 메세지를 주고 받는다 = 주는사람이 있으면 받는 사람도 있다.
  2. 테스트할 객체를 둘러싼 객체망으로 구성하기 image-20220205110539358

    • 실제 대상객체를 만들고
    • 목객체 3개를 원하는 구조를 구성한다(보통 DI구성 or 직접수동으로)
  3. 트리거 발동하여, 회색객체에 첫번째 메세지 발동 image-20220205110656857 image-20220205110707290

    • 테스트 대상객체 내장은 못깐다. 못건들인다고 생각하고
    • 회색객체에서 할 수 있는 것 = 목객체에서 메세지를 쐈다.를 mockery에서 보고받음.
    • 파랑/빨강객체에서 할 수 있는 것 = 목객체로서 메세지 수신했다mockery에서 보고 받음

    image-20220205110855464 image-20220205110905643

    • 전체적으로 mockery가 원하는 시나리오 대로 성립했는지 확인할 수 있다.
      • 대상객체에의 메세지처리가 정상적으로 유통되고 있어.를 판단한다
    • 객체망 참가하는, 대상객체 테스트 방법은, 이방법이 유일하다.
      • 객체 내장은 못깐다.
      • 객체는 책임으로서만 의미가 있다.
        • 책임 = 메세지를 주고 받는 행위다
          • 객체를 바르게 테스트 하는 방법은, 주변객체를 활용해서 메세지 송수신이 제대로 유통되는지를, mockery에서 확인하는 방법밖이다.
    • 목 시스템 외에는 객체테스트가 불가능하다는 결론
      • 테스트 작성 -> 무조건 mock이 필요하다
      • mock없이는 객체지향 테스트는 불가능하다.
    • 객체 메소드 단위테스트정상작동하는 객체다를 증명할 수 없다.
      • 객체는 오직, 객체만이 참가해서, 메세지를 주고받을 때 의미가 있으므로
      • 정말 바른 객체다?를 증명하려면, 메세지를 주고받는 것으로 확인할 수밖에없다.
  • 객체지향 개발을 한다 = mock기반의 테스트를 작성해야만 가능하다.
    • runtime에서 잘 작동할지 알 방법이 없다
    • 보통 주변 객체들이 적시에 그자리에 있냐 or setter로 세팅한 것이 적시에 이루어지냐에서 뻑난다
      • screening 안에서 movie.calculateFee()를 호출한다고 하더라도
        • movie가 적시에 바르게 작동하고 있는지의 문제가 생긴다
    • 안하면 코드를 상상으로 하는 것이다.
      • 선택이 아니다.
      • 객체 테스트는 이 방법 밖에없다.
    • 단위 테스트(메소드)도 객체가 가시성을 내어놓은 상태로 짜는 것. 꽁꽁 숨겨둔 객체라면 못짠다.
  • 객체는 오직 객체통신을 통해서만 = 책임이라는 메세지를 호출하는 것으로만 의미를 가지기 때문에
    • 테스트방법도 메세지 테스트 외에는 방법이 없다.

GRASP과 9 패턴

  • 오브젝트 책 5장의 주제다
  • 객체지향 기본이론(5+4)를 base로 보다 더 민간용으로 나온 설계요령
    • 다른 관점에서 SOLID법칙, 헐리우드법칙, 디미터법칙을 인식하는 좋은 계기가 된다.

image-20220205112944699

  • 9가지 원칙 패턴
    • 거성들의 원칙(5+4)보다 객체지향 적용의 약한 레벨에 적용할 수 있게 좀 풀어놓은 버전
    • 응용범위는 더 좁을 수 있다. 패턴은 특정상황에서 쓰는 것
  • 켄트백은 적용할 수 있는 레벨을 여러가지로 분류했다.
    • 원칙, 가치, 패턴
      • 원칙: 무조건 지켜야하는 것(이유 없이)
      • 가치: 돈이 되는 행위(보다 돈이 방향으로 움직여라)
        • 코드를 잘짜는 것 < 디버깅을 할 필요가 없는 코드를 만들기
          • 개발시간은 디버깅의 1/4밖에 안되므로
      • 패턴: 원칙+가치를 반복적으로 하다보니 만들어진 모범답안
  • 9가지 패턴으로 쓰면
    • 도움이 되고 시간 절약 + 많은 오류 해소
    • 반드시 적용되는 것은 아님
Information Expert

image-20220205113619818

  • SOLID원칙 이전에, 객체지향의 은닉성 자체을 의미
    • 데이터 하이딩, 은닉을 지키고 싶다라면, **데이터를 가진 애책임(메소드)도 가지는게 좋아(패턴) cf)(맞어 ->원칙) **
      • 패턴이라서 ~ 하는게 좋아. 정도의 어감. 항상 지킬 수 있는게 아닌 패턴
        • ex> 어떻게 screening이 reserve를 하나..
      • 은닉성을 잘 지켜지니까, 데이터를 가진 놈이 메소드도 가지면 좋아
Creator

image-20220205113920449

  • 정보 은닉성과 연결된 얘기
    • 니가 객체를 만드는 일이 생겨도 (in runtime) 그 때도 정보 전문가 패턴을 적용할 필요가 있다.
    • 객체를 만드는 것 = 그 객체(가 가질 필드)정보를 많이 알고 있는 or 포함/이용/부분으로 삼는 놈이 -> 그 객체와 친밀한 놈이 그 객체를 만드는게 좋아
      • 실제로 지키기 힘든 패턴이다.
      • 정보전문가패턴과 일치할때도 있고 아닐때도 있다.
        • ex> reservation에 들어갈 필드정보를 많이 알고 있는 screening이 reservation 생성을 맡긴다.
      • 나중에 고친 모델에서는 reservation을 사용하는 theater가 생성을 담당했다.
        • ex> theater가 검표(표 확인)시 reservation을 이용할 것이기 때문에
Controller

image-20220205115356075

  • 컨트롤러가 무슨 패턴이냐 -> 패턴이다.
    • 컨트롤러: 어떤일을 중개할 때, 직접접촉하는게 나은지 아닌지를 판단하는 것
    • 어댑터 패턴미디에이터 패턴을 공통적으로 한꺼번에 적용할 필요가 있을 때 -> 컨트롤러 패턴으로 퉁치는게 좋아.
      • 미디에이터 패턴: 객체가 다수의 객체를 알아야할 경우, 객체망이 너무 복잡해지니, 미디에이터를 중심에 두고, 미디에이터가 다 알게 한 다음 -> 나머지는 미디에이터만 알게 한다
        • 나머지가 하고 싶은일이 있으면 미디에이터에게 위임하면, 알아서 해준다.
        • 하나의 시스템이 -> 여러시스템에 물려서 통신한다. -> 중개자가 처리한다.
        • 모든 case를 알고 있는 법원으로 가서, 사람들이 따지게 하는 방법
      • 어댑터 패턴: 내가 직접하는 것보다 간접적으로 아는게 훨씬 유리하다.
        • MVC에서 Model(데이터) -> Controller(어댑터) -> View(UI, 콘솔, 사운드 등) 여러 구상체로 나뉠 수 있으니, 구상체를 직접아는 것보다. 중간에 어댑터를 알게 하는 것이 더 좋다.
      • 어댑터가 1개 시스템(1개 Model-데이터)만 아는게 아니라, 여러 시스템이 알고 있는 경우가 미디에이터패턴도 해당한다
        • Controller를 만들어서, MVC의 어댑터를 맡기던지, 법원의미디에이터를 맡긴다.
  • 객체지향 설계를 공부하다보면, 고프의 디자인 패턴을 이야기한다.
    • 반대로도 통하는 사람에게 통하니 문제다
    • 학습루트 추천
      • 디자인 패턴(구현물)을 일단 달달 외운다.
      • 객체지향 원리를 공부하면서 외운 패턴을 적용하면서, 패턴이 왜 그렇게 만들었는지 생각해본다.
        • 2개를 동시에 학습할 수 없다.
  • 컨트롤러 패턴이 나온 이유:
    • 어떤 객체가, 어댑터패턴 && 미디에이터패턴을 동시에 사용해야할 대 -> 컨트롤러를 만들어서 사용한다.
    • 컨트롤러를 보면
      • 외부에 대해선 어댑터
      • 내부에 대해선 미디에이터다
Low Coupling

image-20220205121726198

  • 오래된 이론이다.
  • 객체지향에서
    • 커플링(결합도)가 낮다 = 알고 있는 객체가 적다
      • 책에서는 적절한 객체를 알고 있다고 표현해놨다. 비겁한 표현이다.
      • 우리는 알아야만 하는 객체를 알고 있을 뿐이다.
        • 알아야할 필요가 없는 객체 -> 설계 대상이 아니라 리팩토링 대상이다.
  • 알아야만 하는 것에 대해 단방향 의존성이 Low Coupling의 목표다
    • 양방향 의존성(참조)이 되는 순간 -> 상태를 보고서 버그를 확정할 수가 없다.
      • 디버거들이 무력화 된다.
      • 주고 받았기 때문에, 값이 얼만지 모른다.
      • 주고 받지 않아야만, 메세지 보내기 전/후로 나눠서 디버깅 가능해짐.
      • 양방향 참조는 100% 제거해야한다.
        • 순환참조를 통한 양방향 참조도 무서운 것 -> 제거하자.
        • RDB의 기술: M:N -> 1:M으로 바꾸는 것
        • DB-중개테이블처럼 <중개 객체>가 더들어와야지 양방향 참조를 제거할 수 있다.
          • 1개가 늘어날 수 밖에 없다.
            • 어댑터패턴에서도 -> 어댑터 인터페이스 + 어댑터구상체가 늘어나듯이
    • 주고 받았으면 누구 책임인지 알 수 가 없다. runtime에서는 에러를 못잡는다.
  • Low Coupling -> 알고 있는 객체가 적다 -> 양방항참조를 제거한다 -> 제거 과정에서 M:N을 풀듯이 가운데 객체로 인해 형이 1개 추가될 수밖에없지만 두려워말라. 양방향은 디버깅조차 안되므로
    • 형을 통해 단방향으로 바꾸는 수 밖에 없다.
High Cohesion
  • 그에 비해 High Cohesion(높은 응집성)은 달성 방법이 정해져있다.
    • SRP의 책임 -> 변화율 요인을 1개로만들어서 분리
      • 도메인(내가 가진 정보)에 의해 변한다. or 변하는 주기(자주 변하는 부분과 안변하는 부분의 공존시 분리)에 의해 변한다.
      • 최종 목표: 변화율에 따라 코드를 나누고, 변화시 다른 곳에 영향주지 않게 격리시켜라
        • 격리 유일한 방법: 형 경계를 넘지 않게(?)= Dependency를 단방향으로 의존하는 것
Protected Variations

image-20220205124008768

  • 인터페이스 분리 원칙을 적용한 것
    • 가능한 직접적인 형을 참조 하지말고 공통점 찾아 인터페이스로 추상화 하라는 얘기
    • 가능하지 않는 대부분의 이유: 다양한 case들 때문에 추상화(공통점 찾기가)하기 어려움
      • 공통점을 억지로 찾아도 의미가 없다
      • 공통점 찾는 이유: 인터페이스로 노출하여 -> 책임을 할당하기 위함이다. 억지로 찾아봤자다
        • 3~5년 역할책임모델의 눈을 가지게 되는데 오래걸리는 이유
Polymorphism

image-20220205124322870

  • 인터페이스로 추상화하라는 얘기랑 비슷하게 들지만 아니다. 전략패턴을 의미한다.
    • 얘가 하는 행동이 추상적으로 의미가 같지만, 실제 하는 행동은 여러가지로 달라질 수 있을 때
      • 책의 예제, policy나 condition차원에서는 isSatisfiedBy나 calculateFee메소드 2개를 공통적으로 사용해 퉁칠 수 있지만, 내부구현은 다 달라질 수 있을 때
    • 폴리모피즘 패턴으로 전략패턴처럼 구현하는게 더 좋아
      • 추상체를 인자로 받아들이고
      • 구상적인 체들은 달라질 수 있는 부분을 나중에 구현한 뒤, 받아들일 수 있게 하는게 좋아
Pure Fabrication

image-20220205130734458

  • SRP를 AOP적인 관점을 바라볼 수 도 있다고 했었는데
    • 단일책임원칙: 코드수정은 이 때 + 이래서 변하는 변화율 1개로 기준을 잡아서 객체 분리.
      • 공통된 기능을 분리해서 가지고 있으면, 관심사 수정시, 수정여파가 다른 곳으로 가지 못하게 한다.
  • 단일책임원칙에 의거해서,
    • **공통된 기능을 **귀찮다고>
    • 빨리빨리 추상층으로 빼내든지
    • 공통클래스로 빼내라
  • 완성판 객체 = 추상화가 끝난 객체
    • 공통 기능들은 인터페이스를 구현한 추상클래스 템플릿 메소드로 되어있고
      • 소유를 통해 처리하는 public 메소드
        • 훅이 아니라 독립객체 인터페이스 구상한 것을 hasA모델로 추상클래스에 넣어줌
      • 훅을 통해 처리하는 template 메소드가 노출되어있고
    • 그 추상클래스를 구현한 구상클래스에서는
      • 훅들을 구성해서 채워줌
  • 디자인패턴 중 세련되게 + 많이 + 능숙하게 써야하는 패턴은 템플릿메소드패턴+전략패턴이다
    • 밥 먹듯이 써야한다.
    • 템플릿메소드패턴 <-> 전략패턴 밥먹듯이 바꿀 수 있어야한다.(loop<->재귀 바꾸듯)
      • case(객체)들이 확정되었다고 생각해서 템플릿 메소드 패턴을 사용했는데
      • 지금보니 runtime에서 더 많은 case(객체)들을 정의할 것 같다 -> 전략패턴으로 바꿔야지
    • 딱딱할수록 고정 구상층을 가진 추상클래스가 유리
      • 템플릿 메소드 패턴
    • 유연할수록(=확장될 가능성이 있음) 구상층 일일히 다 만드는 귀차니즘이 없도록 인터페이스가 확장성이 유리
      • 전략패턴
    • 2장에 나와있는 인터페이스로 구현한 추상클래스가 훅을 이용하고 + 템플릿메소드를 제공하고 있는지 확인해봐야한다
      • 다 그렇게 되어있다. 추상화를 다 하고 나면, hasA도 쓰고 isA도 같이 쓰기 때문에
Indirection

image-20220205135613477

  • 포인터를 직접 참조하지말고, 포인터의 포인터를 참조해라
    • 왠만하면 직접적으로 가지 않는게 좋다
  • 포인터의 포인터는 다른의미로
    • 인터페이스같은 추상형을 사용하거나
    • 직접만든 데코레이터패턴 or 어댑터패턴을 통해 직접명령이 아닌 걔를 통해 간접적으로 명렁을 내리는 것 를 말하는 것일 수도 있다.
  • 간접참조(껍데기)를 하면, 직접참조를 하는 애(업데이트 되는 내용물)는 바꿀 필요가 없다.
    • SOLID원칙에서 OCP를 말하는 것