알고리즘
컬렉션을 클래스로 한번 더 감싼 것
장점
1. 비즈니스 로직이 관련 도메인에 캡슐화된다.
2. 불변성이 유지된다.
3. 코드 중복이 최소화된다.
4. 테스트하기에 용이해진다.
예
import java.util.Iterator;
import java.util.ArrayList;
public class CustomCollection<T> implements Iterable<T> {
private ArrayList<T> items = new ArrayList<>();
public void add(T item) {
items.add(item);
}
@Override
public Iterator<T> iterator() {
return items.iterator();
}
}
// 사용 예시
public class Main {
public static void main(String[] args) {
CustomCollection<String> customCollection = new CustomCollection<>();
customCollection.add("item1");
customCollection.add("item2");
customCollection.add("item3");
// foreach 사용
for (String item : customCollection) {
System.out.println(item);
}
}
}
클래스에 접근 제어자를 붙이면 테스트에서 각 메소드들을 테스트하기가 쉽지 않다.
이럴땐 리플렉션으로 메소드에 접근할 수 있다.
public class TestUtil {
private static final Map<Class<?>, Class<?>> wrapperToPrimitiveMap = new HashMap<>();
private static final Map<Class<?>, Class<?>> primitiveToWrapperMap = new HashMap<>();
static {
wrapperToPrimitiveMap.put(Integer.class, int.class);
wrapperToPrimitiveMap.put(Long.class, long.class);
wrapperToPrimitiveMap.put(Double.class, double.class);
wrapperToPrimitiveMap.put(Float.class, float.class);
wrapperToPrimitiveMap.put(Boolean.class, boolean.class);
wrapperToPrimitiveMap.put(Character.class, char.class);
wrapperToPrimitiveMap.put(Byte.class, byte.class);
wrapperToPrimitiveMap.put(Short.class, short.class);
primitiveToWrapperMap.put(int.class, Integer.class);
primitiveToWrapperMap.put(long.class, Long.class);
primitiveToWrapperMap.put(double.class, Double.class);
primitiveToWrapperMap.put(float.class, Float.class);
primitiveToWrapperMap.put(boolean.class, Boolean.class);
primitiveToWrapperMap.put(char.class, Character.class);
primitiveToWrapperMap.put(byte.class, Byte.class);
primitiveToWrapperMap.put(short.class, Short.class);
}
public <T> T run(Object target, String methodName, Object... params) throws Exception {
Class<?>[] paramTypes = Arrays.stream(params)
.map(Object::getClass)
.toArray(Class[]::new);
List<Class<?>[]> possibleParamTypes = generatePossibleParamTypes(paramTypes);
for (Class<?>[] possibleParamType : possibleParamTypes) {
Optional<Method> methodOptional = findMethod(target.getClass(), methodName, possibleParamType);
if (methodOptional.isPresent()) {
Method method = methodOptional.get();
method.setAccessible(true);
return (T) method.invoke(target, params);
}
}
throw new NoSuchMethodException(
"No such method found: " + methodName + " with parameter types " + Arrays.toString(paramTypes));
}
private Optional<Method> findMethod(Class<?> clazz, String methodName, Class<?>[] paramTypes) {
try {
return Optional.of(clazz.getDeclaredMethod(methodName, paramTypes));
} catch (NoSuchMethodException e) {
return Optional.empty();
}
}
private List<Class<?>[]> generatePossibleParamTypes(Class<?>[] paramTypes) {
List<Class<?>[]> possibleParamTypesList = new ArrayList<>();
generatePossibleParamTypes(paramTypes, 0, possibleParamTypesList);
return possibleParamTypesList;
}
private void generatePossibleParamTypes(Class<?>[] paramTypes, int index, List<Class<?>[]> possibleParamTypesList) {
if (index == paramTypes.length) {
possibleParamTypesList.add(paramTypes.clone());
return;
}
Class<?> originalType = paramTypes[index];
Class<?> primitiveType = wrapperToPrimitiveMap.get(originalType);
Class<?> wrapperType = primitiveToWrapperMap.get(originalType);
// 일반 오브젝트임
if (primitiveType == null && wrapperType == null) {
generatePossibleParamTypes(paramTypes, index + 1, possibleParamTypesList);
}
// 래퍼 혹은 기본 클래스임
else{
if (primitiveType != null) { // 래퍼클래스임
paramTypes[index] = primitiveType;
} else { // 기본클래스임
paramTypes[index] = wrapperType;
}
generatePossibleParamTypes(paramTypes, index + 1, possibleParamTypesList);
paramTypes[index] = originalType;
generatePossibleParamTypes(paramTypes, index + 1, possibleParamTypesList);
}
}
}
리턴타입 제네릭을 이용하면 호출 후에 형변환을 할 필요가 없어진다.
기본자료형까지 모두 지원하려면 조금 까다로워진다.
getDeclaredMethod
메소드는 이름과 파라미터 타입들이 매칭하는 메소드를 찾는다.
근데 얘가 유도리가 없어서 래퍼클래스와 기본자료형을 철저하게 구분한다.
private int method(Integer num1, int num2);
위와 같은 메소드가 있을 때 getDeclaredMethod
가 찾으려는 파라미터 타입이
{int,int}
이면 NoSuchMethodException
이 발생한다.
결국 완전 탐색으로 래퍼클래스와 기본자료형인 경우를 모두 찾기로 했다.
어차피 함수 인자로 4개 이상이 들어가는 경우는 흔하지도 않아서 큰 부담은 없다.