
package org.example;
import org.example.logic.BubbleSort;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
BubbleSort<String> sort = new BubbleSort<>();
System.out.println("result " + sort.sort(Arrays.asList(args)));
}
}
위 코드는 BubbleSort를 사용하는 Main 함수의 코드입니다. 만약에, 버블 소트를 사용하지 말라는 요구사항이 발생하면 어떻게 해야 할까요? 현재로써는 Main 클래스 내에서 BubbleSort 클래스를 선언하는 부분을 수정해야만 합니다. 이런 경우를
Main 클래스가 BubbleSort 클래스와 강결합 되어있다
라고 표현할 수 있습니다. 최초에, 인터페이스를 작성하고 인터페이스의 구현체를 만드는 방식으로 진행하였다면 문제가 없었겠지만, 이 과정을 학습을 위해 반대로 진행했다고 보면 됩니다.
실무에서는 인터페이스 먼저, 구현체 작성을 나중에 하는 식으로 작업이 필요하겠네요.
최초에 작성한 버블 소트의 구현체는 아래와 같습니다.
package org.example.logic;
import java.util.ArrayList;
import java.util.List;
public class BubbleSort<T extends Comparable<T>> {
public List<T> sort(List<T> list) {
List<T> output = new ArrayList<>(list);
for (int i = output.size() - 1; i > 0; i--) {
for (int j = 0; j < i; j++) {
if (output.get(j).compareTo(output.get(j+1)) > 0) {
T temp = output.get(j);
output.set(j, output.get(j+1));
output.set(j + 1, temp);
}
}
}
return output;
}
}
이제 아래와 같은 인터페이스를 작성하고
package org.example.logic;
import java.util.List;
public interface Sort <T extends Comparable<T>>{
List<T> sort(List<T> list);
}
package org.example.logic;
import java.util.ArrayList;
import java.util.List;
public class BubbleSort<T extends Comparable<T>> implements Sort<T> {
@Override
public List<T> sort(List<T> list) {
List<T> output = new ArrayList<>(list);
for (int i = output.size() - 1; i > 0; i--) {
for (int j = 0; j < i; j++) {
if (output.get(j).compareTo(output.get(j+1)) > 0) {
T temp = output.get(j);
output.set(j, output.get(j+1));
output.set(j + 1, temp);
}
}
}
return output;
}
}
새로 작성한 인터페이스를 BubbleSort가 implements 하도록 코드를 작성했습니다. 마지막으로 Main 클리스의 코드를 수정하면
package org.example;
import org.example.logic.BubbleSort;
import org.example.logic.Sort;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Sort<String> sort = new BubbleSort<>();
System.out.println("result " + sort.sort(Arrays.asList(args)));
}
}
BubbleSort sort = new BubbleSort<>();
라고 작성했던 코드가
Sort sort = new BubbleSort<>();
로 바뀌었네요. 이제 Sort interface를 구현하는 모든 소팅 방법을 사용할 수 있게 되었습니다.
그런데 원래 해결하고자 했던 문제는 여전히 해결되지 못했습니다. 소팅 방식을 바꾸려면 Main 코드를 수정해야 한다는 것입니다. 결합은 느슨하게 했지만 여전히 개선이 필요합니다.
package org.example.service;
import org.example.logic.JavaSort;
import org.example.logic.Sort;
import java.util.List;
public class SortService {
private final Sort<String> sort;
public SortService(Sort<String> sort) {
this.sort = sort;
}
public List<String> doSort(List<String> list) {
return sort.sort(list);
}
}
Main 클래스 내에서 구현체가 바로 사용되는 것을 막기 위해 서비스 클래스를 작성합니다. 서비스 클래스는 구현체를 생성자를 통해 주입받는 방식을 사용하였습니다.
package org.example.service;
import org.example.logic.JavaSort;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class SortServiceTest {
// system under service
private SortService sut = new SortService(new JavaSort<String>());
@Test
void test() {
// given
// when
List<String> actual = sut.doSort(List.of("3", "2", "1"));
// then
assertEquals(List.of("1", "2", "3"), actual);
}
}
작성한 서비스의 테스트 코드를 위와 같이 작성하였습니다. sut를 생성하는 시점에 구현체를 선택할 수 있게 되었습니다.