상세 컨텐츠

본문 제목

hide delegate, remove middle man, inline class

소프트웨어 공학

by ryujt 2013. 7. 3. 05:47

본문

규모가 크고 복잡한 문제를 쪼개서 해결하는데 OOP는 큰 도움이 됩니다.  하지만, 무조건 쪼개는 것만이 능사는 아닙니다.  너무 쪼개다 보면 오히려 전체를 가늠하기 어려운 코드로 변질 될 수 있습니다.  이럴 때 공식같은 것이 있어서 "어디까지 쪼개야 된다"하고 계산 할 수 있다면 좋겠지만, 그것은 불가능에 가깝습니다.


일단은 자신의 직관을 믿어야 합니다.  그것이 최선입니다.  그리고, 이후 리팩토링을 통해서 다듬어 가는 것이 정답입니다.  규모가 크고 복잡한 문제를 한 번에 해결하려는 것 자체가 욕심입니다.


이번 포스트에서는 객체를 쪼개서 접근하는 동안에 발생 할 수 있는 문제들을 살펴보면서 해결방법을 찾아보도록 하겠습니다.


[그림 1]


[그림 1]은 제가 만든 실시간 강의 시스템의 일부를 표현한 것 입니다.  저는 주로 어플리케이션의 핵심 기능들을 Core 클래스에 묶어서 사용합니다.  실제 시스템에는 상당히 많은 클래스와 메소드들이 있지만, 모두 생략하였습니다.


Core 문제는 외부에서 음성 출력 볼륨을 조절하고 싶은데, _AudioOut의 속성이 private이기 때문에 접근이 되지 않고 있습니다.  Core의 객체는 싱글톤으로 작성하며, Core.obj()로 접근하도록 하고 있습니다.


가장 쉬운 방법은 _AudioOut를 public으로 내보내는 방법입니다.


Core.obj().getAudioOut().setVolume(MAX);


하지만, 이러한 경우 너무 많은 정보들이 밖으로 노출되어 소스를 간략하게 유지하는데 방해가 될 수 있습니다.  또한, 외부에서 함부로 접근하면 안되는 정보들이 있을 수도 있습니다.  


예를 들어, 실제 AudioOut 클래스는 아래와 같이 음성 출력을 중단 할 수가 있는데, 이것이 의도치 않은 결과가 될 수 있기 때문에 외부에서 접근하는 것을 설계자인 저는 원하지 않습니다.


Core.obj().getAudioOut().stop();




Hide delegate


"Hide delegate"는 객체를 쪼깨서 사용했는데, 외부에서 쪼개어진 조각 객체의 일부를 접근해야 할 필요가 있을 때 행해지는 리팩토링 방식입니다.


Core 클래스에 setVolume() 메소드를 추가하고 해당 메소드에서 아래와 같이 코딩합니다.


public void setVolume(int aVolume) { ... }


그리고, 외부에서는 아래와 같이 호출합니다.


Core.obj().setVolume(MAX);


이를 통해서 외부의 사용자는 Core 내부를 알지 못하더라도, setVolume() 메소드만으로 원하는 작업을 할 수 있기 때문에 보다 나은 해결책이라고 할 수 있습니다.




Remove middle man


"Remove middle man"는 "Hide delegate"의 반대에 해당하는 리팩토링입니다.  처음에 문제를 제시했을 때처럼 _AudioOut을 public으로 내보내는 것입니다.  


Core.obj().getAudioOut().setVolume(MAX);


결국 설계자의 선택에 달린 문제입니다.  그러면, 선택 기준은 어떻게 될 까요?  일단 저는 "Hide delegate"를 기본으로 삼습니다.  그리고, 아래와 같은 경우에는 "Remove middle man"으로 변경합니다.

  • AudioOut 클래스 또는 Core 하부의 클래스의 정보가 너무 많이 Core에서 중계되고 있을 때
  • 중계를 위해서 추가된 정보(또는 메소드)가 Core 입장에서 너무 쌩뚱 맞을 때

그러면, 얼마나 많은 메소드가 노출되는 것을 많다고 할 수 있을까요?  정답은 없지만, 저는 각 하부 객체마다 최대한 3개를 넘어가지 않도록 하고 있으며, 두 개만 되도 고민을 합니다.


중계를 위해 추가된 메소드가 쌩뚱 맞은 경우는 마치, 한식집에서 햄버거 파는 경우라고 할 수 있습니다.  모든 클래스는 각자의 고유 임무에만 집중해야 합니다.  그래야 소스를 읽기 쉬워집니다.  Core의 경우에는 단순 묶음의 역할만 하고 있고 상당히 다양한 하부 클래스를 가지고 있기 때문에 중계 메소드를 계속 추가하는 것 자체만으로도 혼란을 가져 올 수 있습니다.  


이런 경우는 난감합니다.  "Hide delegate"도 "Remove middle man"도 적절하지 않다고 생각이 드는 경우가 꼭 생기기 마렵입니다.  이러한 경우에는 제가 따로 이름을 붙인 "Interface relay"를 사용합니다.




Interface relay


"Interface relay"는 http://ryulib.tistory.com/201 에서 한 번 다룬 적이 있습니다.  내부의 객체를 외부에서 접근 할 필요가 있을 때, 필요한 인터페이스만 외부에 전달하는 방식입니다.


[그림 2]


사용법은 아래와 같고 "Remove middle man"과 다를 것이 없습니다.  다만, getAudioOut()의 리턴이 AudioOut 클래스 타입이 아닌 IAudioOut  인터페이스 입니다.  따라서, AudioOut 클래스에 어떠한 public 정보도 IAudioOut에 추가하지 않으면 외부에서는 접근이 되지 않습니다.  "중계자를 거치지 않고 메소드를 실행 할 수 있으며, 불필요한 다른 요소들은 노출시키지 않는다"라는 조건을 만족 할 수 있습니다.


Core.obj().getAudioOut().setVolume(MAX);




Inline class 


"Inline class"는 가장 극단적인 방법으로 하부 클래스를 삭제하고 해당 클래스의 소스 코드를 모두 상부 클래스로 옮겨 오는 것 입니다.  "아!!  잘 못 쪼갰구나!"라고 깨달은 순간 취할 수 있는 방법입니다.  


잘 못 쪼갰다는 기준은 다음과 같습니다.

  • 하부 클래스의 대부분의 정보가 중계되고 있을 때
  • "하부 클래스" + "상부 클래스" 일 때 가장 의미 전달이 쉬울 때


하부 클래스의 메소드 개수가 10개인데, 10개 또는 상당 수가 상부에서 중계되고 있다면, "Inline class"를 고민 할 필요가 있습니다.


[그림 3]처럼 하부 클래스와 상부 클래스가 합쳐져 있을 때 의미 전달이 쉽다면, [그림 4]처럼 대부분 합치는 것이 맞습니다.


[그림 3]


[그림 4]





'소프트웨어 공학' 카테고리의 다른 글

The Core of HiMyTV Player  (0) 2013.07.13
움직이는 타켓을 멈추게 하라  (0) 2013.07.10
상속과 위임에 대한 가벼운 예제  (0) 2013.04.29
인터페이스 릴레이  (0) 2012.06.01
Jow Flow 작성의 기초  (0) 2011.12.14

관련글 더보기