2020. 8. 23. 12:57ㆍ카테고리 없음
1.IoC(Invension of Container) 컨테이너란?
제어의 역행(IoC)은 결합도와 관련된 개념으로 이해할 수 있습니다. 기존에 자바 기반으로 애플리케이션을 개발할 때, 객체를 생성하고 객체들 사이의 의존관계를 처리하는 것에 대한 책임은 전적으로 개발자에게 있었으므로 개발자가 어떤 객체를 생성할지 판단하고 객체 간의 의존관계 역시 소스코드로 표현해야 했습니다.
하지만 제어의 역행이라는 것은 어떤 일련의 작업들을 소스코드로 처리하지 않고 컨테이너로 처리하는 것을 의미합니다. 따라서 제어의 역행을 이용하면 소스에서 객체 생성과 의존관계에 대한 코드가 사라져 결과적으로 낮은 결합도의 컴포넌트를 구현할 수 있게 합니다.
2. 결합도(Coupling)가 높은 프로그램
결합도란 하나의 클래스가 다른 클래스와 얼마나 많이 연결되어 있는지를 나타내는 표현이며 결합도가 높은 프로그램은 유지보수가 어렵습니다. 이 결합도와 유지보수 관계를 이해하기 위해서 간단한 예제를 살펴보겠습니다.
2-1. 프로젝트 src/main/java에 'polymorphism'이라는 패키지를 생성한 뒤 'SamsungTV'클래스를 작성합니다.
package polymorphism;
public class SamsungTV{
public void powerOn() {
System.out.println("SamsungTV---전원 켠다.");
}
public void powerOff() {
System.out.println("SamsungTV---전원 끈다.");
}
public void volumeUp() {
System.out.println("SamsungTV---소리 올린다.");
}
public void volumeDown() {
System.out.println("SamsungTV---소리 내린다.");
}
}
2-1. 프로젝트 src/main/java에 polymorphism 패키지 하위에 'LgTV'클래스를 작성합니다.
package polymorphism;
public class LgTV{
public void turnOn() {
System.out.println("LgTv---전원 켠다.");
}
public void turnOff() {
System.out.println("LgTv---전원 끈다.");
}
public void soundUp() {
System.out.println("LgTv---소리 올린다.");
}
public void soundDown() {
System.out.println("LgTv---소리 내린다.");
}
}
}
LgTv클래스에도 SamsungTv클래스와 같은 기능을 수행하는 메소드가 있지만, SamsungTV의 메소드 이름과 다릅니다. 이 두 TV클래스를 번갈아 사용하는 TVUser 프로그램을 구현해보려고 합니다.
2-3. 프로젝트 src/main/java에 polymorphism 패키지 하위에 'TVUser'클래스를 작성합니다.
package polymorphism;
public class TVUser {
public static void main(String[] args) {
//SamsungTv 호출
SamsungTV tv = new SamsungTV();
tv.powerOn();
tv.volumeUp();
tv.volumeDown();
tv.powerOff();
//LgTv 호출
//LgTV tv = new LgTV();
//tv.turnOn();
//tv.soundUp();
//tv.soundDown();
//tv.turnOff();
}
}
위의 예제는 SamsungTV를 시청하는 프로그램이라고 가정했을 때 SamsungTV객체를 생성해서 메소드를 호출했는데,
만약 LgTV를 시청하는 프로그램으로 수정하고자 한다면 SamsungTV와 LgTV는 메소드 시그니처가 다르므로 위의 주석친 부분과 같이 TVUser코드 대부분을 수정해야 TV를 교체할 수 있습니다. 따라서 TVUser와 같은 클라이언트 프로그램이 하나가 아니라 여러 개라면 유지보수는 더욱더 힘들 것이며, TV교체를 결정하기가 쉽지 않을 것입니다.
3. 결합도를 낮추기 위한 다형성 이용
결합도를 낮추기 위해서 여러 방법들이 있겠지만, 객체지향 언어의 핵심 개념인 다형성(Polymorphism)을 이용하여 위의 예제를 수정해보려고 합니다.
다형성을 이용하려면 상속과 메소드 재정의(Overriding), 그리고 형변환이 필요하며, 자바 같은 객체지향 언어는 이를 문법으로 지원합니다.
3-1. 프로젝트 src/main/java에 polymorphism 패키지 하위에 'TV인터페이스를 작성합니다.
TV클래스들의 최상위 부모로 사용할 TV인터페이스를 추가하고, 모든 TV가 공통으로 가져야 할 메소드들을 추상메소드로 선언해줍니다.
package polymorphism;
public interface TV {
public void powerOn();
public void powerOff();
public void volumeUp();
public void volumeDown();
}
3-2. 이제 기존의 SamsungTV와 LgTV 클래스를 수정하여 방금 추가한 TV 인터페이스를 구현하도록 수정합니다.
package polymorphism;
public class SamsungTV implements TV{
public void powerOn() {
System.out.println("SamsungTV---전원 켠다.");
}
public void powerOff() {
System.out.println("SamsungTV---전원 끈다.");
}
public void volumeUp() {
System.out.println("SamsungTV---소리 올린다.");
}
public void volumeDown() {
System.out.println("SamsungTV---소리 내린다.");
}
}
package polymorphism;
public class LgTV implements TV{
@Override
public void powerOn() {
System.out.println("LgTv---전원 켠다.");
}
@Override
public void powerOff() {
System.out.println("LgTv---전원 끈다.");
}
@Override
public void volumeUp() {
System.out.println("LgTv---소리 올린다.");
}
@Override
public void volumeDown() {
System.out.println("LgTv---소리 내린다.");
}
}
결국, 인터페이스를 이요하여 모든 TV클래스가 같은 메소드들을 가질 수밖에 없도록 강제할 수 있게 되었습니다.
3-3. 이제 이 두 TV를 이용하는 'TVUser'클래스를 수정합니다.
package polymorphism;
public class TVUser {
public static void main(String[] args) {
TV samsungTv = new SamsungTV(); //묵시적 형변환
samsungTv.powerOn();
samsungTv.volumeUp();
samsungTv.volumeDown();
samsungTv.powerOff();
TV lgTv = new LgTV(); //묵시적 형변환
lgTv.powerOn();
lgTv.volumeUp();
lgTv.volumeDown();
lgTv.powerOff();
}
}
TVUser클래스는 TV인터페이스 타입의 변수로 SamsungTV객체를 참조하고 있습니다. 이렇게 묵시적 형변환(Promotion)을 이용하여 객체를 참조하면 SamsungTV를 LgTV객체로 변경할 때 객체만 변경하면 되므로 객체를 쉽게 교체할 수 있습니다. 이렇게 다형성을 이용하면 TVUser와 같은 클라이언트 프로그램이 여러 개 있더라도 최소한의 수정으로 TV를 교체할 수 있습니다.
4. 디자인 페턴 이용
결합도를 낮추기 위한 또 다른 방법으로 디자인 패턴을 이용하는 방법이 있습니다. 앞에서 살펴본 다형성을 이용하는 방법은 메소드를 호출할 때 인터페이스를 이용함으로써 좀 더 쉽게 TV를 교체할 수 있었습니다. 하지만 이 방법 역시 TV를 변경하고자 할 때 TV 클래스 객체를 생성하는 소스를 수정해야 합니다.
TV를 교체할 때 클라이언트 소스를 수정하지 않고 TV를 교체할 수만 있다면 유지보수는 더욱 편리해질 것입니다. 이를 위해 Factory 패턴을 적용해야 하는데, Factory 패턴은 클라이언트에서 사용할 객체 생성을 캡슐화하여 TVUser와 TV사이를 느슨한 결합 상태로 만들어줍니다.
4-1. 다음과 같이 polymorphism 패키지에 Factory패턴이 적용된 BeanFactory클래스를 추가하고 다음 코드를 작성해줍니다.
package polymorphism;
public class BeanFactory {
public Object getBean(String beanName) {
if(beanName.equals("samsung")) {
return new SamsungTV();
} else if(beanName.equals("lg")) {
return new LgTV();
}
return null;
}
}
BeanFactory클래스의 getBean()메소드는 매개변수로 받은 beanName에 해당하는 객체를 생성하여 리턴합니다.
4-2. 이제 이 BeanFactory클래스를 이용하여 사용할 TV객체를 획득하도록 TVUser클래스를 수정합니다.
package polymorphism;
public class TVUser {
public static void main(String[] args) {
//3. 디자인패턴이용(팩토리메서드패턴)
BeanFactory factory = new BeanFactory();
TV tv = (TV)factory.getBean("samsung");
tv.powerOn();
tv.volumeUp();
tv.volumeDown();
tv.powerOff();
}
}
실행되는 TV를 변경하고 싶을 때는 명령형 매개변수만 수정하여 실행합니다.
이런 결과를 얻을 수 있었던 것은 TV객체를 생성하여 리턴하는 BeanFactory때문입니다. 클라이언트에 해당하는 TVUser는 자신이 필요한 객체를 직접 생성하지 않습니다. 만약 그랬다면 TV가 변경될 때마다 소스를 수정해야 했을 것입니다.
참고 : 스프링 퀵 스타트 교재[채규태 지음 / 루비페이퍼]