객체지향을 설명하면서 강조했던 세 가지이다.
세 가지가 독립적인 게 아니고 유기적으로 연결되어 있다는 걸 이제는 잘 알 것이다.
이 글 시리즈의 내용 또한 세 가지의 유기적인 흐름을 그대로 보여주었을 뿐이다.
인터페이스와 다형성은 그냥 갈아끼우기라고 했다.
근데 갈아끼우는 게 정말 쉬울까? 함정이 섞여 있는데 오늘은 그걸 좀 다뤄보고 DI가 함정을 객체지향이 하던 대로 극복한 기술이란 걸 알려줄 것이다.
이 세 개의 의미는 알겠으니 코드단에서 어떻게 설계에 반영되는지 보자.
앞선 시리즈에서 한 그대로 하겠다.
그렇다면 계약은 인터페이스로 다음과 같이 설계한다.
interface Car {
void move();
}
class Friend {
private final Car car;
// 친구는 차를 타고 와야한다는 계약을 넣어준다.
public Friend(Car car) {
this.car = car;
}
public void goToAppointment() {
this.car.move();
}
}
여기까진 자연스럽게 잘 설계했다. 저번 예처럼 제네시스 GV80 COUPE BLACK 차량번호 123가 4568을 설계에 직접 넣은 게 아닌, 우리가 평소 말하듯이 그냥 클래스에 차 인터페이스를 자연스럽게 넣어 설계했다. 아주 좋다.
계약만 지키고 알아서 하라는 메시지를 보내야 한다.
이제 자연스럽게 객체지향으로 설계한 걸 써야 한다. 설계를 왜 했겠나? 당연히 쓰려고 만든 거지. 내가 친구한테 차 타고 오라는 실제 상황을 구현하자.
public class Me {
public static void main(String[] args) {
// "나"는 친구를 약속에 오게 하고 싶습니다.
// 그런데... 친구에게 줄 '차'를 '나' 자신이 직접 만들어야 합니다.
Car aFriendsCar = new MyGenesis();
// 그리고 그 차를 가진 친구를 만듭니다.
Friend friend = new Friend(aFriendsCar);
// 이제야 친구에게 약속 장소로 오라고 말할 수 있습니다.
friend.goToAppointment();
}
}
친구한테 메시지를 보내야 한다고 했다.
다시 한번 강조하는데 객체지향은 다음과 같다.
"요구사항과 계약만 지키면 되니까 니가 알아서 해와."
앞의 예에서 요구사항과 계약을 잘 지켰고, 메시지의 역할을 하는 메서드도 인터페이스를 넣어서 잘 만들었다. "이제 니가 알아서 해오라는" 메시지를 잘 보낼 수 있을 것 같다. 근데 이 메시지를 보내는 과정에 함정이 존재한다.
위의 코드에선 내가 친구한테 메시지를 보내기 위해선 구체적인 차 정보를 내가 주입해 줘야 한다.
이건 내가 제네시스 GV80 COUPE BLACK 차량번호 123가 4568이 친구 차임을 아는 상황과 같다.
즉 이 코드는 결국엔 내가 "야 니가 가진 제네시스 GV80 COUPE BLACK 차량번호 123가 4568 타고 와"라고 한 상황과 똑같다.
니가 알아서 해오라는 객체지향을 수고를 들여서 잘 설계했는데, 막상 실행할 때 전부 망쳐버리는 최악의 상황이다.
객체지향이 원래 이런 상황을 해결하는 방법이 뭐지? 앞 시리즈에서 말했듯, 니가 알아서 해오라는 메시지를 보내는 것이다.
위 코드에선 마지막에 행동을 할 친구를 만들어줄 때 친구의 구체적인 차 정보를 내가 주입시켜줘서 망한 상황이다. 즉, 내가 주입해서 망한 상황이다.
그럼 내가 생성자를 주입 안 하고 알아서 주입해오라는 메시지를 보내면 간단하게 해결되는 거 아닌가?
그럼 생성자만 주입하는 놈을 만들어서 그놈한테 명령하자. "야 니가 알아서 주입해라."
그럼 이렇게 표현할 수 있다.
public class FriendCar {
// "차가 필요하니 하나 준비해줘" 라는 요청에 대한 응답
public Car provideCar() {
// 친구차가 뭔지는 이 놈만 안다.
return new MyGenesis();
}
}
그럼 나는 이렇게 메시지를 보낼 수 있다.
public class Me {
public static void main(String[] args) {
// 친구차가 뭔지 알고있는 놈을 부른다.
FriendCar friendcar = new FriendCar();
// 그 놈한테 알아서 구체적인 건 다 맡긴다.
// '나'는 이제 이게 'MyGenesis'인지 전혀 모른다. 그냥 'Car'라고만 알고 있다.
Car aFriendsCar = friendcar.provideCar();
// 뭔 차인지는 모르겠고 그냥 친구한테 "야 차 타고와" 메세지를 보낸다.
Friend friend = new Friend(aFriendsCar);
// 그럼 친구가 알아서 차 타고온다.
friend.goToAppointment();
}
}
내가 친구 차를 알아버리는 함정을 객체지향의 원래 해결 방법으로 해결했다.
친구 차 정보를 주입해주는 놈을 만들어서 니가 알아서 해오라고 메시지만 보냈다.
"야 니가 가진 제네시스 GV80 COUPE BLACK 차량번호 123가 4568 타고 와"가 아닌, "야 차 타고 와"라는 메시지를 완벽하게 구현한 것이다.
DI를 설명할 때 제어의 역전~~ 결합도를 낮춰서~~~ 뭐라뭐라 어렵게 설명하는데,
그냥 함정을 피한 객체지향이 맨날 하던 방법이다. 별거 아니다. 그냥
마지막까지 "니가 알아서 해오라"는 태도를 고수하는 것이다.