목차
복잡한 로직을 수행하는 배치 프로그램이 있다고 가정한다.
로직은 매개변수로 넘어온 인자만큼 루프를 돌면서 list의 앞부분에 데이터를 넣는 로직이다.(앞부분인게 중요)
배치프로그램 코드를 점점 개발자들이 리팩토링 했다는 스토리다.
Ver 1: ArrayList 사용의 성능 문제
package collection.test;
import collection.list.MyArrayList;
public class BatchProcessorV1 {
// MyArrayList에 직접 의존(bad)
private final MyArrayList<Integer> list = new MyArrayList<>();
//엄청 복잡한 로직이라고 가정
public void logic(int size) {
long startTime = System.currentTimeMillis();
//업무를 하다보니 리스트의 앞부분에 데이터를 삽입하는 경우가 많아 배열리스트보다 Linkedlist가 더 낫겠다느 판단이 들어서 코드를 고쳐야 겠다고 마음을 먹음
//그래서 코드를 개선하게 되고 그게 BatchProcessorV2가 됨
for (int i = 0; i < size; i++) {
list.add(0, i);
}
long endTime = System.currentTimeMillis();
System.out.println("크기: " + size + ", 계산시간: " + (endTime - startTime) + "ms");
}
}
제일 먼저 MyArrayList를 사용해서 배치프로그램을 실행시킨 코드다.
이렇게 작성한 프로그램을 실제 돌리다 보니 성능이슈가 발생해서 아래와 같이 개선시켰다.
ArrayList는 앞부분에 데이터를 넣기 위해서는 기존 데이터를 한칸씩 뒤로 밀어야 되는 연산이 발생해서 느리다.
1차 리팩토링 (Ver 2: LinkedList로 개선)
package collection.test;
import collection.list.MyLinkedList;
public class BatchProcessorV2 {
// LinkedList로 바꿔서 성능은 많이 개선되었음
// 하지만 이 코드도 여전히 BatchProcessor클래스가 MyLinkedList에 직접적으로 의존하고 있어서 변경사항이 생기면 이 코드를 또 수정해야 함
// 이를 다형성과 제네릭을 이용해서 개선해보겠음
// 그 코드가 BatchProcessorV3임
private final MyLinkedList<Integer> list = new MyLinkedList<>();
//엄청 복잡한 로직이라고 가정
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");
}
}
코드를 1차 리팩토링 해서 성능이슈는 많이 줄어들었지만 여전히 배치 프로그램이 LinkedList라는 클래스를 직접 의존하고 있어서 변경사항이 생기면 배치 프로그램의 코드를 또 수정해야 된다.
고민하고 있던 찰나 재야의 한 시니어 개발자가 다시 코드를 개선하고 유유히 떠났다.
2차 리팩토링 (Ver 3: 인터페이스 도입과 의존성 주입)
package collection.test;
import collection.list.MyList;
public class BatchProcessorV3 {
/*
MyArrayList와 MyLinkedList의 상위 타입인 MyList를 가지고 있고(추상적인 인터페이스에 의존)
생성자를 통해서 외부에서 해당 인스턴스를 주입받음
MyArrayList와 MyLinkedList는 둘 다 MyList를 구현하고 있어서 아래처럼 MyList타입으로 받을 수 있음
MyList = new MyArrayList;
MyList = new MyLinkedList;
그러면 이 코드의 필드 부분은 전혀 고칠게 없고 사용하는 쪽에서 생성자에서 MyArrayList나 MyLinkedList만 만들어서 넣어주면 됨
또 추가로 MyList를 구현하는 다른 자료구조가 필요하면 MyList를 구현한 클래스만 추가해서 외부에서 생성자로 넣어주면 끝난다.
이 코드를 보니 자연스럽게 스프링의 생성자 의존성 주입이 연상되었다.
*/
private final MyList<Integer> list;
public BatchProcessorV3(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");
}
}
MyList
package collection.list;
public interface MyList<E> {
int size();
void add(E e);
void add(int index, E e);
E get(int index);
E set(int index, E e);
E remove(int index);
int indexOf(E e);
}
MyArrayList
package collection.list;
import java.util.Arrays;
public class MyArrayList<E> implements MyList<E> {
private static final int DEFAULT_CAPACITY = 5;
private Object[] elementData;
private int size = 0;
public MyArrayList() {
elementData = new Object[DEFAULT_CAPACITY];
}
public MyArrayList(int initialCapacity) {
elementData = new Object[initialCapacity];
}
@Override
public int size() {
return size;
}
//...생략
}
MyLinkedList
package collection.list;
public class MyLinkedList<E> implements MyList<E> {
private Node<E> first;
private int size = 0;
@Override
public void add(E e) {
Node<E> newNode = new Node<>(e);
if(first == null) {
first = newNode;
} else {
Node<E> lastNode = getLastNode();
lastNode.next = newNode;
}
size++;
}
//...생략
}
부모 타입이 자식 객체를 참조할 수 있는 다형성과 데이터 타입을 컴파일 시점에 지정하는 제네릭을 활용함으로써, 코드는 더 유연하고 확장 가능해졌으며, OCP 원칙을 준수할 수 있게 되었다.
사용하는 코드
package collection.test;
import collection.list.MyArrayList;
import collection.list.MyLinkedList;
import collection.list.MyList;
public class BatchProcessorMain {
public static void main(String[] args) {
//MyArrayList를 사용, 상위타입으로 받음
MyList<Integer> myArrayList = new MyArrayList<>();
BatchProcessorV3 processor1 = new BatchProcessorV3(myArrayList);
//ArrayList는 데이터를 찾는 건 빠르지만 넣고 나서 데이터를 미는 연산이 필요하니까 느리다.
//9만건
processor1.logic(90_000); // 결과: 5969ms
//MyLinkedList를 사용, 상위타입으로 받음
MyList<Integer> myLinkedList = new MyLinkedList<>();
BatchProcessorV3 processor2 = new BatchProcessorV3(myLinkedList);
//LinkedList니까 앞에 데이터를 넣는 경우는 참조값만 변경하면 되고 실제 데이터의 정렬은 없으니까 속도가 ArrayList에 비해서 훨씬 빠르다.
//9만건
processor2.logic(90_000); // 결과: 3ms
}
}
이제 다른 리스트형태의 다른 자료구조를 사용하려면 MyList를 구현한 클래스를 만들고 메서드 오버라이드해서 외부에서 생성자를 통해 인스턴스를 넣어주기만 하면 된다.(배치 프로그램은 전혀 수정할 필요가 없다.)
개인 스터디 기록을 메모하는 공간이라 틀린점이 있을 수 있습니다.
틀린 점 있을 경우 댓글 부탁드립니다.
'IT > development' 카테고리의 다른 글
[jQuery] 체크 이벤트 강제 발생 시키기 (2) | 2025.01.11 |
---|---|
[Node.js] 무한 스크롤 적용 소스 (feat. mobile) (0) | 2025.01.11 |
[java] 직접 구현하는 연결리스트 > 추가, 삭제 기능 (feat. 자료구조) (9) | 2025.01.05 |
[java] LinkedList(연결 리스트) 내 방식대로 이해 (feat. 자료구조) (16) | 2025.01.04 |
[java] generic review (feat. 동영상 촬영) (27) | 2024.12.27 |
댓글