List - 리스트 추상화2 - 의존관계 주입

2024. 5. 22. 10:21김영한 Java/컬렉션 프레임워크

MyArrayList를 활용해서 많은 데이터를 처리하는 BatchProcessor 클래스를 개발하고 있다고 가정하자. 그런데 막상 프로그램을 개발하고 보니 데이터를 앞에서 추가하는 이링 많은 상황이라고 가정해보자. 데이터를 앞에서 추가하거나 삭제하는 일이 많다면 MyArrayList 보다는 MyLinkedList를 사용하는 것이 훨씬 효율적이다.

 

데이터를 앞에서 추가하거나 삭제할 때 빅오 비교

  • MyArrayList : O(n)
  • MyLinkedList : O(1)

 

다음 예시 코드를 보자.

 

public class BatchProcessor{
     private final MyLinkedList<Integer> list = new MyLinkedList<>(); //코드 변경
    public void logic(int size) {
     for (int i = 0; i < size; i++) {
                list.add(0, i); //앞에 추가
            }
        }
 	
}

 

MyArrayList를 사용해보니 성능이 너무 느려서 MyLinkedList를 사용하도록 코드를 변경해야 한다면 BatchProcessor의 내부 코드도 MyArrayList에서 MyLinkedList를 사용하도록 함께 변경해야 한다.

 

BatchProcessor는 구체적인 MyArrayList 또는 MyLinkedList를 사용하고 있다. 이것은 BatchProcessor가 구체적인 클래스에 의존한다고 표현한다. 이렇게 구체적인 클래스에 직접 의존하면 MyArrayList를 MyLinkedList로 변경할 때 마다 여기에 의존하는 BatchProcessor의 코드도 함께 수정해야 한다.

 

BatchProcessor가 구체적인 클래스에 의존하는 대신 추상적인 MyList 인터페이스에 의존하는 방법도 있다.

 

추성적인 MyList에 의존하는 BatchProcessor 예시

package collection.list;

public class BatchProcessor {
    private final MyList<Integer> list;


    //MyList = new MyArrayList
    //MyList = new MyLinkedList
    public BatchProcessor(MyList<Integer> list) {
        this.list = list;
    }

    public void logic(int size){
        for (int i = 0; i < size; i++) {
            list.add(0,i);
        }
    }
}
main(){
	new BatchProcessor(new MyArrayList());
    new BatchProcessor(new MyLinkedList());
}

그리고 BatchProcessor를 생성하는 시점에 생성자를 통해 원하는 리스트 전략(알고리즘)을 선택해서 전달하면 된다.

이렇게 하면 MyList를 사용하는 클라이언트 코드인 BatchProcessor를 전혀 변경하지 않고, 원하는 리스트 전략을 런타임에 지정할 수 있다.

 

정리하면 다형성과 추상화를 활용하면 BatchProcessor코드를 전혀 변경하지 않고 리스트 전략(알고리즘)을 MyArrayList에서 MyLinkedList로 변경할 수 있다.

 

package collection.list;

public class BatchProcessor {
    private final MyList<Integer> list;


    //MyList = new MyArrayList
    //MyList = new MyLinkedList
    public BatchProcessor(MyList<Integer> list) {
        this.list = list;
    }

    public void logic(int size){
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            list.add(0,i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("크기 : " + size + ",계산 시간 : " + (endTime-startTime)+"ms");
        
    }
}
  • logic(int size) 메서드는 매우 복잡한 비즈니스 로직을 다룬다고 가정하자. 이 메서드는 list의 앞 부분에 데이터를 추가한다.
  • 어떤 MyList list의 구현체를 선택할 지는 실햄 시점에 생성자를 통해서 결정한다.
  • 생성자를 통해서 MyList 구현체가 전달된다.
    • MyArrayList의 인스턴스가 들어올 수도 있고, MyLinkedList의 인스턴스가 들어올 수도 있다.
  • 이것은 BatchProcessor의 외부에서 의존관계가 결정되어서 BatchProcessor 인스턴스에 들어오는 것 같다. 마치 의존관계가 외부에서 주입되는 것 같다고 해서 이것을 의존관계 주입이라 한다.
  • 참고로 생성자를 통해서 의존관계를 주입했기 때문에 생성자 의존관계 주입이라 한다.

 

의존관계 주입

package collection.list;

public class BatchProcessorMain {
    public static void main(String[] args) {
        MyLinkedList<Integer> list = new MyLinkedList<>();

        BatchProcessor processor = new BatchProcessor(list);
        processor.logic(50000);
    }
}