DI(Dependency Injection)
- 의존성 주입
- 클래스나 메서드 내부에서 객체를 새로 생성하면 두 클래스 간 의존성이 높아지고, 캡슐화가 어려워진다.
따라서, 호출부에서 객체를 만들어 이를 클래스나 메서드에 주입하게 되는데, 이 것을 의존성 주입(DI; Dependency Injection)이라고 한다.
- 아래의 예제에서, Person 객체 jobth는 삼송폰만 살 수 있다. buy() 메소드에서 삼송폰 객체를 직접 생성하여 사용하고 있기 때문이다. 만일 jobth가 사과폰을 사고싶다면, buy() 메소드 안의 객체를 다른 것으로 변경해줘야 한다.
public class Main{
public static void main(String[] args) {
Person jobth = new Person("잡스");
jobth.buy();
}
}
class Person{
```
void buy(){
SamsongPone samsongPone = new SamsongPhone("삼송");
System.out.println(name + "님이" + samsongPone.getName() + "폰을 샀습니다.");
}
}
그렇다면, Person 객체 내부에서 phone을 사용하는 메소드가 여러 개면 어떻게 해야 할까? Phone의 종류가 많다면? Phone 종류별로 메소드를 만들고, 각기 수정해야하는 일이 벌어진다.
class Person{
```
void buySamsong(){
SamsongPone samsongPone = new SamsongPhone("삼송");
System.out.println(name + "님이" + samsongPone.getName() + "폰을 샀습니다.");
}
void buyApple(){
ApplePhone applePhone = new ApplePhone("애플");
System.out.println(name + "님이" + applePhone.getName() + "폰을 샀습니다.");
}
void buyLg(){
LgPhone lgPhone = new LgPhone("엘쥐");
System.out.println(name + "님이" + lgPhone.getName() + "폰을 샀습니다.");
}
}
DI 패턴을 이용하면 위와 같은 일을 방지할 수 있다. 호출부에서 객체를 생성하고 주입하기 때문에, 클래스 내부의 메소드는 변경이 필요 없다.
public class Main{
public static void main(String[] args) {
Person jobth = new Person("잡스");
//삼송 폰 구입
Phone samsong = new SamsongPhone("삼송");
jobth.buy(samsong); //잡스님이 삼송폰을 샀습니다.
//사과 폰 구입
Phone apple = new ApplePhone("사과");
jobth.buy(apple); //잡스님이 사과폰을 샀습니다.
}
}
class Person{
```
void buy(Phone phone){
System.out.println(name + "님이" + phone.getName() + "폰을 샀습니다.");
}
}
자바의 객체지향 프로그래밍은 캡슐화와 정보은닉, 추상화, 상속, 다형성이다. 위의 예제에서 이 다섯가지 특징을 모두 살펴볼 수 있다.
- 캡슐화: 각각의 객체는 서로 독립적이다. 즉, 다른 폰을 사고 싶다고 Person 클래스의 메소드를 변경할 필요가 없다.
예제에는 없지만, Phone은 인터페이스이며, SamsungPhone, ApplePhone, LgPhone이 Phone을 상속받는다는 것을 알 수 있다.
- 추상화: SamsungPhone, ApplePhone, LgPhone의 상위 개념, 즉 추상화된 개념이다.
- 상속: SamsungPhone, ApplePhone, LgPhone은 Phone을 상속받는다.
- 다형성: Phone은 SamsungPhone, ApplePhone, LgPhone의 다양한 형태를 가지며, 각각의 인스턴스를 생성할 수 있다.
- 정보 은닉:
+ 객체의 구체적인 타입 은닉(상위 캐스팅)
: Person의 buy는 SamsungPhone, ApplePhone, LgPhone을 받는 것이 아니라 상위인 Person을 받는다. 또한,
SamsungPhone을 직접 생성하는 것이 아니라 더 상위인 Phone타입으로 생성하고, 실제 객체로 SamsungPhone을
받는다.
+ 객체의 필드 및 메소드 은닉: 캡슐화의 개념
+ 구현 은닉:
만일, SamsungPhone, ApplePhone, LgPhone의 이름을 가져오는 메소드명이 getName()으로 통일된 것이 아니라 myName(), brand(), getBrandName() 등 일관되지 않으면 어떻게 될까? getName()이 브랜드 명이 아니라 핸드폰 주인의 이름을 가져온다면? String이 아닌 다른 타입을 반환한다면? 결국 알맞는 메소드를 각각 불러와야 하고, 메소드명과 반환타입에 또다시 의존할 수밖에 없다. 이를 인터페이스(추상 클래스)를 통해 정의하고 인터페이스를 상속받아 구현하는 방식으로 통일해준다. 추상 클래스나 인터페이스에는 구현부가 없고, 상속받은 자식클래스에서 추상 메소드를 구현하게 된다. 이 것을 구현은닉이라고 한다. 이렇게 되면 Person은 buy()를 할 때 SamsungPhone, ApplePhone, LgPhone 각자의 이름을 가져오는 메소드의 구현부와 반환타입을 알 필요가 없고 부모인 Phone의 getName()만 사용하면 된다.
자바의 핵심은 객체지향이고, '객체 각각이 독립적이며, 의존하지 않는다'에 중점을 두고 있다. 의존성을 떨어뜨려 '수정할 필요가 없게'하며, 궁극적으로 '코드를 재사용'할 수 있게 된다. 객체 지향의 핵심은 결국 수정 최소화와 코드 재사용을 통한 생산성 향상에 있다고 볼 수 있다.
여기서 Spring IoC가 등장한다. IoC란, Inversion of Control, 제어의 역전을 의미한다.
스프링으로 웹 서버를 만든다고 하면 기본적으로 controller, service, repository가 필요하다.
controller는 url요청을 받아 service에게 전달하고, service는 repository로부터 데이터를 받아 핵심 로직을 처리한다.
이 때 controller, service, repository 간 의존성을 주입해줘야 하는데, 원래는 개발자가 직접 생성자에 객체를 전달하거나, configuration class에서 bean을 생성해 의존성을 정의해줬다.
이 과정을 모두 생략하고 controller에는 @Controller, service에는 @Service, repository에는 @Repository 어노테이션만 달게 되는데, 개발자가 직접 객체를 생성하고 의존성을 주입하던 것에서, 스프링이 이 제어권(객체 생성 및 주입)을 가져가 자동으로 객체(bean)를 생성하고 의존성을 주입하게 된다. 이 것을 Spring IoC라고 한다.
Spring IoC를 통해 configuration class를 생성하거나 객체를 받는 생성자를 따로 받을 필요가 없어졌으며, 수십 줄의 코드라인을 어노테이션 하나로 해결하여 생산성을 높일 수 있게 되었다.
관련된 자세한 내용은 추후에 포스팅.
'Language > java' 카테고리의 다른 글
JAVA 14: 새로운 switch문 (0) | 2024.08.19 |
---|---|
자바란: 자바 표준 스펙과 구현, 컴파일과 실행 (0) | 2024.08.13 |
java: Optional (0) | 2023.01.31 |
Java:Lambda 표현식 (0) | 2023.01.16 |
Java: Functional Interface(함수형 인터페이스) (0) | 2023.01.16 |