안녕하세요. N` 입니당.


이번 포스팅 에서는 드디어 길고 길었던 "패턴이야기"의 마지막을 장식합니다.


마지막으로 다루는 패턴은 "컴파운드 패턴 (Compound Pattern)" 으로, 이 패턴은 여태까지 배웠던 패턴들의 종합이라 볼 수 있습니다.

 

컴파운드 패턴은 


일반적으로 자주 생길 수 있는 문제를 해결하기 위한 용도로, 

2개 이상의 패턴을 결합해서 사용하는 것


의미합니다.


즉, 이 패턴의 사용한다는 것은 여태까지 배웠던 여러 패턴들을 결합해서 특정 문제를 해결하는 것을 의미하며, 대표적으로 꽤 유명한 패턴 중 하나인 MVC(Model-View-Controller) 패턴은 이러한 컴파운드 패턴에 속합니다.


오늘 리뷰는 요구사항을 살펴보며 그에 따라 여러 패턴을 사용하게 되는 예제와 함께 MVC 패턴 내부의 컴파운드 원리를 간략하게 알아보려 합니다.

(사용하는 예제 및 자료는 제게 OOP 를 가르쳐 주셨던 한국기술교육대 김상진 교수님의 자료를 참고하였습니다.)



1. 오리꽥꽥 시뮬레이터!


요구사항은 간단합니다.

여러가지 특징을 가진 오리들을 제작하고, 오리들이 꽥꽥하며 우는 시뮬레이터 하는 것이죠.


여러 오리를 제작하기 위해, 첫 번째로 설계한 UML 은 아래와 같습니다. (코드 대신 UML 로 대치...)

 

 

 

이 UML 에 따라 제작된 클래스를 시뮬레이션 하는 프로그램을 저는 다음과 같이 제작하였습니다.


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
/**
 * 오리 시뮬레이터 정의.
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class DuckSimulator {
 
    /**
     * 테스트 클래스 제작.
     *
     * @param args
     */
    public static void main(String[] args) {
        DuckSimulator duckSimulator = new DuckSimulator();
        duckSimulator.simulate();
    }
 
    /**
     * 시뮬레이터 작성.
     *
     * <pre>
     *     이 곳에 비지니스 시뮬레이션 기능을 넣을 것!
     * </pre>
     */
    public void simulate(){
        Duck rubberDuck = new RubberDuck();
        simulate(rubberDuck);
    }
 
    /**
     * 오리를 넣어 시뮬레이션.
     *
     * @param duck
     */
    private void simulate(Duck duck){
        duck.quack();
    }
}
 
// CONSOLE RESULT
// 삐이익~~ 
cs



2. 오리가 아닌 다른 동물을 테스트!


테스트 클래스는 현재 잘 작동하고 있습니다만, 오리와 비슷한 성격을 가진 거위 클래스를 이 테스트에 포함하고 싶습니다.


거위 클래스 명세는 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 거위 클래스
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class Goose {
 
    /**
     * 거위의 울음.
     */
    public void honk() {
        System.out.println("Honk");
    }
}
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
/**
 * 거위를 오리로 호환시켜주는 클래스
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class GooseAdapter extends Duck {
 
    private Goose goose;
 
    public GooseAdapter(Goose goose) {
        this.goose = goose;
    }
 
    @Override
    public void quack() {
        goose.honk();
    }
}
 
// TEST CLASS
public class DuckSimulator {
    /**
     * 시뮬레이터 작성.
     *
     * <pre>
     *     이 곳에 비지니스 시뮬레이션 기능을 넣을 것!
     * </pre>
     */
    public void simulate(){
        // 거위를 오리로 호환하여, 테스트
        Duck goose = new GooseAdapter(new Goose());
        simulate(goose);
    }
}
 
// CONSOLE RESULT
// Honk
cs



3. 오리들의 꽥꽥한 횟수 구하기!


추가된 요구사항은 시뮬레이션을 한번 할 때, 전체 오리들이 꽥꽥한 횟수를 구해야하는 것입니다. 

이를 구현하기 위한 방법으로 static 변수를 이용하고자 합니다.


구현 알고리즘으로 떠오르는 것은 quack 메소드가 호출될 때마다 해당 변수를 증가시키면 되지만, 이를 위해서는 Duck 을 구현한 모든 클래스를 손봐야하는 것으로 보입니다. (많이 고쳐야 함.. 짜증...)

(Duck::quack 자체를 추상 메소드에서 일반 메소드로 변경하고 카운트를 증가시켜도 되지만, 이를 위해서는 상속받은 모든 오리클래스들이 quack 을 재정의할 때 super.quack 을 반드시 사용하도록 모두 고쳐야 합니다.)


이를 위해서 제안하는 방법은 꽥꽥한 횟수만을 관리하는 클래스를 제공하는 것을 목표로 합니다.

요구사항인 Duck::quack 을 요청했을 때 카운트가 증가해야하는 재정의가 필요하며, 이는 편의에 맞게 상속을 우회하는 패턴을 생각해 볼 수 있습니다.


우리는 이런 방법론을 배운적이 있습니다.



그렇습니다. 횟수를 관리하는 장식자를 만들 것입니다.

저는 아래와 같이 장식자를 제작하였고, 그에 따라 시뮬레이터 역시 변경하였습니다.


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
/**
 * 꽥꽥한 횟수를 관리하는 장식자
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class QuackCountDecorator extends Duck{
    private Duck duck;
 
    private static int QUACK_COUNT = 0;
 
    public QuackCountDecorator(Duck duck) {
        this.duck = duck;
    }
 
    @Override
    public void quack() {
        // 오리가 울 때마다, 카운트를 관리하도록 처리.
        ++QUACK_COUNT;
 
        duck.quack();
    }
 
    /**
     * 오리가 꽥꽥한 횟수 출력.
     *
     * @return
     */
    public static int GetQuackCount() {
        return QUACK_COUNT;
    }
}
 
/**
 * 오리 시뮬레이터 정의.
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class DuckSimulator {
 
     /**
     * 시뮬레이터 작성.
     *
     * <pre>
     *     이 곳에 비지니스 시뮬레이션 기능을 넣을 것!
     * </pre>
     */
    public void simulate(){
 
        // 고무오리 테스트.
        {
            Duck rubberDuck = new QuackCountDecorator(new RubberDuck());
            simulate(rubberDuck);
        }
 
        // 거위를 오리로 호환하여, 테스트
        {
            // 장식자로 치장.
            Duck goose = new QuackCountDecorator(new GooseAdapter(new Goose()));
            simulate(goose);
        }
 
        System.out.println(String.format("꽥꽥 횟수 출력 : %d회", QuackCountDecorator.GetQuackCount()));
    }
}
 
// CONSOLE RESULT
// 삐이익~~
// Honk
// 꽥꽥 횟수 출력 : 2회
 
cs



4. 장식자 생성의 불편함, 이를 위한 객체 생성의 캡슐화!


장식자를 이용하여, 꽥꽥 횟수에 대한 관심을 한 곳으로 몰아 버린 것은 적절한 선택인 듯 합니다.


하지만, 그에 대한 비용으로 불편한 API 사용이란 대가를 치뤄야 합니다.

대표적으로 주목할 코드는 아래의 어댑터와 장식자를 같이 사용한 사례입니다.


1
 Duck goose = new QuackCountDecorator(new GooseAdapter(new Goose()));
cs


카운트를 모든 오리에 대해서 관리해야 한다면, 매번 위와 같이 장식자를 붙여줘야 할 것으로 보이는데요. 

차라리, 해당 방식으로 객체를 생성해야할 일이 많다면 객체생성 방법 자체를 캡슐화하는 것도 방법으로 보입니다.


특정 목적에 맞게 객체를 생산해주는 클래스를 팩토리라 하며, 카운트에 대한 관심을 지원할 수 있는 팩토리 클래스를 제작해 봅시다.

(이에 대한 자세한 내용은 아래에서 참고!)




이 예제에서는 여러 타입의 객체를 목적에 맞게 생산해주는 추상 팩토리(Abstract Factory) 를 사용하였으며,

그에 대한 코드는 아래와 같습니다.


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
/**
 * 카운트 뿐만이 아닌 특정 관심사에 따라 팩토리를 생성할 수 있음으로, 추상 팩토리를 제작.
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public abstract class AbstractDuckFactory {
 
    /**
     * 고무오리 생산.
     *
     * @return
     */
    public abstract Duck createRubberDuck();
 
    /**
     * 거위 생산.
     *
     * @return
     */
    public abstract Duck createGoose();
}
 
/**
 * 꽥꽥 카운팅 데코레이터를 장식한 오리 객체를 생산하는 팩토리.
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class CountingDuckFactory extends AbstractDuckFactory{
 
    private static CountingDuckFactory INSTANCE = new CountingDuckFactory();
 
    private CountingDuckFactory(){}
 
    /**
     * 싱글톤으로 제작.
     *
     * <pre>
     *     팩토리는 의미적으로 한개여도 충분해 보임.
     * </pre>
     *
     * @return
     */
    public static CountingDuckFactory GetInstance() {
        return INSTANCE;
    }
 
    /**
     * 고무오리 생산.
     *
     * @return
     */
    @Override
    public Duck createRubberDuck() {
        // QuackCountDecorator 으로 장식하였음!
        return new QuackCountDecorator(new RubberDuck());
    }
 
    /**
     * 거위 생산.
     *
     * @return
     */
    @Override
    public Duck createGoose() {
        return new QuackCountDecorator(new GooseAdapter(new Goose()));
    }
}
 
cs


현재까지의 요구사항에서는 AbstractDuckFactory 를 굳이 제작할 필요는 없어보이지만,

추 후 오리들에 대한 새로운 관심사가 생길 경우 그에 맞는 적절한 객체를 생성할 수 있도록 추상 클래스를 제작했습니다.


또한 구현된 CountingDuckFactory 는 현재 의미적으로 한개여도 충분해 보인다는 생각이 들며, 그에 따라 싱글톤 클래스로 제작하였습니다.



팩토리를 제작하였으니, 그에 따라 테스트 클래스는 다음과 같이 편리하게 고칠 수 있을 것 같네요..


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
/**
 * 오리 시뮬레이터 정의.
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class DuckSimulator {
 
    /**
     * 시뮬레이터 작성.
     *
     * <pre>
     *     이 곳에 비지니스 시뮬레이션 기능을 넣을 것!
     * </pre>
     */
    public void simulate(){
 
        // 고무오리 테스트.
        {
            // 팩토리로 객체 생성을 캡슐화
            Duck rubberDuck = CountingDuckFactory.GetInstance().createRubberDuck();
            simulate(rubberDuck);
        }
 
        // 거위를 오리로 호환하여, 테스트
        {
            // 팩토리로 객체 생성을 캡슐화
            Duck goose = CountingDuckFactory.GetInstance().createGoose();
            simulate(goose);
        }
 
        System.out.println(String.format("꽥꽥 횟수 출력 : %d회", QuackCountDecorator.GetQuackCount()));
    }
}
cs



5. 오리떼 관찰!


현재 제작된 오리 시뮬레이터에서는 각각의 오리객체가 꽥꽥하도록 정의되어 있습니다.

하지만 오리 객체를 더 많이 테스트 하고 싶다면, 팩토리에서 객체를 생성하고 DuckSimulator::simulate 메소드를 매번 호출해야 합니다.

(즉 여러 오리 객체에 대한 관리가 불편합니다. ㅡㅡ^)


만약 테스트의 요구사항이 지금과 같이 여러 오리들을 순회하며 Duck::quack 을 실행하는 것이 목적이라면, 아래 패턴을 이용해 볼 수 있을 것 같습니다.



지금부터 오리떼(Flock)를 나타낼 수 있는 클래스의 제작을 목표로 하며, DuckSimulator::simulate 를 사용할 수 있도록 하려 합니다.

이 때 오리들을 순회하면서 동작하고, 기존 Duck 의 인터페이스를 그대로 계승하도록 해야할 것 입니다.


즉, 오리들을 그룹으로 묶은 복합계층(Composite)을 만들고 단일계층(RubberDuck..)과 동일한 인터페이스를 제공할 생각입니다.


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
/**
 * 오리떼 클래스.
 *
 * <pre>
 *     오리떼라는 복합계층을 제작하며, 단일계층과 동일한 취급을 할 수 있게 해줌.
 *     Composite Pattern
 * </pre>
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class Flock extends Duck{
 
    private List<Duck> duckList = new ArrayList<>();
 
    public void addDuck(Duck duck) {
        duckList.add(duck);
    }
 
    @Override
    public void quack() {
        // 순회하면서, Duck::quack 기능을 수행.
        // 반복자 패턴(Iterator)
        duckList.forEach(Duck::quack);
    }
}
 
 
/**
 * 오리 시뮬레이터 정의.
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class DuckSimulator {
 
     /**
     * 시뮬레이터 작성.
     *
     * <pre>
     *     이 곳에 비지니스 시뮬레이션 기능을 넣을 것!
     * </pre>
     */
    public void simulate(){
 
        // 오리떼 생성.
        Flock flock = new Flock();
 
        // 고무오리 추가.
        flock.addDuck(CountingDuckFactory.GetInstance().createRubberDuck());
 
        // 거위 추가
        flock.addDuck(CountingDuckFactory.GetInstance().createGoose());
 
        // 오리떼 테스트.
        simulate(flock);
    }
}
 
cs



여기까지, 오리 시뮬레이터의 요구사항을 일단 마치려고 합니다.

이 곳에서 사용된 패턴은 Adapter, Decorator, Factory, Composite, Iterator 패턴으로 오리 시뮬레이터를 제작하기 위해 연속적으로 패턴이 사용되었습니다.

위의 언급된 예제에서는 일련의 패턴들이 어떻게 연속적으로 사용될 수 있는지 볼 수 있습니다. 


하지만, 이 것이 컴파운드-패턴은 아닙니다. 

여러 패턴을 동시에 사용하였다고 컴파운드-패턴은 아니고, 여러 패턴이 결합되어 특정 용도로 사용되는 것을 말하기 때문이죠.


지금부터는 대표적인 컴파운드-패턴인 MVC 를 살짝 보며, MVC 가 어떤 용도로 여러가지 패턴들을 사용하고 있는지 볼 것입니다.



6. MVC


MVC 는 Model-View-Controller 의 약자로 Model(데이터 및 비지니스 문제)과 View(화면) 사이에 제어하는 층(Controller)를 두는 것을 목표로 합니다.

 


세 가지의 각 컴포넌트는 다음과 같은 역할을 하고 있습니다.


- Model 


모든 데이터 및 비지니스 로직들을 포함합니다. 또한 View 와 Controller 에게 본인의 상태(데이터)를 제공할 수 있는 인터페이스 및 상태 변경 이벤트를 제공하지만, 보통은 다른 두 컴포넌트들에게 별 관심은 없습니다.


- View 


Model 을 표현할 수 있는 기능을 담당합니다. 

데이터를 표현하기 위해 Model 로 부터 상태를 가져오도록 하며, 사용자의 입력을 Controller 로 전달하곤 합니다.


- Controller


View 로부터 사용자의 입력 이벤트를 인지하며, Model 의 상태변경을 요청하거나 View 에게 표시변경을 요청합니다.



이 세 가지 컴포넌트를 패턴 관점에서 보면, 보편적으로 다음과 같이 나누곤 합니다.

 


MVC 의 목적은 Model 과 View 를 분리하는 것으로 이는 꽤 중요한 이슈입니다. 

이 원칙을 지킬 시, 이를테면 아래와 같은 이득을 볼 수 있습니다.


비지니스적인 문제는 일관될 수 있지만, 이를 표현하는 방법은 다양해질 수 있습니다. 

  CUI -> GUI 로 변경 시, 비지니스 문제를 변경할 필요가 있을지 고민해봐야 합니다. 

  혹은 일관된 데이터로 여러 뷰를 표현해야 할 수도 있죠.


표현하기 위한 방법이 분리되어 있다면, 

  비지니스 문제가 변경되도 표현방법만 알맞게 호환해주면 원활하게 개발할 수 있습니다.



이 것으로, Effective OOP 카테고리도 마무리입니다. 

올해부터 시작한 OOP 에 대한 정리가 끝나게 되니, 정말 감회가 새로운 것 같네요. :-) 


여기까지 정리를 할 수 있었고, 공부를 할 수 있게 해준 것은 꾸준히 제 블로그를 봐주고 스터디에 적극적으로 참여해주신 구성원분들 덕이라고 생각을 합니다.

(잊지 못할 것입니다. ㅠㅡㅠ)


또한, 이러한 기반 지식 및 자료를 제게 알려주신 교수님께도 감사합니다. ㅎㅎ


여기까지 공부를 마쳤다면, 저를 포함한 이 글을 읽는 모든 분들은 이 책을 한 권 마스터한 것입니다.

나름, 수준있는 예제를 들고왔기 때문에 이 책을 보며, 블로그를 다시 읽어주신다면 정말로 도움이 되지 않을까 생각이 드네요. 

(여러분들도 이제 패턴병에 걸릴 시간입니다. 히히히)


Head First Design Patterns 헤드 퍼스트 디자인 패턴
국내도서
저자 : 에릭 프리먼(Eric Freeman) / 서환수역
출판 : 한빛미디어 2005.09.04
상세보기



조금 부족한 면이 없는 글들이긴 하지만, 

이 글들을 읽은 모든 분들이 정말 멋있고 훌륭한 개발자가 되는 것에 조금이라도 도움이 되길 바랍니다. ^^







반응형
Posted by N'

안녕하세요. 블로그 주인장 N` 입니다.


10월 한 달을 그대로 태워버린 ESC 행사가 종료되었고, 프롤로그를 썼으니 에필로그 역시 작성하고자 다시 한번 펜을 들었습니다.


한번 살펴보니, 그래도 꽤 많은 글이 쓰여진 것을 확인했습니다.



나름, 이정도면 처음 써본 것 치고는 괜찮은 일지가 아니었을까 하는 생각이 드네요. ^^;



이번 ESC 를 준비하면서 오랜만에 즐겁게 개발해본 것 같았습니다.

만들고 싶은 것을 정하고, 좋은 사람과 개발을 한다는 것은 정말 행복했었습니다.


이번에 준비를 하면서 가장 중요하게 생각했던 것은 [완벽한 작품]이나 [반드시 1등]같은 일회성 보상은 아니었습니다.


정말 하고자 했던 것은 주니어 후배(필자도 주니어 level 이지만...)에게 가장 필요한 "나도 노력하면 무엇이든 할 수 있다는 자신감" 을 주는 것이었습니다.

(나름, 2017년 버킷 리스트 중 하나였습니다.)

 

 


어차피 실무에서는 열심히 구르고 욕을 먹으며 쓰라린 사회의 맛을 보겠지만, 일회성에 불과한 이 프로젝트의 경험 속에서 느꼈던 그 무엇이든지 늘보다 더 나은 자신이 될 수 있도록 도와주면 좋겠네요.. :-)

사실 이 것을 준다고 줄 수 있는 것은 아니고 팔로워들이 잘 따라와줘야 실현되는 이야기인데, 다행히 사람들은 잘 만났습니다. (하하.)

 

아 그리고 조금 자랑을 하자면 다행히 뭐라도 건져서, 상처뿐인 영광 아닌 성공경험이었습니다.

 

 

 

하지만, 단순히 주기만 할 것이라 생각했던 이번 프로젝트에서 저 역시도 받은 것이 있었습니다.

특히 행사 당일 발표 직전 단지 후배로만 봤던 동료에게 "할 수 있다" 라는 용기를 받았고, 이는 큰 무대에서 끊지 않고 발표를 마칠 수 있는 계기가 되었습니다.


여러가지로 2017년 중 가장 뜻깊은 경험이었습니다. 

마지막으로 우리 앱의 로고를 올리며, 이번 카테고리를 마무리 합니다. :-)

 

 

 


















반응형
Posted by N'

안녕하세요! miTHON의 위치 정보 및 안드로이드 서비스 단 담당 강현지입니다.


구글 지도 API 활용하기 두번째 포스팅으로 찾아왔습니다!!!


이번 포스팅은 지난번 포스팅에 말씀드린대로 현재 위치 받아오기실시간 트래킹에 대해 살펴보려고 합니다.



[ 4단계: 현재 위치 받아오기 ]


현재 위치를 실시간으로 받아와서 지도에 마커를 표시하는 코드를 아래와 같이 작성하였습니다. 


/**
* 현재 위치정보 조회.
*
* @return
*/
public Maybe<Location> get_현재위치정보() {
try {
// 권한 체크.
CheckUtil.Check(ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED, "[ACCESS_FINE_LOCATION] 권한체크 에러", Exception.class);
CheckUtil.Check(ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION)
== PackageManager.PERMISSION_GRANTED, "[ACCESS_COARSE_LOCATION] 권한체크 에러", Exception.class);

// 위치정보 조회.
LocationManager locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);

// 위치 정보 사용여부 조회.
{
CheckUtil.Check(get_위치정보_요청가능_여부(locationManager), "위치정보 사용불가능 상태", Exception.class);
}

// 네트워크 정보로부터 데이터 조회.
{
// 네트워크 정보로 부터 위치값 가져오기
if (isNetworkEnabled) {
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
MIN_TIME_BW_UPDATES,
MIN_DISTANCE_CHANGE_FOR_UPDATES
, this
);

if (locationManager != null) {
location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}
}
}

// GPS 정보로부터 데이터 조회.
{
if (isGPSEnabled) {
if (location == null) {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
MIN_TIME_BW_UPDATES,
MIN_DISTANCE_CHANGE_FOR_UPDATES,
this
);

if (locationManager != null) {
location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
}
}
}
}

} catch (Exception e) {
Log.e(MiRunConstDefinition.LOG_NAME, "위치정보 획득 실패", e);
}

return MaybeUtil.JustNullable(location);
}
/**
* 마커 그리기
*
* @param myLocationMaybe
*/
public void setDrawMaker(Maybe<Location> myLocationMaybe) {

MaybeUtil.Subscribe(myLocationMaybe,

location -> {

// Creating a LatLng object for the current location
LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());

CameraPosition cp = new CameraPosition.Builder().target((latLng)).
zoom(17).
build();

// Showing the current location in Google Map
mGoogleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cp));

// 기존 마커 삭제.
if (marker != null) {
marker.remove();
}

// 마커 설정.
MarkerOptions optFirst = new MarkerOptions().
position(latLng).
icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_marker_pink)).
title(MiRunResourceUtil.GetString(R.string.label_record_current_position));

marker = mGoogleMap.addMarker(optFirst);
},
() -> MiRunViewUtil.ShowToast(R.string.label_emergency_request_location_fail));
}






[ 5단계: 실시간 트래킹 ]


실시간 위치정보를 받아 트래킹 라인을 그리는 코드는 아래와 같습니다.


/**
* 지역 정보 목록 세팅.
*
* @param locationList
* @param myLocationMaybe
*/
public void setLocationList(@NonNull List<LocationConcreteVO> locationList, Maybe<Location> myLocationMaybe) {
// TODO khj1219

// 위도경도 목록 제작.
List<LatLng> latLngList = new ArrayList<>();
{

Observable.fromIterable(locationList).
forEach(vo -> {
// 위도 경도 처리.
latLngList.add(new LatLng(vo.getLatitude(), vo.getLongitude()));
Log.d(MiRunConstDefinition.LOG_NAME, String.format("초기화용 데이터 -> 위도 : %f, 경도 : %f, 시간 : %d",
vo.getLatitude(), vo.getLongitude(), vo.getTime()));
});

myLocationMaybe.
map(location -> new LatLng(location.getLatitude(), location.getLongitude())).
subscribe(latLngList::add);
}

// 마지막 라인 마커찍기.
{
if (!ContainerUtil.IsEmpty(latLngList)) {
setDrawMaker(myLocationMaybe);
}
}

AsyncBuilder.Create((FlowableEmitter<Double> emitter) -> {
if(latLngList.size() > 1){
Double result = getRecordDistance(latLngList);
emitter.onNext(result);
emitter.onComplete();
}
}).doOnNext(result -> {
// 거리 업데이트.
if(!Double.isNaN(result)){
setTextByDistance(result);
setTextByDonation(result * MiRunConstDefinition.CONST_DONATION.RUN_PER_REWARD);
}
}).run();

// 달리는 경로 polyline 생성.
drawRunningPolyline(latLngList);
}
/**
* 달리는 경로 polyline 생성(pink)
*
* @param runningPoints
*/
public void drawRunningPolyline(List<LatLng> runningPoints){
PolylineOptions polylineOptions = new PolylineOptions();
polylineOptions.color(MiRunResourceUtil.GetColor(R.color.color_path_pink));
polylineOptions.width(7);
polylineOptions.addAll(runningPoints);
mGoogleMap.addPolyline(polylineOptions);
}




이것으로 구글 지도 API 활용하기 5단계에 대해 모두 살펴보았습니다.


트래킹의 경우 테스트가 매우 힘들었지만 결과적으로 앱이 잘 나와서 매우 행복합니다.


드디어 발표가 내일인데 좋은 결과 있었으면 좋겠네요.


아직까지 열심히 디비 작업중이신 우리 유감독님 더불어 피티엥 혼신의 힘을 다하고 계신 남배우님 정말 존경하고 조금만 힘내세요!!!


우리 모두 화이팅 입니다~~


반응형
Posted by N'

안녕하세요! miTHON의 위치 정보 및 안드로이드 서비스 단 담당 강현지입니다.


MIDAS Research Festival의 해커톤인 ESC 주제로 저희는 마라톤 앱인 miTHON에 지도 관련 기능이 많습니다.


아래 기획서를 받고 구현을 위해 구글 지도 API를 활용하였고, 그 내용을 공유하고자 게시글을 올리게 되었습니다~


그럼 우선 기획서부터 살펴볼까요?



1) [그룹] 이벤트 생성하기 (경로 생성, 식수대 처리)





2) [개인][메인] 기록 시작




위 기획서를 바탕으로 구글 지도 API를 활용하였고, 그 기능을 5단계로 설명하려고 합니다.


해당 게시글에서는 3단계까지 소개하겠습니다.


[ 1단계: 맵뷰 띄우기 ]


1. 사이트에 가서 API Key를 발급 받습니다.

(레퍼런스: https://developers.google.com/maps/documentation/android-api/signup?hl=ko)

 


2. app 폴더 밑에 있는 build.gradle에 가서 구글 맵 API 컴파일 설정을 해줍니다. Google Play Services 전체 라이브러리(com.google.android.gms:play-services)를 추가하는 대신에 Google Maps Android API를 사용하기 위해 필요한 라이브러리만 추가해주는 것이 좋습니다.

그리고 SDK Manger에서 Google Play services를 설치한다. (설치 안하고 빌드시 에러와 동시에 설치하는 링크를 제공할 것입니다.)

// Google Maps Android API
compile 'com.google.android.gms:play-services-maps:11.0.2'
compile 'com.google.android.gms:play-services-location:11.0.2'

구글 플레이 서비스를 이용하려는 해당 뷰에 아래 코드를 넣으면 구글 플레이 서비스를 이용할 수 있습니다.

GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this);

 

3. AndoridManifest.xml에 가서 아래와 같이 meta-data 설정을 해줍니다.

<!-- 구글 API KEY 설정 -->
<meta-data
   
android:name="com.google.android.geo.API_KEY"
   
android:value=" 발급받은 API key'" />

 


4. 맵뷰를 구현할 화면의 onCreate 메소드에 SupportMapFragment를 이용해 지도 객체를 생성합니다. Fragment 객체를 지도를 처리할 Activity)에 추가한다. 프래그먼트에서 getMapAsync()를 호출하여 콜백을 등록합니다.

(레퍼런스: https://developers.google.com/maps/documentation/android-api/map?hl=ko)

@Override
protected void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);

   
// BitmapDescriptorFactory 생성하기 위한 소스
   
MapsInitializer.initialize(getApplicationContext());

   
SupportMapFragment mapFragment = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map));
   
mapFragment.getMapAsync(this);
}

 

5. 맵뷰를 구현할 화면에 OnMapReadyCallback 인터페이스를 구현하여 onMapReady 메소드를 오바라이딩 합니다. onMapReady은 콜백 메소드로서 GoogleMap 객체의 핸들을 가져옵니다.

(레퍼런스: https://developers.google.com/android/reference/com/google/android/gms/maps/OnMapReadyCallback )

public interface OnMapReadyCallback: Callback interface for when the map is ready to be used.

@Override
public void onMapReady(final GoogleMap googleMap) {

   
this.mGoogleMap = googleMap;

   
String coordinates[] = {"37.397446", "127.105714"};
    double
lat = Double.parseDouble(coordinates[0]);
    double
lng = Double.parseDouble(coordinates[1]);

   
LatLng position = new LatLng(lat, lng);
   
GooglePlayServicesUtil.isGooglePlayServicesAvailable(MapActivity.this);

   
// 맵 위치이동.
   
this.mGoogleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(position, 15));

}

 

위의 코드 실행결과 아래와 같이 지정한 위경도 좌표에 포커스가 된 맵뷰를 나타낼 수 있습니다.






[ 2단계: 화면에 마커 표시 및 마커 클릭 이벤트 ]


1. 맵뷰가 구현된 화면에 OnMapClickListener, OnMapLongClickListener, OnInfoWindowClickListener 인터페이스를 구현하여 OnMapClick, OnMapLongClick, OnInfoWindowClick 메소드를 오버라이딩합니다.

(레퍼런스: https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnMapClickListener)

 

2. 1단계에서 오버라이드한 OnMapReady 메소드의 구글 맵 지도에 클릭 이벤트를 세팅합니다.

@Override
public void onMapReady(final GoogleMap googleMap) {
    mGoogleMap = googleMap
;

    
mGoogleMap.setOnMapClickListener(this);
   
mGoogleMap.setOnMapLongClickListener(this);
   
mGoogleMap.setOnInfoWindowClickListener(this); //정보창 클릭 리스너(마커 삭제 이벤트)
}

 

3. 오버라이딩한 OnMapClick, OnMapLongClick, OnInfoWindowClick 메소드에 아래와 같이 클릭했을 때 나타날 이벤트를 설정합니다. 이때 MarkerOptions를 이용해 마커를 커스터마이징 할 수 있습니다. (마커가 표시될 위치, 아이콘, 타이틀 등 추가 가능)

 

- 지도를 클릭했을 때 "출발" 마커 생성

@Override
public void onMapClick(LatLng latLng) {
    MarkerOptions markerOptions =
new MarkerOptions();

       
//add marker
       
markerOptions.position(latLng);
       
markerOptions.icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_marker_pink));
       
markerOptions.title("출발");
       
mGoogleMap.addMarker(markerOptions).showInfoWindow();

       
// 맵셋팅
       
arrayPoints.add(latLng);
       
arrayMarkerOptions.add(markerOptions);
}

 

- 지도를 길게 클릭했을 때 polyLine 생성

@Override
public void onMapLongClick(LatLng latLng) {
   
// TODO
    //
마커를 길게 누를 경우 발생할 이벤트 작성
}

 

- 마커 info창을 클릭했을 때 해당 마커 삭제 이벤트

@Override
public void onInfoWindowClick(Marker marker) {
   
// step2에서만 삭제 가능 && 시작 위치는 삭제 못함
   
if((getViewPager().getCurrentItem() == 1) && !arrayPoints.get(0).equals(marker.getPosition())){
       
// 지도 초기화
       
mGoogleMap.clear();

       
// 리스트에서 마커 삭제
       
arrayPoints.remove(marker.getPosition());
       
arrayMarkerOptions.removeIf(v -> v.getPosition().equals(marker.getPosition()));
   
}
}


        




[ 3단계: 마커를 연결하는 Polyline 생성 ]


1. 마커를 연결할 polyLine 생성은 PolylineOptions를 사용합니다. (라인 색, 두께 등 지정 가능)

// polyline 생성
public void drawPolyline(){
    polylineOptions =
new PolylineOptions();
   
polylineOptions.color(Color.RED);
   
polylineOptions.width(5);
   
polylineOptions.addAll(arrayPoints);
   
mGoogleMap.addPolyline(polylineOptions);
}

 

2. [2단계] OnMapLongClick 메소드에 Polyline을 그리는 로직을 작성하여 마커를 길게 누를 경우 마커 리스트를 연결하는 polyLine을 만들 수 있습니다.

@Override
public void onMapLongClick(LatLng latLng) {
   
if(getViewPager().getCurrentItem() == 1) { // 2page
       
polylineOptions.addAll(arrayPoints);
       
mGoogleMap.addPolyline(polylineOptions);
   
}
}




우선 여기까지 해서 구글 지도 API 활용하기 포스팅을 마치겠습니다.


구글 지도 API는 처음 사용하시는 분들도 쉽게 사용할 수 있을 만큼 API 문서도 잘 정리되어있고, 예제 소스도 많이 있습니다.


생각보다 지도 관련 결과물이 잘 나와서 매우 뿌듯하네요~ 다음번 포스팅은 현재 위치 받아오기실시간 트래킹에 대해 소개해 드리겠습니다.


그럼 다음 포스팅에서 봐요~~


4-GRAM 화이팅입니다!!! 우리 조원 너무 좋아 ^______^!!


반응형
Posted by N'

안녕하세요 :) 저희 앱(miTHON)의 서버운영/DB/백엔드 담당 유덕형입니다.


MIDAS Research Festival 의 강력한 우승후보답게 저희는 상당한 수준의 개발 완성도를 자랑하는데요.


제가 담당한 부분을 이제부터 차근차근 설명을 드리는 시간을 가져보겠습니다.


먼저 저희 서버 아키텍처에 대해 설명을 드려볼까 합니다.


저희 서버는 AWS (Amazon Web Service) 에 올라가있습니다.

AWS 의 장점은 이루 말할 수 없을 정도로 많은데요. (자세한 내용은 저보다 구글을 참고해주세요.)


1. AWS 간략 설계 아키텍처



AWS 에는 각각의 기능에 최적화된 서버를 구성할 수 있도록 다양한 모듈들을 지원하는데요.

저희는 DB를 MySQL 을 사용하므로 Relation Database 를 구축할 수 있는 RDS 를 선택했습니다.

또한 파일 업/다운로드에 용이하며 정적 서버 호스팅도 지원하는 S3 를 사용했습니다. 

S3 의 장점은 바로 다운로드 URL 을 만들어 제공한다는 것인데요. 저희는 파일서버에 구성원 사진과 SNS 사진들을 저장하고 있습니다.


마지막으로 WAS 를 띄워놓는 EC2 인데요. 역시 서버는 우분투가 진리죠.


2. 백엔드 모듈 아키텍처



서버 백엔드 개발은 웹솔루션에서 사용하고 있는 Spring framework 기반 위에서 진행되었습니다.

보시는건 저희 Class Diagram 입니다. 생각보다 클래스가 많네요? 허허 이것이 저의 클라쓰일까요..

이 수많은 클래스들에 제 영혼이 4Gram 정도 들어간 것 같네요. 노동 착취의 향기가 느껴지지 않나요?


3. DB E-R Diagram


1) 논리모델




2) 물리모델


이번 긴 추서 연휴를 이용하여 저희팀의 DBA 이신 남궁용락 차장님에게 전수받은 기술로...! 

DB 설계를 진행해봤습니다. 저희 앱의 큰 틀이 되는 그룹, 그 하위의 이벤트 등 기획자님이 기획서를 잘 써주신 덕에

편안하게 설계를 진행할 수 있었습니다.

여기서 주목할 점은 member 정보를 독립적으로 가져감에 따라 다른 모듈과의 호환성을 조금 더 편리하게 만들었다는 것입니다.


4. 그리고 개발



착즙 세팅이 완료되었다면 그 기반 위에서 이제 저를 갈아넣기만 하면 됩니다.

코드가 알록달록한게 예쁘네요.


5. 마치며...


기술적인 내용은 전혀 없네요. 결과물 위주의 블로그를 써버림.


총 개발기간은 약 1개월 정도 되었네요. 추석기간동안 좀 더 열심히 할걸... 

ESC (Eat, Sleep, Coding) 의 취지에 맞게 1개월동안

퇴근하면 자기 전까지 밥먹고 개발하고 자고 의 for loop 속에서 살았습니다 ㅋㅋㅋㅋ


그럼에도 항상 웃음을 잃지 않고 정말 열정적으로 착즙한 강현지 사원과 남두현 사원님,

그리고 우리 앱의 기획과 디자인을 담당하신 성지윤 사원님! (군대간 유형주 사원도...)

감사하고 즐거운 시간이었다고 말씀드리고 싶습니다.


2017년이 마무리 되어가는 이 시점에, 그래도 한달 정도는 최고로 열심히 살았구나 하는

춥지만 뜨거운 시간이었습니다.


그럼 우리 마지막까지 힘내서 달려보자구요 !

반응형
Posted by N'

지난, 9월 28일 회의과정에서는 앱의 초기 기획안으로부터 Use-case 및 핵심 개념을 도출하게 되는 계기가 되었습니다.


이후, Rom(다른 팀원)님이 이슈관리 시스템에 올려주신 기획내용에 대한 추가 정리가 있었고, S님(기획자)께서는 액슈어라는 기획툴을 이용하여 스토리보드를 작성해주시고 계십니다.


어느정도 마무리가 되어가는 중, 고민거리가 생겼습니다.

그것은 바로 "디자인" 입니다.


현재, 저희 팀원 중에 디자이너는 없으며 또한 포토샵을 다룰 줄 아는 분이 없습니다.

(다룰 줄 모르면, 지금부터 배우면 되는 것이 아닌가? ㅡㅡ^)


이 사실은 추 후 앱 개발에 있어서, 아이콘 하나하나를 모두 구글링해서 찾아야함을 의미합니다.

구글링을 하더라도 배경이나 색감이 안맞으면 그림의 떡입니다. ㅜㅡㅜ


"하지만 이가 없으면 잇몸으로 음식을 씹고, 꿩이 없으면 닭을 사용하는 법"


오늘 살펴 볼 주제는 무려, "포알못(포토샵을 지 못하는 )이 아이콘 만드는 법" 입니다. 


제 개인적인 기준에서는 포토샵은 사실 디자이너나 퍼블리셔의 전유물이라는 편견이 조금 있습니다.

물론 다재다능한 분들은 잘 사용할 것이고, 시간만 충분하다면 저희들 중 누구라도 포토샵을 조금만 공부해서 할 수는 있겠지만 귀찮고 개발시간도 부족할 것 같습니다.


이럴 때 우리는 많은 직군들이 다룰 줄 아는 국민 툴인 PPT(파워포인트)로 아이콘을 만들 수 없을 까라는 고민을 하게 되었고, 이를 통해 아이콘을 제작할 수 있음을 깨달았습니다.


PPT 의 장점은 아래와 같이 많은 도형들을 제공해준다는 것입니다.

대부분 저희가 보는 아이콘들은 이 도형들로 제작이 가능할 것이란 생각이 듭니다.

 

 

 

또한 한가지 더 살펴볼 것은 구글의 디자인 색 가이드입니다.

구글 색 - 디자인 가이드


이 두 가지를 이용하여, 우리는 멋진 아이콘들을 제작할 수 있을 것 같습니다.

이번 포스팅에서 샘플로 만들어 볼 것은 게시판을 나타는 햄버거 목록 아이콘을 제작해 보려 합니다.


일단, PPT 에서 제공하는 도형들을 이용하여 아래와 같이 배치를 해줍니다.

요즘 좋아진 PPT 는 정말 좋아져서, 간격까지도 다 재줍니다.

 

 

 

아이콘의 배치가 다 끝났다면, 구글 색 가이드에서 원하는 색을 스크린 샷으로 찍습니다.

꼭 구글 색 가이드가 아니더라도, 원하는 색이 있다면 캡처를 시도합니다.

 

 

 

이렇게 찍은 스크린샷을 PPT 에 붙여넣고, 도형의 색을 우리가 원하는 색으로 맞춰줍시다.

PPT 의 색 피커를 이용하면, 쉽게 색을 맞춰 줄 수 있습니다.

 

 

 

이렇게 색을 맞춰줬다면, 모든 도형을 선택하고 그룹핑을 시켜줍니다.

 

 


이제 그룹핑된 아이콘을 [선택 -> 마우스 오른쪽버튼]을 누르고, 그림으로 저장을 눌러 줍시다.

 

 


그럼, 짜잔!

배경이 없는 우리가 원하는 PNG 아이콘이 생성되었습니다. 

 


사실, 전문적인 간격, 크기, 해상도에 따른 아이콘 배치 등의 모든 가이드가 맞춰지면 가장 베스트하겠지만, 열악한 환경이고 당장 급하다면 이 방법이 도움이 될 것이란 생각이 드네요. ㅎㅎ


이상, 오늘의 기획일지도 마무리입니다. ^^








반응형
Posted by N'

2017년 9월 28일 기획일지 


지난, 9월 26일 회의[아이디어 재점검]에서는 앱에 필요한 아이디어들은 대부분 확정이 되었습니다.

이 날의 회의가 궁금하다면, 아래 포스팅을 참고 :-)



이와같이 아이디어 재점검이 마무리될 때 쯤 새로운 동료가 생겼습니다.

Developer 로만 구성되었던 팀에 전문적으로 기획을 해주시는 분이 참여를 하게 되었습니다.

(나름, 팀의 밸런스가 맞춰지는 느낌이 듭니다. @.@)


이에따라, S님(편의 상, 계속 기획자님이라고 하기가... ^^; 이 분의 이니셜을 인용...)에게 현재의 기획 상태 및 정리 상황을 브리핑하게 되었습니다. 

"이미 확정이 되었으니, 이렇게 갑시다." 보다는 우리의 요구사항과 현재 프로젝트의 진행도는 이 정도임을 알리고, 전문적인 S님의 의견을 듣고 싶었습니다.


이에 따른 피드백은 간단하지만 묵직했습니다.


"앱 제작을 위한 리소스는 많이 준비가 되었지만, 정리가 조금 안 된 느낌? 정리가 조금 필요합니다."


이에 따라 업무시간을 마치고, 리소스에 대한 정리 및 스토리 보드를 작성하는 회의를 가지게 되었습니다.

이 날 회의에서는 앱 개발을 위한 리소스의 정리와 개념 정의 및 앱 사용 시나리오를 구상했습니다. 


1. 리소스 정리와 개념 정의


S님의 피드백 내용에 따라 가장 먼저 해야할 것은 기획의 정리였고, 앱의 목표를 먼저 다시 그려보는 계기를 가지게 되었습니다.

기존 기획안에는 개발목표라는 항목이 존재했고, 이를 정리해보았습니다.


기존의 개발목표는 아래와 같았으며, 

 

 



이에 대한 구체적인 세부기능들을 회의 전까지 아래와 같이 준비를 했었습니다.




이 안에서, S님의 주도하에 리소스들을 특정 카데고리로 나눠보는 작업을 하였습니다.

앱의 요구사항에 따라 사용자 측면(개인과 관리자)으로 분리하고, 각 사용자 관점에 따라 위에서 도출한 세부기능들을 추가해봤습니다.

 


이 과정을 통해, 관리자와 개인 사용자라는 개념 도출관점에 따른 기능 명세분류에 맞게 정의할 수 있었습니다.

이제는 앱 사용 시나리오를 살펴보면 좋을 것 같네요. ㅎㅎ



2. 앱 사용 시나리오에 따른 스토리보드


다음, 살펴볼 내용은 앱 사용 시나리오에 따른 스토리보드 입니다.

(원래는 S님께서는 혼자해도 된다고 했지만, 블로그 포스팅 및 혼자 끌어가는 것을 방지하고자 같이 참여했습니다. ^^;)


스토리보드 작성은 다같이, 종이 몇장 깔아두고 아래와 같이 진행을 했었습니다.

(빈약한 것 같지만, 원래 이런작업은 아날로그가 최고입니다. ㅋㅋㅋ)




이 과정에서는 개인과 관리자의 앱 사용 시나리오를 구상하였으며, 조금 더 상세 개념을 도출했습니다.


세부적인 기능에 대한 고민 중, 그룹이라는 개념이 한 개 더 생겼으며 그룹에는 마라톤을 진행 이벤트를 등록한다는 유즈케이스(use-case) 를 뽑아냈습니다.

중요한 것은 사용자에 따라 어떤 유즈케이스가 생기는 지를 파악하는게 중요합니다.


이에 따라 마라톤 행사 및 아젠다 등 관리적인 측면에 대하여 관리자가 그룹에 이벤트를 등록을 하며, 사용자가 기록이나 기타 게시물을 이벤트에 따라 기록할 수 있다는 시나리오와 함께 아래와 같은 스토리보드를 작성하게 되었습니다.

 

 

큰 뼈대는 제작이 되었고, 그에 따라 공지 사항 앨범 공유 등 부가적인 기능을 넣기가 더 쉬워진 듯합니다.

이 때 주의할 점은 부가기능을 넣기가 쉽다고 하더라도, 너무 많이 넣으면 프로젝트가 비대화가 될 수 있다는 점입니다.



오늘 회의에서는 요구사항에 대한 정리와 구체적인 기능 명세가 나온 듯 합니다.


일정과 자원 등 현실적인 면에서 기능과 우선순위를 고려해보았고, 이에 따라 S님께서는 플로우 차트를 먼저 작성해주시기로 하였습니다.

플로우 차트는 아래 주소에서 확인이 가능합니다.

[마이톤 기획노트 : http://xbu1aq.axshare.com/#g=1&p=4gram_기획노트]


아직까지는 프로젝트가 잘 진행이 되는 것 같습니다. 느낌상... ㅎㅎ

꼭 성공적으로 마무리가 되었으면 좋겠네요. @.@


다 같이 파이팅 합니닷!!






반응형
Posted by N'

2017년 9월 26일 기획 일지


제품을 출시하기 위해 해야할 가장 첫 번째는 역시 아이디어 검토인 듯 합니다.

잘 팔릴 제품이나 사용성에 좋은 제품을 찾아내야, 실제로 소프트웨어를 제작 후 "상처뿐인 영광"이 되는 상황을 방지할 수 있겠죠..


위의 취지에 맞춰, 4-GRAM(팀 이름)에서는 한달동안 진행할 프로젝트로 마라톤과 SNS 를 융합시킨 어플리케이션을 제작하려 하고 있습니다.

사내에서 진행하는 대회이기 때문에 사내에 도움이 되는 프로젝트가 좋지 않을지에 대한 생각이 들었고, "나눔" 활동을 지향하는 귀사에서 매년 2회 진행하는 마라톤 행사가 저희의 타겟이 되었습니다.


마라톤 행사에 대해 많은 것을 생각해보니 대회 전 연습 행사부터 본 행사까지 "익숙해진 불편함" 들이 많이 있었으며, 꽤 많은 기능을 제공해 줄 수 있을 것이란 생각이 들었습니다.


아이디어의 실효성에 대한 많은 토의가 있었으며, PPT 를 아래와 같이 브레인 스토밍에서 나온 모든 것을 정리하고 있습니다. 

 

하지만 이를 아무렇게나 정리하기 보다는 정해진 양식에 따라 한 번 작성을 해보자는 생각이 들었습니다.

특정하게 정해진 목차를 기술하고, 그에 따라 살을 붙이면 정리가 낙서가 되는 것을 방지할 수 있지 않을까 에 대한 생각이 들더군요.


이를 위해 아이디어 실효성에 대한 면이 부각, 기술적인 스택, 어필할 수 있는 요소를 찾아보며, 우리가 개발해야할 최종 이 무엇인지를 정리할 필요가 있다는 생각이 들었습니다.


이렇게 정해진 목차는 다음과 같았습니다.


[선정배경] -> [개발목적] -> [기술동향] -> [개발목표] -> [프로젝트 어필 홍보] -> [개발내용] -> [피드백] 


각 목차는 기획 단계에서 꽤 괜찮은 정리를 할 수 있었습니다.



1-2. 선정배경 & 개발목적


앞써, 언급한 이 어플리케이션 의 당위성, 실효성을 검증하기 위한 여러 생각을 할 수 있게 생각했습니다.

선정배경이 제대로 작성 되야 "상처뿐인 영광"이 되지 않을 것 같네요...


3. 기술 동향


벤치마킹 단계였습니다. 


마라톤 앱을 많이 안써봤기 때문에 여러 어플에 대한 조사를 많이 해봤으며, 특히 디자인면에서 많은 참고가 되었습니다.

이 단계에서는 아마 경험없는 저희같은 스타트업의 많은 교보재가 될 것으로 예상 됩니다.


(마라톤 앱이 보통 지도를 많이 사용하는데, 지도를 사용하는 어플도 이쁜 UI 를 작성할 수 있다는 것을 깨닫게 되었네요. ㅎㅎ)

 

 


4. 개발 목표


저희가 제작할 어플리케이션의 꼴입니다.


이 있는 것과 없는 것은 소프트웨어 제작에서 정말 큰 차이를 둔다고 생각하고 있으며, 1순위로 제작해야 하는 필수적인 기능 들을 정리해볼 수 있는 기회였습니다.

 

 


5. 프로젝트 어필 홍보


전략적인 사고로 어떻게 하면, 상을 받을 수 있을지에 대한 고민입니다.

사실상 저희에게 가장 중요한 면인데, 실무라고 한다면 어떻게 물건을 팔지에 대한 고민이 되겠군요.

 

 

 


7. 피드백


여러 사람이 회의를 하기 때문에, 각 사람들이 생각지 못한 결론은 많이 등장 하는 것 같습니다.

특히, 기존 목차에서는 "기능 소개" 라는 부분이 없었는데, 이에 대한 지적이 매우 적절 했었습니다.


언제나 마지막 목차에는 각 회의록에 대한 피드백이 있어야 할 것 같습니다. ^^;

 

 

1회 차 회의의 정리 내용은 이와 같았습니다.

정리해준 모든 자료들에 대한 내용인 [ESC 개발기획서 - 가칭 마이톤 (버전 0.2)] 를 이 곳에 첨부합니다.


첨부 : [ESC 개발기획서 - 가칭 마이톤 (버전 0.2)]






반응형
Posted by N'

안녕하세요. 블로그 주인장 Ndroid 입니다.

새로운 게시판을 신설하게 되어 글을 쓰게 되었습니다.


이번 게시판의 이름은 

"ESC 개발일지!!"


이 게시판은 사내에서 진행하는 개발자 페스티벌에서 진행하는 개발 활동에 대한 기록을 남기기 위해 만들게 되었습니다.


행사 카테고리의 이름은 ESC 로 해커톤 행사라고 합니다만, 미리 제작해도 된다고 하니 해커톤보다는 공개소프트웨어 공모전 같은 느낌이 납니다.... @.@

ESC 뜻은 (Eet Sleep Code) 의 약자인데, 예전에 몸담고 있던 SW 멤버십 단체티의 로고라 조금 더 뜻 깊네요. ㅎㅎ


이 카테고리의 작성 이유는 일지를 쓴다는 면에서 추억을 기억하기 위한 것도 있지만, 개발 일지를 작성함으로써 다음에 비슷한 일을 할 때 더 좋은 결과를 도출하기 위한 면으로 작성하고자 합니다.

(작성이 잘 되어야 하는데, 말이죠.... @.@)



아마, 작성하는 내용은 다음과 같을 것 같습니다.


1. 기획 일지


만들고자 하는 제품의 아이디어 및 도출 과정, 상품을 홍보하기 위한 방법 등의 모든 이야기를 기술해보고자 합니다.

(물론 아이디어는 노출되면 안되니, 행사가 종료될 때까지 비공개!)

아이디어가 주최측에서 공개를 했기 때문에 그냥 공개처리로 하겠습니다. ㅜㅡㅜ


이 과정이 굉장히 어려울 것 같습니다만, 잘 작성해보는 것으로......



2. 개발 일지


제작하는 소프트웨어에 담기는 많은 기술에 대해 작성해보고자 합니다.

많은 스타트 업 회사에서는 실제로 기술 스택 문서를 블로그로 많이 운영하는 것을 확인했고, 현재 개발에서도 많이 참고하고 있습니다.


이번에 만드는 제품에는 재미있는 기술을 많이 담아볼 생각입니다.

기술 뿐만 아니라, 현재 스터디(Effective OOP & FP)의 메인 주제인 개발 방법론 역시 담아볼 수 있으면 담아보는 것으로.....



한 달동안 참여하는 팀원(스터디 참여하는 분)들과 뜻깊은 경험을 공유하며 자신감을 주고 싶고, 그 경험을 잘 담았으면 좋겠습니다.


이번 게시판도 성공적으로 마무리 되길 기도합니다. 하하하하 

 

 





반응형
Posted by N'

안녕하세요! 

필명이 "성숙한 개발을 지향하는" 이라는 타이틀이 붙은 Ndroid 입니다. @.@


바로 패턴에 대한 리뷰를 하지 않고, 인사글을 쓰는 이유는 바로 아래 사진 때문입니다.



네, 맞습니다. 이번 글이 100회 기념 포스팅 기 때문이죠. :-)


하지만, 기념 포스팅 이라고 특별하진 않습니다.

지인 기준으로 이름과 댓글을 작성해주면, 커피를 사주는 정도까지만... ^^;

(101 번째 게시글이 작성 되었으니, 이벤트는 종료. @.@)


또한, 100회이기 때문에 조금 더 정성들여 작성하고자 합니다.

(그렇다고, 여태까지 쓴 글에 정성이 없는 것은 아닙니다. ㅡㅡ^)


이제, 본론으로...

이번 포스팅의 주제는 Too much complexity(매우 복잡)한 클래스를 리팩토링할 때 사용하기 괜찮은 방문자(Visitor) 패턴에 대해 알아보려 합니다.


방문자 패턴은 복잡한 데이터를 유지하는 클래스에 다양한 연산과정이 추가되어 클래스가 복잡해지는 것을 방지하는 패턴으로, 일련의 알고리즘과 객체 구조를 분리하는 것을 목표로 합니다.

방문자라는 이름이 붙은 이유는 [알고리즘을 구현한 객체]가 [복잡한 데이터들]을 방문하며 처리하도록 모델링 되었기 때문입니다.


사실, 이 패턴은 [OOP 스터디] 첫 장에서 다뤄봤습니다.



한 번 과제로 진행했던 내용을 다시 한번 리마인드 하며, 아래 내용들을 보면 더 좋을 것 같습니다.


이번 리뷰에서는 대표적으로 알려진 방문자 패턴의 예제와 제가 조금 변형한 N식 예제를 작성해보려 합니다.



1. 복잡한 모델과 행위들, 결국은 복잡한 클래스로...


클래스의 존재 목적은 데이터와 데이터를 이용한 행위의 결합이라 할 수 있습니다.


그렇기 때문에, 이제까지 진행했던 OOP 의 많은 포스팅에서는 SRP(단일 책임 원칙)를 강조하며 클래스들이 각자의 역할에 충실해야한다고 많이 언급 했었습니다.

특히 리팩토링을 다룬 포스팅에서는 메소드 추출, 메소드 이동 등 불필요한 책임을 가진 클래스 수정 내용까지 다뤄본 적까지 있습니다.



이와 관련된 많은 내용에 따라 결론을 조금 내본다면, 클래스 내부의 데이터(멤버변수, 속성)를 이용하는 행위는 대부분 해당 클래스의 메소드로써 존재해야할 것입니다.


아래 예제로 작성된 클래스 처럼 말이죠..


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
/**
 * 계정 클래스 정의
 *
 * Created by ndh1002 on 2017. 9. 10..
 */
public class Account {
 
    // 계정정보
    private String email;
    private String password;
 
    // 회원 기본정보.
    private String name;
    private Date birthDay;
    private String genderFlag;
 
    // 회원 연락처.
    private String mobile;
    private String mobileCompany;
    private String address;
 
    /**
     * 현재 입력된 계정정보의 유효여부를 출력.
     *
     * <pre>
     *     Account 의 모든 멤버변수의 유효여부를 검사.
     * </pre>
     *
     * @return
     */
    public Boolean checkValid() {
        // 유효여부 결과.
        // 각 유효성 체크에 따라 유효여부를 세팅하는 알고리즘을 수행.
        Boolean validYn = true;
 
        // 이메일 유효성 검사.
        {
        }
 
        // 비밀번호 유효성 검사.
        {
        }
 
        // 이름 유효성 검사.
        {
        }
 
        // 생년월일 유효성 검사.
        {
        }
 
        // 이하 모든 정보 유효성 검사.
 
        return validYn;
    }
 
    // SETTER, GETTER 는 생략.
}
 
cs


Account 클래스는 계정에 대한 정보를 가진 클래스로, Account::checkValid 라는 메소드를 가지고 있습니다.

(아마도, 회원가입과 같은 비지니스 문제에서 사용하던 클래스로 보입니다. @.@)


Account::checkValid 에서는 순차적으로 Account 클래스의 멤버변수의 유효여부를 체크하고 있습니다.

해당 메소드가 Account 클래스의 모든 멤버변수를 검사하는 로직이라 가정할 때, 이 메소드의 복잡도는 Account 가 소유하는 데이터만큼의 책임을 가지며 그 책임만큼 수정, 변화가 일어날 것입니다.


뭐, 좋습니다. 잘 작동만 하면, 일단 넘어갈 수 있는 문제입니다.

그러나 언제나 요구사항은 계속 추가되는 법! Account 에 대하여, 아래와 같은 추가 기능(checkValidType2) 이 요청되었습니다.


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
/**
 * 계정 클래스 정의
 *
 * Created by ndh1002 on 2017. 9. 10..
 */
public class Account {
 
    /**    
     * 현재 입력된 계정정보의 유효여부를 출력.
     *
     * <pre>
     *     Account 의 모든 멤버변수의 유효여부를 검사.
     * </pre>
     *
     * @return
     */
    public Boolean checkValid() {
        ...
    }
 
    /**
     * 이름, 생년월일, 연락처 정보의 유효성만 체크하는 메소드.
     *
     * <pre>
     *     각 필드의 유효성 검사 알고리즘은 Account::checkValid 와 동일.
     * </pre>
     *
     * @return
     */
    public Boolean checkValidType2() {
 
        // 유효여부 결과.
        // 각 유효성 체크에 따라 유효여부를 세팅하는 알고리즘을 수행.
        Boolean validYn = true;
 
        // 이름 유효성 검사.
        {
        }
 
        // 생년월일 유효성 검사.
        {
        }
 
        // 연락처 정보 유효성 검사.
        {
        }
 
        return validYn;
    }
    // SETTER, GETTER 는 생략.
}
 
cs


새로운 요구사항은 부분적으로 Account 의 정보를 유효성 검사하는 로직입니다.

기본적으로 Account::checkValid 의 알고리즘을 이용한다고 하였기 때문에, Account::checkValid 에서 특정로직을 메소드 추출 해야할 것 같습니다.


메소드 추출 리팩토링 후, 클래스의 상태는 아래와 같습니다.


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
84
85
86
87
88
89
90
91
92
public class Account {
    /**
     * 현재 입력된 계정정보의 유효여부를 출력.
     *
     * <pre>
     *     Account 의 모든 멤버변수의 유효여부를 검사.
     * </pre>
     *
     * @return
     */
    public Boolean checkValid() {
        // 유효여부 결과.
        // 각 유효성 체크에 따라 유효여부를 세팅하는 알고리즘을 수행.
        Boolean validYn = true;
 
        // 기타 검사 생략
 
        // 이름 유효성 검사.
        checkValidByName();
 
        // 생년월일 유효성 검사.
        checkValidByBirthDay();
 
        // 연락처 정보 유효성 검사.
        {
            // 휴대전화번호 검사.
            checkValidByMobile();
            // 통신사정보 유효성 검사.
            checkValidByMobileCompany();
            // 주소 유효성 검사.
            checkValidByAddress();
        }
 
        // 이후 검사 생략.
    }
 
    /**
     * 이름, 생년월일, 연락처 정보의 유효성만 체크하는 메소드.
     *
     * <pre>
     *     각 필드의 유효성 검사 알고리즘은 Account::checkValid 와 동일.
     * </pre>
     *
     * @return
     */
    public Boolean checkValidType2() {
 
        // 유효여부 결과.
        // 각 유효성 체크에 따라 유효여부를 세팅하는 알고리즘을 수행.
        Boolean validYn = true;
 
        // 이름 유효성 검사.
        checkValidByName();
 
        // 생년월일 유효성 검사.
        checkValidByBirthDay();
 
        // 연락처 정보 유효성 검사.
        {
            // 휴대전화번호 검사.
            checkValidByMobile();
            // 통신사정보 유효성 검사.
            checkValidByMobileCompany();
            // 주소 유효성 검사.
            checkValidByAddress();
        }
 
 
        return validYn;
    }
 
    /**
     * 이름 유효성 검사 처리.
     *
     * @return
     */
    public Boolean checkValidByName() {
 
    }
 
    /**
     * 생년월일 유효성 검사 처리.
     *
     * @return
     */
    public Boolean checkValidByBirthDay() {
 
    }
 
    // SETTER, GETTER 는 생략.
}
 
cs


Account::checkValidByName, Account::checkValidByBirthDay 등 Account 클래스 내부 멤버 변수 한 개 단위로 유효성 검사하는 메소드를 제공하며, 기존 Account::checkValid, Account::checkValidType2 등이 해당 메소드를 사용하도록 하고 있습니다.


많은 OOP 관련 내용에서 하라는 것과 같이 너무 많은 책임을 가진 메소드에 대하여 책임을 분리하였고, 그에 따라 특정 로직 수정에 있어서도 유지보수에 좋을 것이라 생각합니다. 


그러나 한 단위로 유효성 검사하는 메소드를 모두 제작해버리면, 클래스의 멤버변수만큼 메소드가 늘어날 것입니다.


그래요. 그것까지는 OK! 

그러나, 멤버변수들을 가지고 유효성 검사를 하는 것이 아닌 기타 다른 행위1, 행위2 등을 작성해야 한다면 어떻게 하죠?


결국 이 룰에 따라 한번 Account 클래스의 메소드 개수를 어림짐작해보죠..

최소 다음과 같습니다.


Account 클래스의 멤버변수 X Account 가 제공해야하는 기능 = Account 가 가질 최소 메소드 개수.


무언가 잘못되었습니다.. ㅜㅡㅜ



2. Single Dispatch vs Double Dispatch (방문자를 이용한 리팩토링)


앞서, 살펴본 예제에서 딱히 잘못된 것을 느끼지는 못하겠습니다.

사실 잘못된 것이 아닐 수도 있습니다. 심리적으로 많은 메소드를 가지는 것이 불편한 것이죠.... @.@


한번 알고있는 OOP rule 에 따라 고려해봐도, SRP 및 기타 리팩토링 지식을 철저하게 지킨 것 밖에 없습니다.

원칙에 따라 해본 것은 아래정도 될 것 같습니다.


- 클래스가 멤버변수를 가지고 있으니, 멤버변수를 이용한 행위는 해당 클래스에 작성합니다.


- 특정 메소드가 너무 많은 책임을 가지고 있으니, 메소드 추출을 하였습니다.


- 중복된 로직을 캡슐화하여, 재활용하고 있습니다.


그러나, 결과적으로는 많은 메소드들을 가진 복잡하다고 생각할 수 있는 클래스를 제작하게 되었습니다.

이와 같이 한 클래스에 모든 책임을 구현하는 방법을 Single Dispatch 고 하며, 대부분 로직은 이와 같이 구현이 됩니다.


여기에서 Dispatch 란, 메소드를 호출하기 위해 하는 일련의 과정을 말합니다.

조금 더 이 개념을 살펴보면, 컴파일 시점부터 어떤 메소드가 호출될지 정해진 적 디스패치(Static Dispatch)인터페이스의 참조에 따라 어떤 메소드가 호출될지 정해지는 동적 디스패치(Dynamic Dispatch) 존재합니다.


한 클래스에서 모든 책임을 구현하게 된다면, 보통은 정적 혹은 동적 디스패치단일적으로 일어나게 될 것입니다.

(Dispatch 가 한번만 일어나기 때문에, Single Dispatch 라 부릅니다. @.@)


갑자기 Single Dispatch 를 언급한 것은 Double Dispatch 역시 존재하기 때문입니다.

Double Dispatch 는 메소드를 호출하기 위한 행위가 두번 일어나는 것을 말하며,  오늘 포스팅에서는 Double Dispatch 를 이용하는 방문자 패턴으로 복잡한 클래스를 리팩토링해볼 것입니다.


리팩토링의 목표는 모델의 데이터 구조에 따라 제공해야하는 기능 중 한 가지를 모두 구현하는 클래스를 제작할 것이며, 이를 묶어주는 인터페이스 한 개를 만들 것입니다.


일단, 첫 번째로 만들어볼 것은 데이터 구조에 따라 기능을 구현한 클래스입니다.

여기에서 기능을 구현하는 클래스를 방문자(Visitor)로 칭하겠습니다.


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
/**
 * 유효성 체크 방문자 정의.
 *
 * Created by ndh1002 on 2017. 9. 11..
 */
public class CheckVisitor {
 
    /**
     * 이름에 대한 유효성 검사를 수행하는 메소드.
     *
     * @param name
     */
    public void check(Name name) {
        System.out.println("이름에 대한 유효성 검사.");
    }
 
    /**
     * 생년월일에 대한 유효성 검사를 수행하는 메소드.
     *
     * @param birthDay
     */
    public void check(BirthDay birthDay) {
        System.out.println("생일에 대한 유효성 검사.");
    }
    
    // 기타 다른 모델들은 생략...
}
 
cs


오버로딩을 통해, 각 데이터 모델에 따라 유효성 검사를 수행하는 클래스입니다.

즉, 이곳에서는 기존 Account 에 있어야 했던 데이터 단위의 유효성 검사로직이 존재합니다.



이제 이를 묶어줄 인터페이스 한 가지를 제작할 생각입니다. 

해당 인터페이스는 방금 작성한 CheckVisitor 를 파라미터로 받으며, 그에 따른 각 구현 클래스들은 자기자신을 넘김으로 CheckVisitor 의 어떤 check 메소드가 실행할지 결정하도록 할 것입니다.


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
/**
 * 유효성 검사 인터페이스.
 *
 * Created by Doohyun on 2017. 9. 12..
 */
public interface ICheckAble {
    void check(CheckVisitor visitor);
}
 
/**
 * 이름을 저장하는 클래스.
 *
 * Created by Doohyun on 2017. 9. 17..
 */
public class Name implements ICheckAble {
 
    @Override
    public void check(CheckVisitor visitor) {
        visitor.check(this);
    }
}
 
/**
 * 생년월일에 대한 모델.
 *
 * Created by Doohyun on 2017. 9. 17..
 */
public class BirthDay implements ICheckAble {
 
    @Override
    public void check(CheckVisitor visitor) {
        visitor.check(this);
    }
}
cs


이를 이용해, 새롭게 구현된 Account::checkValidType2 는 아래와 같습니다.

기타 다른 모델 클래스들은 살짝 생략했습니다. @.@


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
public class Account {
 
    private Name name = new Name();
    private BirthDay birthDay = new BirthDay();
    private Mobile mobile = new Mobile();
    private MobileCompany mobileCompany = new MobileCompany;
 
    /**
     * 이름, 생년월일, 연락처 정보의 유효성만 체크하는 메소드.
     *
     * <pre>
     *     각 필드의 유효성 검사 알고리즘은 Account::checkValid 와 동일.
     * </pre>
     *
     * @return
     */
    public Boolean checkValidType2() {
 
        CheckVisitor checkVisitor = new CheckVisitor();
 
        // 유효성 검사를 수행할 객체를 방문자가 순회하며, 알고리즘 수행.
        for (ICheckAble checkAble : Arrays.asList(name, birthDay, mobileCompany, mobile)) {
            checkAble.check(checkVisitor);
        }
 
 
        return false;
    }
 
 
    // SETTER, GETTER 는 생략.
}
cs


사용 로직을 살펴보면, 원하는 모델에 따라 목록을 만들어 유효성 검사를 체크하는 방문자를 사용하도록 하고 있습니다.


Account 를 복잡하게 만들었던 알고리즘들을 특정 클래스로 분리해냈고, 추 후 Account 에 또 다른 기능이 추가된다면 방문자 클래스를 만드는 것으로 더 복잡하지 않게 만들 수 있을 것 같습니다.

즉, Account 에는 데이터들과 방문자를 사용하는 메소드만 남게 될 것입니다.


이와 같은 방법을 앞써 언급한 Double Dispatch 와 연관지어 보지 않을 수가 없을 것 같습니다. @.@

로직에서는 ICheckAble::check 를 사용하기 위해 다형성에 의한 동적 디스패치가 한 번 일어났고, 실제 CheckVisitor::check 에서 오버로드된 메소드들 중 어떤 메소드를 사용할 것인지 결정하는 동적 디스패치가 두 번째로 일어납니다.


결과론적으로 복잡한 모델에 대하여, 데이터 구조와 알고리즘을 분리해냄으로 클래스가 더 복잡해지는 것을 방지한 듯 합니다.



3. 제안하는 새로운 방문자


Double Dispatch 를 이용한 방문자 패턴을 통해 클래스를 복잡하게 만드는 것을 방지한 것은 괜찮은 아이디어인 듯 합니다.


하지만, 위에 제시된 방법은 단순하게 원시타입으로 가지고 있어도 되는 필드를 방문자를 사용해야하는 이유로 객체화시키는 것은 매우 불편해 보입니다.


저는 이 점에 착안하여, 새로운 발상을 하게 되었습니다.

기존의 방문자 패턴은 모델이 알고리즘 방문자를 사용하는 방식이라 한다면, 반대로 알고리즘이 모델을 사용하는 방식으로 변경하면 어떨지에 대해 고민했습니다.

알고리즘이 모델로부터 알아서 사용할 데이터를 PULL 방식으로 가져와 수행하는 것이죠..


이를테면, 위의 예제를 아래와 같은 열거형 처럼 변경할 수 있을 듯 합니다.


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
/**
 * 계정의 유효성 정보 체커
 * 
 * <pre>
 *     전략패턴과 유사한 형태의 알고리즘 구현체.
 * </pre>
 *
 * Created by ndh1002 on 2017. 9. 17..
 */
public enum AccountChecker {
 
    NAME {
        @Override
        public void check(Account account) {
            System.out.println("이름에 대한 유효성 검사." + account.getName());
        }
    },
 
    BIRTHDAY {
        @Override
        public void check(Account account) {
            System.out.println("생일에 대한 유효성 검사." + account.getBirthday());
        }
    }
 
    // 이하 생략.
    ;
 
    public abstract void check(Account account);
}
 
cs


기존의 CheckVisitor 처럼 Account 에 존재하던 단일 개체에 대한 유효성 검사 로직을 해당 클래스에 구현하였습니다.


이런 형태의 열거형 사용 형태는 많이 익숙하지 않나요?

네, 이 것은 바로 전략패턴입니다.



이 전략 패턴을 이용하여, 다음과 같이 또 다른 형태의 Account::checkValidType2 를 만들어 볼 수 있을 것 같습니다.


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
/**
 * 계정 클래스 정의
 *
 * Created by ndh1002 on 2017. 9. 10..
 */
public class Account {
    // 회원 기본정보.
    private String name;
    private String birthDay;
 
    // 회원 연락처.
    private String mobile;
    private String mobileCompany;
    private String address;
 
    /**
     * 이름, 생년월일, 연락처 정보의 유효성만 체크하는 메소드.
     *
     * <pre>
     *     각 필드의 유효성 검사 알고리즘은 Account::checkValid 와 동일.
     * </pre>
     *
     * @return
     */
    public Boolean checkValidType2() {
 
        CheckVisitor checkVisitor = new CheckVisitor();
 
        // 유효성 검사를 수행할 객체를 방문자가 순회하며, 알고리즘 수행.
        for (AccountChecker checker : Arrays.asList(
                                            AccountChecker.NAME
                                            , AccountChecker.BIRTHDAY
                                            , AccountChecker.MOBILE_COMPANY
                                            , AccountChecker.MOBILE)) {
            checker.check(this);
        }
 
 
        return false;
    }
 
 
    // SETTER, GETTER 는 생략.
}
 
cs


이 방식은 Double Dispatch 활용을 위해 단일 개체 필드에 대한 모델을 제작할 필요가 없으며, 방문자 패턴의 목적처럼 데이터 구조와 알고리즘을 분리해낼 수도 있습니다.


또한 새로운 기능이 필요하다면, 또 다른 열거형을 제작하면 해결될 일이죠..


이 방식이 제가 새롭게 제안하는 N식 방문자 패턴입니다. @.@

(특별한 것을 바라셨다면, 죄송합니다. ㅜㅡㅜ)



이번에도 정말 긴 글이 된 것 같습니다. @.@


하지만, 이 패턴은 매우 뜻이 깊습니다.

OOP & FP 의 첫 번째 과제였고, 스터디에서 받은 질문을 해결해주며 엄청 기분이 좋았던 것이 기억나에요. 하하하하하..

(아마, 이 과제에 오므라이스가 걸려있었습니다. 잘 해결주셔서 지금도 감사합니다.^^)

또한, 제가 애용하는 전략패턴과 조합한 세 번째 방식은 최근에 개발한 것이라 조금 더 뜻이 깊습니다.


이 주제로 100회를 마무리할 수 있어서 행운입니다. ^^;


이제, Effective OOP 와 관련된 주제로 한 개의 포스팅을 남겨두고 있습니다. (Hello, MVC.)


한번, 마지막까지 파이팅 해보자구용. @.@







반응형
Posted by N'