지난 과제를 돌아보며 생각한 것 (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