김지팡의 저장소
Published 2024. 11. 1. 01:10
의존성과 의존성 역전 이해하기 TIL
728x90

 

 

순환 참조 이해해보기

이번 포스팅에서는 순환 참조에 대해서 이야기를 해보려 한다.  🫧 순환 참조 순환 참조는 무엇일까? 서로 다른 두 클래스가 서로를 참조하고 있는 것을 순환 참조라고 한다.코드로 예시를 들

happygimy97.tistory.com

 

바로 이전 포스팅에서 서로 다른 클래스에서 서로를 참조하는 것을 순환 참조라 하고 Spring에서 자체적으로 이를 허용하지 않는다고 했다.

이번 포스팅에서는 순환 참조에서 이야기한 ‘의존’에 대해서 이야기해보고자 한다.

 

 

📌 의존이 뭘까?

 

‘의존’이라는 단어를 떠올리면 ‘능동적이지 않고 누군가에 의해 움직이는 것’이라 할 수 있다. 그리고, 아래와 같이 의존성을 표현할 수 있다.

public class AService {
    private final BService bService;

    public AService(BService bService) {
        this.bService = bService;
    }

    public void methodA() {
        bService.methodB();
    }
}

 

AService 내에 methodA를 보면 bService의 methodB()를 호출하고 있다.

 

즉, AService는 BService가 없으면 안 되는 것이다. 이걸 AService가 BService에 ‘의존’한다고 표현하고, 이 둘은 의존 관계인 것이다.

다른 말로 의존성의 방향이 AService에서 BService로(한쪽으로) 흐른다고도 이야기한다.

 

또한, AService는 BService에게 작업을 수행하도록 명령하기 때문에 AService를 상위 모듈이라 하고, BService는 하위 모듈이라 할 수 있다.

 

 

🧑‍💻

Spring Framework에서 정석적인 구조로 사용되는 Controller > Service > Repository의 구조에서는 실질적인 동작을 수행하는 Repository가 하위 모듈이 된다.

 

 

📌 의존 관계는 유지 보수성에 안 좋을 수 있다.

 

위의 코드에서 AService 내 methodA는 methodB를 호출하는데, methodB의 이름 혹은 파라미터가 변경되면, AService도 변경해야 한다. 이것은 유지보수성을 해친다.

즉, 의존성이 방향이 단방향이어도 유지 보수성이 안 좋을 수 있다는 것의 예시이다.

 

 

📌 순환 참조를 하지 않아도 유지 보수성이 안 좋을 수 있다면 도대체 어떻게 해야 유지 보수성 높은 코드를 작성할까?

 

의존 관계가 유지 보수성을 해치는 경우가 언제인지를 떠올려보면 답을 찾을 수 있을 것이다.

그 경우는 하위 모듈인 BService의 변경이 일어났을 때, 상위 모듈인 AService까지 변경해야 될 때였다.

 

 

📌 그러면, 하위 모듈인 BService의 변경이 일어났을 때, 상위 모듈인 AService가 변경되지 않도록 하면 되는 걸까?

 

틀린 말은 아니다. 상위 모듈이 변경되지만 않는다면 유지 보수성이 좋다 할 수 있기 때문이다.

하지만, 이렇게 되면 확장성을 따졌을 때, 유지 보수성을 해치게 되는 원인을 제공할 수 있다. 서비스의 확장으로 로직이 변경되면 결국 상위 모듈을 변경해야 되는 때가 올 수 있기 때문이다.

그래서 좀 더 근본적인 원인으로 들어가서 상위 모듈이 하위 모듈에 의존하지 않도록 하는 것을 생각해 볼 수 있다.

이렇게 되면 하위 모듈이 어떻게 바뀌든 상위 모듈을 건드릴 필요가 없어져 유지 보수성이 높아진다.

이를 의존성 역전이라고 하는데, 의존성 역전은 상위 모듈이 하위 모듈을 의존하지 않도록 하는 것이다.

엄연히 말하자면 역전 관계를 끊는 것이지만 의존 방향을 역전한다라고 생각하면 이해하는 데에 도움이 될 것 같기도 하다.

 

 

📌 의존성 역전

 

그렇지만, 의존성 역전은 단순히 상위 모듈이 하위 모듈을 의존하는 것을 끊어내는 것을 이야기하지 않는다. 하위 모듈이던 클래스를 인터페이스로 바꾸고, 구현체를 하나 만드는 것이다. 이렇게 되면 하위 모듈에 변경 사항이 생겨도 상위 모듈을 변경하지 않아도 된다.

 

말로는 이해하기가 어려울 수 있으니 의존성 역전이 되지 않은 코드와 된 코드를 비교해 보며 이야기해 보자.

// 의존성 역전을 하지 않은 코드
public class VehicleFactory {
    private final VehicleProducer vehicleProducer;

    public VehicleFactory(VehicleProducer vehicleProducer) {
        this.vehicleProducer = vehicleProducer;
    }

    public void produceCar() {
        vehicleProducer.producingCar();
    }
}

public class VehicleProducer {

    public void producingCar() {
        // 로직 정의
    }
}


// 의존성 역전을 한 코드
public class VehicleFactory {
    private final VehicleProducer vehicleProducer;

    public VehicleFactory(VehicleProducer vehicleProducer) {
        this.vehicleProducer = vehicleProducer;
    }

    public void produceCar() {
        vehicleProducer.producingVehicle();
    }
}

public interface VehicleProducer {
    void producingVehicle();
}

public class VehicleProducerImpl implements VehicleProducer {
    @Override
    public void producingVehicle() {
        // 로직 정의
    }
}

 

📌 의존성 역전이 된 코드와 그렇지 않은 코드를 비교해 보자.


의존성 역전이 되지 않은 코드. 즉, 상위 모듈이 하위 모듈에 의존하는 경우에는 하위 모듈에 변경이 생기면 상위 모듈까지 변경을 해야 된다.

 

하지만, 의존성 역전이 된 코드는 상위 모듈인 VehicleFactory에서 인터페이스를 참조하고 있기 때문에 VehicleProducer 인터페이스의 구현체가 바뀌어도 VehicleFactory의 코드는 수정을 하지 않아도 된다.

 

어차피 VehicleProducer 인터페이스를 구현하는 구현체는 어느 것이 됐던 producingVehicle 메서드를 구현해야만 하기 때문이다.

 

이렇게 되면 유지 보수성이 높아진다.

 

 

📌 Spring에서 알아서 해주는 의존성 주입

위 코드를 보면, VehicleFactory 클래스 내에서 VehicleProducer의 구현체인 VehicleProducerImpl 클래스의 메서드를 사용하는 것이 아니라 vehicleProducer.producingVehicle()를 호출하고 있다. 

 

VehicleProducer는 인터페이스이고, 해당 인터페이스의 producingVehicle() 메서드를 선언만 하고 구현부가 없는데, 실행한 결과값은 VehicleProducer의 구현체인 VehicleProducerImpl 클래스의 메서드를 실행한 결과가 나온다.

 

이 이유는 Spring의 DI 컨테이너가 인터페이스의 구현체를 자동으로 주입해주기 때문이다.

 

반대로, 순수 자바에서는 Spring의 DI 컨테이너처럼 구현체를 자동으로 주입해주지 않는다. new 키워드를 통해 직접 객체를 생성해야 되는데, List<Integer> arr = new ArrayList<>(); 혹은 Set<Integer> arr = new HashSet<>(); 같은 것들이 그 예시이다. List와 Set 인터페이스의 구현체인 ArrayList와 HashSet 객체를 생성함으로써 해당 기능들을 사용할 수 있는 것이다.

 

 

📌 마무리

 

의존성과 의존성 역전에 대해서 이해해 보는 시간을 가졌다. 생각보다 글이 길어졌지만, 그만큼 꽤나 좋은 공부를 했다는 생각이 들었던 시간이었다.

 

상위 모듈이 하위 모듈에 의존하는 관계에서 유지 보수성을 해치는 행위를 개선하고자 의존성 역전을 했다. 하지만, 의존성 역전을 하고도 상위 모듈의 코드를 수정해야 될 때도 있을 것이다.

 

인터페이스 내 메서드의 이름과 파라미터가 바뀌면 상위 모듈의 코드도 변경이 불가피해지기 때문이다.

 

반대로, 상위 모듈이 하위 모듈에 의존하는 관계에서도 상위 모듈에서 호출되는 하위 모듈의 메서드의 이름과 파라미터가 절대 변하지 않는다면, 유지 보수성이 높다고 말할 수 있을 것이다.

 

하지만, 그럼에도 불구하고 의존성 역전에 대해 고민해 보는 것은 그런 상황에 대한 대비를 위함인 것 같다.

 

서비스가 커지고 코드의 복잡성이 걷잡을 수 없이 높아지면 유지 보수성에 문제가 될 요소들이 분명 생기게 될 것이다. 물론, 난 그런 서비스를 만들어 본 적도 없지만 그걸 대비한 학습 과정이니깐 😆

728x90
profile

김지팡의 저장소

@김지팡

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!