지난 번 '객체지향 프로그래밍 도대체 무엇인가?' 포스팅에 이어 Head First의 Design Pattern 책을 보며 정리를 해보려고 합니다. 원문에 대해 번역을 하며 진행을 할 예정입니다. 저작권 관련 문제가 있다면 말씀해주시면 바로 관련 내용은 삭제하도록 하겠습니다.
Design Pattern 이 무엇인가?
많은 개발자들이 design pattern에 대한 같은 고민을 했을 것이고 또 그것에 대한 여러가지 해답에 대해 다룰 것입니다. 이런 것을 하기 전에 design pattern이 주는 장점과 디자인 패턴의 이론, 그리고 어떻게 디자인 패턴을 풀어나갈지 알아야 합니다. 패턴을 사용하기 위해 가장 좋은 방법은 현재 구현되어 있는 여러 다른 실제 소스를 보고 고민하고 나만의 것으로 만드는 것입니다. 코드 재사용 보다 경험으로 얻은 패턴을 재사용 해야 하는 것에 의미를 두어야 합니다.
아래 이야기를 통해 '다형성'과 '상속' 및 '인터페이스'에 대해 생각해봅시다.
이를테면, 어떤 게임을 만든다고 생각해봅시다. 이 게임은 오리가 수영을 하며 소리를 내는 기능을 가지고 있습니다. 전체적인 구조의 디자인을 잡아보면, 일반적인 디자인 패턴을 적용하며, '오리'라는 supre class를 만들고 다른 오리캐릭터들이 이 super class들을 상속받는 디자인으로 구현합니다.
super class Duck
quack()
swim()
display()
// 다른 오리들의 methods
이 Duck 클래스는 모든 오리들이 공통적으로 가지고 있는 '소리내기'와 '수영하기' 라는 메서드들을 가지고 있습니다. '화면에 보여지기'라는 method는 abstract 메서드로 이 super class를 상속받는 오리들마다 제각각의 다른 모습을 보이도록 구현합니다.
class ADuck extends Duck
display(){
// looks like A
}
class BDuck extends Duck
display(){
// looks like B
}
ADuck과 BDuck은 Duck 클래스를 상속받아 abstract 메소드인 display를 각자의 특성에 맞게 보여주게 됩니다. A와 B뿐만 아니라 다른 오리캐릭터를 생성할 때마다 Duck 클래스를 상속받아 만들어지겠죠?
A와 B는 Duck을 상속받았기 때문에 당연히 '울수있고', '물에뜰수있고' 생김새는 다르기 때문에 display를 오버라이드해서 각각 생김새 마다 다르게 정의를 해줄 수 있습니다.
이렇게 하던 중 오리의 행동 중 '날다'라는 것이 필요하다는 것을 깨닫게 됩니다. fly()라는 메서드를 추가하고, 또 다른 '고무오리'클래스를 추가 했습니다. 고무오리 역시 Duck을 상속받았기에 '울수있고', '물에뜰수있고', '보여지고', '날수'있도록 설계되어 있습니다. 하지만 '고무오리'는 실제로 날지도 못하고 울지도 않습니다!
바로 이 점이 메서드를 상속받아서 만들었을 때 생기는 문제점입니다.
서브 클래스에 코드가 중복이 된다는 점!
두번째 문제는 모든 오리의 행동을 다 적을수 없다는 점!
세번째 문제는 코드를 변경했을 때 혹은 새로운 메서드를 추가했을 때 다른 오리들이 영향을 받을 수 있다는 점!
네번째 문제는 오리를 만들어내는 시점에서 각각의 특징을 바꾸기 힘들다는 점!
입니다.
그렇다면, 이렇게 코드를 중복하지 않고, 각각의 오리행동을 쉽게 만들고, 코드 변경 시에 다른 오리들이 영향을 적게 받으며, 오리의 특성을 원하는 대로 바꿀 수 있는 솔루션이 무엇일까!?
1번 솔루션을 보자.
메소드의 선언만 가지고 있고, 구현은 되지 않은 인터페이스로 행동을 분리해보자.
Duck super class
+ display()
모든 서브 Duck 클래스들은, Duck이라는 슈퍼클래스를 상속받고, fly와 quack은 각각 원하는대로 인터페이스를 상속받아 쓴다고 생각해보자.
이렇게 되면, '울지 못하고 날지 못하는' 1개의 오리 때문에 나머지 오리들이 전부 2개의 인터페이스를 가져다가 구현을 해야만한다... 이건 아니다.
2번 솔루션을 보자.
현재 문제는, "변하는 부분"과 "변하지 않는 부분"을 분리하고, 일부만 날수 있거나 울수없게 만들어야 한다.
Flyable Interface
슈퍼클래스 Duck을 위와 같이 만들어보자.
Flyable, Quackable 이란 interface를 선언하고 있다. 그리고 performFly와 performQuack을 통해 서브클래스 오리가 울지 안울지 날지 어떻게 날지 인터페이스가 책임지게 한다. 공통되어 실행되어 지는 '행동'을 인터페이스로 만들고 인터페이스 가진 공통되지만 다른 클래스를 만든다.
중요한 부분은, performFly(), performFly() 두개의 메소드다!
위를 보면, 인터페이스를 상속받아 슈퍼클래스에게 '이렇게 울거에요' '이렇게 날거에요'라고 특성에 맞게 적어줄 수 있다.
이 것을 다형성이라고 한다.
정확히 위키에서는 이를,
프로그램 언어의 다형성(polymorphism; 폴리모피즘)은 그 프로그래밍 언어의 자료형 체계의 성질을 나타내는 것으로, 프로그램 언어의 각 요소들(상수, 변수, 식, 오브젝트, 함수, 메소드 등)이 다양한 자료형(type)에 속하는 것이 허가되는 성질을 가리킨다. 반댓말은 단형성(monomorphism)으로, 프로그램 언어의 각 요소가 한가지 형태만 가지는 성질을 가리킨다.
부모클래스가 다양한 요소를 가질 수 있도록 허락하게 되면, 코드가 짧아지진 않지만, 앞서 이야기했듯이 상속에 의한 재사용이 높고 한 곳을 수정해도 다른 곳이 영향을 받지 않는다.(자 위와 같이 구현을 하면 '만약 나는 행동'을 고쳐야 하면, FlyWithWings만 고치면 다른 곳에 영향을 주지 않는다.)
만약 "로켓트로 날아다니는 오리"를 만들어야 한다면, flyable이란 인터페이스만 가져다 쓰면 되게 된다!!
(물론 이렇게 확장성이 넓게끔 쓰려면 이해와 많은 연습이 필요하다)
위 패턴은 '스트래티지 패턴'이라고 한다 :)
스트래피티 패턴을 간단하게 요약해보면,
- "변하는 부분은 따로 떼어 분리시켜라"
- "달라지는 부분을 찾아내고 달라지지 않은 부분으로부터 분리시켜 캡슐화한다"
- "상속보다는 구성을 활용한다"
디자인 패턴의 정확한 패턴들은 모르지만, 왜 이 패턴을 써본 적이 없을까 곰곰히 고민해봤다.
음... 일단 게임이 아니고, 공통된 특성과 공통된 행동을 하는 모델들이 존재하는 프로젝트가 거의 없었던 것 같다. 음... 있는데 기억을 못하는건가...? 모델에게 행동을 지시하는 것에 대해 왜이렇게 부담이 가는지 모르겠다!
[보충내용]
스트래티지 패턴(Strategy Pattern)은 왜 스트래티지 패턴이라고 불릴까요?
Strategy는 '전략'이란 의미를 가지고 있습니다. 위 패턴의 좋은 점 중 하나는, 최초에 생성자에서 속성을 생성하고 변경할 수 없는 것이 아니라 중간에(runtime) (전략적으로) 속성들을 변경할 수 있는 점 때문입니다.
이럴 때에도 많이 쓰인다고 하는군요! 하드웨어 업체에서 위와 같이 어떤 행동에 따른 interface를 만들어놓고 쓰면, 각 드라이브 벤더업체들은 해당 interface와 하드웨어의 속성에 맞춰 행동을 정해놓으면, 각 드라이버마다 통일성있고 확장성있게 관리가 가능하죠!
'옛글 > 번역본' 카테고리의 다른 글
프로그래밍 Design Pattern 이해하기 - 6 커맨드 패턴 (0) | 2014.08.25 |
---|---|
프로그래밍 Design Pattern 이해하기 - 4 팩토리 패턴 (0) | 2014.08.14 |
Java Generic Programming Guide #1 (0) | 2013.11.14 |
안드로이드 아키텍쳐 (Analysis of the Android Architecture) #2-2 안드로이드 구조 (3) | 2013.10.25 |
Object-Oriented Programming Concepts 객체지향의 컨셉 (0) | 2013.10.18 |