OBJECT 04 Type, Condition, 책임기반개발(코드스핏츠)
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/
- 참고 유튜브 : 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이 아니라모든 것을 형으로 바꿔서 생각
하는 개념이다.-
~형(Type)
: 객체지향에서 구현 가능한 유일한 개념 -> 무조건 ~형으로 구현한다.- Type(형)으로 만들 수 없으면, 개념을 구현할 수 없는 것이다.
-
- Type(형)의 사용
-
Role(역할): 역할은 반드시 Type으로 구현되어야한다.
-
인터페이스
가 됬든class
가 됬든enum
이 됬든 Type으로 구현해야 역할을 지정한 것이다.
-
-
Responsibility(책임): 책임(~할수있음을 수행)도 Type으로 드러난(로직을 구현)다.
- 어떤 애가 예약을 받을 수 있다 ->
Reservationable
인터페이스의 형(Type)- Reservationable이라는 형(Type)이
예약받을 수 있음을 증명
한다 =책임
- Reservationable이라는 형(Type)이
- 내가 수행해야할 책임(역할 중 특정 책임 수행시에도) = 형으로 드러난다
- 람다로 써서 함수로 넘기면요? -> 함수의 시그니쳐도 다 형(func형)이다.
- 어떤 애가 예약을 받을 수 있다 ->
- **Message(메세지): 특정 역할끼리 or 특정 책임끼리 협력을 위해 -> 메세지를 주고 받을 때도 그 메세지 조차도 형(Type)으로 되어있다. **
- 궁금한 점: 다른 객체에게 메서드 호출해서 인자를 보낼 때, 그 인자가
string or integer
라면 형(type)으로 봐야할까?- (X) string or integer는 값이다.
- 형과 값의 차이는 ?
값
:불변
이며,보내도 복제
된 객체를 만들어낸다.형
보내면객체의 주소(참조)
를 보낸다.
-
값
은상태관리의 책임이 없
으며, (참조X ->)캡슐라이즈를 할 수 없
기 때문에, 객체지향에서 쓰지 않는다.- 값을 주고 받으면 -> 책임 위임이 안됨 -> 협력대상 X 동등한 대상X
- 원하는 역할/책임(로직)/메세지를 받으면 형으로 바꿔야한다.
- 궁금한 점: 다른 객체에게 메서드 호출해서 인자를 보낼 때, 그 인자가
-
Protocal(프로토콜): 객체 양자간 협의에 의해 서로 동의된 공통요소를 쓰자고 합의를 본 내용
- swift: 인터페이스 -> 프로토콜이라고 씀. -> 다 형으로 표현되어야 의미가 있다.
- 이펙티브 자바 책: 마커 인터페이스 -> 우리가 원하는 계약을 형으로 표현하기 위해, 몸체는 없지만 형을 가지게 한다.
-
Role(역할): 역할은 반드시 Type으로 구현되어야한다.
static, enum, class: jvm 동원 가능 3가지 타입
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으로 만드는게 유리하다.
-
-
enum은 명시된 제한된 수의 instance만 만들며, jvm이 static보다 더 빨리 만들어주고 시작한다.
-
일반적으로
- 1개의 인스턴스 생성할 때 ->
static context
- 여러개의 인스턴스를 확정적으로 생성할 때 ->
enum
-
몇 개 만들지 모를 때, 무제한 인스턴스시 ->
class
- 많은 경우에 인스턴스가 무한이 아니라는 알고 있어서, class -> enum 으로 대체되는 경우가 많다.
- enum은 값이 아니라 참조라는 점에서 좋다
- but 제네릭에 사용할 수 없다.는 것이 단점이다. -> 형 대체가 불가능하다.
- 그래서 마커 인터페이스를 비록한, 다양한 class, 인터페이스로 형을 지정할 수 밖에없다.
- 1개의 인스턴스 생성할 때 ->
- 정리: 동원할 수 있는 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속 메서드
-
class 속 메소드에
Condition: 조건
- 조건: 특정 상태(인스턴스의필드or변수)에 대한 연산
- 만약 특정상태(인스턴스의 필드 or 지역변수 or 변수)에 의존하지 않는 연산이라면, 고정되어서 분기될 일이 없어진다.
- 안쪽이 변수, 필드라서 runtime이 바뀔 가능성이 있어야 분기의 의미가 있다.
- 분기 = 특정 상태에 의한 분기 -> 상태(필드, 변수)가 더 중요한 말이다.
- 만약 특정상태(인스턴스의 필드 or 지역변수 or 변수)에 의존하지 않는 연산이라면, 고정되어서 분기될 일이 없어진다.
-
조건 분기는 결코 제거할 수 없다.
-
우리는 코드안의
if
를 절대 제거할 수 없다.- 신입사원 연봉이 if 6개월 지났으면 100만원 더 오른다 -> if 제거하면 구현 못한다.
- if는 몇단계 중첩까지 감당할 수 있을까?
- if 2단계부터는 60%이상의 사람들은 감당 못한다 -> if 1단계까지만 감당한다.
- 사람의 머리는 분기에 약하기 때문 -> 분기 늘어나면 경우의 수를 빼먹게 된다.
- if 2단계부터는 60%이상의 사람들은 감당 못한다 -> if 1단계까지만 감당한다.
-
우리는 코드안의
-
조건 분기에 대해 쓸 수 있는 방법은 2가지 뿐이다.
-
내부에 응집성있게 모아두기- switch…
- 장점: 모든 경우의수를 한눈에 파악 (관리가 편하다)
- 단점: 분기 늘어날 때마다 코드 전체를 변경 -> 수많은 위험과 회귀테스트
- spring @annotation 대신 if문이라면..
- switch…
-
외부에 분기를 위임하여 내부에선 처리기만 호출 -> 외부에서 분기를 선택하는 방식
- 함수 내부에 있던 if + else -> 함수 바깥에 if + else를 둔다.
-
조건 분기를 처리하는 방식은 내->외부로
위치를 바꾸는 방법
밖에 없다.- 장점: 분기 늘어날 때마다 외부 처리기에 if만 1개 추가 해준다. (전체코드변경X)
-
단점: 모든 경우의 수를 내부에서 파악할 수 없다.
- 외부에서 if분기가 끝난 객체 1개만 -> 내부에서 받는다.
- spring에서 @annotation 방식으로 url을 지정 -> 총 몇개 url있는지 모르는 것과 마찬가지
- 책과 디자인패턴의 대부분이 이것을 구현하는 것
- 외부에서 if분기가 끝난 객체 1개만 -> 내부에서 받는다.
- 디자인 패턴에서는 전략패턴, 상태패턴 등 대부분의 패턴이 이 방식이 따르고 있다.
- 전략 패턴: 전략 객체에 가서 처리 -> 전략 객체 5개면, 5개의 if문을 함수안에 넣어도 되는데,
바깥에서 5개의 경우의 수만큼 전략객체
를 미리 만들고바깥에서 전략객체를 선택
해서 주면 -> 내부는 받아서 수행만
- 전략 패턴: 전략 객체에 가서 처리 -> 전략 객체 5개면, 5개의 if문을 함수안에 넣어도 되는데,
-
내부 cohesion응집성 있는 코드(한 눈에 잘보이는 코드)
- processCondition하려면, 상태인 condition을 받아서
- if로 해당 컨디션인지 검사한 뒤
- 거기에 맞는 처리기()를 호출해주고 있다.
- if로 해당 컨디션인지 검사한 뒤
분기를 외부화한 코드
- 바깥에서, 이미 정해진 분기마다, 처리기를 생성하도록 해놓고
- 생성된 처리기만 로 넘겨준다. ![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
는 없어지지 않는다.외부로 옮겼을 뿐
**- 이 분기를 외부화했을 때의 장점은?
-
내부 코드는 수정할 필요가 없어진다.
- 외부코드 = 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분기 등을 제외하고 배포해야한다.- 변화율 큰 코드(
무기 추가하는 분기 코드
) -> 변화율 큰 쪽에 두어 어차피 수정할때 수정하게 한다
- 변화율 큰 코드(
-
- 외부코드 = client코드 = client마다 새로 작성해야할 코드 -> 어차피 원래 새로 작성/수정되는 곳에 if분기를 옮겨놨다.
-
내부 코드는 수정할 필요가 없어진다.
- 이 분기를 외부화했을 때의 장점은?
-
IF분기(를 포함한 자주바뀌는 코드)는 외부로 옮기고, 각 경우마다 객체를 만들어서 공급해주자.그럼 내부 2단계 분기 -> 외부1단계 + 내부 1단계로 고칠 수 도 있다.
- if의 depth를 1개까지만 주자. 2개 이상의 복잡한 코드라면, 외부로 if를 빼고, 그 경우마다 객체를 만들어서 공급해주자.
- my) 메소드라면, 미호출 메소드 = 객체로 넘긴다.
- if를 내부에서 제거하는 방법은 이것 밖에 없다
- if의 depth를 1개까지만 주자. 2개 이상의 복잡한 코드라면, 외부로 if를 빼고, 그 경우마다 객체를 만들어서 공급해주자.
책임기반 개발
- 2003년 웨슬리에서 나온 책이 2004년 번역되어서 나왔다. 구할 수 있음 구해봐라.
- [Evans 2004] Evans, Domain Driven. Addison Wesley,2004. or
- [Fowler 2003] Martin Folwer 등, Patterns of Enterprise Application Architecture, Addison Wesley, 2003.
-
책임기반의 개발
,전문가 패턴
등 패턴들을 다루게 된다. -
오브젝트
책은 이 책을 쉽게 해설한 책이다.
-
책임(들) -> 역할 -> 추가 책임
-
책임기반의 개발에서 나오는 여러가지 개념을 쉽게 설명하도록 하자.
-
어떤 시스템의 가치는 무엇이냐? 사용자에게 기능을 제공함으로써 가치가 생김
-
사용자가 사용할 기능 = 가치 = 시스템의 책임
- 예매사이트 들어갔는데 예매가 안된다? 싸이트의 가치는 0임.
- 사이트가 최우선적 방어해야할 책임 = 예매(사용자 기능)
- 책임 = (기능 =) 가치 그자체(
시스템
,객체
,역할
무엇이 됬든, 유일한가치
는책임(사용자 기능)
과 일치한다)
-
(시스템의 책임에만 집중하면 된다.) 시스템 차원의 책임 -> 더 작은 단위의 책임으로 분할하자. 그리고 책임은 = 기능이며 = (알고리즘 코드가 들어있는 진짜 working하고 있는) 함수 (인터페이스, 추상클래스는 X 프로토콜만 있는지 workingX 껍데기일 뿐. 구상클래스의 메소드가 책임)
-
예매 = 기능 = 코드가 있지지만 너무 복잡한 함수 -> depth2이상의 if 등 -> 분리시킨다.
- 예매가 잘되어야한다? -> 방대한 책임이다 -> 머리가 나빠서 다 이해할 수 없다.
- 딱딱 잘라지진 않는다. -> 이게 바로 실력(3~5년 걸리는 역할모델의 책임)
- 응집성은 높으면서도(한눈에 보임) 의존성=결합도가 낮은 부분으로 쪼갤 수 있느냐
-
-
책임(쪼갠 함수)들의
공통점/공통의존기능을 추상화
한 것을 ->역할
이라고 정의할 수 있다. 만약 성공했다면, 반대로 새로운 책임(케이스, 사례)을 추가할 수 도 있다.-
함수 분리과정에서 함수끼리
공통점
or공통의존 기능
을 발견하게 된다. -
역할
은 여러개의 책임(=기능=함수)가 아니다.- **1개의 책임(기능)이라도 더 높은 수준으로 정의 -> 나중에 공통점이 모일 수도 있도록
추상화
한 것 **
- **1개의 책임(기능)이라도 더 높은 수준으로 정의 -> 나중에 공통점이 모일 수도 있도록
-
책임 -> 더 높은 수준 형태로 추상화 ->
역할로 승격
되는 것임.- cf) 1-1시간에 배운
추상화
-객체지향적 추상화
-일반화
: 인터페이스, 추상클래스 이용
- cf) 1-1시간에 배운
-
책임과 역할을 따로 생각하는 것이 어렵다.
- 무엇보다 다양한 책임에서 -> 공통된 점(코드, 의존기능)을 뽑아내서 -> 역할로 정의하는게 굉장히 어렵다.
-
다양한 책임
으로부터 ->역할 정의
에 성공하면, 사례들 -> 추상적인 것을 만들어낸연역법
이다.- 성공했으면, 추상화된 역할을 가지고 -> 다른 사례를 만들어낸 귀납법을 사용할 수 있다.
- 일단 추상클래스를 만들어낸 순간 -> 다른 사례들을 만들어낼 수 있다.
- if문 처리기를 더 만들어낼 수 있다.
- if분기만큼의 객체를 -> 외부에 위임할 수 있다는 말이다.
- depth if문 유일한 처리방법 : if문을 바깥쪽에 빼서, if문 case만큼 다른 객체들을 만드는 수 밖에 없다.
- 만들어지는 객체들간에 공통점이 없는 것 같은데.. -> 비슷한 사례들로 찾아내서 만든 역할 -> 추상화된 역할로부터 새로운 다른객체를 생성하게 된다.
- 어떤 사례들로 인해 객체가 생성되었어도 -> 내부에선 Runnable이 왔으니까 run()만 시켜주면 된다.
- 여기선 Runnable이
실행되다라는 <공통기능> 역할
이다. - 어떤 케이스로 인해 진입해서 객체를 생성했어도
- 생성된 객체는 일괄 Runnable == 공통점
- 서로 다르게 구상되어있는 각 구상클래스 속 run()메소드들의 구상화 코드들이
책임
- **
책임
들의공통점
을 찾아서추상화 성공
한 것이Runnable이라는 역할
**- 공통점1: 각각의 인자들을 인자를 안받고 return도 안한다
- 공통점2: …
-
추상화 성공한 역할
Runnable
을 통해 -> d , e에 대한 케이스도 구상화해서 추가할 수 있게 된다.- 추상화과정은 위로 올라가기도 하지만(책임들 -> 공통점 -> 역할 = 인터페이스 or 추상클래스 정이 성공) 아래로 내려가기도 한다(역할 -> 새로운 책임 -> 새로운 처리기(케이스) 추가도 가능하다)
- 만약, 인자를 받는 경우라면? 내부 라이브러리 레이어들도 다 바뀌어야한다.
- 추상화 능력이 떨어지면, 수시로 라이브러리가 업데이트 된다.
- 여기선 Runnable이
- 여러 케이스들을 받아들이는 처리기 -> 각 케이스들의 공통점을 찾아내야한다.
-
-
역할에 따라 협력이 정의된다.
- 협력단계를 섵불리 책임단계에서 정의하고 싶지 않다.
- 책임단계에는 다양한 형식이 있음. 책임이 추가되면 협력도 깨져버림
-
보다 높은 단계인
역할
단계에서협력을 구축
하고 싶다.- 하나하나의 lambda함수 run()수준(책임=함수)에서 프로세스를 만드는게 아니라 Runnable수준에서 프로세스를 만들어야 귀찮은 일이 발생하지 않는다.
- 어설프게 책임(함수)단계에서 협력이 정의된다 -> 책임 추가/변경시마다 협력시스템까지 위험해진다.
- 협력단계를 섵불리 책임단계에서 정의하고 싶지 않다.
-
-
여기까지 정리
- 값이 아니라 형Type을 써야한다
- 조건분기에 대해 쓸 수 있는 방법은 2가지 밖이다.
- 그걸이용해서 설계하는 방법은.. 책임-> 역할