Design Pattern
디자인 패턴이 얼마나 유용한지는 모르겠다. 설계를 하다보면 당연하게 사용하는 개념들인데 거창하게 이름만 붙인 것 같고, 몇몇 패턴은 현실의 복잡한 문제를 풀기에는 크게 도움이 안되는 것 같다. 경험으론 둘 중 하나였던 것 같다. 당연하거나 쓸모 없거나. 그래도 이해해 두면 나쁠 것은 없으니 정리 차원에서 UML로 한 번 그려봤다.
- Factory Method
- 물류 시스템을 만든다고 해보자. 일단 기본 물류 클래스를 설계할 것이다. 기본 물류 클래스의 인터페이스는 주문(); 이동(); 등이 될 것이고, 기본 물류 클래스에서 파생된 육상 물류, 해상 물류, 항공 물류 등의 클래스들도 만들 것이다. 그리고 이런 클래스들을 코드 내 필요한 부분에서 생성하여 전체 시스템을 만들 것이다. 그런데 이 클래스들의 인스턴스를 생성하는 코드가 전체 소스 코드에 흩뿌려져 있다면 어떨까? 물류 종류가 추가되거나 삭제될 경우 흩뿌려진 코드들을 모두 검토해야 하는 불편이 따른다. 따라서 인스턴스 생성을 한 군데에서 관리해줄 필요가 있다. 인스턴스 생성만 해주는 역할이므로 팩토리라는 이름이 어울릴 것이다. 팩토리는 언제나 기본 물류 클래스의 인터페이스만을 리턴하므로, 물류의 종류가 추가/삭제 된다고 하더라도 전체 코드에 영향을 주지 않는다. 따라서 코드의 유지 보수가 쉽다.
- 물류 시스템을 만든다고 해보자. 일단 기본 물류 클래스를 설계할 것이다. 기본 물류 클래스의 인터페이스는 주문(); 이동(); 등이 될 것이고, 기본 물류 클래스에서 파생된 육상 물류, 해상 물류, 항공 물류 등의 클래스들도 만들 것이다. 그리고 이런 클래스들을 코드 내 필요한 부분에서 생성하여 전체 시스템을 만들 것이다. 그런데 이 클래스들의 인스턴스를 생성하는 코드가 전체 소스 코드에 흩뿌려져 있다면 어떨까? 물류 종류가 추가되거나 삭제될 경우 흩뿌려진 코드들을 모두 검토해야 하는 불편이 따른다. 따라서 인스턴스 생성을 한 군데에서 관리해줄 필요가 있다. 인스턴스 생성만 해주는 역할이므로 팩토리라는 이름이 어울릴 것이다. 팩토리는 언제나 기본 물류 클래스의 인터페이스만을 리턴하므로, 물류의 종류가 추가/삭제 된다고 하더라도 전체 코드에 영향을 주지 않는다. 따라서 코드의 유지 보수가 쉽다.
- Abstract Factory
- 팩토리 메소드 패턴에서 팩토리들이 오브젝트들을 생성할 때 참조하는 클래스들이 어느정도 정형화 되어 있을 때, 이를 묶어 군을 만들 수 있다. 즉, 팩토리들이 생성하는 일련의 오브젝트 군들이 생기는 것이고, 이런 오브젝트들의 클래스들을 참조하여 추상화된 팩토리 인터페이스를 만들면, 그것이 추상 팩토리 패턴이다.
- 팩토리 메소드 패턴에서 팩토리들이 오브젝트들을 생성할 때 참조하는 클래스들이 어느정도 정형화 되어 있을 때, 이를 묶어 군을 만들 수 있다. 즉, 팩토리들이 생성하는 일련의 오브젝트 군들이 생기는 것이고, 이런 오브젝트들의 클래스들을 참조하여 추상화된 팩토리 인터페이스를 만들면, 그것이 추상 팩토리 패턴이다.
- Builder
- 객체를 구성하는 단계를 추상화시켜 빌더 클래스를 만들고, 감독 클래스에서 어떤 빌더의 어떤 단계를 선택할지 결정한다. 그에 따라 최종 객체를 얻는다. 객체를 구성하는 단계를 추상화 시켜서 그에 따라 다양한 객체를 만드는 경우가 글쎄.. 얼마나 있을까? 단계가 깊어질수록 변경에 취약해진다. 그리고 감독 클래스에서 구성 단계를 선택한다면, 추상화는 무슨 의미가 있을까? 오히려 구성 단계가 계속 추가되고 덩치가 커지면서 쓸모 없는 빌더가 될 가능성이 크다. 실제로 쓰는 경우를 본 적이 없는 패턴.
- Prototype
- 어떤 오브젝트의 복사본을 만든다고 생각해보자. 해당 오브젝트의 클래스를 가지고 새로운 오브젝트를 생성한 뒤, 필드 값들을 그대로 복사해줘야 한다. 여기에는 두 가지 문제점이 있다. 1. 접근할 수 없는 필드가 있을 수 있기 때문에 복사를 못할 수 있다. 2. 오브젝트 생성시, 클래스를 참조해야하기 때문에 생성 코드 부분이 해당 클래스에 종속된다. 이 문제를 해결하기 위해, clone()이라는 인터페이스를 갖는 프로토타입 클래스를 정의하고 이 인터페이스를 구현하는 클래스들은 자기 자신의 복사본을 만들어 리턴한다.
- 어떤 오브젝트의 복사본을 만든다고 생각해보자. 해당 오브젝트의 클래스를 가지고 새로운 오브젝트를 생성한 뒤, 필드 값들을 그대로 복사해줘야 한다. 여기에는 두 가지 문제점이 있다. 1. 접근할 수 없는 필드가 있을 수 있기 때문에 복사를 못할 수 있다. 2. 오브젝트 생성시, 클래스를 참조해야하기 때문에 생성 코드 부분이 해당 클래스에 종속된다. 이 문제를 해결하기 위해, clone()이라는 인터페이스를 갖는 프로토타입 클래스를 정의하고 이 인터페이스를 구현하는 클래스들은 자기 자신의 복사본을 만들어 리턴한다.
- Adapter
- 가장 흔하고 쉬운 패턴이지 싶다. 디자인 패턴들이 다 그렇지만 패턴이라고 하기도 좀 뭐하다. 암튼 클래스 A가 클래스 B의 x 인터페이스를 사용하도록 설계되었는데, A에서 y 인터페이스를 가진 레거시 클래스 C도 사용하고 싶다고 하자. 클래스 A에서는 B의 x 인터페이스 밖에 못쓰니, 클래스 C의 y 인터페이스를 B의 x 인터페이스로 변경해줘야 한다. 두 가지 방법이 있는데, B와 C를 상속받은 D클래스를 만들고, D의 x 인터페이스에 C클래스의 y 인터페이스를 넣어두는 방법 하나. B를 상속받은 D클래스를 만들고, 인자로 C클래스의 인스턴스를 받아 D의 x 인터페이스에서 C의 y인터페이스를 호출하도록 하는 방법. 이때 D클래스는 마치 어댑터와 같으므로 이를 어댑터 패턴이라고 한다.
- 가장 흔하고 쉬운 패턴이지 싶다. 디자인 패턴들이 다 그렇지만 패턴이라고 하기도 좀 뭐하다. 암튼 클래스 A가 클래스 B의 x 인터페이스를 사용하도록 설계되었는데, A에서 y 인터페이스를 가진 레거시 클래스 C도 사용하고 싶다고 하자. 클래스 A에서는 B의 x 인터페이스 밖에 못쓰니, 클래스 C의 y 인터페이스를 B의 x 인터페이스로 변경해줘야 한다. 두 가지 방법이 있는데, B와 C를 상속받은 D클래스를 만들고, D의 x 인터페이스에 C클래스의 y 인터페이스를 넣어두는 방법 하나. B를 상속받은 D클래스를 만들고, 인자로 C클래스의 인스턴스를 받아 D의 x 인터페이스에서 C의 y인터페이스를 호출하도록 하는 방법. 이때 D클래스는 마치 어댑터와 같으므로 이를 어댑터 패턴이라고 한다.
- Bridge
- 브릿지는 서로 다른 두 개를 연결하는 존재다. 그렇다면 왜 연결할까? 뭔가 함께 해야 할 일이 있기 때문일 것이다. Linux와 Windows에서 동작해야 하는 어떤 GUI클래스를 구현한다고 해보자. GUI클래스는 Linux던 Windows던 일관된 인터페이스를 가져야 그걸 사용하는 Application의 이식성이 높아질 것이다. 그래서 보통 GUI클래스의 인터페이스를 정의하고, 그 인터페이스를 상속받아 Linux용 GUI클래스와 Windows용 GUI클래스를 각각 만들게 된다. 그런데 이 때 문제 발생. GUI클래스가 세분화 된다면? 예를 들어 어린이용 GUI클래스가 필요하고, 노인용 GUI클래스가 필요하다면? 각각에 대해 모두 Linux, Windows 버전을 만들어야 한다. 뭔가 잘못된 느낌이 든다. GUI클래스의 구현은 2가지 역할을 해야한다. 하나는 GUI 자체의 로직. 하나는 OS에 의존적인 동작. 로직 구현은 추상화된 인터페이스에 따라 구현되었으나, OS에 의존적인 동작은 아직 단순 구현에 머물러있다. 따라서 OS의존적인 동작을 추상화된 OS 클래스로 만들고, 그에 따라 각각(Linux/Windows) 구현한 후 두 구현을 브릿지로 연결한다. OS 클래스가 하위 Layer 개념이므로, GUI 클래스에서 OS 클래스의 구현 참조를 가지도록 하고, 필요에 따라 OS 클래스의 인터페이스를 호출하도록 하자. 참조가 브릿지인 셈. 이렇게 되면 로직의 추상화는 GUI클래스가 담당하게 되고, OS의 추상화는 OS 클래스가 담당하게 된다. 당연히 확장,수정,이식에 유리해진다.
- 브릿지는 서로 다른 두 개를 연결하는 존재다. 그렇다면 왜 연결할까? 뭔가 함께 해야 할 일이 있기 때문일 것이다. Linux와 Windows에서 동작해야 하는 어떤 GUI클래스를 구현한다고 해보자. GUI클래스는 Linux던 Windows던 일관된 인터페이스를 가져야 그걸 사용하는 Application의 이식성이 높아질 것이다. 그래서 보통 GUI클래스의 인터페이스를 정의하고, 그 인터페이스를 상속받아 Linux용 GUI클래스와 Windows용 GUI클래스를 각각 만들게 된다. 그런데 이 때 문제 발생. GUI클래스가 세분화 된다면? 예를 들어 어린이용 GUI클래스가 필요하고, 노인용 GUI클래스가 필요하다면? 각각에 대해 모두 Linux, Windows 버전을 만들어야 한다. 뭔가 잘못된 느낌이 든다. GUI클래스의 구현은 2가지 역할을 해야한다. 하나는 GUI 자체의 로직. 하나는 OS에 의존적인 동작. 로직 구현은 추상화된 인터페이스에 따라 구현되었으나, OS에 의존적인 동작은 아직 단순 구현에 머물러있다. 따라서 OS의존적인 동작을 추상화된 OS 클래스로 만들고, 그에 따라 각각(Linux/Windows) 구현한 후 두 구현을 브릿지로 연결한다. OS 클래스가 하위 Layer 개념이므로, GUI 클래스에서 OS 클래스의 구현 참조를 가지도록 하고, 필요에 따라 OS 클래스의 인터페이스를 호출하도록 하자. 참조가 브릿지인 셈. 이렇게 되면 로직의 추상화는 GUI클래스가 담당하게 되고, OS의 추상화는 OS 클래스가 담당하게 된다. 당연히 확장,수정,이식에 유리해진다.
- Composite
- 재귀적 구조의 패턴이다. 가장 흔한 그래픽 클래스를 예로 들자. 버튼, 리스트, 윈도우 등이 있다고 하자. 버튼, 리스트의 그리기() 인터페이스는 그냥 구현하면 된다. 그런데 윈도우 클래스의 그리기()는 어떨까? 윈도우는 그 안에 버튼이나 리스트를 또 가질 수 있고, 그릴 때, 해당 요소들을 다 같이 그려줘야 한다. 가장 쉬운 방법은? 추상 그래픽 클래스에 그리기() 인터페이스를 두고, 버튼, 리스트, 윈도우는 모두 그래픽 클래스를 상속받도록 한다. 그리고 윈도우의 그리기()에서 가지고 있는 각 그래픽 클래스 오브젝트들의 그리기()를 호출해서 그려준다. 이런 Composite 패턴을 쓰면 원하는 기능을 재귀적으로 깔끔하게 구현할 수 있다.
- 재귀적 구조의 패턴이다. 가장 흔한 그래픽 클래스를 예로 들자. 버튼, 리스트, 윈도우 등이 있다고 하자. 버튼, 리스트의 그리기() 인터페이스는 그냥 구현하면 된다. 그런데 윈도우 클래스의 그리기()는 어떨까? 윈도우는 그 안에 버튼이나 리스트를 또 가질 수 있고, 그릴 때, 해당 요소들을 다 같이 그려줘야 한다. 가장 쉬운 방법은? 추상 그래픽 클래스에 그리기() 인터페이스를 두고, 버튼, 리스트, 윈도우는 모두 그래픽 클래스를 상속받도록 한다. 그리고 윈도우의 그리기()에서 가지고 있는 각 그래픽 클래스 오브젝트들의 그리기()를 호출해서 그려준다. 이런 Composite 패턴을 쓰면 원하는 기능을 재귀적으로 깔끔하게 구현할 수 있다.
- Decorator
- 어떤 base 오브젝트에 기능이 계속 적립식으로 추가된다면, 고려해볼만한 패턴이다. 커피 주문을 생각해보자. 기본적으로 커피는 원두를 짜면 에스프레소가 된다. 거기에 물을 더 넣으면? 아메리카노가 된다. 거기에 우유를 더 넣는다면? 카페라떼가 된다. 추상 커피 주문 클래스에 주문() 인터페이스를 만들고, 이를 상속받은 데코레이터 클래스를 만든다. 데코리에터 클래스는 다른 커피 주문 클래스의 인스턴스를 인자로 받아서, 주문() 인터페이스에서 해당 인스턴스의 주문() 인터페이스를 호출하도록 한다. 즉, 데코레이터 클래스는 다른 커피 주문 클래스의 주문() 구현을 사용 할 수 있게 된다. 이렇게 하면, 데코레이터 클래스를 상속받은 클래스들은 이전의 커피 주문 클래스의 주문() 구현에 “데코레이트”해서 주문()을 구현할 수 있게 된다.
- 어떤 base 오브젝트에 기능이 계속 적립식으로 추가된다면, 고려해볼만한 패턴이다. 커피 주문을 생각해보자. 기본적으로 커피는 원두를 짜면 에스프레소가 된다. 거기에 물을 더 넣으면? 아메리카노가 된다. 거기에 우유를 더 넣는다면? 카페라떼가 된다. 추상 커피 주문 클래스에 주문() 인터페이스를 만들고, 이를 상속받은 데코레이터 클래스를 만든다. 데코리에터 클래스는 다른 커피 주문 클래스의 인스턴스를 인자로 받아서, 주문() 인터페이스에서 해당 인스턴스의 주문() 인터페이스를 호출하도록 한다. 즉, 데코레이터 클래스는 다른 커피 주문 클래스의 주문() 구현을 사용 할 수 있게 된다. 이렇게 하면, 데코레이터 클래스를 상속받은 클래스들은 이전의 커피 주문 클래스의 주문() 구현에 “데코레이트”해서 주문()을 구현할 수 있게 된다.
- Facade
- 복잡한 인터페이스를 잔뜩 가진 상세 클래스들이 잔뜩 있다고 가정하자. 이런 클래스들을 가지고 전체 시스템을 꾸미려면, 인터페이스들이 너무 많아 복잡하다. 중간 수준의 추상화된 인터페이스가 필요하다는 얘기. 복잡한 인터페이스를 가진 클래스들을 목적에 맞게 묶어서 중간 수준의 추상화 인터페이스 클래스를 만들어야 한다. 뭐 당연한 얘기를 패턴처럼 얘기하는 것도 좀 웃기지만, 처음부터 시스템을 꾸미는 것이 아니라, 기존에 존재하는 여러 클래스들을 재사용해서 시스템을 꾸민다면 한 번쯤 생각해 볼 만하다.
- Flyweight
- 게임에서 사용되는 총알 클래스를 생각해보자. 총알의 좌표, 모양 이미지, 속도, 효과 이미지 등이 총알 클래스의 데이터가 될 것이다. 그런데 생각해보면, 각 총알마다 달라야하는 데이터는 좌표, 속도 등이고, 총알의 모양 이미지나 효과 이미지 등은 아마 대부분이 같을 것이다. 게임 내에서 사용되는 총알의 모양과 효과는 몇 개 없을 테니까. 따라서 총알 오브젝트가 모두 모양 이미지나 효과 이미지를 가지는 것은 낭비다. 총알 모양이나 효과 데이터를 다른 오브젝트에 생성해 두고, 총알 클래스에서는 참조만 하는 형태로 하는 것이 효율적이다. 이것도 써놓고 보니 당연한건데..암튼 이런 걸 플라이웨이트 패턴이라고 한다. 그리고 당연히 총알 모양이나 효과 데이터를 담은 오브젝트들은 수정 불가다.
- Proxy
- MMORPG 서버를 떠올려보자. 플레이어가 플레이 도중 득템하면 서버는 해당 아이템을 플레이어의 DB에 기록해야 한다. 실시간으로 기록하면 좋겠지만, DB에 실시간으로 무언가를 기록하는 것은 부하가 큰 작업이다. 이 문제를 해결하려면 어떻게 해야할까? 바로 DB Proxy를 두는 것이다. Proxy는 DB와 동일한 인터페이스(update, insert, delete, …)를 제공한다. 따라서 서버 입장에서는 DB인지 DB Porxy인지 신경쓰지 않고 실시간으로 인터페이스를 사용만 하면 된다. DB Proxy는 적절히 서버의 요청을 캐싱하고 있다가 주기적으로 실제 DB에 적용한다. 결과적으로 실제 DB에 걸리는 부하를 조절할 수 있다.
- MMORPG 서버를 떠올려보자. 플레이어가 플레이 도중 득템하면 서버는 해당 아이템을 플레이어의 DB에 기록해야 한다. 실시간으로 기록하면 좋겠지만, DB에 실시간으로 무언가를 기록하는 것은 부하가 큰 작업이다. 이 문제를 해결하려면 어떻게 해야할까? 바로 DB Proxy를 두는 것이다. Proxy는 DB와 동일한 인터페이스(update, insert, delete, …)를 제공한다. 따라서 서버 입장에서는 DB인지 DB Porxy인지 신경쓰지 않고 실시간으로 인터페이스를 사용만 하면 된다. DB Proxy는 적절히 서버의 요청을 캐싱하고 있다가 주기적으로 실제 DB에 적용한다. 결과적으로 실제 DB에 걸리는 부하를 조절할 수 있다.
- Chain of Responsibility
- 서버에서 사용자를 인증하는 기능을 구현한다고 해보자. 사용자의 인증은 여러 단계를 포함한다. ID/PASS가 맞아야하고, 세션 DB에 기록을 남겨야 하며, 짧은 시간에 여러번 인증을 시도하면 무시되어야 한다. 어느 하나의 단계라도 문제가 발생하면 인증은 실패하며, 그 이후 단계는 진행할 필요가 없다. 따라서 인증 요청을 처리하는 공통 인터페이스를 정의하고 각 단계가 이 인터페이스를 받아 구현하고 연결한다면, 동일한 요청을 처리하는 체인을 만들 수 있다. 사용자 인증 요청 처리 부분에서는 이 체인을 순환 하면서 성공인지 실패인지만 확인하면 된다. 실패하면 즉시 실패를 리턴하면 되고. 요청을 처리한다는 것이 책임(Responsibility)이므로 이는 책임의 연결이라고 할 수 있다. 사실 이것도 패턴이라기 보다는 상식적으로 생각할 수 있는 것인데, 암튼 이런 패턴을 Chain of Responsibility라고 한다.
- 서버에서 사용자를 인증하는 기능을 구현한다고 해보자. 사용자의 인증은 여러 단계를 포함한다. ID/PASS가 맞아야하고, 세션 DB에 기록을 남겨야 하며, 짧은 시간에 여러번 인증을 시도하면 무시되어야 한다. 어느 하나의 단계라도 문제가 발생하면 인증은 실패하며, 그 이후 단계는 진행할 필요가 없다. 따라서 인증 요청을 처리하는 공통 인터페이스를 정의하고 각 단계가 이 인터페이스를 받아 구현하고 연결한다면, 동일한 요청을 처리하는 체인을 만들 수 있다. 사용자 인증 요청 처리 부분에서는 이 체인을 순환 하면서 성공인지 실패인지만 확인하면 된다. 실패하면 즉시 실패를 리턴하면 되고. 요청을 처리한다는 것이 책임(Responsibility)이므로 이는 책임의 연결이라고 할 수 있다. 사실 이것도 패턴이라기 보다는 상식적으로 생각할 수 있는 것인데, 암튼 이런 패턴을 Chain of Responsibility라고 한다.
- Iterator
- 컨테이너(map, list, stack)에 상관없이 컨테이너 내 요소를 탐색할 수 있는 동일한 이터레이터 인터페이스를 제공한다. 이 인터페이스를 구현하여 탐색의 특징(예를 들면 깊이 우선 탐색, 너비 우선 탐색 등) 별로 이터레이터를 만들 수도 있다.
- Mediator
- GUI 객체들의 동작을 프로그래밍 한다고 생각해보자. 예를 들어 에디트 박스에 텍스트를 입력하고 버튼을 눌렀을 때, 해당 텍스트가 리스트에 추가되며 체크박스가 활성화 되어야 한다고 해보자. 한 두개라면 문제가 안되겠지만, 이런 동작들이 계속 늘어나게 되면, 모든 GUI 객체들이 그물처럼 의존관계를 가지며 얽히게 된다. 복잡도가 매우 증가하므로 당연히 좋지 않다. 관리도 안되고. 이 때, 중재자(Mediator) 역할을 하는 다이얼로그라는 것을 만들어서 모든 GUI 객체들이 이 다이얼로그를 통해서만 통신하게 하면, 의존 관계가 단순해지고 구조가 단순해 지는 효과를 얻을 수 있다.
- Memento
- A클래스는 B와 C클래스를 가지고 있다. A라는 클래스에서 Undo를 구현한다고 해보자. Undo를 구현하려면, 액션이 발생할 때마다 B와 C의 상태 값들을 계속 저장해야 하는데, 이는 A가 할 수 없다. B와 C클래스의 상태 값들은 캡슐화 되어 있어 접근이 안되기 때문에. 따라서 각 클래스의 상태 값은 스스로 저장해야 한다. 상태 값 저장을 담당하는 메멘토 클래스를 정의하고, A,B,C 클래스의 저장해야 하는 값들을 기반으로 메멘토 클래스를 상속받아 구현한다. 메멘토 클래스의 생성자에 저장해야 하는 값들을 전달한다. A,B,C클래스는 메멘토 A,B,C클래스에게 상태 값 저장을 위임한다. 이제 A클래스에서는 메멘토 A클래스를 통해 상태 값을 저장/불러오기 할 수 있게 되고, 특정 상태 값들을 가진 시점으로 되돌리는 Undo를 구현할 수 있다.
- Observer
- 그나마 자주 언급되는 디자인 패턴 중에 하나. 어떤 컴포넌트에서 발생한 이벤트를 여러 서로 다른 컴포넌트들에 전달하고 싶을 때 사용한다. 단순한 인터페이스를 가진 옵저버 클래스를 하나 만들고, 이벤트를 받고 싶은 클래스들은 모두 이 옵저버 클래스의 인터페이스를 상속받아, 이벤트가 발생했을 때의 동작을 각각 구현한다. 이벤트를 발생시키는 클래스는 이벤트를 받을 컴포넌트들(옵저버를 상속받아 구현한 클래스들의 인스턴스)을 등록할 수 있는 인터페이스를 제공한다. 이벤트가 발생하면, 등록된 옵저버 컴포넌트들의 인터페이스가 호출되며, 옵저버는 해당 인터페이스에서 이벤트를 처리한다.
- State
- State 머신 구현에 대한 거다. switch를 써서 State를 관리하면, State 동작을 추가하고 State 변경을 관리하는 것이 섞여 있기 때문에 복잡하다. State 변경 로직에 구멍이 생길 가능성이 커진다는 얘기. 따라서 State를 추상 클래스로 만들고, 각 State는 이 클래스를 상속받아 State에 해당하는 동작을 구현한다. 이와같이 State의 동작은 각 State 클래스가 구현하고 있기 때문에, State를 변경하는 주체는 State 객체의 전환만. 그러니까 State 변경에만 신경쓰면 된다. State의 동작과 변경을 분리하는 것이다.
- Strategy
- 네비 앱을 짠다고 해보자. 처음엔 자동차로 이동하는 경로만 출력하면 되었는데, 점차 도보 이동 경로, 자전거 이동 경로, 큰길로 가는 이동 경로 등 여러 종류의 출력이 요구되었다고 해보자. 달라지는 것은 경로 계산 알고리즘 뿐이기 때문에 네비 앱에 필요한 모든 인터페이스를 다 추가 구현할 필요가 없다. 따라서 경로 계산 알고리즘(전략) 인터페이스 클래스만 따로 떼어내서 인터페이스를 정의/구현한 뒤, 기존 네비 프레임워크에 끼워넣도록 디자인한다. 이것이 전략 패턴이다. 역시나 당연한 거다.
- Template Method
- 집 짓기의 단계를 나눠 추상화 시켜보면, 지반 다지기, 골격 올리기, 벽 쌓기, 창문 넣기, 지붕 쌓기 등으로 나눌 수 있다. 각 단계의 실제 구현은 자유롭다. 지반을 100미터로 다져도 되고, 5미터만 다져도 된다. 벽도 콘크리트로 바를수도 있고, 통나무로 바를 수도 있다. 변하지 않는 것은 추상화된 단계들이다. 이 단계들을 템플릿 메소드라고 한다. 말 그대로 메소드들의 템플릿이다. 프레임워크 개발에 있어 빠질 수 없는 부분인데, 경험적으로 잘 설계하기는 어렵다. 하나의 인터페이스도 변경되기 쉬운 마당에 이를 엮어놓은 프레임워크는 더더욱 변경에 취약할 수 밖에 없다.
- Command
- GUI에서 버튼이 눌렸을 때 동작은 어떻게 구현할까? 각 버튼의 이벤트 핸들러에 직접 동작을 구현할까? 그렇게 하면 각 버튼이 가지고 있는 동일한/비슷한 기능들이 점점 쌓이게 된다. 이를 피하기 위해 중복 제거하고, 함수로 묶고, 추상화 하고.. 이러다 보면 서서히 MVP/MVVM 패턴으로 가게 된다. 그러다가 더 일반화를 추구하다 보면 결국 커맨드 패턴으로 가게 된다. 간단히 말해, 어떤 동작을 요청/수행으로 분리하여 구현하는 것이다. 공통의 요청 인터페이스/수행(응답) 인터페이스를 정의하고, 이 인터페이스에 따라 요청 파라미터를 넘기고, 수행 부에서 넘어온 파라미터를 가지고 동작을 구현하는 방법이다. UI와 로직을 구분하는데 많이 사용되고.. 사실 뭐 많은 부분에서 이런 패턴을 사용한다. 이런걸 굳이 패턴이라고 부를 필요가 있나 싶다.
- Visitor
- 어떤 클래스 그룹이 있고, 그 그룹이 가져야하는 공통의 기능이 있다고 하자. 예를 들면 html저장() 과 같은. 그런데 html저장() 외에 xml저장()의 기능이 추가 되어야 한다고 해보자. 어떻게 구현할까? 각 클래스에 xml저장() 인터페이스를 추가하고 구현할까? 그룹이 너무 많고, 수정이 쉽지 않다면? 이 때에는 각 클래스가 visitor라는 클래스를 받아들이는 인터페이스를 갖게 하고, 넘어온 visitor에게 해당 역할을 위임하면 문제를 해결할 수 있다. visitor 클래스를 상속 받은 새로운 xml저장용 visitor 클래스만 새로 구현하고, 각 클래스들에게 이 visitor를 넘겨주면 된다.