최근 사내의 선배에게 소프트웨어 개발에 대한 이야기를 하던 중 다음과 같은 이야기가 있었습니다.


"개발은 혼자할 수 있지만, 소프트웨어는 다수에 의해 만들 수 밖에 없는 듯...."


작은 규모의 프로젝트라면 슈퍼 개발자 혼자서 요구사항의 범위를 모두 충족할 수 있지만,

어느정도 중소 이상 급의 규모의 프로젝트라면 협업은 불가피해 보입니다.

(사실, 개발 뿐 아니라 모든 일이 그런 듯.... ㅡㅡ^)


그렇기 때문에, 개발자들은 타인에 의해서 작성된 모듈을 많이 사용하며 또한 본인이 만든 모듈 역시 배포를 해야할 것입니다.


앞써, 우리는 타에 의해서 배포된 모듈을 본인이 작성한 인터페이스 형식으로 변경하기 위한 패턴을 배웠습니다. 

네, 맞습니다. 바로 Adapter 패턴입니다. :-)



이번에는 반대로 본인이 만든 어떤 소프트웨어의 복잡한 모듈에 대하여 [간략화 된 인터페이스] 를 제공함으로써, 라이브러리 사용자들이 쉽게 사용하며 이해할 수 있도록 도움을 주는 Facade 패턴에 대해 다뤄보려고 합니다.

(Facade 는 "건물의 정면"을 뜻합니다.)


Facade 패턴의 목표는 자주 사용하는 일련의 공통적인 작업에 대한 편의 메소드를 제공해주는 것이며, 이번 리뷰에서는 이해를 위해 간단한 코드를 제공할 생각입니다.


언제나처럼 첫 시작은 요구사항입니다.


1. 요구사항


신입 IT 기술 매니저인 당신은 Cafe 의 인프라를 관리하는 부서로 발령을 받았습니다.


이 부서에서는 Cafe 의 여러 시스템들을 관리하는 프로그램을 배포하고 있으며, 

주 목적은 Cafe 개업 시작과 종료 시 [전등, 영업기계(POSS), 커피머신 세팅, 음악볼륨 조절] 등의 인프라를 끄고 키는 것을 관리하는 시스템을 목적으로 합니다.


이 과정에는 일련의 방법 및 매뉴얼(예를들어 커피머신은 영업 시작 1시간 전에는 미리 켜야 합니다.)이 존재하며, 매번 고객사에 판매할 때마다 이 작업을 교육 시켜야 합니다.


OOP 지식의 전문가인 당신은 반복적인 룰(Rule)을 모든 모듈 사용자가 숙지할 필요가 없다고 생각하고 있으며, 개선방법을 고려해보고자 기존 모듈들을 살펴보고 적절한 방법을 찾고자 합니다.



2. 이미 만들어진 복잡한 API


모듈을 확인해본 결과,


모든 인프라(전등, POSS, 커피머신 등)들은 단순하게 개업 시작, 종료에만 사용하는 것이 아닌 여러 상황(Event)의 변화에 따라 해야할 행위들이 존재했습니다.

(개업 시작, 종료 역시 큰 범위에서는 Event 의 한 종류라고 생각할 수 있겠군요.)


예를들어, 살펴본 CoffeeMachine 인프라 클래스입니다.


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
/**
 * 커피 머신.
 *
 * Created by Doohyun on 2017. 7. 2..
 */
public class CoffeeMachine {
    /**
     * 커피 머신 스위치 올리기.
     */
    public void clickOnSwitch(){
        System.out.println("커피머신 스위치 올리기");
    }
 
    /**
     * 커피 머신 스위치 내리기.
     */
    public void clickOffSwitch(){
        System.out.println("커피머신 스위치 내리기");
    }
 
    /**
     * 스팀 기계 체크.
     */
    public void checkSteam() {
        System.out.println("스팀 체크");
    }
 
    /**
     * 에스프레소 필터 체크
     */
    public void checkEspressoShopFilter() {
        System.out.println("에스프레소 필터 체크");
    }
 
    /**
     * 커피콩 추가.
     */
    public void addCoffeeBean() {
        System.out.println("커피콩 추가");
    }
    
    // 이하 생략...  이 클래스의 행위는 너무 많음 ㅡㅡ^
}
 
cs



문제는 모든 인프라 클래스들이 행위가 너무 많다는 것이며, 

모든 기능이 해당 클래스가 책임져야 할 문제이기 때문에 메소드 추출 및 이동과 같은 리팩토링을 하는 것은 오히려 API 의 혼란을 가져올 여지가 있어 보입니다. 


이미 모든 고객사들은 특정 상황에 따라 문서 및 교육에 따라 아래처럼 모듈을 사용하고 있습니다.

 

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
/**
 * 스타벅스 카페
 *
 * Created by Doohyun on 2017. 7. 2..
 */
public class StarbucksCafe {
 
    private CoffeeMachine coffeeMachine;
    private MusicPlayer musicPlayer;
    private Poss poss;
 
    private String masterId = "MASTER_ID";
    private String masterPwd = "MASTER_PWD";
 
    public StarbucksCafe() {
        this.coffeeMachine = new CoffeeMachine();
        this.musicPlayer = new MusicPlayer();
        this.poss = new Poss();
    }
 
    /**
     * Event 1 : 카페 오픈.
     */
    public void open() {
        
        // STEP1 커피 머신 켜기.
        {
            this.coffeeMachine.clickOnSwitch();                     // 커피머신 스위치 켜기
            this.coffeeMachine.checkSteam();                        // 스팀 체크
            this.coffeeMachine.checkEspressoShopFilter();           // 에스프레소 샷 필터 체크
            this.coffeeMachine.addCoffeeBean();                     // 커피콩 추가
        }
 
        // STEP2 포스 켜기
        {
            this.poss.clickOnSwitch();                              // 포스켜기
            this.poss.inputIdentity(masterId, masterPwd);           // 포스 로그인
        }
 
        // STEP3 음악 켜기
        {
            this.musicPlayer.clickOnSwitch();                       // 음악플레이어 켜기
            this.musicPlayer.loginToMelon(masterId, masterPwd);     // 멜론 로그인
            this.musicPlayer.runMusic();                            // 음악켜기
        }
    }
 
    /**
     * Event 2 : 카페 종료.
     */
    public void close() {
        
        // STEP1 커피 머신 정리
        {
            this.coffeeMachine.checkEspressoShopFilter();           // 에스프레소 샷 필터 체크
            this.coffeeMachine.checkSteam();                        // 스팀 체크
            this.coffeeMachine.clickOffSwitch();                    // 커피머신 스위치 끄기
        }
 
        // STEP2 음악 끄기
        {
            this.musicPlayer.logoutFromMelon();                     // 멜론 로그아웃
            this.musicPlayer.clickOffSwitch();                      // 음악플레이어 끄기
        }
        
        // STEP3 포스 끄기
        {
            this.poss.logout();                                     // 포스 정산 및 로그아웃
            this.poss.clickOffSwitch();                             // 포스 종료
        }
    }
}
 
cs


STARBUCKS 카페의 경우 문서에 따라, 잘 정리하여 운영을 하고 있습니다.

문서가 잘 정리되어 있고, 고객사의 개발자들이 그에 따라 잘 개발해준다면 문제가 없어보입니다.

(위와 동일한 코드를 작성해주면 되니, 아무 문제가 없을수도 있습니다.)


하지만 위와 같은 복잡한 API 를 모든 개발사의 개발자가 알고 있어야 한다는 것은, 그만큼의 API 에 대한 문서 및 교육이 필요함을 의미합니다. 


또한, 우리가 제공하는 API 가 기능추가를 의도할 때(예를들어 새로운 infra 가 추가 되었고, 이는 모든 Cafe 의 카페 오픈 하는 비지니스에 포함이 되어야할 경우), 모든 고객사에게 이를 알려야 하며 잘 사용하고 있는지에 대한 검토 또한 필요할 수 있습니다.

(A/S 가 부실하면, 고객사를 잃을 수도 있겠죠? ㅡㅡ^)


즉, 고객사의 편의를 위한 새로운 방법이 필요할 수 있어 보입니다.

 


3. 포장(wrapping) 을 통한 편리한 API 사용.


프로그래밍 기법 중에 우리는 포장(Wrapping)을 하는 경우는 흔히 볼 수 있습니다.


대표적인 것이 Java 의 Number 관련(Integer, Double...) Wrapper 클래스들입니다. 

이 Wrapper 클래스들은 원시타입인 int, double 등에 대하여 Boxing 과정을 통해 클래스로써의 특성을 가질 수 있도록 해줍니다.


즉, 기존 어떤 상태를 특정 목적에 따라 포장하는 것을 Wrapping 이라고 칭할 수 있을 것 같으며, 우리는 이 과정을 알게 모르게 많이 배웠습니다.


- Adapter 패턴


이미 "제공되는 것" 과 "필요한 것" 의 차이를 극복하기 위해, 타모듈의 객체를 본인에게 유리한 인터페이스로 Wrapping 합니다.


- Decorator 패턴


주어진 상황에 따라 기존 객체에게 책임이나 속성을 덧붙이기 위해, 장식으로 대상객체를 Wrapping 합니다. 

이는 상속을 통해서만 책임을 확장할 수 있던 방법의 대안이 되었습니다.


Decorator 패턴에 대한 리뷰는 아래에서 참고! :-)



Facade 패턴 역시 Wrapping 을 사용하는 방법으로, 

복잡하게 제공할 수 밖에 없는 API 들을 일종의 매뉴얼화 시킨 객체를 제공함으로써 라이브러리 사용을 극대화시키도록 합니다.


즉, 매뉴얼을 코드화 시켰다고 생각하는 것이 좋겠군요.

StarbucksCafe 에서 제작하도록 지시했던 메뉴얼을 다음과 같은 Wrapping 클래스로 제공해보죠.


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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/**
 * 카페 운영을 위한 파사드 정의.
 *
 * Created by Doohyun on 2017. 7. 2..
 */
public class CafeOperateFacade {
    private CoffeeMachine coffeeMachine;
    private MusicPlayer musicPlayer;
    private Poss poss;
 
    private String masterId;
    private String masterPwd;
 
    public CafeOperateFacade(String masterId, String masterPwd) {
        this.coffeeMachine = new CoffeeMachine();
        this.musicPlayer = new MusicPlayer();
        this.poss = new Poss();
 
        this.masterId = masterId;
        this.masterPwd = masterPwd;
    }
    
    //  직접적인 컴포넌트 사용을 위해, GETTER 를 제공.
 
    /**
     * 카페 머신 출력.
     * 
     * @return
     */
    public CoffeeMachine getCoffeeMachine() {
        return coffeeMachine;
    }
 
    /**
     * 음악 플레이어 출력.
     * 
     * @return
     */
    public MusicPlayer getMusicPlayer() {
        return musicPlayer;
    }
 
    /**
     * 포스 출력.
     * 
     * @return
     */
    public Poss getPoss() {
        return poss;
    }
 
    /**
     * Id 출력.
     * 
     * @return
     */
    public String getMasterId() {
        return masterId;
    }
 
    /**
     * 패스워드 출력.
     * 
     * @return
     */
    public String getMasterPwd() {
        return masterPwd;
    }
 
    /**
     * Event 1 : 카페 오픈.
     */
    public void open() {
 
        // STEP1 커피 머신 켜기.
        {
            this.coffeeMachine.clickOnSwitch();                     // 커피머신 스위치 켜기
            this.coffeeMachine.checkSteam();                        // 스팀 체크
            this.coffeeMachine.checkEspressoShopFilter();           // 에스프레소 샷 필터 체크
            this.coffeeMachine.addCoffeeBean();                     // 커피콩 추가
        }
 
        // STEP2 포스 켜기
        {
            this.poss.clickOnSwitch();                              // 포스켜기
            this.poss.inputIdentity(masterId, masterPwd);           // 포스 로그인
        }
 
        // STEP3 음악 켜기
        {
            this.musicPlayer.clickOnSwitch();                       // 음악플레이어 켜기
            this.musicPlayer.loginToMelon(masterId, masterPwd);     // 멜론 로그인
            this.musicPlayer.runMusic();                            // 음악켜기
        }
    }
 
    /**
     * Event 2 : 카페 종료.
     */
    public void close() {
 
        // STEP1 커피 머신 정리
        {
            this.coffeeMachine.checkEspressoShopFilter();           // 에스프레소 샷 필터 체크
            this.coffeeMachine.checkSteam();                        // 스팀 체크
            this.coffeeMachine.clickOffSwitch();                    // 커피머신 스위치 끄기
        }
 
        // STEP2 음악 끄기
        {
            this.musicPlayer.logoutFromMelon();                     // 멜론 로그아웃
            this.musicPlayer.clickOffSwitch();                      // 음악플레이어 끄기
        }
 
        // STEP3 포스 끄기
        {
            this.poss.logout();                                     // 포스 정산 및 로그아웃
            this.poss.clickOffSwitch();                             // 포스 종료
        }
    }
}
 
cs


위와 같이, 공통적으로 사용할 법한 기능들을 간편한 API 로 제공하였습니다.

또한, 각 인프라 역시 직접적으로 사용할 수 있도록 Getter 메소드를 제공하고 있습니다.


공통적으로 사용할 법한 기능들은 각 Cafe 클래스에서 사용하고, 특수한 기능들은 알아서 직접적으로 모듈을 사용하라고 열어둔 셈입니다.


자, Facade 를 제공함으로써 각 고객사 모듈이 어떻게 변했는지 확인해볼까요?


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
/**
 * 스타벅스 카페
 *
 * Created by Doohyun on 2017. 7. 2..
 */
public class StarbucksCafe {
 
    private CafeOperateFacade cafeOperateFacade;
 
    private String masterId = "MASTER_ID";
    private String masterPwd = "MASTER_PWD";
 
    public StarbucksCafe() {
        this.cafeOperateFacade = new CafeOperateFacade(masterId, masterPwd);
    }
 
    /**
     * 카페를 오픈한다.
     */
    public void open() {
        cafeOperateFacade.open();
    }
 
    /**
     * 카페를 닫는다.
     */
    public void close() {
        cafeOperateFacade.close();
    }
}
cs


복잡했던, 모듈이 매우 심플(Simple) 해졌군요. :-)

복잡한 API 문서를 정성들여 제공하는 것보다, 쓰기 좋은 라이브러리를 제공해주는 편이 고객사 입장에서는 더 좋겠죠?


또한, 우리는 고객사의 코드를 관리할 수 있습니다.

예를들어 우리는 라이브러리의 CafeOperateFacade::open 을 수정하면, 모든 고객사들의 소스에 수정사항에 대한 영향을 줄 수 있습니다.


그렇지만 이는 사실 단순하게 좋다고만 볼 수는 없습니다. 

미치는 영향이 잘못하면 고객사들에게는 큰 피해가 갈 수도 있겠죠?



이번 리뷰에서 다룬, Facade 의 개념은 우리가 사용하는 많은 라이브러리에 적용되어 있습니다.


복잡한 Javascript 문법을 편리하게 하기 위하여 jQuery 가 생겼으며, JDBC 의 불편한 Mapping 과정 때문에 iBatis, Hibernate 등의 ORM 이 많이 사용되고 있습니다.


즉, 복잡한 라이브러리를 Wrapping 하여 편리한 API 만을 볼 수 있게함으로써 많은 개발자들의 개발기간을 감축(FACADE 의 최종 목적)시켰으며, 이는 OOP 의 개념에서 가장 큰 목적이 아닐까 생각을 합니다.


이 글이 도움이 되길 바랍니다. :-)











반응형
Posted by N'