블로그 내 검색

2013. 4. 30.

[Book :: Effective Java] 자바에서의 전략 패턴 구현과 함수객체

What is a 'Function-Object'?

자바는 객체지향을 구현하기 위해 모든 것을 클래스라는 일종의 인스턴스 템플릿으로 구현하도록 되어 있다.

단순한 유틸성 기능을 구현할 때에도 XXXUtils 이라는 뭔가 우주에서 날아온 듯한 이름으로 클래스를 만들고 그 안에 static을 사용한 메서드로 기능을 구현한다.

클래스는 자바에서 절대적이다.
자바는 언어 차원에서 모든 걸 클래스화 해야만 하며, 이러한 점은 여러 상황에서 불편한 점이 있을때가 많다.

간단한 예를 들어보자.
공식 API의 Camparator 는 정렬을 위한 함수 객체 인터페이스이다. 자바에서는 정렬 전략으로 사용하기 위해 저 클래스를 풀로 구현해야 한다.

이런 식으로 말이다.
// 이러한 문법적 껍데기를 반드시 선언해야 한다.
public class StringLengthComparator implements Comparator {

    // 우리가 필요한건 이 메소드의 기능 뿐인데도...
    public int compare(String s1, String s2) {
      return s1.length() - s2.length();
    }
}
// 아래와 같이 사용
Arrays.sort(array, new StringLengthComparator());
하지만 일부 언어에서는 함수 포인터(function pointer), 위임(delegate), 람다식(lamda expression) 또는 이와 유사한 기능을 제공하여 프로그램에서 특정 함수의 호출을 저장하거나 전달할 수 있다.
// C의 함수 포인터 방식
#include <stdio.h>
void hello(char *name) {
    printf ("Hi %s\n", name);
}

int main() {
    // 반환값이 void 이고 매개변수가 캐릭터인 함수 포인터 선언
    void (*Func)(char *);

    // 그 포인터에 hello 가르키게 함
    Func = hello;

    // Func 는 변수로 사용할 수 있지만 본질은 함수이다.
    // 함수 포인터 실행
    Func("test");
}

/* ============================================================ */

// C#의 위임 방식
// delegate 타입 선언
public delegate void DoSomething(string command);

// 위임할 메서드
public void Action(string direction){
    // 메서드 구현
}
...
...
// delegate 생성
DoSomething done = new DoSomething(Action);

// 위임 객체를 통해 변수로서 함수를 취급가능하다.
done("left");

/* ============================================================ */ 

// 자바스크립트의 익명 람다 함수
// each의 람다 함수를 지정
$.each(function(key, value) {
    // each 구현
});
그렇다면, 위 기능을 새로 구현해보자.
javascript 에서는 익명 함수를 지원하며, 위와 같은 구현이 아주 간단하다.
// 익명 함수를 사용한 배열 정렬법
arrayObject.sort(function(s1, s2) {
    return s1.length - s2.length;  
})
자바는 위 예제의 자바스크립트처럼 함수(혹은 메서드)만을 생성할 수 없고 반드시 클래스와 쌍으로 움직여야 한다.

그래서 새로운 업무 방식이 생겨 객체에 특정한 알고리즘을 교체하려면 아예 그 객체를 새로 만들거나(설마..), 그 업무 방식에 맞는 새로운 '알고리즘을 구현한 메소드를 포함한 클래스' 를 생성하여 처리 메서드로 전달해야 한다.

위에 굵은 글씨로 표현한 클래스 객체를 '함수 객체' 라고 부른다.

보통은 메서드를 하나만 가지는 인터페이스 형식으로 구현되나, 이 함수 객체를 사용하는 측에 따라서는 메서드 여러개가 구현되어 있을 수도 있다.

전략 패턴 (Strategy Pattern)

함수 객체가 제일 많이 쓰이는 곳은 역시 전략 패턴이다.

전략 패턴(Strategy Pattern) 은 기본적인 목적인 비슷하지만 흐름에서 변화하거나 교체할 수 있는 부분을 따로 분리하여 쉽게 변화시키고 새로 지정할 수 있게 해주는 패턴이다. 여러 메서드를 하나의 클래스에 집어넣은 것보다 유연성이 좋고 잘 디자인 된 전략은 다른 객체에서도 활용할 수 있다.

자바의 전략 패턴은 함수 객체를 사용해야 가능하다.
위에 예시로 든 Comparator 는 전략 패턴의 모범생 같은 구현이라고 볼 수 있겠다.

이제 예제와 함께 더 살펴보자.
만일 한 어느 학원 수강생 처리시스템에서 모든 등록 수강생의 주소를 새로운 주소로 바꾸는 작업을 해야 한다고 가정한다.

보통은 이렇게 처리할 것이다.
public List<Student> changeNewAddress(List<Student> student) {
    for(Student s : student) {
        String newAddr = getNewAddress(s.getAddress);
        s.setAddress(s);
    }
    return student;
}
수강생의 주소를 바꾸는 일 외에 학생들의 프로필 사진도 전부 섬네일화 해야 하는 작업이 생겼다고 가정해보자. 

그렇다면 간단한 구현으로는 새로운 createProfilePhotoToThumb 메소드가 생길 것이다.

하지만 이렇게 접근하지 말고 다른 방법을 써 보자.

일단, 하려는 작업은 둘다 학생에 대한 배치 처리이다.
학생 하나하나를 순회하며 해당 학생의 특정 필드에 대해 작업을 수행한다.
여기서 "새로운 주소 변경" 과 "섬네일 만들기" 는 전략으로 볼 수 있고 위에서 설명한 함수 객체로 구현해볼 수 있다.

그렇다면 먼저 함수 객체 인터페이스는,
interface Transfer<T> {
    T transfer(T value);
}
그렇다면 전략 객체 구현은,
// 들어온 주소 문자열을 새 주소 변경하는 함수 객체 클래스.
class ChangeAdressJob implements Transfer<Student> {
    public Student transfer(Student address) {
        // 구 주소를 새 주소로 바꾸는 로직
    }
}

// 들어온 사진을 섬네일화 하는 함수 객체 클래스
class CreateThumbnail implements Transfer<Student> {
    public Student transfer(Student photo) {
        // 프로필 파일을 분석하여 해당 파일을 섬네일을 만듬
    }
}
그렇다면 이제 저 함수 객체를 사용하는 클래스는 다음과 같을 것이다.
class StudentManager {

    private final List&;ltTransfer> transferStrategy = new LinkedList<Transfer>();

    public List<Student> loadStudents() { ... }

    // 배치 처리.
    // 학생 리스트에 대해 전략 함수 객체(Transfer 구현체)를 하나하나 실행함.
    public void executeBatch(List<Student> students) {
        for(Student s : students) {
            for(Transfer t : transferStrategy) {
                t.transfer(s);
            }            
        }
    }
    
    // 전략 객체를 전략 셋에 추가한다.
    public void addTransfer(Transfer t) {
        transferStrategy.add(t);
    }

    public static void main() {

        StudentManager manager = new StudentManager();
        List<student> students = manager.loadStudents();

        manager.addTransfer(new ChangeAdressJob());
        manager.addTransfer(new CreateThumbnail());

        manager.executeBatch(students);
    }
}

댓글 4개:

  1. private final List&;ltTransfer> transferStrategy = new LinkedList();
    죄송하지만, 제가 아직 이걸 필터링할 수준이 안되서 물어봅니다.
    private fianl List transferStrategy가 맞는건가요?

    답글삭제
    답글
    1. private final List transferStrategy = new LinkedList(); 입니다.
      Transfer 제네릭이예요~

      삭제
  2. 추가로 transferStrategy은 왜 있는지 잘 이해가 가질 않습니다. 뭘 위해서 존재하는 객체인가요??

    답글삭제
    답글
    1. 학생 리스트에 대해 Transfer 형식의 로직을 일괄적으로 수행하려는 객체입니다. :)
      예제에서는 주소 변경(ChangeAdressJob), 섬네일 생성(CreateThumbnail) 이 두개 뿐이지만 필요에 따라 나중에 더 추가해서 구현해서 넣을 수 있죠.

      삭제