안녕하세요. 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'