Programming/Java

[Effective Java] item5 - 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라.

VSFe 2021. 1. 28. 17:28
자원을 직접 명시하지 말고 의존 객체 주입을 사용하라!

전국 피자집의 목록을 받아, 특정 지역에 있는 가게만 출력하는 클래스를 설계한다고 하자.

딱히 객체가 필요하진 않을 것 같고, 메소드만 적절히 활용하는 유틸리티 클래스를 만들어보자.

import java.util.List;

public class RegionFoods {
    private static final Food pizza = new Food();

    private RegionFoods() {} // 객체 생성 방지

    public static boolean isRegion(String food) {}
    public static List<String> regionList() {}
}

만약에 치킨집도 받고, 중국집도 받고 그래야 한다면 어떡할까? 당장 지금 생성한 클래스는 Food 객체를 단 하나만 쓰고 있고 고정되어 있기 때문에 다양한 방법으로 활용하기엔 조금 무리가 있다.

그럼 이런건 어떨까?

import java.util.List;

public class RegionFoods {
    private static Food food;

    private RegionFoods() {} // 객체 생성 방지

    public static boolean isRegion(String food) {}
    public static List<String> regionList() {}
    public static void changeFood(Food food) {}
}

일단 되긴 되는데, 좀 이상하다. 그리고 멀티스레드 환경이 되는 순간... 어음...

즉, 사용하는 자원에 따라 동작이 달라지는 클래스는 정적 유틸리티 클래스나 싱글턴 방식으로 하지 않는 것이 좋다.

그럼 뭘 어떤식으로 하면 좋을까? 의존성 주입 패턴 (Dependency Injection Pattern) 이라고 불리는 것이 바로 그 해답이 될 수 있다.

사실 스터디원들은 다 스프링에 대한 기반 지식이 꽤 탄탄한 편이라 이 부분에 대해 잘 알고 있지만, 난 스프링을 공부한지 얼마 되지 않았기에 작정하고 깊~~~게 써보려고 한다.

일단 의존성이 뭘까?

public class Dependency {
    public SubClass sc;

    public Dependency() {
        this.sc = new SubClass();
        this.sc.sayHello();
    }
}

class SubClass {}

이런식으로 특정 클래스를 내부에 변수로 사용하게 될 경우, Dependency 클래스는 SubClass에 의존성이 있다고 할 수 있다.

만약에 SubClass의 구성을 바꿀일이 생긴다면, 당연히 Dependency 클래스도 바꿔야 할 것이고, 또 비슷한 구성인데 SubClass가 아닌 다른 클래스를 받아서 사용하는 경우라면 또 별도의 클래스로 분리해서 작업해야 하니 매우 매우 매우 매우 비효율적이다...

음식을 만들어보자! 하지만 우리는 음식을 만들 능력이 부족하고, 시간도 없으니 그냥 요리 키트를 사서 뜯어서 만들기로 했다.

import java.util.List;

public interface Foodkit {
    public void openKit();
    public List<String> pickUpMenu();
    public Boolean makeFood();
}

우리가 할 것은 별거 없다. 포장지를 뜯고, 재료를 꺼내고, 조리법대로 음식을 만들 것이다. 그런데 우리가 음식을 한 종류만 만들게 아니라, 여러 종류를 만들거니 각각의 클래스를 생성하도록 하자.

public class Steak implements Foodkit {
    /* Some Code */
}
public class Pasta implements Foodkit {
    /* Some Code */
}
public class SixTimes implements Foodkit {
    /* Some Code */
}

마지막은 육회다. 아무튼 육회다.

public class FoodMaker {
    private Foodkit food;
    public FoodMaker() {
        this.food = new Steak();
        // or
        this.food = new Pasta();
    }
}

이런식으로 일일히 만들고 앉아 있으면 굉장히 괴로워진다... 만약 만들어야 할 사람이 뷔폐 조리사면 100종류의 음식에 대해 별개의 100개의 클래스를 생성해야 하는데, 이건 정말...

그래서 코드를 살짝 바꿔보았다!

public class FoodMaker {
    private Foodkit food;
    public FoodMaker(Foodkit food) {
        this.food = food;
        this.food.openKit();
        // Some Code...
    }
}

자! 직접 의존성이 있는 객체를 생성하는게 아니라, 생성 과정에서 의존성이 필요한 객체를 주입함으로써 코드를 유연하게 짤 수 있다!

사실 이렇게만 보면 '읭? 별 차이 없는데요?' 라고 생각할 수 있는데, 재사용성 측면에서 엄청나게 좋아졌고, 의존 관계의 클래스가 수정되어도 다른 클래스의 구현이 바뀔 필요가 없다는 장점은 부정할 수 없다. 우리는 이것을 의존성 주입이라고 부른다.

생성자에 자원 팩토리 메소드를 넘겨주는 방법도 있다. 우리는 이것을 "팩토리 메소드 패턴" 이라고 부른다.

물론 의존성 주입이 좋은 패턴이긴 한데, 규모가 커지면 코드가 복잡해지는 단점이 있다. 하지만 우리는 프레임워크를 통해 이 문제를 해결한다! (스프링 공부 빨리 좀 하자..)