함수형 프로그래밍을 시작하는 첫 장에서는 


- 함수란 무엇인가?


- 다형성(Polymorphism)의 필요성과 이를 이용하기 위한 OOP 의 한계


- 이를 극복하기 위한, 기존 Java8 이전의 방법과 편리해진 람다와 메소드 레퍼런스


정도의 내용을 다루었습니다. (위의 개념에 대해 생각이 안난다면, 꼭 복습을 권장합니다.)


이에 대한 포스팅 정보는 아래에서 확인 :-)



그리고 이를 실습해보기 위한 과제가 있었고, 이번 포스팅에서는 과제에 대한 리뷰를 해보고자 합니다.


1. SwitchAndSwitch


첫 번째 숙제는 람다에 대한 튜토리얼 진행을 해보고자 했던 내용이었습니다.


요구사항은 아래의 소스를 리팩토링하는 것이었습니다.

메소드 내부에 존재하는 코드들은 일련의 공통적인 작업들이 많이 보입니다.


보통은 이런 작업에 대해서 따로 메소드 추출 등의 리팩토링을 하겠지만, 이 로직이 오직 switchAndSwitch 메소드에서만 사용될 것 같았기 때문에 다른 방법을 찾아보자는 것이었습니다.

(추 후, 다른 로직에서도 사용이 된다면 그 때 캡슐화하는 것으로...)


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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public void switchAndSwitch() {
 
    List<GenerateModel1> generateModel1List = Model1Dao.GetInstance().selectList(Arrays.asList(1,2,3));
 
    // something work1
    {
        List<Integer> memberSnList = Arrays.asList(2,3);
 
        for (GenerateModel1 model1 : generateModel1List) {
             if (memberSnList.contains(model1.getMemberSubjectSn())) {
                String name = model1.getName();
 
                switch (name) {
                case "강현지":
                    System.out.println("IF 란 사치임을 증명한 " + name);
                    break;
                
                case "유덕형":
                    System.out.println("한 수에 버그를 말살하는 " + name);
                    break;
                case "유형주":
                    System.out.println("한 메소드에 5줄 이면 충분한 " + name);
                    break;
                }
            }
        }
    }
 
    // something work2
    {
        List<String> filterNameList = Arrays.asList("강현지""유덕형");
 
        for (GenerateModel1 model1 : generateModel1List) {
            if (filterNameList.contains(name)) {
                String name = model1.getName();
            
                switch (name) {
                case "강현지":
                    System.out.println("IF 란 사치임을 증명한 " + name);
                    break;
                
                case "유덕형":
                    System.out.println("한 수에 버그를 말살하는 " + name);
                    break;
 
                case "유형주":
                    System.out.println("한 메소드에 5줄 이면 충분한 " + name);
                    break;
                }
            }
        }
    }
}
cs


우리는 일련의 동작 즉 함수를 값으로 가질 수 있다는 개념을 알았고, 이를 이용해서 굳이 메소드 추출을 안하고 이 문제를 해결할 수 있었습니다.


네, 람다를 이용해보는 것이죠.


그 전에 프로그래밍 원칙 중 중요한 원칙이 한 개 있습니다.


"변하는 부분과 변하지 않는 부분을 분리하라!"


이 법칙에 근거했을 때, 저는 해당 로직에 대해 다음과 같이 정의를 했습니다.


- 변하는 부분 : GenerateModel1 에 대한 필터 로직

- 변하지 않는 부분 : Loop 를 돌며, switch 문을 수행하는 과정.


이에 따라 저는 다음과 같이 리팩토링 할 수 있었습니다.


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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public void switchAndSwitch() {
 
    List<GenerateModel1> generateModel1List = Model1Dao.GetInstance().selectList(Arrays.asList(1,2,3));
 
    /**
     *  Predicate 를 받아, 일련의 공통작업을 수행하는 소비자.
     */
    Consumer<Predicate<GenerateModel1>> switchConsumer =
        (predicate) -> {
            for (GenerateModel1 model1 : generateModel1List) {
                
                if(predicate.test(model1)) {
 
                    String name = model1.getName();
                    
                    switch (name) {
                    case "강현지":
                        System.out.println("IF 란 사치임을 증명한 " + name);
                        break;
 
                    case "유덕형":
                        System.out.println("한 수에 버그를 말살하는 " + name);
                        break;
 
                    case "유형주":
                        System.out.println("한 메소드에 5줄 이면 충분한 " + name);
                        break;
                    }
                }
            }
        };
 
    // something work1
    {
        List<Integer> memberSnList = Arrays.asList(2,3);
 
        // 프리디케이트가 정의되는 부분이 변하는 부분.            
        switchConsumer.accept(model1 -> memberSnList.contains(model1.getMemberSubjectSn()));    
    }
 
    // something work2
    {
        List<String> filterNameList = Arrays.asList("강현지""유덕형");
 
        // 프리디케이트가 정의되는 부분이 변하는 부분.            
        switchConsumer.accept(model1 -> filterNameList.contains(model1.getName()));    
    }
}
cs


람다에 대한 첫 튜토리얼로 나름 나쁘지 않았다고 생각합니다. :-)


사실 개인적으로 람다표현식 보다도 위의 규칙이 더 중요하며, 저 규칙만 잘지켜도 좋은 프로그래머가 될 수 있을 것이란 생각이 드는군요.



2. Template-Method 의 극복? 이 방법은 정말로 좋은가?


동적 파라미터화를 이용하면, 굳이 클래스를 상속받아 구현하지 않아도 다형성을 사용할 수 있음을 알았습니다.


또한 JAVA8 에서는 기본 함수형 인터페이스까지 제공해주기 때문에 동적파라미터화를 하기 위해 따로 인터페이스를 제작하지 않아도 될 것 같아 보이는데요.


기본형 함수형 인터페이스에 대한 내용은 아래 포스팅에서 참고.



이 숙제는, 정말로 이 방법이 좋은 지에 대해 다뤄보는 내용이었습니다.


요구사항은 다음과 같았습니다.


ORM 으로 제작된 클래스들의 필드는 종종 비슷한 경우가 많지만, 아쉽게도 제너레이터에 의해 제작되기 때문에 이 곳을 수정하는 것은 문제가 있어보입니다.

(즉 상속 등 클래스 관계를 지어줄 수 없으며, 이는 꽤 골치아픈 문제가 될 수 있습니다.)


즉 이러한 이유로 다형성 등의 개념을 이용할 수 없는 것처럼 보이며, 이는 비슷한 로직도 재활용이 쉽지 않음을 의미합니다.


이를 극복하기 위한 여러가지 방법을 다뤘으며, 대표적인 방법 중 한 가지는 Template-Method 패턴을 이용해보는 것이었습니다.



하지만, 굳이 한 메소드 제작을 위해서 복잡한 클래스 구조를 가질 수 있어 보이는 Template-Method 를 사용하는 것은 부담이라 생각하였습니다.

(이런 생각은 귀차니즘에서 보통 비롯하곤 합니다. ㅡㅡ^)


그러던 중, 동적 파라미터화 및 기존 제공 함수형 인터페이스가 있는 것을 배웠고 이를 이용해 Template-Method 와 비슷한 효과를 낼 수 있을 것 같았습니다.


즉, 어떠한 인터페이스나 추상클래스를 만들지 않고 Template-Method 를 흉내내는 것이 이 과제의 목적이었습니다.


아래 소스는 과제로 제출해준 한 분의 소스입니다.

(제작해주셔서 감사합니다.^^)


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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/**
 * 제너레이트 된 모델에 대한 비지니스 로직 정의.
 *
 *
 * @param <G>
 * @param addTargetMemberSnList
 * @param excludeTargetMemberSnList
 */
public <G> void createByGenerateModel(
    List<Integer> addTargetMemberSnList
    , List<Integer> excludeTargetMemberSnList
    , Function<Integer, G> toGenerateModel
    , Function<List<Integer>, List<G>> selectList
    , Function<G, Integer> toMemberSubjectSn
    , Consumer<G> insertData
    , Consumer<List<Integer>> deleteByMemberSnList) {
        
    final HashMap<Integer, G> groupByMemberSnMemberMap = new HashMap<>();
    {    
        for (Integer memberSn : addTargetMemberSnList) {
            // 일단은 MemberSn 만 넣는다고 가정.
                
            G generateModel = toGenerateModel.apply(memberSn);
            groupByMemberSnMemberMap.put(memberSn, generateModel);
        }
    }
 
    // 이미 존재하는 구성원이거나 제외대상자는 입력 대상에서 제외.
    {
        // 이미 존재하는 구성원순번 또는 제외 타겟 순번 집합.
        HashSet<Integer> excludeTargetMemberSnSet = new HashSet<>();
        {
            // 이미 존재하는 구성원 순번 목록 삽입.
            List<G> existList = selectList.apply(groupByMemberSnMemberMap.keySet().stream().collect(Collectors.toList()));
            for (G model : existList) {
                excludeTargetMemberSnSet.add(toMemberSubjectSn.apply(model));
            }
 
            // 제외 대상 파라미터도 추가.
            excludeTargetMemberSnSet.addAll(excludeTargetMemberSnList);
        }
 
        // 추가대상 그룹에서 제외 대상 집합을 삭제한다.
        groupByMemberSnMemberMap.keySet().removeAll(excludeTargetMemberSnSet);
    }
 
    // 데이터 트랜잭션
    {
        // 데이터 삽입.
        for (G model : groupByMemberSnMemberMap.values()) {
            insertData.accept(model);
        }
 
        // 제외대상 삭제.
        deleteByMemberSnList.accept(excludeTargetMemberSnList);
    }
}
 
// 메소드 사용 예
// Model1 에 대한 데이터 처리.
ModelSampleService.GetInstance().createByGenerateModel(Arrays.asList(12), Arrays.asList(3),
                    (Integer memberSn) -> {
                        GenerateModel1 generateModel1 = new GenerateModel1();
                        generateModel1.setMemberSubjectSn(memberSn);
                        return generateModel1;
                    },
                    (List<Integer> memberSnList) -> {
                        List<GenerateModel1> list = Model1Dao.GetInstance().selectList(memberSnList);
                        return list;
                    },
                    (GenerateModel1 generateModel1) -> {
                        Integer memberSn = generateModel1.getMemberSubjectSn();
                        return memberSn;
                    },
                    (GenerateModel1 generateModel1) -> {
                        Model1Dao.GetInstance().create(generateModel1);
                        return;
                    },
                    (List<Integer> targetMemberSnList) -> {
                        Model1Dao.GetInstance().deleteByMemberSnList(targetMemberSnList);
                        return;
                    }
);
cs


의도 했던 바와 같이 어떠한 [인터페이스, 추상메소드] 없이, OCP 를 지킨 코드는 나왔습니다.

추 후, GenerateModel 이 또 등장하였고, 비슷한 로직을 사용한다면 각 함수형 인터페이스를 구현해주면 됩니다.


하지만, 일단 메소드 사용성 면에서 많은 불편함을 느꼈을 것입니다.

특정 프로토콜로 묶인 추상메소드의 구현이 아니기 때문에, 각 람다를 구현할 때마다 무슨 기능을 했었는지 살펴봐야 합니다.

이는 가독성도 떨어진다고 볼 수 있을 것 같네요..

(인터페이스나 추상클래스로 구현 했다면, 보다 동적인 구현에 있어서 무슨 일을 하는지 명확했을 수 있습니다.)  


이 과제의 의도는 새로운 지식을 맹신하지 말라는 일종의 견제를 해주고 싶었고(패턴병과 유사한 함수병), 요구사항과 현재 상황에 따라 적절한 대처가 필요함을 느낄 수 있도록 하는 것이 목적이었습니다.

(요구사항이 기껏 한 두개의 함수형 인터페이스만 사용할 정도라면, 깔끔할 수 있었습니다.)



3. 계속 존재했던 동적 파라미터화


마지막 과제는 명령패턴을 이용해, undo/redo 를 구현해보고자 하였습니다.


이와 관련된 내용은 아래 포스팅을 참고.



과제 자체는 사실 이 패턴에 대한 이해도 중요했지만, 꼭 동적 파라미터화 같은 방법이 JAVA8 에서 등장한 것은 아니었다는 것에 대한 실습이었습니다.


명령패턴은 일종의 요청을 캡슐화하고 컨트롤러가 이를 관리하게 함으로써, 실제 요청자와 실행자 사이의 관계를 느슨하게 하게 하는 것을 목적으로 합니다.


대부분 이 패턴의 예제에서는 명령을 캡슐화하기 위해 인터페이스를 구현하는 구현클래스를 제작하지만, 이 과제에서는 굳이 클래스를 제작하지 않고 동적 파라미터화를 이용하여 즉시 구현하는 것을 목적으로 하였습니다.

(다양한 요청에 대해서, 재활용 안 할 구현클래스를 만드는 것은 일종의 낭비이지 않을까요?)


일단, 이 패턴을 구현하기 위한 interface 는 다음과 같이 제작했습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * 커맨드 인터페이스 정의.
 *
 * Created by Doohyun on 2017. 7. 10..
 */
public interface ICommand {
 
    /**
     * 어떤기능이든 undo 구현.
     */
    void undo();
 
    /**
     * 어떤기능이든 실행 구현.
     */
    void execute();
}
cs


이를 실행하기 위한 실행자(Receiver)와 컨트롤러(Controller) 는 다음과 같습니다.

(대부분의 내용은 앞써 언급한 포스팅에서 복사했습니다. 이 패턴이 궁금하다면 해당 링크를 참고하세용.)


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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
 * 명령을 관리하는 컨트롤러
 */
public class RemoteController {
     
    // 일반 명령을 위한 스택
    private Stack<ICommand> commandStack = new Stack<>();
    // UNDO 명령을 위한 스택
    private Stack<ICommand> undoStack = new Stack<>();
 
    // 명령을 추가
    public void setCommand(ICommand commandWithUndoable) {
        commandStack.push(commandWithUndoable);
    }
 
    /**
     * 일반적인 실행. (REDO 포함)
     */
    public void execute() {
        if (!commandStack.isEmpty()) {
            // [일반명령 스택]에서 가장 마지막에 담긴 명령객체를 추출 후 실행.
            ICommand command = commandStack.pop();
            command.execute();
 
            // 해당 명령을 UNDO 스택에 삽입.
            undoStack.push(command);
        }
    }
 
    /**
     * 작업 취소 (Undo)
     */
    public void undo() {
        if (!undoStack.isEmpty()) {
            // [UNDO 명령 스택]에서 가장 마지막에 담긴 명령객체를 추출 후 실행.
            ICommand command = undoStack.pop();
            command.undo();
 
            // 일반 실행 스택에 데이터 삽입.
            commandStack.push(command);
        }
    }
}
 
/**
 * 글씨를 입력하는 데모 클래스.
 *
 * Created by Doohyun on 2017. 7. 10..
 */
public class TextWatcherDemo {
 
    private StringBuilder builder = new StringBuilder("");
 
    /**
     * 텍스트 입력.
     *
     * <pre>
     *     텍스트를 입력하고, 현재 상태를 표시한다.
     * </pre>
     *
     * @param text
     */
    public void addText(String text) {
        builder.append(text);
        System.out.println(builder.toString());
    }
 
    /**
     * 텍스트 삭제.
     *
     * <pre>
     *     텍스트를 삭제하고, 현재 상태를 표시한다.
     * </pre>
     */
    public void deleteText() {
        builder.deleteCharAt(builder.length() - 1);
        System.out.println(builder.toString());
    }
}
cs


재료는 다 만들었고, 이를 실제 실행하는 부분만 만들어보면 될 듯 합니다.


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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
RemoteController remoteController = new RemoteController();
TextWatcherDemo textWatcherDemo = new TextWatcherDemo();
 
        
 
// 텍스트를 입력받아, 컨트롤러에 명령을 세팅하고 실행하는 소비자 정의.
Consumer<String> InputText = (text) -> {
        // 명령 세팅.
        // 동적으로 행위를 정의. 즉 동적파라미터화
        remoteController.setCommand(
            new ICommand(){
        
                @Override                                                    
                public void undo(){
                    textWatcherDemo.deleteText();
                }
 
                @Override
                public void execute(){
                    textWatcherDemo.addText(text);
                }
            }
        );
 
        // 실행
        remoteController.execute();
};
 
// 메소드 레퍼런스
// undo 실행을 위한 함수 정의.
Runnable undo = remoteController::undo;
 
// 람다 버전의 REDO 함수.
// redo 실행을 위한 함수 정의.
Runnable redo = () -> remoteController.execute();
        
InputText.accept("안");
InputText.accept("녕");
 
undo.run();
redo.run();
 
// CONSOLE LOG
// 안
// 안녕
// 안
// 안녕
cs


이런 방식은 사실 이벤트 처리 기반 시스템을 만들다보면, 꽤 많이 해봤을 것입니다.

결국 [JAVA8 in Action] 에서 메소드를 파라미터로 넘긴다는 이야기는 이전부터 있던 개념이며, 람다나 메소드 레퍼런스는 이를 보다 쉽게 사용할 수 있는 개념이라 볼 수 있을 것 같네요.

(추 후 공부하게 될, Stream API 사용을 위해서는 이를 적극적으로 사용해야 합니다.)



이로써, 지난 주에 실습한 내용에 대한 리뷰가 끝났습니다.

첫 시간이지만, 꽤 많은 내용을 다뤘던 듯 합니다. 그에 따라 과제도 좀 많이 있었죠. ^^;


이 과제를 언급할 때, 최근에 시청한 한 프로그램의 출연자가 했던 대화 중 하나를 같이 말했습니다.


 Knowing is nothing, Doing is the best. 

(아는 것은 중요하지 않다. 하는 것이 가장 좋다.)


단순히 듣기만 하는 것이 아니라, 한번 해보는 것은 정말 중요한 듯 합니다.

(이런 행동들은 조금씩 우아한 방법을 찾아 보는 것에 도움이 되겠죠?.. 아마도...)


어쨌든 개인시간을 투자하며, 계속 지금과 같은 시간을 같이 보내준 여러분들께 감사합니다.

(맥주를 한 병 더 먹었다면 아마 더 감성글이 됐을지도...)



반응형
Posted by N'

계속해서 람다와 관련된 포스팅을 하고 있습니다. 


람다는 익명 클래스를 어느정도 대체를 해줄 수 있는 것처럼 보이며, 간단한 처리는 대부분 람다를 이용하게 되지 않을까요? 

(예를들어 안드로이드에서 이벤트 처리 시, 익명클래스의 대체가.. ㅡㅡ^ ) 


람다와 람다를 왜 써야하는지 보려면 이 곳으로...



오늘은 람다표현식과 더불어, 간결성과 더불어, 가독성까지 챙길 수 있는 메서드 레퍼런스에 대하여 포스팅하려 합니다.


JAVA 8 IN ACTION 에 따르면, 메서드 레퍼런스는 특정 메서드만을 호출하는 람다의 축양형 이라고 볼 수 있다고 합니다. 

즉 람다를 통해 어떻게 어떤식의 메서드를 사용하라가 아닌, 메서드의 이름만을 참조하여 처리한다고 볼 수 있습니다. (그래서 레퍼런스라고 하나 봅니다... ㅎㅎ)


예를 들어, 이런 것을 볼 수 있을 것 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
class Apple {
    private int weight;
 
    public int compareTo(Apple apple){
        if (weight >= apple.getWeight()) return 0;
        else if (weight <= apple.getWeight()) return 1;
        else return -1;
    }
}
 
inventory.sort((Apple a, Apple b) -> a.compareTo(b));
inventory.sort(Apple::compareTo); // -> 메서드 레퍼런스
 
cs


저는 이 것을 보면서 처음에 이해가 안됐던 부분이 파라미터는 어떻게 넣을 것이냐라는 것이었습니다. 

람다의 경우 파라미터 영역이 따로 명시되어 있지만 메서드 레퍼런스는 (Apple::compareTo) 다음과 같이 명시가 되어있지 않습니다. 

그러나 이 것은 sort 매개변수 넣을 당시, 람다표현식에 대한 형식검사를 함을 알 수 있습니다. sort의 파라미터로 람다 표현식을 사용 시, 람다 디스크립터를 맞춰야 한다는 것은 지난 포스팅 때 언급을 했었습니다.




즉 이런한 점을 이용해, 람다 디스크립터에 맞춰 메서드 레퍼런스를 사용해주면 됨을 알게 되었습니다.


JAVA8 IN ACTION 에서는 메서드 레퍼런스를 만드는 법은 크게 세가지로 보고 있습니다.


1. 정적 메서드 레퍼런스 (정적 메서드 - static 메서드라 생각합시다.)


예를들어 Integer 클래스의 parseInt 메서드는 정적 메서드로, String 객체를 Integer 형태로 변경해줍니다. 해당 메서드는 다음과 같이 인수로써 가질 수 있습니다.


1
2
3
Function<String, Integer> parse = Integer::parseInt;
int a = parse.apply("5");
 
cs



2. 다양한 형식의 인스턴스 메서드 레퍼런스 (일반 메서드라 생각합시다.)


클래스에 정의 된 일반 메서드를 인수로 가질 수 있음을 말합니다. 아래는 List의 메소드인 size() 를 인수로 가지는 예제입니다.


1
2
3
4
5
6
7
8
9
10
List<String> strList = new ArrayList<String>() {
    {
        add("A");
        add("B");
        add("C");
    }
};
 
Function<List, Integer> size = List::size;    // 일반 메서드의 메서드 레퍼런스
System.out.println(size.apply(strList));     // 3 
cs




3. 기존 객체의 인스턴스 메서드 레퍼런스 


인스턴스 객체를 이용하여, 메서드 레퍼런스를 사용할 수 있습니다. 

예를들면 이런식으로 사용이 가능해보입니다. 


1
2
3
4
5
6
7
8
9
10
List<String> strList = new ArrayList<String>() {
    {
        add("A");
        add("B");
        add("C");
    }
};
        
Consumer<String> consume = strList::add
consume.accept("E");
cs


메소드레퍼런스를 사용하려는 대상 메소드가 다형성에 의해 가려질 때 사용할 듯 합니다.



4. 생성자 레퍼런스


new 키워드를 사용하여, 생성자의 레퍼런스를 만들 수 있습니다. 아래와 같이 작성이 가능합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Apple {
    private String color;
    private int weight;
    
    public Apple(){}
    
    public Apple(String color, Integer weight){
        this.color = color;
        this.weight = weight;
    }
}
 
Supplier<Apple> creator = Apple::new;    // 기본 생성자에 대한 메서드 레퍼런스
Apple apple = creator.get(); 
 
// color 와 weight 를 받아 Apple을 만드는 생성자의 메서드 레퍼런스
BiFunction<String, Integer, Apple> creator2 = Apple::new
Apple apple2 = creator2.apply("green"100);
 
cs


위와 같이 다양한 시그니처 생성자에 대하여 메서드 레퍼런스를 사용할 수 있습니다.



이러한 메서드 레퍼런스를 람다표현식을 직접 사용하지 않고 사용하는 이유는 복잡한 람다식을 일일이 명시하는 것보다 가독성이 좋기 때문입니다. 


즉 람다식이 복잡하다면, 차라리 메소드로 정의하여 처리하는 것이 좋아보입니다.



자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기




반응형

'개발이야기 > 함수형 프로그래밍' 카테고리의 다른 글

Stream API  (0) 2016.08.03
람다 조합  (0) 2016.08.02
람다의 실제 형식  (0) 2016.07.28
클로저와 람다  (2) 2016.07.28
원시타입을 위한 함수형 인터페이스  (0) 2016.07.28
Posted by N'

이번 포스팅은 함수형 프로그래밍의 꽃이라고 할 수 있는 람다 표현식에 대해 알아보려 합니다.


람다를 사용하는 이유는 계속 전 포스팅부터 봐왔지만 동작파라미터화를 통한 코드의 유연함을 가질 수 있도록 하고, 실행 시 유연하게 작동할 블록에 대하여 간결하게 만들자는 의미에서 등장했습니다.


동작파라미터화가 무엇인지 모른다면? 이곳으로 가서 먼저 보시길 ㅎㅎ



일단 람다(Lambda)가 무엇일까요?


람다는 미적분학의 기호 중 λ (람다 대수)에서 유래되었습니다. 이 것은 함수 정의, 함수 적용, 귀납적 함수를 추상화한 형식 체계를 나타내는데요. 이 뜻대로, 람다가 JAVA8에 구현되었습니다. (익명 클래스를 대체하여, 추상메소드를 전달한다는 점에서 비슷한 것 같네요. ㅡㅡ^)


람다 표현식은 메소드로 전달하는 익명 함수를 단순화 시킨 것으로, 파라미터 리스트, 반환 형식, checked exception 등 기존 메소드 정의 시 해야 했던 것을 모두 해야합니다. JAVA8 IN ACTION에서는 람다의 특징을 다음과 같이 정의했습니다.


1. 익명

보통의 메소드와 달리 이름이 없으니 익명입니다. 


2. 함수

클래스에 종속되는 메소드와 달리 함수라고 부릅니다.


3. 전달

매개변수로 전달이 가능합니다. 또한 함수를 변수에 저장할 수 있습니다. 

(자바가 자바스크립트 처럼 되어가네요. 이렇게요.)


1
2
3
var func1 = function() {
    console.log('변수로 가지고 있는 함수입니다.');
}
cs


4. 간결성

이건 뭐, 너무 언급해서... 굳이 객체로 감싸지 않아도 됩니다.



람다는 크게 세 부분으로 나누어져 있습니다. (파라미터 리스트, 화살표, 바디)

파라미터 리스트는 말 그대로 매개변수 정의부, 바디는 메소드의 바디, 화살표는 파라미터 리스트와 바디를 연결해주는 것을 말합니다.


(parameter) -> expression 


이라 보면 됩니다. expression이 길어진다면 {}을 붙일 수 있습니다.


람다를 통해서, 보통 다음과 같은 일을 할 수 있습니다.


1. boolean 표현식 

아래는 파라미터 한개를 받아, boolean을 표현합니다.


1
(int a) -> a > 5
cs


2. 객체 생성

아래는 나이와 이름을 받아, People 인스턴스를 만들고 있습니다.


1
2
3
4
5
6
 (int age, String name) -> {
            People ret = new People();
            ret.setAge(age);
            ret.setName(name);
            return ret;
        };
cs


3. 객체 소비 (프로시저)

보통 void 함수와 같은 역할이라 보면 됩니다.


1
2
3
 (People people) -> {
            System.out.println(people.toString());
        };
cs


4. 객체에서 선택/추출

객체에서 특정 내용을 추출합니다. (people 객체에서 나이를 선택해서 뽑아냅니다.


1
 (People people) -> people.getAge()
cs


5. 두 값을 조합

객체에서 두 값을 조합한 결과를 출력합니다.


1
(int a, int b) -> a + b
cs


6. 두 값을 비교

두 값을 비교한 결과를 출력합니다.


1
(int a, int b) -> a >= b
cs


보통 위와 같은 6가지 패턴으로 주로 사용하고 있으며, 이러한 방법들은 우리가 보통 함수를 만들고 사용하던 방식과 차이가 없어보입니다. (즉 처음 프로그래밍을 배우기 시작하며, 함수라는 개념을 배우던 그 때로 돌아간 것이라 볼 수 있네요. ㅡㅡ^)


다음 포스팅에서는 JAVA8 에서 람다 표현식에 개념과 함께 등장한 함수형 인터페이스와 편의성을 위해 미리 구현된 몇가지의 인터페이스를 보고자 합니다.





자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기

 

반응형
Posted by N'

이번 포스팅에서는 지난번에 포스팅했던 함수형 프로그래밍을 통한 방법과 기존의 객체지향 관점의 디자인 패턴과 비교를 해보고자 합니다.



이 포스팅을 올리려고 생각한 이유는 현재 보고 있는 책인 "JAVA8 IN ACTION" 에서는 메소드를 코드로 넘기는 것을 통해 간단히 동작 파라미터화를 하는 것을 보여줌으로써, 기존의 전략 패턴을 사장시킬 수 있을까에 대한 생각이 들었습니다.


하지만 꼭 그런 것은 아닌 경우가 몇가지가 있었습니다.


1. 전략 자체가 상태를 가지고 있어야 하는 경우.


보통 전략 패턴을 사용하여, 전략 객체를 만들 때, 필자의 경우에는 


* 전략이 상태를 가지고 있는 경우라면, interface를 만들고 concrete class 를 구현

* 전략이 상태를 가지지 않는 경우라면, enum을 사용해 singleton 으로 생성


으로 구현하였습니다.


함수형 프로그래밍에서 사용하는 메소드 역시, 특정 상태를 저장한 체로 유지할 수 없으며, 이는 상태가 필요한 전략에서는 사용할 수 없음을 말할 것 같습니다. 


2. 전략 자체가 구현해야할 추상 메소드가 다수 있는 경우.


AppleFilter 예제에서는 간단히 필터의 역할만을 Predicate 인터페이스를 통해 함수 자체로 넘기는 것이 가능했지만 구현해야할 메소드가 다수라면 기존 전략 패턴을 사용해야 할 것으로 보입니다.


물론 기능단위로 더욱 쪼개거나, 동작을 파라미터로 더 받을 수도 있겠지만, 아래 UML과 같이 한 전략에 대해서 각 구현 클래스가 concrete하게 가지고 있는 것이 유지보수성에서 더 좋아보입니다. 특히 한 전략에 대해서 해야할 다른 일들이 늘어난다면 더욱 이 방법을 해야할 것 같습니다. ㅡㅡ^.



아직 모든 장을 만나본 것은 아니지만, 간단하게 처리할 수 있는 비지니스 로직은 함수형프로그래밍으로, 보다 재사용성이 높고 복잡도가 있는 작업은 기존 객체지향적인 사고가 필요하지 않을까라는 생각이 드네요. 

(아직 책에서 이야기하는 함수형 사고가 안길러져서 그런 걸 수도 있습니다. ㅡㅡ^)



자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기


반응형
Posted by N'

포스팅 전 먼저 서론을 조금 써보자면, 보통 학교 교과목 숙제에서는 흔히 생기지 않는 일이지만, 공모전이나 조금 덩치가 있는 프로그램 혹은 일을 하다보면 가장 많이 생기는 것이 있는 것 같아요. 


그것은 바로 "변화" 라는 생각이 드는데요


회사에서도 일을 하다보면, 기획이나 디자인 혹은 개발과정 중 여러 상황 등 기타 이유로 인하여, 심심치 않게 변화가 등장함을 알 수 있습니다.


변화란 나쁜 것인가? 라고 생각했을 때 전 아닌 것 같습니다. 어디서 흔히 들은 이야기론,

 

"프로젝트의 변화가 나쁜 게 아니라, 변화에 적응하지 못하는 개발자의 무능함이 나쁘다" 


이런게 있습니다. "변화를 사랑하는 개발자가 됩시다. ㅡㅡ^"


서론 부분에 변화에 대한 이야기를 쓴 이유는 함수형 프로그래밍의 4번째 포스팅인 동작 파라미터화(Behavior parameterization)와 관련된 이야기와 조금 관련이 있어서 조금 꺼내봤습니다.


이 이야기와 비슷한 포스팅을 아마, 



이 곳에서 진행을 했었습니다. 이 곳에서 진행한 코드를 조금 더 디테일하게 리뷰해보고자 합니다. (귀찮아서 그러는게 아닙니다. ㅡㅡ^)


copy&paste가 변화에 유연하지 못함과 그렇기 때문에 이를 해결하기 위한 객체지향적 관점에서 여러 디자인 패턴이 등장했고, 지난번에는 그 중 추상화 과정을 통해 전략패턴을 사용해보았음을 알 수 있습니다.


그 중 우리가 다시한번 살펴볼 곳은, 아래 코드입니다. 


/**
     * 사과 객체를 분류하기 위한 함수
     * 
     * @param appleList
     * @param appleFilter
     * @return
     */
    public static List<Apple> GetFilterApple(List<Apple> appleList, AppleFilterAble appleFilter) {
        ArrayList<Apple> resultList = new ArrayList<>();
 
        for (Apple apple : appleList) {
            if (appleFilter.isContain(apple)) {
                resultList.add(apple);
            }
        }
 
        return resultList;
    }
cs


우리는 보다 GetFilterApple을 유연하게 사용하기 위해 interface를 만들어 inContain 에 대한 명세를 추상화하였고, 사용처에서 익명클래스로 할일에 대한 최종 명세를 표기하였습니다. 아래와 같이 말이죠.


1
2
3
4
5
6
7
GetFilterApple(inventory, new AppleFilterAble(){
    @Override
    public boolean isContain(Apple apple){
        return "green".equals(apple.getColor());
    }
});
 
cs


여기에서 우린 isContain이라는 동작을 파라미터화(이번 포스팅의 주제)하여, GetFilterApple 메소드로 넘기게 되었고, 보다 더욱 변화에 유연하게 사용할 수 있게 되었습니다.


그러나, 이 곳에서 우리는 단지, inContain 이란 메소드를 넘기기 위해, 인터페이스를 만들고, 객체로 한번 감싸 넘기고 있습니다. 이런 과정이 보다 복잡하고, 어려운 작업이 될 수 있지 않을까요? (복잡한 것을 좋아하는 사람은 없습니다. ㅡㅡ^)


그렇기 때문에 람다 표현식과 메소드를 코드로 넘기는 방법 같은 방법이 제시 되었음을 알 수 있습니다. 아래는 지난번에 했던 람다표현식을 통해, 함수만 넘기는 예제입니다.


1
GetFilterApple(inventory, (Apple a) -> "green".equals(a.getColor()));
cs


우리는 동작을 추상화하고, 파라미터로 넘기는 것이 변화에 대해 유연함을 줄 수 있다는 것을 알았고, 함수형 프로그래밍을 통해 간결함까지 사용할 수 있음을 알 수 있게 되었습니다. 단순히 람다가 등장하여 코드가 짧아졌다는 것만을 알게 아니라, 이러한 전과정을 모두 알면 좋을 것 같아서 코드에 대한 리뷰를 다시 해보게 되었습니다.^^;



자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기


반응형
Posted by N'