지난 과제를 돌아보며 생각한 것 (with 객체지향)
(1) mvc의 필요성
•
지난 과제에서 mvc를 적용하려 한 사람들이라면 누구나 공감할 만한 부분이 있다. 바로, 사용자로부터 3자리 숫자를 입력하는 부분을 view에서 하기 까다롭단 것이다. 사실 이번 과제에선 mvc를 쓰지 않고 3자리 숫자 입력을 특정 모델이 책임지게 했으면 좀 더 객체지향적이며 자연스레 메서드도 작은 단위로 추출될 수 있었을 거 같다.
•
무슨 말이냐면, "숫자를 입력해주세요" 부분은 Player의 행위를 위한 객체에 속하면 더 좋았을 거 같다. 대신 이렇게 되면 mvc 개념을 넣긴 힘들어질것이다. 하지만, mvc란게 애초에 사람들이 구현을 하다 어떤 임계점에서 특정 패턴을 정리한 것뿐이지 무조건 써야 하는 것이 아니기에, 돌이켜보면 1주차 과제는 그 임계점을 넘지 않는 과제가 아니였을까 싶다.
(2) 객체지향 vs 절차지향(함수지향)
•
객치지향과 절차지향은 반댓말이 아니다. 그 이유는 절차지향을 하다가 발전된 게 객체지향이기 때문이다. 따라서 객체지향적인 코드가 절차지향적인 특성을 아예 안 갖는 건 아니다. 객체지향 역시 절차적으로 실행된다. 그렇다면 뭐가 다른 걸까? 이 실행의 목적이 다르다. 절차지향은 오직 순차적으로 실행되는 데에 목적을 두고 있고, 객체지향은 객체 간의 관계가 실행되는 데에 목적을 두고 있다.
•
좀 더 직관적으로 말해보자면, 절차지향은 데이터를 중심으로 함수를 구현하는 거고 객체지향은 기능을 중심으로 함수를 구현한다. 그렇다면 어떻게 해야 데이터 중심으로 함수를 구현하지 않고 기능 중심으로 함수를 구현할 수 있을까? 바로 데이터 위주의 사고가 아닌 행동 위주의 사고를 하는 것이다.
•
데이터 위주의 사고는 예를 들면 “자동차는 바퀴, 프레임, 엔진, 방향, 속도, 값들을 갖고 있어야겠다”라고 생각한 후 Car { private List<Wheel> wheels; private Speed speed; } 와 같은 식으로 코드를 짜는 것이다.
•
반면, 행동 위주의 사고는 “자동차는 달릴 수 있어야 하고, 속도 조절할 수 있어야 하고, 방향도 바뀔 수 있어야겠다.”라고 생각한 후 Car { public void drive(); public void accelerate(Speed speed) } 와 같은 식으로 코드를 짜는 것이다.
•
사실 전자의 결과물은 struct를 만든 거고 후자의 결과물이 class를 만든 것이다. 행동 위주의 사고를 하는 게 객체지향적인 코드를 짜게 될 확률이 올라간다.
내 코드
참고
(3) 연관관계 vs 의존관계
•
지난 과제에서 모델 간 의존성을 떨어트리고 싶었는데 사실 모델끼리 그 어떤 관계도 맺지 않는 건 어려운 일이었다. 특정 모델이 다른 모델을 attribute로 가져야 할 수도 있고, 다른 모델을 사용해야 할 수도 있다. 그렇다면 어디까지가 의존성을 높이지 않는 선에서 허용되는 건지 궁금해졌었다.
•
이에 대해 어느 정도 해답을 준 게 연관관계와 의존관계 라는 키워드였다. 연관관계는 우리가 알고 있는 has a 관계다. Car { private Speed speed; }와 같이 Car가 Speed를 변수로 갖는 Association 관계이다. 의존관계를 사실 잘 몰랐는데 이는 Car { public void accelerate(Speed speed) { return new Speed(); }와 같이 Car의 매개변수 혹은 반환타입으로 Speed를 사용하는 것이다. 이 경우 Car가 Speed에 의존적이라고 한다.
•
그렇다면 둘 중 모델 간 의존성을 높이는 것은 무엇일까? 이건 아래 강연를 보고 더 고민해보았다. 결론은 설계에 따라 다르다. 의존성이란건 특정 클래스가 변경될 때 다른 클래스도 변경될 가능성이 있단 걸 의미하는데, 의존성이 있어도 설계를 잘하면 변경으로 인한 파장 효과를 없앨 수도 있다. 이를 위한 기법으로 강의에선 event handler를 이야기 하는데 얼핏 저게 ddd에서 나오는 용어란 걸 알 수 있었다.
•
사실 나는 아직 객체지향도 완벽히 이해하지 못했는데 ddd까지 욕심 부리는 건 무리라고 판단했다. ddd가 정확히 뭔지도 잘 모르고 말이다.. 따라서 이번 2주차 과제에선 어쩌면 프리코스 기간 동안엔 의존성 결합에 대해 크게 집착하지 않기로 했다.
참고
[강연] ‘의존성을 이용해 설계 진화시키기’를 듣고
•
설계 : 코드를 어떻게 배치할 것인지에 대한 의사 결정
◦
같이 변경되는 코드를 같이 넣고, 같이 변경되지 않으면 따로 넣자
•
의존성 : A가 B에 의존(A→B)한단건 B가 변경될 때 A도 같이 변경될 수 있음(가능성)을 의미함
◦
변경은 클래스 이름, 메서드 이름, 구현 등 뭐든 될 수 있음
◦
의존성이 있다고 해서 무조건 변경되는 건 아님. 설계 잘하면 B가 변경되더라도 A에 영향 주지 않을 수도 있음
•
클래스 의존성
◦
연관관계 : A에서 B로 이동할 수 있음 (영구적 경로)
class A {
private B b; // 객체 참조
}
Java
복사
◦
의존관계 : A와 B가 협력하는 시점에 관계맺고 헤어짐 ( 의존성 아님ㅎ)
class A {
public B method(B b) { // 인자나 반환타입에서 B가 나오거나
return new B(); // 메서드 안에서 B를 생성
}
}
Java
복사
◦
상속관계 : B의 구현만 바뀌어도 영향 받음
class A extends B { }
Java
복사
◦
실체화관계 : B의 인터페이스 시그니처 바뀌었을 때만 영향 받음
class A implements B { }
Java
복사
•
패키지 의존성
◦
패키지에 포함된 클래스 사이의 의존성
◦
클래스 열었을 때 import에 다른 패키지 이름 있으면 의존성 있는 거임
•
의존성 관리 규칙 : 설계 시 가이드
◦
가급적이면 양방향 의존성을 피하라
▪
양방향 의존성을 가질 때 사실 그 두 클래스는 하나의 클래스여도 무방한 데 억지로 찢은 게 아닐지 의심해보기
▪
양방향은 동시성 관리하기 빡셈
◦
다중성이 적은 방향을 선택하라
▪
일대다 말고 다대일로 표현하기
▪
일대다의 경우 List, Collection, Set 같은 걸 변수로 가져야 할텐데 성능 이슈 관리나 객체들 관계 유지가 빡셈
◦
되도록이면 의존성이 필요없다면 제거하라
◦
패키지 사이의 의존성 사이클을 제거하라
▪
패키지 사이엔 양방향 의존성이 있으면 안됨
•
예제 : 여러 객체에 분산된 validation logic 분리
◦
validation 로직과 주문 처리 로직은 변경 시점이 다를 거임
◦
따라서 Order 객체에서 validation 로직은 떼어냈음
◦
때로는 절차지향이 객체지향보다 좋다. 객체 안에 validation 로직 다 넣을 필요 없다.
◦
상태 조금 체크하는 거면 객체 안에 넣는 게 나은데, 이를 체크하기 위해 여러 객체를 사용해야 한다면 떼어내는 게 나을 수 있다.
◦
이가 객체의 결합도는 높이지만 응집도는 낮출 수 있다.
•
예제 : 도메인 로직의 순차적 실행으로 인한 도메인 간 높은 결합도 이슈 해결
◦
첫 번째 방법 : 절차지향적으로 로직 모으기 → 비즈니스 플로우 한 눈에 볼 수 있음
▪
이때 사이클 생기면 → 인터페이스 사용해서 의존성 역전시키기
◦
두 번째 방법 : 도메인 이벤트 퍼블리싱
Today in 프리코스
TIL 작성하기
준비
메일 꼼꼼히 읽고 정리하기
몰입
지난 과제 돌아보며 생각했던 점들 전부 정리하며 확장시키기
우아한 객체지향 강연에서 설계 부분 보기
Search