의존성 주입과 객체 지향 원칙

namkun·2022년 3월 6일
0

Spring

목록 보기
3/18

해당 내용은 '스프링 입문을 위한 자바 객체 지향의 원리와 이해'와 인프런 김영한님의 '스프링 핵심 원리 - 기본편' 강의를 참고하였습니다.


이전 글에서 객체 지향 설계 원칙의 함정이라는 내용을 썼었다.

그 글의 마지막에 스프링에서는 DI를 통해서 다형성, 그리고 OCP, DIP를 가능하게 하였다고 했는데 우리는 그에 대한 코드를 보며 확인해보자.

이해를 위해 간단한 코드를 작성하였다.

빠른 예시 작성을 위해 설계는...그말싫 :3

GasolineOil.java

public class GasolineOil implements FuelTank{

    @Override
    public Energy getEnergy() {
        String type = "Gasoline";
        Energy energy = new Energy();
        energy.setEnergy(type);
        return energy;
    }
}

ElectricBattery.java

public class ElectricBattery implements FuelTank {

    @Override
    public Energy getEnergy() {
        String type = "eletric";
        Energy energy = new Energy();
        energy.setEnergy(type);
        return energy;
    }
}

CarImpl.java

public class CarImpl implements Car{

//    private final FuelTank fuelTank = new GasolineOil();
    private final FuelTank fuelTank = new ElectricBattery();

    @Override
    public Energy runEngine() {
        return fuelTank.getEnergy();
    }
}

위의 코드에서 구현 객체를 변경하려면 클라이언트 코드를 변경해야한다. (Gasoline -> ElectricBattery)

그렇기에 이는 소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경에는 닫혀있어야 한다 라는 OCP 원칙을 위반하는 코드라고 할 수 있다.

또한 위의 코드에서 CarImpl는 CarFuelTank 라는 추상화 클래스에 의존하는 것 뿐아니라, ElectricBattrey라는 구체화 클래스에도 동시에 의존하고 있다.

즉, CarImpl 클라이언트는 구현 클래스를 직접 선택(의존)하고 있다고 할 수 있다.

이는 프로그래머는 추상화에 의존해야한다.는 DIP 원칙에도 위반된다고 할 수 있다.

위의 코드에 대해 우리는 의존성 주입을 통해 문제점을 해결할 것이다.

아래와 같은 파일을 하나 만들어보자.

ApplicationConfig.java

public class ApplicationConfig {
    public Car carFuelTank() {
        return new CarImpl(getFuelTank());
    }

    public FuelTank getFuelTank() {
        return new GasolineOil();
//        return new ElectricBattery();
    }
}

위의 코드를 통해서 우리는 이제 구현 객체를 생성하고, 연결하도록 위의 코드에 책임을 쥐어줄 것이다.

또한 이제 위처럼 ApplicationConfig의 생성에 따라 위의 CarImpl 클래스의 코드는 다음과 같이 고쳐져야 할 것이다.

CarImpl.java

public class CarImpl implements Car {
    //    private final FuelTank fuelTank = new GasolineOil();
    //    private final FuelTank fuelTank = new ElectricBattery();

    private final FuelTank fuelTank;

    public CarImpl(FuelTank fuelTank) {
        this.fuelTank = fuelTank;
    }

	@Override
    public Energy runEngine() {
        return fuelTank.getEnergy();
    }
}

그 전 코드에서 구현객체의 생성에 대한 책임, 그리고 메서드를 실행하는 책임이라는 2가지 책임을 떠맡았던 CarImpl 클라이언트는 이제 한 가지 책임만 갖게 되었고, 이는 곧 한 클래스는 하나의 책임만 가져야한다 라는 SRP 원칙에 맞는 객체지향 설계라는 것을 알 수 있다.

자, 이제 저 생성자를 통해서 들어오게 되어있는 FuelTank는 어디서 들어올까?

위의 ApplicationConfg 코드를 보면 getFuelTank() 메서드를 통해서 생성된 객체를 주입받는 것을 알 수 있다.

자, 이제 앱을 실행하는 부분을 보자.

CarApp.java

public class CarApp {
    public static void main(String[] args) {
        // DI 도입
        ApplicationConfig applicationConfig = new ApplicationConfig();
        Car car = applicationConfig.carFuelTank();
        System.out.println("energy = " + car.runEngine().getEnergy());
    }
}

위의 코드처럼 ApplicationConfig 객체를 생성해서 이를 통해 의존성들을 주입받는다.

우리는 아까 위에서 가솔린 오일을 연료로 쓰기로 하였으니 결과는 다음과 같이 나올 것이다.

energy = Gasoline

이제 연료를 바꾸려면 어떻게 해야할까?

우리는 이제 맨 처음처럼 OCP와 DIP를 위반하지 않으면서 바꿀 수 있다.

이제 CarImpl는 처음의 코드처럼 구체화 클래스인 GasolineOil또는 ElectricBattery 에 의존하지 않는다. 이제는 오직 FuelTank라는 추상화 클래스에만 의존한다.

이제 ApplicationConfg가 GasolineOil 객체 인스턴스를 클라이언트 코드 대신에 클라이언트 코드에 의존 관계를 주입해준다!

이제 프로그래머는 추상화에 의존해야한다.라는 DIP 원칙이 적용되었음을 알 수 있다.

또한 연료를 변경할 때, ApplicationConfig가 의존관계를 GasolineOil 에서 ElectricBattery 로 변경해서 CarImpl 클라이언트 코드에 주입하기에, 클라이언트 코드는 변경하지 않아도 된다.

이를 통해서 소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경에는 닫혀있어야 한다 는 OCP 의 원칙에도 새롭게 변경한 코드가 적용된다는 것을 알 수 있다.

이를 통해서 의존성 주입을 통해서 우리는 코드를 작성할 때 좋은 객체지향 설계를 위한 원칙을 적용할 수 있었다!

profile
개발하는 중국학과 사람

0개의 댓글