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