제공 : 한빛 네트워크
저자 : Sean Moore
역자 : 채수원 / (블로그 doortts.tistory.com)
원문 : Exploring the Strategy Design Pattern


[역자 도입 설명]
본 글에도 나오지만 Strategy(스트래터지, 전략) 디자인 패턴은 알고리즘을 캡슐화하는 것이 기본 목적인 패턴입니다. 클라이언트의 특정 동작 방식을 상황에 따라 전략적으로 변경할 수 있게 한다는 의미에서 Strategy(전략) 디자인 패턴이라고 불리게 되었습니다. 그런데 사실 Strategy 를 ‘전략’으로 번역해 ‘전략 디자인 패턴’이라고 하면 다소 어색하게 들립니다. Strategy는 ‘스트래터지’ 이지 ‘전략’은 좀 그렇잖아! 라고 말하실 수 도 있다는 이야기 입니다. 왜 어색할까요? Singleton은 ‘싱글톤’이고, Factory 는 ‘팩터리’인 이유는 뭘까요? 자바의 메소드가 ‘방법’이라 불리지 않는 것과 일맥 상통한다 할 수 있겠습니다만, 디자인 패턴을 국내에 처음으로 소개한 번역서에서 해당 패턴의 이름을 번역하지 않고, 원문 영어 그대로 남겨놓는 식으로 번역자체를 유예해 버렸다는 것이 한 가지 큰 이유가 되었다 생각합니다. 오히려 잘 되었다 생각하실 수도 있으시겠지만, 개인적으로는 다소 아쉬움이 남는 부분입니다. :) 어쨌든, 본 번역 글에서는 Strategy가 패턴이름을 의미할 때에는 영어 원문으로, 그리고 캡슐화된 하나의 알고리즘이나 알고리즘의 선택을 이야기 할 때는 ‘전략’ 이라는 단어로 표기하였습니다. ‘전략’이란 말은 단어자체의 의미처럼 ‘어떤 일을 달성할 수 있도록 미리 세워놓은 계획’을 의미하며, 그 계획들 중 최고의 효과를 거둘 수 있는 안을 상황에 맞게 선택했느냐 못 했느냐가 전략이 유효했느냐 그렇지 않았느냐를 구분한다 하겠습니다. 그래서 다소 어색하게 들림에도 Strategy 패턴을 ‘전략’ 패턴이라 부르고자 하는 것도 같은 의미에서 입니다.
자, 도입이 다소 길었습니다. 이제부터, 벌거벗은 임금님의 옷도 볼 수 있을 것만 같은 열린 마음으로 ‘Strategy 디자인 패턴’에 대해 공부해 보도록 하겠습니다.

개요

이 글의 목적
이 글은, 당신이 Strategy(전략) 디자인 패턴에 대해 좀 더 잘 이해할 수 있도록 돕는 것을 목표로 한다. Strategy 패턴은 어떤 애플리케이션 내에서 변하지 않는 영역을 그렇지 않은 다른 부분들로부터 분리해 내기 위해 사용된다. 이 디자인패턴은 몇몇 기본 OOP 원칙들의 최상위에 자리잡고 있다. 이를테면, Strategy 패턴은 ‘구현보다는 인터페이스’ 라는 프로그래밍 개념을 사용한다. 또한, 상속보다 구성(composition, 역주1)을 선호한다. Strategy 패턴을 사용하는 이유는 어떤 클래스로부터 알고리즘을 추상화 하고, 그 알고리즘에 바탕을 둔 새로운 클래스를 만들기 위해서이다. 다형성(polymorphism)을 이용함으로써, 알고리즘들이 애플리케이션이 실행되고 있는 도중에 구성 클래스에 의해 변경될 수 있다.

(역주1) 구성(composition) : 흔히 ‘컴포지션’이라고 부른다. 특정 객체의 기능을 이용하기 위해서 해당 객체를 자신의 속성(attribute) 중 하나로 품어 안는 것을 말한다. 흔히 맴버변수(Field)로 다른 클래스 타입이 선언되어 있는 모습을 생각하면 된다.

디자인 패턴들의 해당 주제에 대해 내가 그랬던 것만큼 당신도 마찬가지로 흥미롭게 느꼈으면 한다.
자 그럼, 시작해 보도록 하자!

선수 지식

OOP 기초들
기본적인 OOP 기초들에 대해 확실히 아는 것이 본 기사를 이해하는 데에 있어 중요하다. Strategy 디자인 패턴은 OOP 핵심 개념들을 사용한다. Strategy 패턴은 OOP의 네 가지 주요 원칙들 중 세 가지에 의존한다.

(역주2) 여기서 OOP의 4대 주요원칙은 추상화, 상속, 캡슐화, 다형성을 말한다.

OOP 관련 링크:
만일 당신이 OOP 원칙들의 내용을 제대로 이해하고 있지 않다면, 다음 내용들에 대해 살펴보는 시간을 가져야 할 것이다.

ActionScript 3.0과 함께 하는 객체지향 프로그래밍
http://www.adobe.com/devnet/actionscript/articles/oop_as3.html

객체 지향
http://www.tricedesigns.com/tricedesigns_home/blog/2006/12/object-orientation.html
http:/www.tricedesigns.com/tricedesigns_home/blog/2006/12/object-orientation.html

수업: 객체 지향 프로그래밍 개념들
http://java.sun.com/docs/books/tutorial/java/concepts/

Strategy 패턴 샘플 예제들

본 기사는 두 개의 프로젝트와 함께 진행되며 모든 다이어그램들도 프로젝트 내에 함께 포함되어 있으며 다운로드가 가능하다.

AS3 Strategy (구현은 없음)
본 프로젝트는 아주 기초적인 Strategy 패턴을 정의하는 기본 AS3 프로젝트이다. 이 예제를 위한 실질적인 구현은 없다. 대신 전형적인 Strategy 패턴에서 사용되는 주요 클래스들에 대해 이해할 수 있도록 되어 있다. (추가적인 문서를 원할 경우 마찬가지로 압축파일의 as3-strategy-pattern-asdoc 디렉터리 내부를 살펴 보아라.)

AS3StrategyPatternExample.zip

데이터 매니저 클라이언트(DataManagerClient)
이 예제는 데이터를 저장하기 위해 사용되는 세 개의 서로 다른 메소드들이 어떻게 각자의 객체로 캡슐화되고 구성 클래스로 동적 할당되는지를 보여준다. 이 압축파일에는 두 개의 실행 가능한 Flex 애플리케이션이 들어있다.

DataManagerClient.zip

UML 다이어그램들
strategy-uml.zip

Strategy 패턴의 개념들

Strategy 패턴의 공식적인 정의
"4인방(Gang of Four)이 쓴 책(역주3)”에서 말하는 Strategy 패턴의 정의는 아래와 같다.
알고리즘의 군집을 정의하고, 각각을 캡슐화한 다음, 그 캡슐화한 알고리즘들을 교체 가능하게 만든다. Strategy (패턴) 은 특정 알고리즘을 이용하는 클라이언트로부터 알고리즘을 독립적으로 바꿀 수 있게 한다 – 4인방(GOF)의 디자인 패턴
이 정의는 패턴의 요소들(things)을 설명하는 걸로 시작한다. 그러나 우리는 본 기사에서 이 패턴에 대해 좀 더 깊이 살펴볼 예정이다. Strategy 패턴은 디자인 패턴 학습을 시작하기에 좋은 패턴이다. 팩토리(Factory), 데코레이터(Decorator), 컴포지트(Composite)와 이터레이터(Iterator)들은 Strategy 패턴과 함께 뒤이어 배우기에 좋은 패턴들이다.


(역주3) 4인방(Gang Of Four)으로 불리는 네 명은 Erich Gamma(현재 IBM distinguished Engineer로 활동 중), Richard Helm(현재 Boston Consulting Group에 근무 중), Ralph Johnson(일리노이 대학 컴퓨터학부 교수로 재직 중), John Vlissides(2005년 추수감사절에 44세를 일기로 생을 마감)를 말하며, 여기서 말하는 GOF의 책이란 그 네 명이 함께 쓴, IT 서적 역사상 가장 크게 성공했다고 일컬어 지는 책, 바로 “Design Patterns, Elements of Reusable Object-Oriented Software”을 지칭한다. 흔히 줄여서 “GOF책”이라고도 부른다. 참고로, GOF(Gang of Four)라는 명칭은 중국의 문화 혁명을 이끌었던 4명(모택동(毛澤東)의 부인 강청(江靑), 왕홍문(王洪文), 장춘교(張春橋), 요문원(姚文元) )을 지칭하던 단어로, 그들처럼 SW업계에서 개발의 혁명을 이끈 네 명이란 의미로 차용해 사용하게 되었다고 한다.

핵심 개념과 사용법
Strategy 패턴 뒤에 있는 개념을 이해하는 것이 몇 가지의 OOP 기본개념에 대한 이해를 증진시킬 것이다. Strategy패턴은 세 개의 핵심 OOP 개념인 “캡슐화, 상속, 그리고 다형성”으로 구성된다. 덕분에 Strategy 패턴은 해당 개념들을 학습하거나 배우기에 훌륭한 패턴이다. 이 패턴을 한 번 이해하면, 아마도 다른 프로젝트들과 특정 애플리케이션용 소스코드들 안에 있는 패턴을 알아볼 수 있게 될 것이다. Strategy 패턴은 어떤 클래스의 행동이 자주 변경되어야 하거나, 런타임 환경에서 행동이 바뀔 필요가 있을 때 사용된다. Strategy 는 상속을 여러 개 만들어야 하고, 하위 클래스들에 있는 메소드 몸체의 중복은 피하고 싶을 경우에 효과적이다. Strategy 디자인 패턴이 사용되는 또 다른 경우는 복잡한 조건문이나 분기 로직을 리팩터링 하기 위해서일 경우이다.

알고리즘을 캡슐화하기
Strategy 패턴 뒤에 있는 핵심 아이디어는 ‘한 클래스 안에 하나의 알고리즘을 캡슐화 한다’는 아이디어이다. 이들 알고리즘 기반 클래스들은 하나의 알고리즘 인터페이스의 구체적인 구현체들이 될 것이다. 이 아이디어는 알고리즘을 구현하고 있는 클래스들이 현재 사용되는 알고리즘을 동적으로 바꾸는 걸 가능하게 해주고, 그래서 구현 클래스가 제공하는 행동을 변경 가능하게 한다.

패턴 참가자들
Strategy 디자인 패턴의 주요 참가자들에 대한 개요는 아래와 같다.
  • Context 상위클래스 (Context superclass , 흐름상의 상위 클래스)
    모든 Context 하위 클래스들을 위한 메소드들과 속성들을 정의하고 해당 Context가 사용하는 strategy 인스턴스를 변경하는 메소드를 정의한다.
  • Context 하위클래스들 (Context subclasses, 흐름상의 하위 클래스들)
    전형적인 Strategy 디자인 패턴에서는, Context 클래스의 하위 클래스들이 생성될 것이다. Strategy 패턴이 사용되는 경우, 메소드의 구현내용이 하위 클래스들 사이에서 중복되진 않을 것이다. 대신, 메소드들은 그들 자신의 클래스들(혹은 “전략을 갖고 있는 클래스들strategies”)로 옮겨지게 된다.
  • 알고리즘 인터페이스 (Algorithm interface)
    알고리즘 인터페이스는 각각의 구체적인 strategy 구현체에서 이용가능한 메소드들을 정의할 것이다.
  • 구체화된 알고리즘 클래스들 (Concrete algorithm classes)
    교체 가능한 알고리즘 전략들의 집합체
  • 클라이언트 클래스(Client class)
    Context 클래스의 인스턴스와 알고리즘 구현체에 해당하는 인스턴스를 하나씩 생성한다. Context 인스턴스의 전략(strategy)을 담당하는 속성(property)에 앞서 만든 알고리즘 인스턴스를 할당한다.
핵심 패턴 다이어그램
Strategy 패턴의 순수한 형태는 아래와 같은 모습으로, 상세화된 구현이 없는 깔끔한 디자인 패턴이다. 기본적으로는 GoF 책에서 소개된 것과 동일한 다이어 그램이다. 본 글에서 소개하는 패턴의 구체화된 구현체들을 살펴 보고, 핵심 패턴을 되돌아 보는 데에 이 다이어그램이 도움을 줄 것이다.



OOP의 핵심과 Strategy
세 개의 기본적인 OOP 원칙들이 이 디자인 패턴 안에서 사용되고 있다. 그 세 개는 “캡슐화(encapsulation), 상속(inheritance), 다형성(polymorphism)” 이다. 당신은 이 패턴이 ‘상속보다는 구성(composition)’이라는 개념도 쓰고 있다고 지적할 텐데, 그게 OOP의 또 다른 핵심 개념이다.

데이터 매니저 예제

Strategy 패턴 구현 샘플
어떤 패턴에 대해 더 잘 이해하기 위해 코드 예제를 만들어 보는 것은 종종 좋은 생각이 될 수있다. 데이터 매니저 예제는 데이터를 저장하는 세 개의 서로 다른 “전략들(역주4)”을 정의하며 각각의 데이터 저장 알고리즘(혹은 전략)은 하나의 클래스 안으로 캡슐화 될 것이다. 모든 데이터 저장 전략들은 하나의 공통된 데이터 저장 알고리즘 인터페이스를 구현할 것이다. 그리고, 실제 데이터 저장 알고리즘들은 구체화된 전략 구현체들 안에서 구현될 것이다.

(역주4) 여기서 전략들(strategies)이란 전략(=알고리즘)을 담고 있는 클래스들을 지칭한다.

데이터 매니저(Data Manager) 클래스들
이 예제는 다음과 같은 클래스들로 구성되어 있다.
  • DataManagerClient
    strategy 패턴에서 사용되는 객체들을 생성하고 조립한다. setSaveDataMethod 메소드를 통해서 런타임 시에 Context 클래스들의 알고리즘을 바꿔치기 할 수 있는 옵션을 갖는다.
  • DataManager
    DataManager 클래스는 Context 클래스이다. 그리고 (Context 클래스가 그러하듯) 상위 클래스 DataManager의 변종쯤에 해당하는 하위 클래스(이를테면 ServerDataManager 클래스 같은 클래스)를 위한 클래스이다.
  • ISaveData
    이 인터페이스는 모든 구체화된 데이터 저장 전략 클래스들이 지켜야 하는 계약을 정의한다. 인터페이스 사용이 다형성을 가능케 하고, Strategy 디자인 패턴의 핵심이 된다는 점을 기억해라.
  • SaveDataToSharedObject
    이 클래스는 데이터를 Shared Object 형태로 로컬에 저장하는 알고리즘을 담고 있다. (참고: Shared Object 퍼시스턴스(persistence,역주5)를 위한 실제 코드는 본 예제에 포함되어 있지 않다) (역주5) Persistence (영속성, 영속성개체): 어떤 특정 값이나 상태를, 프로그램의 동작 순간 이외에도 영구적으로 유지할 수 있게 만들어 주는 시스템이나 개체. 쉽게 말하면 데이터를 저장하고 있는 파일(시스템)이나 DBMS 등이 이에 해당한다.
  • SaveLocalSQLiteData
    이 클래스는 데이터를 로컬 SQLite 데이터베이스에 저장하는 알고리즘을 담고 있다.(참고: SQLite 퍼시스턴스를 위한 실제 코드는 본 예제에 포함되어 있지 않다.) 이 알고리즘은 어도비 AIR 기반 애플리케이션에서만 사용 가능하게 될 것이다.
  • SaveServerSideData
    이 클래스는 데이터를 서버 사이드 데이터 퍼시스턴스(persistence) 매커니즘에 저장하는 알고리즘을 담고 있다. (참고: 서버 사이드 퍼시스턴스용 실제 코드는 본 예제에 포함되어 있지 않다.).
  • MessageLogger
    메시지 로깅 클래스는 예제에서 실행 정보 표시를 도와주는 매우 간단한 클래스이다.
데이터 저장 “전략들”
각각의 알고리즘을 (특정한) 하나의 클래스로 옮기는 것은 해당 알고리즘을 사용하는 어떠한 클래스로부터든, 알고리즘의 독립적인 업데이트를 가능케 한다. 이런 방식은, 여러 개의 하위 클래스들이 있는 상위 클래스를 갖고 있고, 각각의 서브 클래스들에 있는 알고리즘들용 메소드들은 내장하고 싶지 않은 경우에 유용하다.

인터페이스 사용으로 다형성 이루기
데이터 매니저 예제에서 ISaveData 인터페이스는, 모든 구현 클래스들에서 선언되어야 하는 메소드들을 정의한다. 다시 말해, 이것이 다형성의 핵심 개념 중 하나이다. ISaveData인터페이스의 구체적인 구현체들을 이용하는 클래스들, 다시 말해 구상 클래스들은 런타임 시에 사용하던 ISaveData 구현체를 다른 구현체로 변경할 수 있다.

세 개의 데이터 저장 전략
데이터 매니저 예제는 세 개의 구체화된 ISaveData 인터페이스 구현체들을 갖고 있다. SaveServerSideData, SaveLocalSQLiteData , 그리고 SaveDataToSharedObject 가 바로 그 세 개이다. 예제에서 이들 구현체들은 내용이 비어 있긴 하지만 데이터 저장 알고리즘들이 위치할 장소가 된다. 이 시점에서 Strategy 패턴의 강점이 명확해 지기 시작해야 한다. :) 이들 알고리즘을 활용하는 클래스들은, 원할 때는 언제든지 데이터 저장 전략을 바꿀 수가 있다.



전략 변경하기
데이터 매니저 예제에서 Context 클래스에 해당하는 클래스는 DataManager 클래스이다. DataManager 클래스는 setSaveDataMethod 메소드를 정의하고 있는데, 이 것이 Strategy 패턴을 구축하기 위해 필요한 또 다른 핵심 요소이다. 이를 통해 애플리케이션 동작 중에 DataManager의 인스턴스들이 데이터 저장 메소드의 ‘전략(=알고리즘)’을 변경할 수 있게 된다. 이것이 바로 인터페이스를 사용하는 이유이며, 또한 해당 인터페이스의 구체화된 구현체들(concrete implementations) 을 만드는 이유가 된다.

데이터 매니저 예제 실행하기


[DataManagerClient를 실행시킨 모습]

데이터 매니저 예제를 실행하면, 간단한 애플리케이션이 보여지고 거기에는 세 개의 라디오 버튼 컨트롤이 애플리케이션 뷰에 보여진다. 라디오 버튼들 중 하나가 선택될 때 클라이언트는 DataManager 인스턴스가 사용하는 데이터 저장 알고리즘을 변경한다. 예제에서 각각의 알고리즘 클래스는 TextArea 로 보내는 고유한 메시지를 갖고 있다. TextArea 에서 보이는 메시지는 Save 버튼을 누를 때 변경될 것이다. 애플리케이션을 실행해서 한번 테스트 해 보아라. 애플리케이션의 코드는 추후에 좀 더 자세히 살펴볼 예정이다.

DataManager 클래스 코드 살펴보기
DataManager.as 소스코드를 살펴보자.
package com.seantheflexguy.dataManager
{
 	import mx.collections.ArrayCollection;
 	
 	public class DataManager
 	{
  		
  		private var saveDataMethod:ISaveData;
  		
  		public function DataManager()
  		{
   		}
  		
  		public function setSaveDataMethod(newMethod:ISaveData):void
  		{
   			saveDataMethod = newMethod;
   		}
  		
  		public function save(dataCollection:ArrayCollection):void
  		{
   			saveDataMethod.save(dataCollection);
   		}
  
  	}
}
이 클래스는 앞서 실행한 애플리케이션 내에서 데이터 관리의 기반이 되는 클래스이다. 이어지는 예제에서 이 클래스가 어떻게 확장되어지고, 데이터 저장 행동방식이 어떻게 각자의 클래스로 분리되는지를 보게 될 것이다. setSaveDataMethod 메소드에 주목해라. 이 메소드는 newMethod라는 ISaveData 데이터 타입을 인수로 받아들인다. 데이터 저장 알고리즘의 실제 타입은 기존의 데이터 저장 알고리즘 중 하나가 될 수 도 있고, 새로운 알고리즘이 될 수도 있다. 실제DataManager 는 수정하지 않아도 된다는 사실을 명심해라. 마찬가지로, 하위 클래스들도 데이터 저장 알고리즘의 동작방식을 바꾸기 위해 수정을 가하지 않아도 된다. 알고리즘들은 교체가 가능하다. save 메소드는 데이터 저장 알고리즘의 save 메소드를 호출하고 저장할 데이터를 save 메소드의 인수(dataCollection:ArrayCollection)로 넘겨준다.

ISaveData 인터페이스 코드 살펴보기
ISaveData.as 에서 정의된 인터페이스는 단순하지만, 매우 효과적이다.
package com.seantheflexguy.dataManager
{
 	import mx.collections.ArrayCollection;
 	
 	public interface ISaveData
 	{
  		function save(dataCollection:ArrayCollection):void;
  	}
}
ISaveData 인터페이스에서 save메소드를 정의하는 것은 일종의 계약을 작성하는 것이다. 그리고, 그 계약이란 이 인터페이스를 구현하는 모든 클래스들이 반드시 지켜야 하는 계약을 의미한다. 이로 인해 구체화된 ISaveData 구현체들의 다형성 구현이 가능하게 된다.

SaveServerSideData 클래스 코드 살펴보기
ISaveData 인터페이스의 구체적인 구현은 아래와 같다.
package com.seantheflexguy.dataManager
{
 	import mx.collections.ArrayCollection;
 	import mx.utils.ObjectUtil;
 	
 	
 	public class SaveServerSideData implements ISaveData
 	{
  		public function SaveServerSideData()
  		{
   		}
  
  		public function save(dataCollection:ArrayCollection):void
  		{
   			MessageLogger.log(">>save: SaveServerSideData - "+
   					    "save data to a remote server, "+
   					    "perhaps a database.");
   			MessageLogger.log(">>save: dataCollection: n"+
   					    ObjectUtil.toString(dataCollection));
   			// ...
   		}
  		
  	}
}
ISaveData 를 구현한 클래스를 주의 깊게 보라. 실제적인 데이터 퍼시스턴스 알고리즘은 예제에서 생략되어 있다. (데이터 퍼시스턴스는 본 글의 범위를 벗어나므로 여기에서는 다루지 않는다.)

SaveLocalSQLiteData 클래스 코드 살펴보기
ISaveData 인터페이스의 또 다른 구체적인 구현모습은 아래와 같다.
package com.seantheflexguy.dataManager
{
 	import mx.collections.ArrayCollection;
 	import mx.utils.ObjectUtil;
 	
 	public class SaveLocalSQLiteData implements ISaveData
 	{
  		public function SaveLocalSQLiteData()
  		{
   		}
  
  		public function save(dataCollection:ArrayCollection):void
  		{
   			MessageLogger.log(">>save: SaveLocalSQLiteData - "+
   					    "save data to a local SQLite database. " + 
   					    "this strategy could only be used for "+
   					    "AIR based applications.");
   			MessageLogger.log(">>save: dataCollection: n"+
   					    ObjectUtil.toString(dataCollection));
   			// ...
   		}
  		
  	}
}
이 클래스는 데이터를 로컬 SQLite 데이터베이스에 저장하기 위해 필요한 알고리즘을 포함하고 있다. 물론, 이 알고리즘은 어도비 AIR 애플리케이션에서만 사용되어야 한다. 왜냐하면 SQLite는 Flex 애플리케이션에선 곧장 사용할 수 없기 때문이다. 다시 한번 이 부분에서 Strategy 패턴 뒤에 숨어있는 논리를 눈치채기 시작할 것이다. 데이터 저장 “알고리즘/전략” 중 하나는 애플리케이션이 “배포/온라인” 상태에 있을 때 사용되고, 또 다른 데이터 저장 “알고리즘/전략”은 애플리케이션이 “로컬/데스크탑” 상태일 때 사용된다.

SaveDataToSharedObject 클래스 코드 살펴보기
ISaveData 인터페이스의 구체적인 구현이 아직 하나 더 남아있다.
package com.seantheflexguy.dataManager
{
 import mx.collections.ArrayCollection;
 import mx.utils.ObjectUtil;
 	
   public class SaveDataToSharedObject implements ISaveData
   {
 	public function SaveDataToSharedObject()
  	{
   	}
  
 	public function save(dataCollection:ArrayCollection):void
 	{
 		MessageLogger.log(">>save: SaveDataToSharedObject - "+
 				    "save data to a local shared object.");
 		MessageLogger.log(">>save: dataCollection: n"+
 				    ObjectUtil.toString(dataCollection));
   			// ...
   	}
  		
  }
}
만약 데이터가 로컬에 저장되어야 한다면 이 구현체를 애플리케이션의 “배포/온라인” 버전에서 사용 할 수 있다.

DataManagerClient MXML 파일 살펴보기
DataManagerClient MXML 은 Strategy 패턴의 조각들을 한 곳으로 모아주는 실행 가능한 Flex 애플리케이션이다.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
	backgroundGradientAlphas="[1.0, 1.0]" 
	backgroundGradientColors="[#F5F5F5, #E9E9E9]">
	<mx:Script>
		<![CDATA[
		
	import mx.collections.ArrayCollection;
	import mx.events.ItemClickEvent;
			
	import com.seantheflexguy.dataManager.DataManager;
	import com.seantheflexguy.dataManager.ISaveData;			
	import com.seantheflexguy.dataManager.SaveDataToSharedObject;
	import com.seantheflexguy.dataManager.SaveLocalSQLiteData;
	import com.seantheflexguy.dataManager.SaveServerSideData;			
			
	private var dataManager:DataManager = new DataManager();
	private var collection:ArrayCollection = new ArrayCollection();
			
	private function radioGroupClick(event:ItemClickEvent):void
	{
		var saveDataMethod:ISaveData = event.item as ISaveData;
		dataManager.setSaveDataMethod(saveDataMethod);
	}
			
	private function save():void
	{
		collection.addItem({data:'D1', label:'data object 01'});
		collection.addItem({data:'D2', label:'data object 02'});
		collection.addItem({data:'D3', label:'data object 03'});
		dataManager.save(collection);
	}
			
		]]>
	</mx:Script>
	<mx:TextArea id="message" 
		width="414" height="204"/>
	<mx:RadioButtonGroup id="saveDataMethodRadioGroup" 
		itemClick="radioGroupClick(event);" />
	<mx:RadioButton groupName="saveDataMethodRadioGroup" 
		label="Save to server" 
		value="{new SaveServerSideData()}"/>
	<mx:RadioButton groupName="saveDataMethodRadioGroup" 
		label="Save to local SQLite Database" 
		value="{new SaveLocalSQLiteData()}"/>
	<mx:RadioButton groupName="saveDataMethodRadioGroup" 
		label="Save to local Shared Object" 
		value="{new SaveDataToSharedObject()}"/>
	<mx:Button click="save();" 
		label="Save" />
</mx:Application>
클라이언트는 데이터 저장용 ‘전략’을 한데 모아놓고, DataManager 인스턴스가 사용할 전략을 변경할 수 있는 시각적인 방법을 제공한다. 애플리케이션을 실행시켜서, 저장 알고리즘을 변경하기 위해서 각기 다른 라디오 버튼 중 하나를 선택한 다음, “Save” 버튼을 누를 때에, UI Form 에 각각 다른 로깅 메시지가 쓰여지는 것을 보아라.


[실행 초기 화면]


[버튼을 선택하고 Save 버튼을 누른 상태]

슈퍼클래스인 DataManager
DataManager 클래스의 하위 클래스 들은 또한 Strategy 패턴내에서 정의된 관계형식의 장점을 누릴 수 있다. 다음 예제인 Server Data Manager는 DataManager 의 하위 클래스들이 어떻게 생성될 수 있는지, 그리고 이미 만들어져 있고 정의되어 있는 서로 다른 데이터 저장 알고리즘들을 어떻게 이용할 수 있는지를 확실히 보여줄 것이다. 마찬가지로 또한, 미래에 새로운 전략들이 추가될 수도 있다는 점을 명심해라.

서버 데이터 매니저 예제

DataManager 의 하위 클래스를 만들기
이 샘플 시나리오에서는, 프로젝트의 고객으로부터 ‘데이터를 서버 측에 저장하는 기능을 갖고 있으며 그 이외에는 어떤 다른 일도 하지 않는 애플리케이션을 생성 하는 것’을 요청 받았다고 가정한다. 이런 경우가 바로 Strategy 패턴이 초점을 맞추고 있는 또 다른 부분이다. 우리는 프로젝트 고객의 요구사항을 만족시키기 위해서 DataManager 의 하위 클래스를 하나 만들 수 있다. 그 하위 클래스는 DataManager 클래스의 간략화 버전이 될 것이고, 비록 DataManager에 필요 없는 속성값 들을 담게 되긴 하겠지만, 런타임 시에 저장 방식을 교체하는 능력을 포함해서 우리가 DataManager 에서 원했던 기능들도 마찬가지로 갖게 된다.

서버 데이터 매니저 클래스들
본 예제는 다음과 같은 클래스들로 구성된다.
  • ServerDataManagerClient
    strategy 패턴에서 사용될 객체들을 만들고 조립한다. 예제에서 이 클라이언트는 전략들을 변경하진 않을 것이다. 하지만, 그렇게 할 수 있는 능력은 가지고 있다. 사실, 서버 쪽에 데이터를 저장하는 새로운 알고리즘이 만들어질 수 있고, 그 새로운 알고리즘이 setSaveDataMethod 메소드 호출을 통해ServerDataManagerClient 클래스에서 사용될 수도 있다. setSaveDataMethod 메소드는 새로운 서버 사이드 데이터 저장 알고리즘을 포함하고 있는 새로운 구체화된 전략 클래스의 인스턴스를 넘겨받게 될 것이다.
  • ServerDataManager
    ServerDataManager 클래스는 DataManager 의 하위 클래스이다. ServerDataManager 클래스는 구체적인 ISaveData의 구현 클래스의 인스턴스를 저장하기 위한 구성(composition)에 의존한다.
  • ISaveData
    이 인터페이스는 모든 구체화된 데이터 저장 전략 클래스들이 지켜야 하는 계약을 정의한다. 인터페이스 사용이 다형성을 가능케 하고, Strategy 디자인 패턴의 핵심이 된다는 점을 기억해라.
  • SaveServerSideData
    이 클래스는 데이터를 서버 측 데이터 영속성(persistence) 매커니즘에 저장하는 알고리즘을 갖고 있다. (참고: 서버 사이드 퍼시스턴스용 실제 코드는 본 예제에 포함되어 있지 않다.).
  • MessageLogger
    메시지 로깅 클래스는 예제에서 실행 정보 표시를 도와주는 매우 간단한 클래스이다.
서버 데이터 매니저 클라이언트
Strategy 디자인패턴을 구현하는 해당 클라이언트는 SaveServerSideData 객체를 생성하는 ServerDataManager 객체 의 인스턴스용 save 메소드를 설정할 것이다. 클라이언트는 향후 데이터 저장 전략을 바꿀수 있는 능력을 갖고 있다. 지금은 단지 SaveServerSideData 전략만을 사용할 예정이다.



ServerSideDataManager 클래스 코드 살펴보기
본 클래스는 상위클래스 DataManager 의 하위 클래스이다.
package com.seantheflexguy.dataManager
{
 	public class ServerDataManager extends DataManager
 	{  		
  		public function ServerDataManager()
  		{
   			super();
   		}  		
  		public function pingServer():void
  		{
   			MessageLogger.log(">>pingServer: ServerDataManager - "+
   					    "ping the remote server.");
   			//...
   		}		
  	}
}
저장 알고리즘을 수행하는 실제 코드가 상위 클래스인 DataManager에도, 하위 클래스에 해당하는 ServerSideDataManger 에도 들어있지 않다는 것을 눈치챘을 것이다. (두 클래스 안에 들어가 있는) 대신에, 알고리즘은 자기 자신에 해당하는 클래스로 옮겨져서는, 공통 인터페이스를 구현하고 있다. 이 공통 인터페이스는 DataManager 와 ServerSideDataManager 가 알고리즘을 사용하거나, 실행하고자 하는 알고리즘으로 교체하는 것을 원할 때면 언제든 가능하게 만들었다.

ServerSideDataManagerClient MXML 파일 살펴보기
ServerSideDataManagerClient MXML 은Strategy 패턴의 조각들을 한 곳으로 모아주는 실행 가능한 Flex 애플리케이션이다. 이 애플리케이션은 데이터가 오로지 원격 데이터 베이스에서만 영속성을 갖게 만들어 주는 DataManager 의 간략화 버전이다. 데이터를 서로 다른 종류의 원격 데이터 베이스에 저장하기 위한 새로운 전략들을 만들어 내는 것이, Strategy 디자인 패턴을 사용함으로써 어떻게 달성 가능해 질지에 대해 잠시 생각해 보는 시간을 갖자.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
	backgroundGradientAlphas="[1.0, 1.0]" 
	backgroundGradientColors="[#F5F5F5, #E9E9E9]"
	creationComplete="init();">
	<mx:Script>
		<![CDATA[
		
	import mx.collections.ArrayCollection;
	
	import com.seantheflexguy.dataManager.ISaveData;
	import com.seantheflexguy.dataManager.ServerDataManager;
	import com.seantheflexguy.dataManager.SaveServerSideData;
		
	private var serverDataManager:ServerDataManager = new ServerDataManager();
	private var saveDataMethod:ISaveData = new SaveServerSideData();
	private var collection:ArrayCollection = new ArrayCollection();
			
	private function init():void
	{
 		serverDataManager.setSaveDataMethod(saveDataMethod);
 	}
			
	private function save():void
	{
 		collection.addItem({data:'D1',label:'data object 01'});
 		collection.addItem({data:'D2',label:'data object 02'});
 		collection.addItem({data:'D3',label:'data object 03'});
 		serverDataManager.save(collection);
 	}
		]]>
	</mx:Script>
	<mx:TextArea id="message" 
		width="414" height="204"/>
	<mx:Button click="save();" 
		label="Save" />
	<mx:Button label="Ping Server" 
		click="serverDataManager.pingServer();"/>
</mx:Application>
이 애플리케이션은 데이터 매니저 클라이언트와 거의 똑같다. 하지만 이 애플리케이션에서는 데이터 저장 전략을 바꾸는 것이 전혀 의미가 없다는 점에 주시해라. 훗날 서로 다른 원격 데이터 베이스에 데이터를 저장하는 알고리즘 구현이 필요해 질 수도 있다. 혹은 이 애플리케이션을 어도비(Adobe) AIR 모드에서 로컬 SQLite 데이터 베이스에 저장 가능하도록 만들 필요가 생길 수도 있다. 애플리케이션이 스스로 생명이 다할 때까지 맞닥뜨리게 될 변경 사항들이란 결코 끝이 없고, 대비 한다는 것도 쉽지 않다. 다만, 디자인 패턴들을 사용해 당신의 코드들을 꾸준히 성장케 하고 발생할 수 밖에 없는 변경에 대해 준비해 놓도록 도울 수는 있다. 이 글의 현재 시점에서, 부디 당신은 이 특별한 패턴의 몇 가지 장점들에 대해 알아가기 시작하는 중 이길 바란다.

정리

한 곳으로 모으기
멀찌감치서 모든 것들을 전체적으로 되돌아 보면, 끌어안고 가야 하는 수많은 핵심 개념들이 보인다. 알고리즘을 분리시켜서 한 클래스 안으로 캡슐화 한다는 개념은 기본적으로 사용되는 개념이다. 또한 “변하는 것을 캡슐화해라”라는 말은, 전략 인터페이스와 구체화된 구현체들로 다형성을 사용하라는 것이다. Server Data Manager예제는 어떻게 하면 메소드 그룹을 상위 클래스의 메소드나 하위 클래스의 메소드로부터 깔끔하게 분리시킬 수 있는지를, 그래서 어떻게 메소드의 구체적인 내용들이 손쉽게 변경가능한지를 풀어서 보여준다.

요약
나는 이 글이 Strategy 패턴의 강력함에 대한 통찰과 당신이 작성하는 애플리케이션에서 좀더 많이 디자인 패턴들을 사용하게 하는 열린 창을 제공했으면 한다. 디자인 패턴의 이점을 갖는 애플리케이션 소스코드들은 수정하거나 유지하기에 종종 더 쉽다. 그리고 그런 코드들은 마찬가지로 미래의 변경에 좀 더 어려움 없이 대할 수 있다. 샘플 애플리케이션, 그리고 UML 다이어그램들을 반드시 다시 한번 살펴보는 시간을 갖길 바란다. 다이어그램들과 코드 예제들로 구성된 이 글은, 당신의 작업물 내에서 Strategy 패턴을 쓸 수 있도록 해 줄 것이다.

추가 정보

웹페이지:

Strategy 패턴
위키피디아(Wikipedia)
http://en.wikipedia.org/wiki/Strategy_pattern

Strategy UML 다이어그램 –GoF 버전
http://www.tml.tkk.fi/~pnr/GoF-models/html/Strategy.html

자바 디자인패턴 전략
www.fluffycat.com
http://www.fluffycat.com/Java-Design-Patterns/Strategy/

Strategy
www.dofactory.com
http://www.dofactory.com/Patterns/PatternStrategy.aspx#_self1

책들:

Design Patterns: Elements of Reusable Object-Oriented Software
(디자인 패턴: 재사용 가능한 객체 지향 소프트웨어의 요소들)
Addison-Wesley Professional - Erich Gamma, Richard Helm, Ralph Johnson, John M. Vlissides
http://www.informit.com/store/product.aspx?isbn=0201633612

Head First Design Patterns(헤드 퍼스트 디자인 패턴)
O'Reilly Media, Inc.: Eric Freeman, Elisabeth Freeman, Kathy Sierra, Bert Bates
http://oreilly.com/catalog/9780596007126/

ActionScript 3.0 Design Patterns
O'Reilly Media, Inc.: William B. Sanders, Chandima Cumaranatunge
http://oreilly.com/catalog/9780596528461/

Refactoring to Patterns(패턴을 활용한 리팩터링)
Addison-Wesley Professional: Joshua Kerievsky


[출처 : http://www.hanb.co.kr/network/view.html?bi_id=1581]

헤드퍼스트 디자인 패턴 책에는 오리를 이용하여 스트래티지 패턴을 설명하였습니다. 스트래티지 패턴은 OOP의 중요한 원칙 중 하나인 “상속보다는 합성” 원칙을 직접 표현하고 있기 때문에 반복학습을 통한 확실한 이해를 하기 위해 오리가 아닌 액션어드벤쳐 게임을 주제로 삼아 이 패턴을 다시한번 설명하도록 하겠습니다.

전체 클래스와 인터페이스를 한눈에 보기 위해 starUML을 이용해 클래스 다이어그램을 작성해 보았습니다.

Strategy Pattern - Action Adventure Game

Strategy Pattern - Action Adventure Game

먼저 Character 클래스는 Knight 와 Wizard 클래스의 추상층입니다. 각 직업들이 가지는 공통적인 특성을 정의하여 서브클래스에서 확장(상속) 사용하도록 합니다.

모든 캐릭터들은 무기를 사용할 수 있고 갑옷을 입을 수 있기 때문에 이 두가지의 기능을 커다란 알고리즘으로 분류하여 각각 인터페이스를 만듭니다.
무기를 사용하는 것은 useWeapon() 메서드를 호출하여 무기를 사용하는 모습을 그래픽으로 처리하게 하고, 갑옷을 착용하면 갑옷 종류에 따라 다른 보너스 hp 를 얻도록 할 계획입니다.

아래의 IArmor 인터페이스를 보면 wearBonus() 메서드의 리턴값이 int 로 되어 있음을 볼 수 있습니다. 이것은 갑옷 종류에 따른 hp 보너스 포인트를 리턴할 수 있도록 한 것입니다.

0
1
2
3
4
5
6
package
{
	public interface IArmor
	{
		function wearBonus():int;
	}
}

IArmor 인터페이스를 구상하는 클래스중 하나인 Plate 클래스를 보면 인터페이스에 있는 메서드를 구현하고 있고 wearBonus() 메서드에서 int 값 50을 리턴하고 있습니다.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package
{
	public class Plate implements IArmor
	{
		public function Plate ()
		{
			trace( "판금갑옷을 착용하였습니다." )
		}
 
		public function wearBonus():int
		{
			trace( "현재 hp에 + 50" )
			return 50;
		}
	}
}

이런식으로 아무것도 입지 않은 Naked 클래스를 포함하여, 필요한 갑옷마다 hp 보너스를 설정하여 인터페이스를 구현합니다.

그럼 이제 IWeapon 과 IArmor 인터페이스를 사용하게 되는 Character 클래스를 보면 모든 캐릭터들이 공통적으로 필요한 변수를 설정하고 몇가지에는 getter 를 설정하였습니다. 각 메서드에 대한 설명은 주석으로 달아놓았습니다.

0
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
package
{
	public class Character
	{
		protected var weapon:IWeapon;
		protected var armor:IArmor;
		protected var _hp:int = 0;
		protected var _name:String;
		protected var _characterClass:String;
 
		public function Character ()
		{
			trace( "-----새로운 캐릭터를 생성합니다.-----" )
		}
 
		//무기를 장비함
		public function equipWeapon( $weapon:IWeapon ):void
		{
			weapon = $weapon;
		}
 
		//무기를 사용함
		public function weaponAction():void
		{
			weapon.useWeapon()
		}
 
		//갑옷을 입음
		public function equipArmor( $armor:IArmor ):void
		{
			armor = $armor;
 
			//해당 갑옷의 보너스 hp를 리턴받아 캐릭터의 hp에 더함
			_hp += armor.wearBonus();
 
			//방어력, 이동속도 등 다른 착용효과 구현 가능
		}
 
		//현재 장비하고 있는 무기를 리턴
		public function getObjectWeapon():IWeapon
		{
			return weapon
		}
 
		//캐릭터 이름, 직업, hp 현황을 리턴
		public function getStat():String
		{
			return _name + "(" + _characterClass + ") 현재 hp : " + _hp
		}
 
		public function get hp():int { return _hp; }
 
		public function get name():String { return _name; }
 
		public function get characterClass():String { return _characterClass; }
	}
}

그리고 Character 클래스를 확장한 Knight 클래스에서는 Character 클래스에서 protected 로 선언하여 상속한 변수들에 초기값을 입력하여 캐릭터를 생성하게 됩니다.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package
{
	public class Knight extends Character
	{
		public function Knight ( $name:String )
		{
			_hp = 100;
			_name = $name;
			_characterClass = "기사"
			trace( name , " (이)라는 이름을 가진 ", characterClass ," 캐릭터를 생성하였습니다." )
			weapon = new Fist();
			armor = new Naked();
		}
	}
}

여기까지 하면 주먹( new Fist(); ) 이라는 무기와 아무것도 걸치지 않은 맨몸( new Naked(); )의 hp 100짜리 기사 캐릭터가 생성 됩니다.

IArmor를 짚어나가고 있으므로 그 부분을 집중해서 보도록 하겠는데요, 여기서 중요한 부분은 Character 클래스에서 IArmor 데이터형으로 인스턴스 변수를 생성한 armor 에[01] new Naked(); 라는 갑옷를 대입하여 현재 갑옷상태를 아무것도 입지 않은 상태로 초기 지정하였습니다. 즉, 추상층[02] 에서는 상위 형(type)인 인터페이스 형(type)으로 변수 선언을 하고, 구상층에서 상속받은 그 변수에 구체적인 형태를 대입하는 것입니다. 이렇게 하는 이유는 아래 호스트 코드에서 살펴보도록 하겠습니다.

0
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
package
{
	import flash.display.Sprite;
 
	public class Main extends Sprite
	{
		public function Main():void
		{
			var player1:Character = new Knight( "세계의끝" );
							//출력 : -----새로운 캐릭터를 생성합니다.-----
							//출력 : 세계의끝  (이)라는 이름을 가진  기사  캐릭터를 생성하였습니다.
							//출력 : 맨주먹입니다. (무기가 없습니다)
							//출력 : 아무것도 착용하지 않았습니다.
			trace( player1.getStat() );		//출력 : 세계의끝(기사) 현재 hp : 100
			player1.weaponAction();		//출력 : 주먹을 휘두릅니다.
			player1.equipWeapon( new Sword() );	//출력 : Sword 를 장비하였습니다.
			player1.weaponAction();		//출력 : Sword 를 휘두릅니다
			player1.equipArmor( new Plate() );	//출력 : 판금갑옷을 착용하였습니다.
							//출력 : 현재 hp에 + 50
			trace( player1.getStat() );		//출력 : 세계의끝(기사) 현재 hp : 150
			trace( player1.getObjectWeapon() );	//출력 : [object Sword]
 
			var player2:Character = new Wizard( "댄스댄스댄스" );
							//출력 : -----새로운 캐릭터를 생성합니다.-----
							//출력 : 댄스댄스댄스  (이)라는 이름을 가진  마법사  캐릭터를 생성하였습니다.
							//출력 : 맨주먹입니다. (무기가 없습니다)
							//출력 : 아무것도 착용하지 않았습니다.
			trace( player2.getStat() );		//출력 : 댄스댄스댄스(마법사) 현재 hp : 80
			player2.weaponAction();		//출력 : 주먹을 휘두릅니다.
			player2.equipWeapon( new Staff() );	//출력 : Staff 를 장비하였습니다.
			player2.weaponAction();		//출력 : Staff 에서 마법을 발사합니다.
			player2.equipArmor( new Leather() );	//출력 : 가죽갑옷을 착용하였습니다.
							//출력 : 현재 hp에 + 30
			trace( player2.getStat() );		//출력 : 댄스댄스댄스(마법사) 현재 hp : 110
			trace( player2.getObjectWeapon() );	//출력 : [object Staff]
		}
	}
}

8 번 라인을 보면, 위에서 언급한 것과 마찬가지로 new Knight( “세계의끝” ) 를 상위 type 인
var player1:Character 로 받고 있는데요, 이런 형태의 캐스팅을 하게 되면, 인스턴스 변수를 사용할 때에 그게 어떤 구체적인 형태인지 호스트코드에서 알 필요 없이 알아서 사용됩니다.
위의 player1은 type 이 Character 지만 Knight 클래스라는 것을 player1 은 이미 알고 있다는 의미 입니다. 마찬가지로 Character 클래스의 equipArmor() 메서드에서 armor.wearBonus() 를 하면 캐릭터가 뭘 입었는 몰라도,[03] 보너스 hp는 조사하면 다 나온다는 것이죠.

이러한 구조는 아래와 같은 절차지향 코드의 조건문을 상쇄한 효과를 냅니다.

0
1
2
3
4
5
6
7
8
9
10
function wearBonus():int {
	var bonus:int;
	if ( armor == "Plate" ) {
		bonus = 50;
	} else if ( armor == "Leather" ) {
		bonus = 30;
	} else {
		bonus = 0;
	}
	return bonus;
}

게다가 새로운 갑옷이 늘어날 때마다 else if 를 추가하지 않아도 됩니다. 무엇보다 확장에 유연하고 수정사항이 생겼을때 어디를 얼마만큼 고쳐야 하는지 명확하게 알 수 있게 됩니다.

zelda_link_to_the_past_stamp

Strategy Pattern : Action Adventure Game 액션스크립트 코드 다운로드 (93)


[출처 : http://ufx.kr/blog/163]
- 추상 알고리즘(Strategy)을 통해 알고리즘을 구현한 부분(ConcreteStrategy)들을 교환 가능
- 알고리즘을 교체해서 동일한 문제를 다른 여러가지 방법으로 해결하는 패턴
- 실행중에 알고리즘의 변경이 가능


1. Strategy (interface)
   - 여러 알고리즘들의 추상 알고리즘의 역할
    - Strategy strategy = new ConcreteStrategy1(); // Strategy strategy = new ConcreteStrategy();

2. Context 
    - Strategy를 구성(Composition) 으로 가지고 있음.
   - Strategy를 구현한 ConcreteStrategy1 or ConcreteStrategy2 의 인스턴스를 통해 알고리즘 변경 가능

3. ConcreteStrategy
   - Strategy 를 구현한 실제 알고리즘들

관련패턴)
   - Flyweight 패턴, Abstract Factory 패턴, State 패턴
Sample)

1. Sort 인터페이스를 구현한 BubbleSort 와 QuickSort 클래스의 sorting() 에 실제 정렬 알고리즘 구현체가 존재
2. ContextSort는 Sort를 구성으로 가지고 contextSorting()에서 알고리즘을 교체한다.


이번에는 가장 많이 사용하는 패턴 중 하나인 FactoryMethod 패턴에 대해 살펴본다.
정의를 내려보자면 생성될 객체의 정확한 클래스를 명시하지 않고 특정 메소드에서 결정한다.
역시 와닿지 않는다....

Observer 때와 마찬가지로 스타크래프트를 예로 들어본다.
테란의 메카닉 유닛을 생산하는 건물이 마침 Factory 니까... 그걸로 설명 한다.

사용자 삽입 이미지

FactoryMethod 는 말 그대로 생산, 생성의 의미를 가지고 있다.
생산될 대상은 Product 이고 이는 스타크래프트의 팩토리가 생산하는 유닛들이다.
이 유닛들은 SiegeTank, Vulture 같은 유닛들로 구체화된다.
그리고 이들 유닛을 생성하는 처리를 담당하는 Creator 를 사용함으로써 FactoryMethod 패턴이 구성된다.
위의 그림과 같이 SiegeTank 란 유닛을 생산하기 위한 SiegeTankCreator 로써 존재하는데.
Creator 에서 유닛을 생성하여 반환하는 메소드를 factoryMethod 라 했다. 
그렇다면 Creator 가 곧 스타크래프트의 팩토리 일까?
아니다 Creator 는 각각의 유닛별로 고유한 생산 로직을 담은 모듈에 해당된다.
스타크래프트의 팩토리는 그 Creator 를 모아놓은 것이다. (이는 좀 더 뒤에 다른 패턴에서 다루게 된다.)
Creator 는 게이머가 팩토리를 선택하고 시즈탱크를 생산하겠다라고 명령을 내릴때 동작한다.
유닛 생산시 필요한 시간을 소비시키고 적합한 애니메이션이나 소리를 들려주는 등의 처리가 Creator 에서 일어나는 일이 될 것이다.

예제 소스를 통해 살펴보자..
나름 재미가 있길래 조금 오바를 했다. FactoryMethod 패턴을 설명하기 위한 핵심은 진하게 표시된 부분이다.
public abstract class Unit {
    protected final String name;
    protected Unit(String name) {
        this.name = name;
    }
    public String getName() { return this.name; }
    public void command1() { System.out.println(name + ":이동"); }
    public abstract void command2();
    public abstract void command3();
}
유닛은 공통적인 처리나 로직등을 담을 필요가 있다 판단해서 인터페이스를 사용하지 않고 추상 객체로 작성했다.
유닛의 이름이나 command1 에서 일어나는 이동 같은 것들이 이런 이에 해당한다.
이 유닛을 상속하여 SiegeTank와 Vulture 를 만들수 있다.
public class Vulture extends Unit {
    private int countSpiderMine;
    Vulture() {
        super("벌처");
        this.countSpiderMine = 3;
    }
    public void command2() { System.out.println(name + ":수류탄"); }
    public void command3() {
        if ( this.countSpiderMine > 0 ) {
            this.countSpiderMine--;
            System.out.println(name + ":마인심기");
        } else {
            System.out.println(name + ":마인없음");
        }
    }
}
public class SiegeTank extends Unit {
    private boolean siegeMode;
    SiegeTank() {
        super("탱크");
        this.siegeMode = false;
    }
    public void command1() {
        if ( !this.siegeMode ) {
            super.command1();
        } else {
            System.out.println(name + ":이동못함");  // 시즈모드일때는 이동하지 못한다.
        }
    }
    public void command2() {
        if ( this.siegeMode ) {
            System.out.println(name + ":시즈포");
        } else {
            System.out.println(name + ":통통포");
        }
    }
    public void command3() {
        if ( this.siegeMode ) {
            this.siegeMode = false;
            System.out.println(name + ":탱크모드");
        } else {
            this.siegeMode = true;
            System.out.println(name + ":시즈모드");
        }
    }
}
나름  SiegeTank와 Vulture 의 특성을 구현해 보았다. 
이렇게 해서 만들어야 할 대상 Product 가 준비되었다. 이제 Creator 를 만들어본다.
public abstract class Creator {
    protected Creator() {}
    public abstract Unit createUnit();
}
위의 경우는 인터페이스로 작성해도 되지만 추상 객체로 만든 이유는 어떠한 공통 로직이 나중에라도 필요하지 않을까 해서이다. 이제 SiegeTank와 Vulture 를 만드는 각각의 Creator 를 작성한다.
public class SiegeTankCreator extends Creator {
    public Unit createUnit() {
        Unit unit = new SiegeTank();
        System.out.println(unit.getName() + " 생산");
        return unit;
    }
}
public class VultureCreator extends Creator {
    public Unit createUnit() {
        Unit unit = new Vulture();
        System.out.println(unit.getName() + " 생산");
        return unit;
    }
}
각각의 Creator 가 Unit 이라는 추상 객체로 반환하지만, 내부에서 각각 new SiegeTank(), new Vulture() 했다.
다시 한번 SiegeTank, Vulture 의 소스를 보면 Constructor 이 package private 이기 때문에 package 밖에서는 생성할 수 없다. 오로지 createUnit 으로만 생산할 수 있다. FactoryMethod 패턴을 사용하는 의미를 바로 안다면 SiegeTank, Vulture 는 결코 밖에서 보이면 안되고, 오로지 Unit 으로만 핸들링 되어야 한다. 그렇지 않다면 구지 FactoryMethod 패턴을 사용할 이유는 없다.
이제 이들 Creator 들을 모아놓은 팩토리를 만들고 테스트 해본다.
public class Factory {
    private Creator creator1;
    private Creator creator2;
    public Factory() {
        this.creator1 = new SiegeTankCreator();
        this.creator2 = new VultureCreator();
    }
    public Unit createUnit1() { return this.creator1.createUnit(); }
    public Unit createUnit2() { return this.creator2.createUnit(); }
}
public static void main(String[] args) {
    Factory factory = new Factory();
    Unit unit1 = factory.createUnit1();
    unit1.command1();
    unit1.command2();
    unit1.command3();
    unit1.command2();
    unit1.command1();
    unit1.command3();
    unit1.command1();

    Unit unit2 = factory.createUnit2();
    unit2.command1();
    unit2.command2();
    unit2.command3();
    unit2.command3();
    unit2.command3();
    unit2.command3();
    unit2.command1();
}

사용자 삽입 이미지

실행된 모습


그런데 왜 객체를 직접 생성하지 않고, FactoryMethod 패턴을 사용하여 생성할까..?
이유는 간단하다. ExtentionPack Blood War 를 만들기 위해서다.
스타크래프트 오리지날에 골리앗이란 유닛이 없었다고 가정하고...
확장팩에 골리앗이라는 새로운 유닛을 만들어 추가로 넣고자 한다...
FactoryMethod 패턴이 적용되어 있지 않다면 꽤나 많은 수정이 일어날 것이다.
FactoryMethod 패턴을 바탕으로 설계되어 있다면
먼저 Unit 을 상속해 특화된 기술 요소를 넣은 골리앗을 만든다.
그리고 이를 생성하는 Creator 를 만들고, 그곳에 빌드 타임이나 제한 요소등을 넣을 수 있다.
지금까지 새로운 객체를 추가만 했지 기존 소스의 수정은 없었다는 점이 중요 포인트다...
앞서의 예에서 팩토리에 골리앗을 만드는 Creator 를 넣는 부분은 팩토리를 수정해야 하지만...
차후 이 부분은 다른 패턴을 가지고 설명할 기회가 있을 것이다.

참고 삼아서 스타크래프트의 사용자 인터페이스를 잠시 말한다.
게이머가 유닛을 선택하고 그 유닛에 내리는 명령의 가짓수는 몇가지 일까?
한 유닛에 한번에 선택할 수 있는 명령은 우측 하단의 최대 9개 버튼뿐이다.
마우스 왼쪽버튼, 오른쪽버튼으로 맵을 찍거나, 키보드로 단축키를 넣는 것은 
Observer 패턴에 의해 9개 버튼중의 하나인 명령을 실행하는 것뿐이다.
건물짖기를 누르면 지을 수 있는 건물로 버튼이 바뀌는 것은 Shift 일 뿐이다.
예제 소스에서 유닛의 명령메쏘드 이름을 move, attack 으로 하지 않고, command1, command2 로 한건 이 때문이다.
이렇게 해서 각각의 개성넘치는 유닛들의 기능이 한 곳에서 단순한 구성으로 제어가 가능해진다.
여기서 이 얘기를 하는 것은 FactoryMethod 패턴에서 모든 유닛이 Unit 이란 슈퍼 클래스하나로 사용될 수 있어야 함을 이야기 하고자 한 것이다. 이것은 FactoryMethod 패턴의 활용 가치를 높여주는 핵심적 사항이다.
결국 제일 어려운 것은 FactoryMethod 가 아니라 Product 를 결정하는 것이다.

스타크레프트 예제도 어려우면 다음을 보자..
public class creator1 {
    public Number createNumber() {  return new Integer(1234); }
}
public class creator2 {
    public Number createNumber() {  return new Long(1234); }
}

이 예제도 객체(Integer, Long)의 생성이 메쏘드 내에서 이루어 지고, 
그렇게 생성된 객체의 슈퍼클래스 형태(Number)로 리턴되는 FactoryMethod 패턴이다.
즉, createNumber() 란 메쏘드가 객체를 생성해서 반환하는 공장 메소드란 것이다.
그리고...
지금 필요한 건 Integer 와 Long 이었지만, 혹시 나중에 new Double 을 붙일 필요가 있다면...
그것만 쉽게 만들어 붙일 수 있도록 공통적인 부분을 인터페이스나 추상 객체로 만들어 놓은 것일 뿐이다.

아마 객체를 상속해서 쓰기 좋아했던 개발자라면...
실제 이게 팩토리메소드 패턴이야라고 알지 못하고도 그간 사용하고 있었을 수도 있다.

 


FACTORY METHOD

Factory Method 패턴이란 객체를 생성하되 직접 객체 생성자(Constructor)를 호출해서 객체를 생성하는 것이 아니라 대행 함수를 통해 간접적으로 객체를 생성하는 방식을 Factory Method 패턴이라고 한다. 또한 이때 객체 생성을 대행해주는 함수를 Factory Method라고 한다.

 

Factory Method 패턴의 유용한 경우

  • 구체적으로 어떤 클래스의 객체를 생성해야 할지 미리 알지 못할 경우 Factory Method 패턴이 유용하다. 왜냐하면 Factory Method 패턴을 적용하면 구체적으로 생성될 객체의 자료형이 각각의 하위 클래스에 의해 결정되며, 이러한 하위 클래스는 필요할 경우 계속 확장 가능하기 때문이다.
  • 하위 클래스가 객체를 생성하기를 원할 때 Factory Method 패턴이 유용하다. Factoty Method 패턴은 객체의 생성을 하위 클래스에게 위임하는 것이라고 볼 수 있다.
  • Factory Method 패턴은 하위 클래스들에게 개별 객체의 생성 책임을 분산시켜 객체의 종류별로 객체 생성과 관련된 부분을 국지화시킬 때 유용하다.

 

Factory Method 패턴의 장·단점

  • Factory Method 패턴의 장점은 어떤 객체를 생성할 것인지와는 무관하게 동일한 형태로 프로그래밍이 가능하다는 점이다. 이는 두 개의 상위 클래스가 존재하기 때문에 어떤 객체를 생성하든지 상관없이 클래스 두 개가 제공하는 인터페이스를 이용해서 생성할 객체의 자료형과는 무관하게 독립된 형태로 프로그래밍을 할 수 있다는 점이다.
  • Factory Method 패턴의 또 다른 장점은 직접 생성자를 호출해서 객체를 생성하는 것보다 훨씬 유연하고 확장성 있는 구조라는 점이다. 왜냐하면 새로운 객체를 생성하거나 이전 객체를 확장해서 생성하고자 할 때 굳이 기존 소스코드를 분석해서 변경할 필요없이 새로운 하위 클래스를 정의하고 Factory Method에 해당하는 멤버 함수만 재정의 시키면 충분하기 때문이다.
  • Factory Method 패턴에서 Factory Method 멤버 함수는 Creator 클래스에 의해서만 불리워질 수 있는 게 아니라 외부에서도 얼마든지 불러서 사용할 수 있다. Factory Method 패턴은 Abstract Factory 패턴에서처럼 서로 연관된 클래스들의 상속 관계가 병렬로 존재해서 한 쪽 상속 관계의 클래스들이 다른 쪽 상속 관계의 클래스 객체들을 생성할 때 편리하다.
  • 상속 관계에 있는 클래스들의 멤버 함수가 동일한 프로그램 로직을 가지고 있으면서 내부적으로 생성할 객체만 서로 다를 때 Factory Method 패턴을 적용하면 편리하다. 왜냐하면 클래스마다 동일한 프로그램 로직의 멤버 함수를 각각 구현하지 않고, 상위 클래스에서 한 번만 구현한 다음 각각의 클래스는 객체를 생성해주는 멤버 함수만 다시 구현해주면 되기 때문이다.
  • Factory Method 패턴의 잠재적인 단점은 생성할 객체의 종류가 달라질 때 마다 새로운 하위 클래스를 정의해야 한다는 점이다. 이는 불필요하게 많은 클래스를 정의하게 만들 수 있는 문제를 내포하고 있다.


사실 객체지향 설계를 처음 접하기 이전부터 MVC 모델에 대한 개념적인 지식은 한번쯤 접해 보았을 것입니다가장 기본적인 정의를 설명하자면 M( Mode ) ApplicationBusiness Logic , 객체 또는 데이터의 가공을 책임지는 컴포넌트들의 집합체이고 C ( Control )이란 객체 또는 데이터의 흐름을 책임지는 컴포넌트 , V( View )는 객체 또는 데이터의 생성을 책임지는 컴포넌트로써 일반적으로 Data의 입력 부분과 출력 부분을 담당합니다

하지만 MVC 모델을 설명한 대부분의 설명서를 보면 위와 같이 너무 기본적인 정의에만 초점을 두고 있어 초급 개발자들은 자신이 개발해야 할 프로그램에서 어는 부분을 M으로 V C로 나누어야 할지 개념을 잡기 힘들뿐만 아니라 설사 각각의 업무에 맞게 class를 만들었다 하더라도 각각의 컴포넌트또는 Class ) 사이의 관계를 어떻게 설계해야 할지 판단하기 힘든 상황이 발생하게 됩니다이런 어려움을 가지고 있는 초급 개발자에게 조금이나마 도움이 될까 하여 MVC모델에 대한 개괄적인 내용을 아래와 같이 설명하고자 합니다

1.       M, V, C 전체 개념보기

2.       M, V, C 나누기

3.       M, V, C에 관계 적용하기


1.       M, V, C 전체 개념보기

Model은 객체나 Data의 가공을 책임지는 컴포넌트

View는 객체나 Data의 생성입력( User Action ), 출력( Display )을 책임 지는 컴포넌트

      Control은 객체나 Data의 흐름을 책임지는 컴포넌트

View
먼저 데이터객체 )의 입력과 출력을 담당하는 View는 필요에 따라 Model로부터 객체의 상태를 요청할 수 있고 응답 받은 상태에 따라 다른 출력 형식을 가지 수 있으며 Controller에게 전달 시 상태정보를 같이 보낼 수 있습니다여기서 중요한 것은 Model로부터 응답 받은 객체의 상태에 의해 View가 직접 제어가공을 하는일이 없도록 해야 합니다다시 말하면 View는 상태에 따라 출력형식만 다르게 가야하고 모든 제어나 가공은 Controller에게 위임을 해야 합니다.
Controller

객체 또는 데이터의 흐름을 책임지는 Controller는 활성화 된 View로부터 넘겨 받은 메시지(User Action) 또는 객체를 파악하여 해당 객체를 어떤 Model로 전달할지를 결정하고 필요한 객체를 데이터를 가공할 Model로 전달합니다기본 MVC 모델에서는 Model에서 Controller쪽으로 Event를 보낼 수 없도록 되어 있지만 구현참조가 아니라 인터페이스 참조일경우 Model에서 Controller쪽으로 Event를 보낸는것이 오히려 MVC 모델 흐름을 더욱 유연하게 만들기도 합니다.

Model

Model Controller로부터 전달받은 객체 또는 데이터를 Application의 Business Logic에 따라 가공처리하는 책임을 가지고 있으며 필요에 따라 View Controller에게 변경된 객체의 상태를 전달하게 됩니다.


1.       M, V, C 나누기

간단한 예를 통한 MVC 도출

 ) Database을 이용해서 고객정보를 읽고 쓰는Application


       
Cust_Dlg ( View )      고객 정보를 입력 또는 출력하는 GUI 관련 class

       Cust_Ctrl (Controller )  :  GUI로부터 입력 받은 고객 정보 데이터 또는 객체를 Model로 전달받는 Class

       Cust_DB ( Model ) : Controller로부터 전달 받은 객체 또는 데이터를 DB에 입력 또는 조회하는Class


 

2.       M, V, C에 관계 적용하기

 

3.       결론

M. V. C 모델의 궁극적인 목적은 각각의 컴포넌트에 대해 역할을 서로 독립적으로 부여하고 또한 각각의 컴포넌트의 관계는 구연 참조가 아니라 인터페이스 참조로 설계하여 차후 새로운 요구사항에 대해 최소한의 비용으로 보다 유연하게 대처하고자 하는 목적이며 이는 객체 지향 설계방법의 기본적인 목적이기도 합니다.





MVP(Model-View-Presenter) 패턴을 적용한 GUI Architecture 설계

1. MVP 패턴 소개

사용자 삽입 이미지
 

그림 1 MVP Pattern Diagram

MVP 패턴은 MVC(Model-View-Controller) 패턴에 기반을 둔 UI 프리젠테이션 패턴입니다.
MVP 패턴은 Model, View, View Interface, Presenter 네 개의 컴포넌트(구성요소)로 구성됩니다.
각 컴포넌트에서 담당하는 책임은 다음과 같습니다.

  • View
    View Interface의 Display 멤버(Properties, Display Methods)를 구현하여 실제적인 UI 요소를 그려줍니다.
  • View Interface
    Presenter에서 Concrete View를 직접 참조하지 않고 View Interface를 참조함로써 Concrete View와의 커플링을 감소시키고 View의 실제 UI 요소가 어떻게 구현되는지 몰라도 데이터를 올바르게 표현할 수 있도록 합니다.
  • Presenter
    View와 Model간의 상호작용을 담당합니다. Model의 데이터를 View Interface를 통해 Concrete View에 출력(바인딩)해 주고 사용자의 이벤트를 View에서 구독하여(실제적인 이벤트 핸들러 구현) Model의 데이터를 갱신하는 역할을 수행합니다.
    Presenter를 통해 View와 Model간의 의존관계를 없앨 수 있습니다.
  • Model
    데이터와 상태를 유지하며 데이터 처리 로직을 포함합니다. 일반적으로 비즈니스 엔티티와 비즈니스 로직을 Model 컴포넌트로 간주합니다.

2. MVP 패턴 적용

위에서 소개한 MVP 패턴을 적용하여 샘플 어플리케이션을 만들어 보도록 하겠습니다.
샘플 어플리케이션의 Class Diagram은 다음과 같습니다.

사용자 삽입 이미지

그림 2 MVP Pattern이 적용된 샘플 어플리케이션의 Class Diagram

Model 컴포넌트는 Member Class이며 Member Entity와 데이터 처리 로직을 포함하고 있습니다. 
Presenter 컴포넌트는 MemberPresenter Class이며 View와 Model을 참조합니다. 그리고 View의 이벤트 핸들러를 구현합니다.
View Interface 컴포넌트는 IMemberView Interface이며 View의 속성과 이벤트를 정의합니다.
Concrete View 컴포넌트는 MemberView Form이며 IMemberView를 구현하며 UI요소를 렌더링합니다.

먼저 Model 컴포넌트인 Member Class를 만들도록 합니다.
Member Class는 실제 데이터 소스에 접근하여 데이터를 검색하거나 갱신하는 로직을 포함합니다. 또한 Member의 Age, Name 속성을 갖는 Entity Class의 역할도 수행합니다.
데이터 소스는 실제 데이터베이스가 아닌 <표 1>과 같이 더미 데이터를 임시로 구현하여 사용합니다.

static class DummyMember
{
    #region 회원 더미 데이터 생성

    public static List<Models.Member> List = new List<Models.Member>();

    static DummyMember()
    {
        List.Add(new Models.Member("홍길동", 20));
        List.Add(new Models.Member("김갑수", 10));
    }

    #endregion

}

표 1 DummyMember Class

public int SaveMember()
{
    #region 저장

    if (id == null)
    {
        // 추가
        Data.DummyMember.List.Add(new Member(this.name, this.age));
        id = Data.DummyMember.List.Count - 1;
    }
    else
    {
        // 수정
        Data.DummyMember.List[id.Value].Name = name;
        Data.DummyMember.List[id.Value].Age = age;
    }


    #endregion

    return 0;
}

public bool LoadMember(int id)
{
    if (id >= Data.DummyMember.List.Count || id < 0)
    {
        this.id = null;
        return false;
    }

    this.id = id;

    name = Data.DummyMember.List[id].Name;
    age = Data.DummyMember.List[id].Age;

    return true;
}

표 2 Member Class의 데이터 처리 메서드


다음으로 IMemberView Interface를 정의합니다.
IMemberView Interface는 Concrete View에서 구현해야할 UI 요소와 표현 메서드를 정의합니다. 일반적으로 UI요소는 Property로 정의하며 Concrete View의 UI 요소에서 이벤트가 발생했을 때 이를 Presenter에서 처리할 수 있도록 이벤트를 정의합니다.

interface IMemberView
{
    int ID { get; set; }
    string Name { get; set; }
    int Age { get; set; }

    event EventHandler LoadMember;
    event EventHandler SaveMember;
}

표 3 IMemberView Interface

세 번째로 UI 요소를 출력하는 MemberView Form을 생성합니다.
MemberView Form은 System.Windows.Forms.Form Class를 상속하는 WinForm Class입니다. 닷넷의 WinForm Control이 UI 요소가 됩니다.
또한 IMemberView Interface를 구현하여 Presenter에서 UI 요소에 데이터를 채우거나 이벤트 핸들러를 구현하여 View를 제어할 수 있도록 합니다.

public partial class MemberView : Form, IMemberView
{
    MemberPresenter presenter;

    public MemberView()
    {
        InitializeComponent();

        presenter = new MemberPresenter(this);    
    }

    #region IMemberView 멤버

    int IMemberView.ID
    {
        get { return (int)numericUpDown1.Value; }
        set { numericUpDown1.Value = value; }
    }

    string IMemberView.Name
    {
        get { return textBox1.Text; }
        set { textBox1.Text = value; }
    }

    int IMemberView.Age
    {
        get { return (int)numericUpDown2.Value; }
        set { numericUpDown2.Value = value; }
    }

    event EventHandler IMemberView.SaveMember
    {
        add { button1.Click += value; }
        remove { button1.Click -= value; }
    }

    event EventHandler IMemberView.LoadMember
    {
        add { button2.Click += value; }
        remove { button2.Click -= value; }
    }

    #endregion
}

표 4 MemberView Form

주목할 부분은 SaveMember, LoadMember 이벤트를 구현한 부분입니다. button1.Click과 button2.Click의 실제 이벤트 핸들러는 Presenter에서 구현하는데, 이 곳에서 Model의 데이터를 검색하고 갱신하는 코드를 작성하게 됩니다.

마지막으로 View와 Model의 상호작용을 처리하는 MemberPresenter Class를 구현합니다. 
MemberPresenter Class는 Concrete View의 UI 요소(WinForm Control)의 실제 이벤트 핸들러를 구현하며 Member Class의 데이터 처리 메소드를 호출해 데이터를 검색하거나 갱신하는 코드를 구현합니다. 또한 IMemberView 인터페이스를 통해 Concrete View인 MemberView Form의 UI 요소(WinForm Controls)에 데이터를 출력하는 코드도 구현하고 있습니다.

class MemberPresenter
{
    IMemberView view;
    Models.Member model;

    public MemberPresenter(IMemberView view)
    {
        this.view = view;
        this.view.SaveMember += new EventHandler(view_SaveMember);
        this.view.LoadMember += new EventHandler(view_LoadMember);

        this.model = new SingleLayerMVP.Models.Member();
    }


// 회원정보 불러오기
    void view_LoadMember(object sender, EventArgs e)
    {
        int id = view.ID;

        if (model.LoadMember(id))
        {
            view.Name = model.Name;
            view.Age = model.Age;
        }
        else
        {
            MessageBox.Show("존재하지 않는 회원입니다.");

            view.Name = string.Empty;
            view.Age = 0;
        }
    }


    // 회원정보 저장하기
    void view_SaveMember(object sender, EventArgs e)
    {
        model.Name = view.Name;
        model.Age = view.Age;

        if (model.SaveMember() == 0)
        {
            MessageBox.Show("성공");
        }
        else
        {
            MessageBox.Show("실패");
        }
    }
}

MemberPresenter Class는 IMemberView와 Member의 참조를 모두 갖게 되며 view와 model간의 상호작용을 담당하는 코드를 실제 구현하게 됩니다. 이로써 View와 Model간의 커플링을 없앰과 동시에 View와의 상호작용을 IMemberView Interface를 통해 처리함으로써 실제 Concrete View인 MemberView Form과의 커플링도 없앨 수 있게 되는 것입니다.

3. Multi Layer Application Architecture 에서 MVP Pattern 적용하기

사용자 삽입 이미지

그림 3 MVP Pattern Diagram with C/S Application

일반적으로 비즈니스 레이어와 Presentation Layer가 분리 되어 있는 어플리케이션에서 MVP Pattern의 적용은 <그림 3>과 같은 구조를 적용하면 됩니다. Model의 Business Entity는 직렬화가 가능하도록 [Serializable] 어트리뷰트를 추가하여 개발하고 Client Application에서는 Web Service참조를 Model로 간주하여 Presenter에 구현하면 됩니다.

[Serializable]
public class Member
{
    int? id = null;

    public int? Id
    {
        get { return id; }
        set { id = value; }
    }

표 5 직렬화 가능한 Member Entity

class MemberPresenter
{
    IMemberView view;
    WebService.Service1 model;

    public MemberPresenter(IMemberView view)
    {
        this.view = view;
        this.view.SaveMember += new EventHandler(view_SaveMember);
        this.view.LoadMember += new EventHandler(view_LoadMember);

        this.model = new Client.WebService.Service1();
    }


    // 회원정보 불러오기
    void view_LoadMember(object sender, EventArgs e)
    {
        int id = view.ID;

        WebService.Member member = model.wpLoadMember(id);

표 6 MemberPresenter Class에서 Web Service 참조를 Model로 참조하기

4. View를 위한 단위 테스트 코드 작성

우선 단위 테스트 코드는 NUnit Framework를 사용하도록 합니다. 
NUnit Framework의 다운로드와 설치는 다음 링크를 참조하세요.
http://www.nunit.org

일반적으로 GUI에 대한 테스트는 사용자 액션(이벤트 발생)을 시뮬레이션하기 어렵기 때문에 자동화하기가 어렵습니다. 그래서 마우스와 키보드 동작을 레코드하여 매크로 동작으로 테스트하는 상용화된 테스팅 툴을 사용하게 됩니다. 
하지만 MVP 패턴을 적용하게 되면 IMemberView 인터페이스를 사용해서 View만을 위한 단위 테스트 코드를 작성할 수 있습니다. 이것이 의미하는 것은 GUI 개발을 테스트 지향적(TDD, 테스트 코드 먼저구현->실제 코드 구현)으로 개발할 수 있다는 것이기도 합니다.

단위 테스트 코드를 작성하기 위해 먼저 IMemberView Interface를 구현하는 DummyView Class를 생성합니다.

public class DummyView : IMemberView
{
    int id;
    int age;
    string name;

    event EventHandler loadMember;
    event EventHandler saveMember;

    #region IMemberView 멤버

    …

    #endregion

    #region 테스트용 인터페이스

    public void TestLoadMember()
    {
        loadMember(this, null);
    }

    public void TestSaveMember()
    {
        saveMember(this, null);
    }

    #endregion
}

표 7  DummyView Class

DummyView Class에서 IMemberView Interface를 구현한 것과 테스트용 메서드인 TestLoadMember()와 TestSaveMember() 메서드를 추가한 것입니다. 실제 View의 UI 요소를 제어하는 코드가 있는 Class는 MemberPresenter이기 때문에 IMemberView와 MemberPresenter를 모두 테스트하는 코드라고 볼 수 있습니다.

using NUnit.Framework;

namespace SingleLayerMVP.Tests
{
    [TestFixture]
    public class MemberViewTest
    {
        IMemberView view;
        MemberPresenter presenter;

        [SetUp]
        public void SetUp()
        {
            view = new DummyView();
            presenter = new MemberPresenter(view);
        }

        [TearDown]
        public void TearDown()
        {
            // null
        }

        /// <summary>
        /// Test LoadMember()
        /// </summary>
        [Test]
        public void TestLoadMember()
        {
            view.ID = 0;
            (view as DummyView).TestLoadMember();

            Assert.AreEqual(view.Name, "홍길동");
            Assert.AreEqual(view.Age, 20);
        }

        /// <summary>
        /// Test SaveMember()
        /// </summary>
        [Test]
        public void TestSaveMember()
        {
            view.ID = 0;
            view.Name = "홍길삼";
            view.Age = 21;
            (view as DummyView).TestSaveMember();

            Assert.AreEqual(view.Name, "홍길삼");
            Assert.AreEqual(view.Age, 21);           

        }


    }
}

표 8 IMemberView와 MemberPresenter 단위 테스트 코드

NUnit Framework를 사용해서 테스트 코드를 작성하는 방법은 http://www.nunit.org 사이트를 참조하세요.

TestLoadMember() 테스트 메서드와 TestSaveMember() 테스트 메서드를 보면 실제 GUI의 동작을 시뮬레이션하는 코드가 구현되어 있는 것을 볼 수 있습니다.

TestLoadMember() 메서드를 자세히 살펴보죠.
view.ID = 0; 이라는 코드에서 사용자가 ID UI 요소에 0이란 값을 입력한 것을 의미하고,
(view as DummyView).TestLoadMember(); 코드에서는 “불러오기” 버튼을 클릭(또는 유사한 동작)한 것을 의미합니다.

Assert.AreEqual() 구문에 의해서 DummyMember.List[]의 첫 번째 데이터인 {‘홍길동’, 20}이 정상적으로 View에 출력되었는지 확인하게 됩니다.

NUnit에서 컴파일된 어셈블리를 로드하여 테스트를 수행하면 다음과 같이 테스트가 통과한 것을 볼 수 있습니다.
 

사용자 삽입 이미지

그림 4 NUnit 테스트 결과

물론 MemberView 폼을 대상으로 테스트를 수행하지 않은 것은 아쉬운 점입니다. 특히 View의 입력 요소에 값이 입력될 때 유효성 체크하는 코드를 작성할 수 없다는 것도 역시 아쉬운 점입니다. 이 두 항목을 포함하여 본 문서에서 미흡하게 다뤘거나 또는 건너 뛴 부분(MVC 패턴, MVC와 MVP 패턴의 차이점과 장단점…)들은 참조문헌에 있는 링크를 참조하세요.

이상으로 MVP 패턴을 적용한 Gui Archicecture 설계를 마치도록 하겠습니다.

5. 참조문헌

5.1. MSDN Magazine, Model View Presenter : 
http://msdn.microsoft.com/msdnmag/issues/06/08/DesignPatterns/
5.2. MVC or MVP Pattern – What’s the difference :
http://blogs.infragistics.com/blogs/tsnyder/archive/2007/10/17/mvc-or-mvp-pattern-whats-the-difference.aspx
5.3. Martin Fowler’s GUI Architectures :
http://www.martinfowler.com/eaaDev/uiArchs.html
5.4. MSDN, Model View Controller
http://msdn2.microsoft.com/en-us/library/ms978748.aspx
5.5. MSDN, Implementing Model-View-Controller in ASP.NET
http://msdn2.microsoft.com/en-us/library/ms998540.aspx
5.6. MSDN, Page Controller
http://msdn2.microsoft.com/en-us/library/ms978764.aspx
5.7. MSDN, Front Controller
http://msdn2.microsoft.com/en-us/library/ms978723.aspx
5.8. KLDP, M-V-C가 서로의 영역을 전혀 침범하지 않고 개발하는 것이 과연 가능한가?
http://kldp.org/node/70219
5.9. Javaworld, JSP의 MVC 모델1과 모델2
http://www.javaworld.com/javaworld/jw-12-1999/jw-12-ssj-jspmvc.html


[출처 :http://blog.jeidee.net/321]

객체지향의 기초 
추상화 , 캡슐화 , 다형성 , 상속

객체지향의 원칙 

  • 바뀌는 부분을 캡슐화한다.
  • 상속보다는 구성을 활용한다.
  • 구현이 아닌 인터페이스에 맞춰서 프로그래밍한다.
  • 서로 상호작용을 하는 객체사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.
  • OCP(Open-closed Principle) : 클래스는 확장에 대해서는 열려있어야 하지만, 코드 변경에 대해서는 닫혀 있어야 한다.
  • 추상화된 것에 의존하라. 구상 클래스에 의존하지 않는다.
  • 친한친구들하고만 이야기한다.
  • 먼저 연락하지 마세요. 저희가 연락 드리겠습니다.
  • 클래스를 바꾸는 이유는 한 가지 뿐이어야 한다.

디자인 원칙

  • 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다.(인터페이스와 구현부의 분리) 
    : 달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 캡슐화합니다. 그러면 코드를 변경하는 과정에서 의도하지 않은 일이 일어나는 것을 줄이면서 시스템의 유연성은 향상시킬 수 있습니다. 
  • 서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.
  • 추상화된 것에 의존하도록 만들어라. 구상 클래스에 의존하도록 만들지 않도록 한다.
  • 최소 지식의 원칙 : 정말 친한 친구하고만 얘기하라
  • 객체지향 프로그래밍에서는 친구가 하나만 있는 것이 좋습니다.
  • 구현이 아닌 인터페이스에 맞춰서 프로그래밍한다. 
    : 객체가 코드에 의해서 고정되지 않도록 , 어떤 상위 형식(supertype)에 맞춰서 프로그래밍함으로써 다형성을 활용해야 한다. 구체적으로 구현된 객체는 실행시에 대입하는 것 -> 상속을 쓸때 떠안게 되는 부담을 전부 떨쳐 버리고도 재사용성의 장점을 그대로 누릴 수 있습니다.

전문용어의 위력

  • 서로 알고 있는 패턴 용어는 정말 막강합니다.
  • 패턴을 이용하면 간단한 단어로 많은 것을 얘기할 수 있습니다.
  • 패턴수준에서 이야기를 하면 디자인에 더 오랫동안 집중할 수 있습니다.
  • 전문용어를 사용하면 개발 팀의 능력을 극대화할 수 있습니다.
  • 전문용어는 신참개발자에게 훌륭한 자극제가 됩니다.

    객체지향 패턴 
    스트래티지 패턴(strategy pattern) : 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다. 스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수가 있다.

    옵저버 패턴(Observer Pattern) : 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신이 되는 방식으로 일대다(one-to-many) 의존성을 정의합니다.
    ex) java.util.Observable

    데코레이터 패턴(Decorator Pattern) :  객체에 추가 요소를 동적으로 더할 수 있습니다. 데코레이터를 사용하면 서브 클래스를 만드는 경우에 비해 훨씬 유연하게 기능을 확장할 수 있습니다 .
    ex) java.io에 관련된 클래스들

    팩토리 메소드 패턴(Factory Method Pattern) : 팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 됩니다. 팩토리 메소드 패턴을 이용하면, 클래스의 인스턴스를 만드는 일을 서브클래스에게 맡기는 것

    추상팩토리 패턴(Abstract Factory Pattern) : 추상 팩토리 패턴에서는 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상클래스를 지정하지 않고도 생성할 수 있습니다.

    싱글턴 패턴(singleton pattern) : 해당 클래스의 인스턴스가 하나만 만들어지고, 어디서든지 인스턴스에 접근할 수 있도록 하기 위한 패턴입니다.

    커맨드 패턴(Command Pattern) : 커맨드 패턴을 이용하면 요구사항을 객체로 캡슐화 할 수 있으며, 매개변수를 써서 여러가지 다른 요구 사항을 집어 넣을 수도 있습니다. 또한 요청 내역을 큐에 저장하거나 로그로 기록할 수도 있으며, 작업취소 기능도 지원합니다.

    어댑터 패턴(Adapter Pattern) : 한 클래스의 인터페이스를 클라이언트엗서 사용하고자 하는 다른 인터페이스로 변화합니다. 어댑터를 이용하면 인터페이스 호환성 문제때문에 같이 쓸수 없는 클래스들을 연결해서 쓸수가 있습니다.

    퍼사드 패턴(Facade Pattern) : 어떤 시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공합니다. 퍼사드에서 고수준 인터페이스를 정의하기때문에 서브시스템을 더 쉽게 사용할 수 있습니다.

    템플릿 메소드 패턴(Template Method Pattern) : 메소드에서 알고리즘의 골격을 정의합니다. 알고리즘의 여러 단계중 일부는 서브클래스에서 구현할 수 있습니다. 템플릿 메소드를 이용하면 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를 재정의할 수 있습니다.
    ex)  알고리즘의 틀을 만들기 위한 패턴 

    이터레이터 패턴(Iterator Pattern) : 컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체 안에 들어있는 모든 항목에 접근할 수 있게 해주는 방법을 제공해 줍니다.

    컴포지트 패턴(Composite Pattern) : 객체들을 트리구조로 구성하여 부분과 전체를 나타내는 구조로 만들 수 있습니다. 이 패턴을 이용하면 클라이언트에서 개별 객체와 다른 객체들로 구성된 복합 객체(Composite)를 똑같은 방법으로 다룰수 있습니다.

    • 컴포지트 패턴이 클라이언트가 보기에 투명하게 작동하려면 복합 객체 내에 있는 모든 객체들의 인터페이스가 똑같아야 합니다. 인터페이스를 통일시키다가 보면 객체에 따라 아무 의미가 없는 메소드도 생길 수 있습니다.
      의미없는 메소드의 처리 방법
      1. 아무일도 하지 않거나 널 또는 false를 상황에 맞게 리턴하는 방법
      2. 예외를 던지는 방법

    스테이트 패턴(State Pattern) : 객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀수 있습니다. 마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있습니다.

    • 객체에 수많은 조건문을 집어넣는 대신에 사용할 수 있는 패턴

    프록시 패턴(Proxy Pattern) : 어떤 객체에 접근을 제어하기 위한 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴

    의존성 뒤집기 원칙에 위배되는 객체지향 디자인을 피하는데 도움이 되는 가이드 라인

    • 어떤 변수에도 구상 클래스에 대한 레퍼런스를 저장하지 맙시다
    • 구상 클래스에서 유도된 클래스를 만들지 맙시다.
    • 베이스 클래스에 이미 구현되어 있던 메소드를 오버라이드하지 맙시다.

    감상문 : OOP의 가장 이상적인 모델
    인터페이스로 통신 및 접근을 하고, 구현체는 추상화되어 있어서 인터페이스 기반으로 설계를 한다. 객체를 생성하는 코드를 남발하지 않고, 한군데서 관리하도록 한다. 
    강력한 추상화,캡술화만이 OOP를 잘하는 지름길이다.

  • [출처 : http://www.ologist.co.kr/266]

    + Recent posts