티스토리 뷰

객체지향의 핵심은 캡상추다로 요약된다고 말한 적이 있다 

캡슐화로 내부 구현을 감추어 외부에서 내부 구현에 얽매이지 않게 하고

추상화를 통해 OCP를 준수해 변경에는 닫혀있고 확장에는 열린 포인트를 만들어준다

sub-class가 아닌 sub-typing을 통해 타입 계층을 만들어 전략 패턴 사용이 가능하게 하여 다형성을 이루어낸다

 

결국 객체지향의 최종 목표는 재사용성 향상과 유지보수성 향상을 위함이다

객체지향의 목표를 이루려면 클래스 설계, 컴포넌트 설계뿐만 아니라 메서드 단위에서의 재사용성도 중요하다

쉽게 말해 범용적인 메서드를 만들어야 여러 곳에서 활용이 가능하다는 말이다

 

https://www.tutorialspoint.com/java/java_generics.htm

 

정렬 메서드나 컬렉션 두 개를 합쳐주는 메서드를 사용할 때 하나의 메서드로 여러 곳에서 활용할 수 있다면 편리할 것이다

이런 상황에서 범용적인 메서드 작성을 위해 필수적인 것이 제네릭이다, 아래의 두 메서드는 모두 범용적으로 사용 가능하다

첫번째 raw-type union 메서드는 타입 안정성이 보장되지 않는다

두 번째 Generics를 활용한 메서드는 그와 반대로 타입 안정성을 보장한다

 

public static Set union(Set s1, Set s2) {
  Set result = new HashSet(s1);
  result.add(s2);
  return result;
}

// Generics 활용
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
  Set<E> result = new HashSet<>(s1);
  result.add(s2);
  return result;
}

 

메서드 인자와 반환 타입에 타입을 지정해야 하고 하나 더 신경 써야 하는 것이

접근 제한자와 반환 타입 사이에 타입 매개변수를 반드시 지정해줘야 한다는 점이다

책이나 문서로만 보면 이해가 어려울 수 있으나 직접 작성하다 보면 익숙해질 것이다

왜냐? 타입 매개변수를 지정하지 않으면 빨간줄 대잔치가 열리기 때문..

 

아래는 개인 프로젝트에서 JPA를 활용한 페이징 구현 시 동적 정렬을 위한 메서드를 작성한 것이다

Pageable로 넘어온 Sort 조건을 활용해 동적 정렬을 하는 유틸성 메서드인데

Sort, Path<?>를 넘겨 OrderSpecifier를 반환해준다

Path parent에는 QueryDSL 사용 시에 생성되는 QInstance의 타입을 넘겨주면 된다

@SuppressWarnings({"rawtypes", "unchecked", "cast"})
public static OrderSpecifier<?>[] getSortedColumn(Sort sorts, Path<?> parent) {
  return sorts.stream()
      .map(sort -> {
        Order direction = sort.getDirection().isAscending() ? Order.ASC : Order.DESC;
        SimplePath<Object> fieldPath = Expressions.path(Object.class, parent, sort.getProperty());
        return new OrderSpecifier(direction, fieldPath);
      })
      .toArray(OrderSpecifier[]::new);
}

 

반환하려는 OrderSpecifier의 타입은 Path가 가진 필드의 타입을 따라가기 때문에 <?>로 표현되었고

Expressions.path()에서 Object.class로 받은 것이 범용적으로 작성하기 위해 타입 안전성을 포기한 부분이다

정렬을 할 때, 몇 가지 기준을 두어 문자열, 숫자만 받도록 할 수도 있지만

확장성을 고려하여 작성한 것으로 어떤 타입이던 Object의 하위 타입이라면 정렬이 가능하도록 만든 것이다

따라서 parent로 넘어온 QInstance에서 sort.getProperty()로 표현된 필드가 없다면 메서드가 터진다

 

요 부분에서 깊게 들어가보자면 Pageable로 넘어올 때 정렬할 필드를 제대로 넘기지 않았기 때문이고

이 문제는 페이징 시점이 아니라 데이터 바인딩 시점에 터져야 옳다

따라서 Controller에서 Pageable로 받거나 자신만의 PagingVO로 받을 때

필드 값 검증을 수행해야 페이징 메서드까지 넘어가지 않는다

 

그 외의 상황에서 즉 QInstance에 필드가 있다면 정상 작동된다

이 부분을 더 안전하게 만드려면 try-catch로 묶어 빈 OrderSpecifier를 넘기거나 하면 될 것 같다

예를 들어 람다 안에서의 try-catch문으로 감싸는 방식인데 보다시피 가독성이 좋지 않다

return sorts.stream()
    .map(sort -> {
      try {
        Order direction = sort.getDirection().isAscending() ? Order.ASC : Order.DESC;
        SimplePath<Object> fieldPath = Expressions.path(Object.class, parent, sort.getProperty());
        return new OrderSpecifier(direction, fieldPath);        
      } catch (Exception e) {
        return new OrderSpecifier();
      }
    })
    .toArray(OrderSpecifier[]::new);
}

 

이 부분도 개선해보려면 Lambda Wrapper에 대해 찾아보자

람다 내에서 발생하는 try-catch를 감싼 평범한 잡기술이다

다만 이 녀석도 범용적으로 작성하면 수행할 Function은 기본적으로 넘기고

예외가 터질 경우 어떻게 할 것인지에 대한 여러 처리 방법을 넘기도록 할 수 있다

예를 들어 다른 예외로 감싸 던진다거나, new OrderSpecifier()를 넘겨 예외가 터지면 얘를 반환하게 한다거나 등등

 

하나 더 개선사항을 보자면 지금은 Sort 하나만 넘기기 때문에 정렬 조건이 하나가 된다

List<Sort>로 넘길 수도 있고 Map<String, Direction>과 같은 형태로도 넘겨 다중 정렬 또한 가능하게 할 수도 있다

저 메서드 하나만 작성해 온갖 종류의 QInstance, 정렬 조건에 적용할 수 있다

개발자라면 반복 작업을 끔찍이 싫어하는데 메서드 작성도 예외일 순 없다

Generics를 활용해 반복적인 메서드도 없애버리도록 하자

댓글
링크
글 보관함
«   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
Total
Today
Yesterday