OBJECT 07 SOLID 5원칙 + DDHL + GRAPS 9패턴(코드스핏츠)
object 책을 강의한 코드스핏츠 유튜브 요약
📜 제목으로 보기
- 참고 유튜브 : 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에서의 책임:
- 쓸데 없는 책임을 벗어나게 하는 방법:
- SRP는 spring API처리에서 많이 언급된다.
- 똑같이 트랜잭션처리+로그처리가 어느 클래스든 공통로직으로 들어가있다.
- 이 공통로직이 되어있다면, 공통로직 수정 -> 수많은 클래스 같이 수정해야하는 문제여러>
-
클래스 변화 이유: 도메인이어야하는데,
일반로직까지 같이 가지고 있기 때문에 문제
가 생긴다.- 변화하는 이유 : 도메인이어야한다. -> 여러 이유(+일반로직까지)를 가지고 있다.
- cf) 공통로직 -> 유틸리티 클래스로 빼면 된다.
- 똑같이 트랜잭션처리+로그처리가 어느 클래스든 공통로직으로 들어가있다.
- SRP는 spring API처리에서 많이 언급된다.
- SRP가 제대로 지켜지 않을 때:
- 1가지 변화 이유로 수정시작 ->
수많은 클래스를 고치게 되는 문제점(산탄총 수술, shotgun surgery)
- 변화는 이유:
trigger
는 1개가 되도록 ->클래스도 거기에 맞게 나눠서 구축
하자
- 1가지 변화 이유로 수정시작 ->
OCP
-
OCP(개방폐쇄원칙, Open and Close)
-
다형성
이라 생각해도 무관하다. 구현보다는인터페이스를 참조
하자. -
open:확장이 열려있다. / close: 수정이 닫혀있다.
- a -> b 직접 참조시
- b의 수정 -> a에도 충격 : close원칙에 위배
- b를 안고침 -> a에도 변화X : a의 open원칙을 위배
- 안고쳤다 = 수정이나 확장에 열려있지 않다
- 직접참조 = 건들이면 망가지고 안건들이면 확장이 없음
- a -> b 직접 참조시
-
-
직접참조를 피
하면OCP
를 만족시키는 것이다.- 포인터의 포인터: 포인터를 직접참조하지말자.
- a -> b의 외부 interface -> 다양한 b 구현체orClass
LSP
- LSP(리스코프 치환 원칙, Liskov Substitusion, 업캐스팅 안전 때문에 존재)
- **
부모CLASS형 자리에는 자식Class형을 집어넣을 수 있다.
**- -> 그렇지 않을 땐, 부모class(추상층의) 메소드를 따로 빼내서 자식들의 공통점만 가지게 수정한다.
- 모든 case를 다 알아아야하고, 공통점만 가지도록 -> 구상화된 메소드는 인터페이스로 뺀다
- 이게 왜 원칙일까?
-
추상층이 너무 구체적이면 구상층 구현에 문제가 생기므로
추상층을 어떻게 잘만들까? 어떻게 추상층이 함부러 나대지 않게 만들까?
에 관한 얘기다 -
추상층에
생물
을 만들고, 메소드 2개 정의- 숨을 쉰다
- 다리로 이동한다
-
구상층으로서 생물->
사람
/ 생물->타조
를 구현하고- 리스코프 치환 원칙에 따라,
부모
자리에자식
을 넣어보자. - 지금까지는 어색하지 않다
- 사람이 숨을 쉰다. / 사람이 다리로 이동한다.
- 타조가 숨을 쉰다. / 타조가 다리로 이동한다.
- 리스코프 치환 원칙에 따라,
-
문제는 다른 구상층들 중에서 발생한다
아메바
,독수리
,고래
는 다리로 이동할 수 없다.-
이러한 일은 왜 일어났을까?
추상층에 있는
다리로 이동한다메소드
가앞으로 일어날 구상층의 확장
에서도메인을 반영할 수 없기 때문
이다.
-
너무 나댄 것이다. 욕심이 나지만.. 함부러 추상층에서 정의해선 안됬다.
- 사람, 타조만 보고선 -> “어? 공통인데 추상화해서 추상층으로 빼면 좋지 않을까” 라고 욕심이 났다
-
-
위의 예에 따르면
리스코프 치환 원칙
=추상층은 구상층의 확장을 모두 포용할 수 있는 교집합만을 가지고 있어야한다.
- my) 여러 구상층들을 미리 생각해서 -> 그것들의 교집합, 공통점만 추상층에 올리도록 해야한다. -> 머리가 좋은 사람들만 할 수 있다. 다 파악해야한다. 그래서 어렵다.
- 발견하자마자 추상층 메소드를 수정/제거할 수 있어야한다.
-
그럼 일부 구상층만 가지는 메소드는? ->
인터페이스로 따로 빼놓고, 해당하는 구상층들만 추가 impl
하게 한다- 추상층 - 생물 - 숨을 쉰다() 메소드 1개만
- 다리로 이동() -> 인터페이스로 빼내기
- 구상층
- 생물만 구상하다가 -> 해당하는 구상층만 다리이동()인터페이스 추가 impl
- 나머지들은 생물만 구상
- 추상층 - 생물 - 숨을 쉰다() 메소드 1개만
ISP
-
ISP(인터페이스 분리 원칙, Interface Segregation)
-
구상형으로 쓰지말고, 외부호출별(?) 모듈별(?) 접근하는 객체별(?)
외부 접근을 인터페이스(형)별로 분리시켜 엉뚱한 접근을 막는다.
한다.- 안그럴시, 밖에서 엉뚱한 메소드를 호출하더라도 못막는다?
-
엉뚱한 호출(
메소드접근권한
)을 막는 방법 :형
밖에 없다 ->인터페이스별로 분리
한다- ex> 모듈A야 너는 인터페이스A에 있는 메소드 2개만 바라봐
- ex> 모듈B야 너는 인터페이스B에 있는 메소드 1개만 바라봐
-
예를 들어, 어떤 객체에 메소드가 6개가 있다. 아래와 같이 짜면 안된다.
-
왼쪽 2개 메소드 -> 모듈A에서 사용
-
가운데 1개 메소드 -> 모듈B에서 사용
-
오른쪽 1개 메소드 -> 모듈C에서 사용
-
나머지 2개 메소드 -> 객체 자체에서 사용
-
(멍청한) 우리가 Class를 짜면 항상 이런식으로 작성된다.
- 이렇게 하지말라는 말 -> 왜?
인터페이스 분리가 안되어있다.
메소드들이 모여있으면, 모듈A가 다른 엉뚱한 메소드들을 호출해도 못막는다.
- 이렇게 하지말라는 말 -> 왜?
-
(멍청한) 우리가 Class를 짜면 항상 이런식으로 작성된다.
-
첫번째 ISP 방법
-
hasA방법
: 각각을 모듈별로 인터페이스를 가지도록 객체자체를 나눈 뒤 -> 모든 객체를 소유
-
-
두번째 ISP 방법
-
일반적인
인터페이스 모델
방법:- 인터페이스별로 메소드를 분리
- implements A, B, C -> 각각의 메소드들 구현
- 자체메소드 2개 구현
-
일반적인
-
경우에 따라서
소유모델(hasA)
와인터페이스(형)모델
을 섞어서 쓰기도 한다.
-
DIP
-
DIP(의존성 역전 원칙, Dependency Inversion, 다운캐스팅 금지)
- 어려운말로 나타내면 위와 같다. 고쳐 말하면
-
빨간색 말만 봐라보면 ->
구상 클래스에 의존X 추상 인터페이스나 추상 클래스에 의존하자.
- 구상화된 형을 가리키는 코드가 있다면 -> 추상화된 형으로 바꿔줘야한다.
- 그러려면 4. 인터페이스 분리(ISP, 접근별로 인터페이스를 분리해놓고 impl하자)가 되어야하고
- 그러기 위해선 3. 업캐스팅 안전(LSP. 모든 case를 다 알아아야하고, 공통점만 가지도록 -> 구상화된 메소드는 인터페이스로 뺀다) 도 되어야한다.
- 그래서 어렵다.
그외 설계요령 추가 4원칙
- 로버티 마틴이 줄여서 말한게 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가 가질 수 있는 최대 지식은?
- 필드 / 필드들의 형 / 자기가 만든 객체 / 인자로 넘어온 객체 / 지역변수들
-
나머지는 알면 안된다.
1차원 관계의 것들만 이용하자. 1차원이 안되면,직거래를 유도하도록 래핑메소드를 제공해서 1차원적으로 만들자.
- 인자로 넘어간 객체가 호출 메소드의 return값
- 필드에 있는 객체가 호출한 메소드의 return값
- 다 이용하지말자.
- 직거래만 하자.
타고 가서 return되는 간접거래는 하지마
. 간접적으로 아는 것들(걔의 집안사정)은 머리가 나빠서 기억못한다.모르는게 최고
-
어기면
열차전복 (train wreck)
사고가 난다.- A.B.C 다 연결되서 문제가 생기고 의존성이 생긴다.
-
DI: 의존성 주입(Dependency Injection)
의존성 부패방지와 최소지식의 모순
- 지금 나오는 것을 암기 하지 않으면 책을 읽을때 이해가 안된다.
- 참고할정도도 아니고 총
9개 설계원칙
은 코드짤 때 항상 머리속에서 벨리데이션 하고 있어야한다.- solid + DDHL
- 헐리우드 원칙(묻지말고=응답(return객체) 바라지말고, 시키자)
- 디미터 원칙(최소지식만 요구 = 알고 있는 1차원의 것들만 직거래)
- 참고할정도도 아니고 총
-
헐리우드 원칙(시키기만) + 디미터 원칙(보이는 것만 직거래) 두 원칙을 지키다 보면 모순점이 발생
- 객체망에서 핵심내용은 메세지만 주고받아 객체간통신은 가능하지 내부를 들여다 보진 않는다.
- 내장은 까지 않는다.
-
내가 메세지를 주면서 시켰는데, 걔가 잘했는지 확신은 어떻게 하지?
- 내가 던지기만 하면 그만인가? 객체지향은 무책임한가?
-
우리가 던전 메세지가 제대로 작동했는지 확인하는 코드가 필요
하다.- work를 확인하는게 아니라
메세지가 제대로 수신되었는지만 확인
한다. - 그 메세지로부터
다른 메세지들도 잘 퍼져나갔는지가 중요
- 실제 작동은 안궁금하다.
- work를 확인하는게 아니라
- 객체망의 성립은 쉽지가 않다.
- 단방향이지만 알아야한다.
- runtime에 만들어지기도 해야한다
- 어떤 것은 set으로 할당되기도 하지만, 어떤 것은 생성자에서 만들어진다.
- 활성화되었는지 껍데기 객체인지도 모른다.
- 메세지 전파부터 제대로 해야한다.
-
클라코드에서 screening.reserve() 호출하는 것은 보았지만
-
더 중요한 실제 movie의 calculateFee()를 호출하는지
- 메세지 전파 -> 다른 메세지 제대로 전파되었는지 확인
우리는 reserve() 내장을 깔 방법이 없기 때문에 들여다볼 방법이 없다.
-
더 중요한 실제 movie의 calculateFee()를 호출하는지
-
객체 생성이라는 것은 runtime에 이루어지기 때문에, 코드를 짜는 우리로서는 제대로 객체통신(메세지전달)을 확인할 방법이 없다.
- 객체지향은 runtime context를 극한으로 = lazy하게 이용하려고 하기 때문에 생기는 문제점
- 즉 runtime context 시점에서만 확인가능. 코드로는 정적분석 불가능
-
클라코드에서 screening.reserve() 호출하는 것은 보았지만
- 객체망에서 핵심내용은 메세지만 주고받아 객체간통신은 가능하지 내부를 들여다 보진 않는다.
-
객체망 통신(메세지 전달)을 확인하려면 (
가운데 객체로 확인
)- 회색 -> 테스트객체로 메세지를 보내고
- 테스트 객체 -> 파/빨강 객체로 다시 메세지를 잘 보내어지는지
-
테스트 객체와
(원래 내장까는) 이웃한 객체들의 메세지 수신
조사해서 확인한다.- 회색 -> trigger객체 -> 대상객체 -> 파/빨이 다시 메세지 제대로 수신했는지 확인해서
-
가운데 테스트 객체를 깔 방법은 없다 ->
(처음부터 내장까지는 전/후 이웃) 3 객체들
을 까서 조사한다- 대상 객체의 단위테스트는 안중요하다
- 객체망 진짜 테스트 -> 감싸고 있는 회/빨/파 내장까지는 객체가 메세지 수신하는지?
- 대상 객체의 단위테스트는 안중요하다
-
내장까지는 객체는 어떻게 만들까?
- Mock객체를 활용해서 검증한다.
- 내부를 log찍는 인터페이스를 구현 -> 인터페이스를 구상하는 객체 -> 구현
mock객체 활용 검증
-
내부를 log찍는 인터페이스 구현도 반복적이므로
mock
객체를 쓴다 -
단위테스트를 하는데 mock을 사용하지?
- 실환경에 존재하고 해주는 일을 하는 객체를 대신하여 사용하는 것일까?
- 아니다.
- 실환경에 존재하고 해주는 일을 하는 객체를 대신하여 사용하는 것일까?
-
우리는 객체망을 통해 문제를 해결하는데, 해당객체를 추론해서 볼 수 없으므로
객체망검증
을위해 mock객체가 필요하다- 회색/파란/빨강 객체 -> mock객체 -> 대상 테스트 객체가 제대로 작동하는지를 검증할 수 있다.
-
목 객체 용어
- mockery(모조 객체) vs
mock(목 객체)
- 테스트 관리 객체 vs
테스트용 모의 객체
- mock(모의 객체)들의 우두머리 -> mockery
-
mockery
는 일종의 테스트관장하는 우두머리context
라고 볼 수 있따.
- mockery(모조 객체) vs
- 일단 context인 mockery부터 배치를 한다.
-
필요한 목객체 생성은
context
로부터 생성한다.-
context는 자기로부터 생성된 목객체들을 감시할 수 있다.
-
아까 봤던 회색 / 파란 / 빨간색 객체를 생성한다.
- mockery인 context를 안고 생성하게 한다.
- 해당 인터페이스 구현부에 만들어진다?
- 안에 context에 보고하는 로직으로 구현되어있다
-
대상 객체를 둘러싼, 객체망을 검증할 수 있는 주변 객체들을 생성했다
- 객체 본질 = 책임 = 메세지를 주고 받는다 = 주는사람이 있으면 받는 사람도 있다.
-
-
테스트할 객체를 둘러싼 객체망으로 구성하기
- 실제 대상객체를 만들고
- 목객체 3개를 원하는 구조를 구성한다(보통 DI구성 or 직접수동으로)
-
트리거 발동하여, 회색객체에 첫번째 메세지 발동
- 테스트 대상객체 내장은 못깐다. 못건들인다고 생각하고
- 회색객체에서 할 수 있는 것 =
목객체에서 메세지를 쐈다
.를 mockery에서 보고받음. - 파랑/빨강객체에서 할 수 있는 것 =
목객체로서 메세지 수신했다
를 mockery에서 보고 받음
-
전체적으로 mockery가 원하는 시나리오 대로 성립했는지 확인할 수 있다.
대상객체에의 메세지처리가 정상적으로 유통되고 있어.
를 판단한다
- 객체망 참가하는, 대상객체 테스트 방법은, 이방법이 유일하다.
- 객체 내장은 못깐다.
- 객체는 책임으로서만 의미가 있다.
- 책임 = 메세지를 주고 받는 행위다
- 객체를 바르게 테스트 하는 방법은, 주변객체를 활용해서 메세지 송수신이 제대로 유통되는지를, mockery에서 확인하는 방법밖이다.
- 책임 = 메세지를 주고 받는 행위다
-
목 시스템 외에는 객체테스트가 불가능하다
는 결론- 테스트 작성 -> 무조건 mock이 필요하다
- mock없이는 객체지향 테스트는 불가능하다.
-
객체 메소드 단위테스트
는정상작동하는 객체다
를 증명할 수 없다.객체는 오직, 객체만이 참가해서, 메세지를 주고받을 때 의미가 있으므로
- 정말 바른 객체다?를 증명하려면, 메세지를 주고받는 것으로 확인할 수밖에없다.
- 객체지향 개발을 한다 = mock기반의 테스트를 작성해야만 가능하다.
-
runtime
에서 잘 작동할지 알 방법이 없다 -
보통 주변 객체들이
적시에 그자리에 있냐
orsetter로 세팅한 것이 적시에 이루어지냐
에서 뻑난다- screening 안에서 movie.calculateFee()를 호출한다고 하더라도
- movie가 적시에 바르게 작동하고 있는지의 문제가 생긴다
- screening 안에서 movie.calculateFee()를 호출한다고 하더라도
- 안하면 코드를 상상으로 하는 것이다.
- 선택이 아니다.
- 객체 테스트는 이 방법 밖에없다.
- 단위 테스트(메소드)도 객체가 가시성을 내어놓은 상태로 짜는 것. 꽁꽁 숨겨둔 객체라면 못짠다.
-
- 객체는 오직 객체통신을 통해서만 = 책임이라는 메세지를 호출하는 것으로만 의미를 가지기 때문에
- 테스트방법도 메세지 테스트 외에는 방법이 없다.
GRASP과 9 패턴
- 오브젝트 책 5장의 주제다
-
객체지향 기본이론(5+4)를 base로
보다 더 민간용으로 나온 설계요령
- 다른 관점에서 SOLID법칙, 헐리우드법칙, 디미터법칙을 인식하는 좋은 계기가 된다.
- 9가지
원칙패턴- 거성들의 원칙(5+4)보다 객체지향 적용의 약한 레벨에 적용할 수 있게 좀 풀어놓은 버전
- 응용범위는 더 좁을 수 있다. 패턴은 특정상황에서 쓰는 것
- 켄트백은 적용할 수 있는 레벨을 여러가지로 분류했다.
- 원칙, 가치, 패턴
- 원칙: 무조건 지켜야하는 것(이유 없이)
-
가치: 돈이 되는 행위(보다 돈이 방향으로 움직여라)
- 코드를 잘짜는 것 < 디버깅을 할 필요가 없는 코드를 만들기
- 개발시간은 디버깅의 1/4밖에 안되므로
- 코드를 잘짜는 것 < 디버깅을 할 필요가 없는 코드를 만들기
- 패턴: 원칙+가치를 반복적으로 하다보니 만들어진 모범답안
- 원칙, 가치, 패턴
- 9가지 패턴으로 쓰면
- 도움이 되고 시간 절약 + 많은 오류 해소
- 반드시 적용되는 것은 아님
Information Expert
- SOLID원칙 이전에, 객체지향의 은닉성 자체을 의미
- 데이터 하이딩, 은닉을 지키고 싶다라면, **
데이터를 가진 애
가책임(메소드)도 가지는게
좋아(패턴) cf)(맞어 ->원칙) **- 패턴이라서 ~ 하는게 좋아. 정도의 어감. 항상 지킬 수 있는게 아닌 패턴
- ex> 어떻게 screening이 reserve를 하나..
은닉성을 잘 지켜지니까, 데이터를 가진 놈이 메소드도 가지면 좋아
- 패턴이라서 ~ 하는게 좋아. 정도의 어감. 항상 지킬 수 있는게 아닌 패턴
- 데이터 하이딩, 은닉을 지키고 싶다라면, **
Creator
- 정보 은닉성과 연결된 얘기
- 니가 객체를 만드는 일이 생겨도 (in runtime) 그 때도 정보 전문가 패턴을 적용할 필요가 있다.
-
객체를 만드는 것 =
그 객체(가 가질 필드)정보를 많이 알고 있는
or포함/이용/부분으로 삼는 놈
이 ->그 객체와 친밀한 놈이 그 객체를 만드는게 좋아
- 실제로 지키기 힘든 패턴이다.
- 정보전문가패턴과 일치할때도 있고 아닐때도 있다.
- ex> reservation에
들어갈 필드정보를 많이 알고 있는 screening
이 reservation 생성을 맡긴다.
- ex> reservation에
- 나중에 고친 모델에서는 reservation을
사용하는 theater
가 생성을 담당했다.- ex> theater가 검표(표 확인)시 reservation을
이용할 것
이기 때문에
- ex> theater가 검표(표 확인)시 reservation을
Controller
- 컨트롤러가 무슨 패턴이냐 -> 패턴이다.
- 컨트롤러: 어떤일을 중개할 때, 직접접촉하는게 나은지 아닌지를 판단하는 것
-
어댑터 패턴
과미디에이터 패턴
을 공통적으로 한꺼번에 적용할 필요가 있을 때 ->컨트롤러 패턴
으로 퉁치는게 좋아.-
미디에이터 패턴
: 객체가 다수의 객체를 알아야할 경우, 객체망이 너무 복잡해지니, 미디에이터를 중심에 두고, 미디에이터가 다 알게 한 다음 -> 나머지는 미디에이터만 알게 한다- 나머지가 하고 싶은일이 있으면 미디에이터에게 위임하면, 알아서 해준다.
- 하나의 시스템이 -> 여러시스템에 물려서 통신한다. -> 중개자가 처리한다.
- 모든 case를 알고 있는 법원으로 가서, 사람들이 따지게 하는 방법
-
어댑터 패턴
: 내가 직접하는 것보다 간접적으로 아는게 훨씬 유리하다.- MVC에서 Model(데이터) -> Controller(어댑터) -> View(UI, 콘솔, 사운드 등) 여러 구상체로 나뉠 수 있으니, 구상체를 직접아는 것보다. 중간에 어댑터를 알게 하는 것이 더 좋다.
- 어댑터가 1개 시스템(1개 Model-데이터)만 아는게 아니라, 여러 시스템이 알고 있는 경우가 미디에이터패턴도 해당한다
- Controller를 만들어서, MVC의
어댑터
를 맡기던지, 법원의미디에이터
를 맡긴다.
- Controller를 만들어서, MVC의
-
- 객체지향 설계를 공부하다보면, 고프의 디자인 패턴을 이야기한다.
- 반대로도 통하는 사람에게 통하니 문제다
-
학습루트 추천
- 디자인 패턴(구현물)을 일단 달달 외운다.
- 객체지향 원리를 공부하면서 외운 패턴을 적용하면서, 패턴이 왜 그렇게 만들었는지 생각해본다.
- 2개를 동시에 학습할 수 없다.
- 컨트롤러 패턴이 나온 이유:
- 어떤 객체가, 어댑터패턴 && 미디에이터패턴을 동시에 사용해야할 대 -> 컨트롤러를 만들어서 사용한다.
- 컨트롤러를 보면
- 외부에 대해선 어댑터
- 내부에 대해선 미디에이터다
Low Coupling
- 오래된 이론이다.
- 객체지향에서
-
커플링(결합도)가 낮다 =
알고 있는 객체가 적다
- 책에서는 적절한 객체를 알고 있다고 표현해놨다. 비겁한 표현이다.
- 우리는 알아야만 하는 객체를 알고 있을 뿐이다.
- 알아야할 필요가 없는 객체 -> 설계 대상이 아니라 리팩토링 대상이다.
-
커플링(결합도)가 낮다 =
-
알아야만 하는 것
에 대해단방향 의존성
이 Low Coupling의 목표다- 양방향 의존성(참조)이 되는 순간 -> 상태를 보고서 버그를 확정할 수가 없다.
- 디버거들이 무력화 된다.
- 주고 받았기 때문에, 값이 얼만지 모른다.
- 주고 받지 않아야만,
메세지 보내기 전/후로 나눠서 디버깅
가능해짐. -
양방향 참조
는 100% 제거해야한다.순환참조
를 통한 양방향 참조도 무서운 것 -> 제거하자.- RDB의 기술: M:N -> 1:M으로 바꾸는 것
-
DB-중개테이블처럼 <중개 객체>가 더들어와야지 양방향 참조를 제거할 수 있다.
-
형
1개가 늘어날 수 밖에 없다.- 어댑터패턴에서도 -> 어댑터 인터페이스 + 어댑터구상체가 늘어나듯이
-
- 주고 받았으면 누구 책임인지 알 수 가 없다. runtime에서는 에러를 못잡는다.
- 양방향 의존성(참조)이 되는 순간 -> 상태를 보고서 버그를 확정할 수가 없다.
-
Low Coupling -> 알고 있는 객체가 적다 -> 양방항참조를 제거한다 -> 제거 과정에서 M:N을 풀듯이 가운데 객체로 인해 형이 1개 추가될 수밖에없지만 두려워말라. 양방향은 디버깅조차 안되므로
- 형을 통해 단방향으로 바꾸는 수 밖에 없다.
High Cohesion
- 그에 비해 High Cohesion(높은 응집성)은 달성 방법이 정해져있다.
- SRP의 책임 -> 변화율 요인을 1개로만들어서 분리
- 도메인(내가 가진 정보)에 의해 변한다. or 변하는 주기(자주 변하는 부분과 안변하는 부분의 공존시 분리)에 의해 변한다.
- 최종 목표: 변화율에 따라 코드를 나누고, 변화시 다른 곳에 영향주지 않게 격리시켜라
- 격리 유일한 방법: 형 경계를 넘지 않게(?)= Dependency를 단방향으로 의존하는 것
- SRP의 책임 -> 변화율 요인을 1개로만들어서 분리
Protected Variations
- 인터페이스 분리 원칙을 적용한 것
-
가능한 직접적인 형을 참조 하지말고 공통점 찾아
인터페이스로 추상화
하라는 얘기 - 가능하지 않는 대부분의 이유: 다양한 case들 때문에 추상화(공통점 찾기가)하기 어려움
- 공통점을 억지로 찾아도 의미가 없다
- 공통점 찾는 이유: 인터페이스로 노출하여 -> 책임을 할당하기 위함이다. 억지로 찾아봤자다
- 3~5년 역할책임모델의 눈을 가지게 되는데 오래걸리는 이유
-
가능한 직접적인 형을 참조 하지말고 공통점 찾아
Polymorphism
- 인터페이스로 추상화하라는 얘기랑 비슷하게 들지만 아니다.
전략패턴
을 의미한다.-
얘가 하는 행동이 추상적으로 의미가 같지만, 실제 하는 행동은 여러가지로 달라질 수 있을 때
- 책의 예제, policy나 condition차원에서는 isSatisfiedBy나 calculateFee메소드 2개를 공통적으로 사용해 퉁칠 수 있지만, 내부구현은 다 달라질 수 있을 때
-
폴리모피즘 패턴
으로전략패턴
처럼 구현하는게 더 좋아- 추상체를 인자로 받아들이고
- 구상적인 체들은 달라질 수 있는 부분을 나중에 구현한 뒤, 받아들일 수 있게 하는게 좋아
-
얘가 하는 행동이 추상적으로 의미가 같지만, 실제 하는 행동은 여러가지로 달라질 수 있을 때
Pure Fabrication
- SRP를 AOP적인 관점을 바라볼 수 도 있다고 했었는데
-
단일책임원칙: 코드수정은 이 때 + 이래서 변하는 변화율 1개로 기준을 잡아서 객체 분리.
- 공통된 기능을 분리해서 가지고 있으면, 관심사 수정시, 수정여파가 다른 곳으로 가지 못하게 한다.
-
단일책임원칙: 코드수정은 이 때 + 이래서 변하는 변화율 1개로 기준을 잡아서 객체 분리.
- 단일책임원칙에 의거해서,
- **
공통된 기능
을 **귀찮다고> - 빨리빨리
추상층으로 빼내든지
공통클래스로 빼내라
- **
-
완성판 객체 =
추상화가 끝난 객체
는- 공통 기능들은
인터페이스를 구현한 추상클래스
에 템플릿 메소드로 되어있고-
소유를 통해 처리하는 public 메소드
- 훅이 아니라 독립객체 인터페이스 구상한 것을 hasA모델로 추상클래스에 넣어줌
- 훅을 통해 처리하는 template 메소드가 노출되어있고
-
소유를 통해 처리하는 public 메소드
-
그 추상클래스를 구현한 구상클래스
에서는- 훅들을 구성해서 채워줌
- 공통 기능들은
-
디자인패턴 중 세련되게 + 많이 + 능숙하게 써야하는 패턴은
템플릿메소드패턴
+전략패턴
이다- 밥 먹듯이 써야한다.
-
템플릿메소드패턴 <-> 전략패턴 밥먹듯이 바꿀 수 있어야한다.(loop<->재귀 바꾸듯)
- case(객체)들이 확정되었다고 생각해서
템플릿 메소드 패턴
을 사용했는데 - 지금보니 runtime에서 더 많은 case(객체)들을 정의할 것 같다 ->
전략패턴
으로 바꿔야지
- case(객체)들이 확정되었다고 생각해서
-
딱딱할수록
고정 구상층을 가진 추상클래스
가 유리- 템플릿 메소드 패턴
-
유연할수록(=확장될 가능성이 있음) 구상층 일일히 다 만드는 귀차니즘이 없도록
인터페이스가 확장성이 유리
- 전략패턴
- 2장에 나와있는 인터페이스로 구현한 추상클래스가 훅을 이용하고 + 템플릿메소드를 제공하고 있는지 확인해봐야한다
- 다 그렇게 되어있다. 추상화를 다 하고 나면,
hasA
도 쓰고isA
도 같이 쓰기 때문에
- 다 그렇게 되어있다. 추상화를 다 하고 나면,
Indirection
- 포인터를 직접 참조하지말고, 포인터의 포인터를 참조해라
- 왠만하면 직접적으로 가지 않는게 좋다
- 포인터의 포인터는 다른의미로
- 인터페이스같은 추상형을 사용하거나
- 직접만든 데코레이터패턴 or 어댑터패턴을 통해 직접명령이 아닌 걔를 통해 간접적으로 명렁을 내리는 것 를 말하는 것일 수도 있다.
-
간접참조(껍데기)를 하면, 직접참조를 하는 애(업데이트 되는 내용물)는 바꿀 필요가 없다.
- SOLID원칙에서 OCP를 말하는 것