직접 코드를 작성해보고 코드를 개선하고 싶다는 생각이 들기시작했다. Serivce 부분에서 코드들이 중복되는 것들도 많고 수정을 해야하면 수정 범위가 너무 넓어지는 것을 느꼈다. 처음에는 그러려니 하고 수정했지만 시간이 지날수록 조금 귀찮음을 느낀것 같다. 이때 방법을 찾아보며 DIP, DI, IoC 등의 개념을 볼 수 있었다.
프레임워크가 존재하기 전에는 객체의 생성, 설정 등의 객체의 생명주기를 프로그래머가 직접 관리해야했을 것이다. 하지만 프레임워크를 사용함으로써 이 과정을 프레임워크에게 위임할 수 있게되었다.
일반적으로 개발자가 코드에서 필요한 객체를 직접 생성하고, 그 객체의 메서드를 호출해서 작업을 수행한다. 이 경우 객체의 생성과 관리에 대한 제어권은 개발자에게 있다. 하지만 IoC가 도입되면 이런 작업을 개발자가 아닌 프레임워크가 대신하게 된다.
IoC의 도입은 DIP와 DI 개념을 등장시켰다.
"상위 모듈은 하위 모듈에 의존해서는 안되며, 둘 다 추상화에 의존해야한다"
DIP를 공부하기위해 여러 글을 찾아보면 이 문구를 가장 많이 볼 수 있다. 처음 이 문구를 봤을 때는 도무지 이해가 가지 않았다. 하지만 보충 설명들과 예시를 보고 직접 코드를 작성해 보며 조금씩 이해하기 시작했다.
조금 더 풀어말하면, 상위 수준의 모듈이 하위 수준의 모듈의 구현에 의존하지 않도록 함으로써, 모듈 간의 결합도를 낮추고 콛의 유연성을 높여야 한다는 말이다.
public class FrontEndDeveloper {
public void writeJavaScript() {
// 코드
}
}
public class BackEndDeveloper {
public void writeJava() {
// 코드
}
}
public class Project {
private FrontEndDeveloper frontEnd;
private BackEndDeveloper backEnd;
public Project() {
this.frontEnd = new FrontEndDeveloper();
this.backEnd = new BackEndDeveloper();
}
public void implement() {
frontEnd.writeJavaScript();
backEnd.writeJava();
}
}
위 코드를 보면 Project 클래스가 FrontEndDeveloper와 BackEndDeveloper 클래스에 직접적으로 의존하고 있는것을 알 수 있다. 의존한다는 말이 잘 이해가 안된다면 이렇게 클래스안에서 인스턴스 변수로 있는 구조일 때 Project클래스가 이 인스턴스 변수들에 의존하고 있다는거구나~~ 하고 이해하면 좀 편할수 있다.
이때 만약 FrontEndDeveloper를 변경해야 한다면 Project클래스도 수정해야하는 문제가 발생한다. 이렇게되면 변경에 취약한 코드가 되고 유지보수가 어려워진다. 또한 SOLID 원칙중 OCP(개발/폐쇄 원칙)과도 맞지 않는다.
위 코드에 DIP를 적용해서 개선한다.
public interface Developer {
void writeCode();
}
public class FrontEndDeveloper implements Developer {
public void writeCode() {
}
}
public class BackEndDeveloper implements Developer {
public void writeCode() {
}
}
public class Project {
private Developer frontEnd;
private Developer backEnd;
public Project() {
this.frontEnd = new FrontEndDeveloper();
this.backEnd = new BackEndDeveloper();
}
public void implement() {
frontEnd.writeCode();
backEnd.writeCode();
}
}
이제 Project 클래스는 구체 클래스가 아닌 추상화된 Developer 인터페이스에 의존하게 된다. FrontEndDeveloper가 변경되더라도 Project 클래스를 수정할 필요가 없어졌다.
이것이 바로 DIP의 원리이다.
Spring 에서는 생성자 주입에 대해 권장하는 기준이 있다. 필요한 의존성을 모두 포함하는 생성자를 만들고 해당 생성자의 인자를 외부로부터 받는 방법이다.
하지만 위 코든 내부에서 객체를 생성하고 있으므로 수정이 필요하다
DI는 IoC를 구현하는 다양한 패턴중 하나이다. 객체간의 의존성을 외부에서 주입하는 패턴이다.
이는 DIP를 지키면서도 객체의 생성과 소멸을 개발자가 아닌 프레임워크가 관리하도록 해서 코드의 결합도를 낮추고 유연성을 높이는데 도움을 준다.
위 코드를 외부에서 의존성을 주입바도록 수정해본다.
public interface Developer {
void writeCode();
}
public class FrontEndDeveloper implements Developer {
public void writeCode() {
// 코드 작성
}
}
public class BackEndDeveloper implements Developer {
public void writeCode() {
// 코드 작성
}
}
public class Project {
private Developer frontEnd;
private Developer backEnd;
public Project(Developer frontEnd, Developer backEnd) {
this.frontEnd = frontEnd;
this.backEnd = backEnd;
}
public void implement() {
frontEnd.writeCode();
backEnd.writeCode();
}
}
public class Main {
public static void main(String[] args) {
Developer frontEnd = new FrontEndDeveloper();
Developer backEnd = new BackEndDeveloper();
Project project = new Project(frontEnd, backEnd);
project.implement();
}
}
이제 Project 클래스는 Developer 객체를 직접 생성하지 않고, 생성자를 통해 외부에서 주입받습니다. 이를 통해 Project 클래스는 FrontEndDeveloper와 BackEndDeveloper와의 결합도를 완전히 제거했다.
DI를 통해 개발자는 객체 생성과 생명주기 관리에 신경 쓸 필요 없이 비즈니스 로직에 집중할 수 있게 되고 코드의 유연성이 향상시켜 변경에 더 잘 대응할 수 있게 해준다.
이와 같이 DI는 IoC와 DIP의 원칙을 구현해서 더 효과적인 코드를 작성하도록 해준다.