티스토리 뷰

하나 이상의 자원에 의존하는 클래스를 만들때는 주의할 점이 있는데요, 예시로 간단하게 아래와 같은 코드를 작성해봤습니다.

public class Delivery {
    private String type;
    private String area;

    public Delivery(String type, String area) {
        this.type = type;
        this.area = area;
    }

    public String getType() {
        return type;
    }

    public String getArea() {
        return area;
    }
}

배달과 관련된 class를 작성해봤습니다. 필드로는 어떤 type의 음식을 판매하는지 나타내고 싶었고, area는 어떤 지역에 있는지 나타내고 싶었습니다.

public class DeliveryChecker {
    private static final List<Delivery> deliveryList = List.of(new Delivery("햄버거", "부산"), new Delivery("치킨", "제주"));

    private DeliveryChecker() {
    }

    public static boolean isValid(Delivery delivery) {
        return deliveryList.contains(delivery);
    }

    public static List<Delivery> findArea(String area) {
        return deliveryList.stream()
                .filter(delivery -> delivery.getArea().equals(area))
                .collect(toList());
    }
}

위와같은 정적 유틸리티 클래스를 만들수 있습니다. 
Delivery 배열 자원을 가지고 있고, 외부에서 Delivery가 존재하는지 확인 및 area에 따라 부분 Delivery를 넘겨주는 메서드를 구현했습니다. 

위의 코드와 비슷하게 싱글톤으로 구현하는 것도 어렵지 않습니다.

public class DeliveryChecker {
    private final List<Delivery> deliveryList = List.of(new Delivery("햄버거", "부산"), new Delivery("치킨", "제주"));

    private DeliveryChecker() {
    }
    
    public static DeliveryChecker INSTANCE = new DeliveryChecker();


    public boolean isValid(Delivery delivery) {
        return deliveryList.contains(delivery);
    }

    public List<Delivery> findArea(String area) {
        return deliveryList.stream()
                .filter(delivery -> delivery.getArea().equals(area))
                .collect(toList());
    }
}

위의 두가지 방식 모두 좋은 방법은 아닌것처럼 보입니다. 만약 우리의 서비스가 너무 커져서 영어 버전의 배달 리스트를 만들어야 한다고 합니다. 그러면 기존에 만든 배열만으로는 모두 대응할 수가 없기에 새로운 배열을 만들어야하죠.

위의 싱글톤 코드를 이용해서 DeliveryChecker가 여러 버전의 List를 사용하고 싶다면 어떻게 해야할까요? 가장 단순하게 final을 지우고 setter를 넣는 방법을 생각 해볼 수 있습니다.

public class DeliveryChecker {
    private List<Delivery> deliveryList = List.of(new Delivery("burger", "Busan"), new Delivery("chicken", "Jeju"));

    private DeliveryChecker() {
    }

    public static DeliveryChecker INSTANCE = new DeliveryChecker();


    public boolean isValid(Delivery delivery) {
        return deliveryList.contains(delivery);
    }

    public List<Delivery> findArea(String area) {
        return deliveryList.stream()
                .filter(delivery -> delivery.getArea().equals(area))
                .collect(toList());
    }

    public void setDeliveryList(List<Delivery> deliveryList) {
        this.deliveryList = deliveryList;
    }
}

이제 멀티 스레드 환경에서 돌아가는 우리의 서비스는 엄청난 버그를 만납니다.

Thread 1이 수행중인데, Thread 2가 배열을 변경해버렸어요. setter를 열어놓으면 Thread 1이 동작하는 도중 공유 자원이 변경되는 이러한 문제가 발생할 수 있습니다.

결론은 이렇게 사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글톤 방식이 적합하지 않다는 것입니다.
대신 클래스가 여러 자원 인스턴스를 지원해야 하며, 클라이언트가 원하는 자원을 사용해야 합니다. 

이 조건을 만족시킬 수 있는 아주 간단한 패턴이 있는데, 바로 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식입니다.
어디서 본것 같지 않나요? 바로 DI(Dependency Injection) 입니다. DI를 적용해 DeliveryList를 주입하도록 코드를 변경하면 아래와 같습니다.

public class DeliveryChecker {
    private final List<Delivery> deliveryList;

    public DeliveryChecker(List<Delivery> deliveryList) {
        this.deliveryList = deliveryList;
    }

    public boolean isValid(Delivery delivery) {
        return deliveryList.contains(delivery);
    }

    public List<Delivery> findArea(String area) {
        return deliveryList.stream()
                .filter(delivery -> delivery.getArea().equals(area))
                .collect(toList());
    }
}

이 예시에서는 deliveryList라는 딱 하나의 자원만 사용하지만, DI를 사용하면 자원이 몇개든 상관없이 잘 동작합니다. 또한 자원이 불변이기 때문에 여러 클라이언트가 의존 객체들을 안심하고 공유할 수도 있습니다.

만약 클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 동작에 영향을 준다면 싱글톤과 정적 유틸리티 클래스는 사용하지 않는 것이 좋습니다. 대신 필요한 자원을 주입하는 방식으로 생성자에 넘겨주는 방식을 사용하는면 유연성, 재사용성, 테스트 용이성을 개선해줄 수 있습니다.

Ref : 이펙티브 자바 Effective Java 3/E

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함