티스토리 뷰
MVC(Model-View-Controller) 디자인 패턴의 이해와 활용
MVC 패턴의 이해
1. 이 글은 누구를 위한 무엇인가
본 글은 디자인 패턴에 대해 이해는 하고 있으나 실제 작업에서 어떻게 활용해야 하는지 부담을 느끼는 독자들에게 도움이 될 것이다. 개별 패턴에 대한 이해를 목적으로 제작된 디자인 패턴 관련 서적은 시중에 많다. 그러나 개별 디자인 패턴들을 이해하더라도 실제 작업에서 활용할 수 없다면 효율적으로 프로그래밍을 하는 데 큰 도움을 주지 못할 것이다. 이 글에서는 이러한 접근을 지양하고 MVC 패턴을 활용해 봄으로써 디자인 패턴을 왜 사용하는지에초점을 두고 설명한다.
OOP 언어에서 디자인 패턴을 꽃이라고 한다면 MVC(Model-View-Controller) 패턴은 꽃다발이라고 할 수 있다. MVC 패턴은 디자인 패턴으로 이루어진 패턴이고 OOP 언어에서 자주 사용되는 디자인 패턴의 요소들을 포함하기 때문이다. 디자인 패턴을 제대로 이해하지 못한 채로 MVC 패턴을 이해하기가 힘들지도 모른다. 그러나 낱말을 몰라도 문맥을 통해 낱말의 의미를 유추할 수 있듯이 MVC 패턴을 이해함으로써 역으로 접근해 보면 좋을 것이다.
본 글에서는 MVC 패턴 이외에 다른 디자인 패턴들은 특별히 언급하지 않는다. MVC 패턴이 어떻게 활용되었으며 그 안에서 사용된 개별적인 디자인 패턴들이 MVC 패턴에 어떤 도움을주는지 느껴보길 바란다.
2-1. MVC 패턴에 대한 이해
[그림 1] MVC 패턴 개념도 / 출처: http://gpwiki.org/
[그림 1]에서 알 수 있듯이 MVC 패턴은 모델(Model), 뷰(View), 컨트롤러(Controller)로 이뤄진다. 오디오를 컨트롤할 때, 리모컨을 이용하여 기기에 명령을 내리고 입력한 명령을 기기에서 받아 처리하여 스피커를 통해 출력하듯이, OOP 언어에서는 이러한 구조를 MVC 패턴이라 부른다. 자세한 내용은 위키백과를 참고하면 된다(http://ko.wikipedia.org/wiki/MVC_%ED%8C%A8%ED%84%B4)
2-2. MVC 패턴의 사용 형태와 흐름
[그림 2] MVC 패턴 다이어그램
모델
모든 데이터의 상태와 로직을 처리한다. 뷰와 컨트롤러에서 데이터 상태를 처리하거나 가져오는 인터페이스를 제공하는데 모델은 뷰와 컨트롤러에 직접 관여하지 않으며 독립적으로 존재한다. 이것은 MVC 패턴에서 가장 중요한 부분인데 모델의 독립성이 소프트웨어를 좀 더 유연하게 만들어주기 때문이다.
뷰
모델이 가진 데이터를 표현하는 방법을 제공한다. 일반적으로 화면을 표현하는 데 필요한 처리를 담당한다. 뷰는 모델 데이터를 직접 변경하지 않으므로 뷰는 모델로부터 데이터가 업데이트되는 것을 청취하고 데이터를 가져온다. 모든 뷰는 모델에 대한 정보를 알지만 모델은 뷰에 대한 정보를 알지 못하도록 하는 것이 핵심이다. 하나의 모델은 다양한 뷰의 모델이 될 수 있다. 그러나 모델은 여러 개의 뷰가 어떻게 처리되는지 알 필요가 없다.
컨트롤러
사용자로부터 입력 받은 데이터를 모델에게 상태 변경을 요청하고 필요에 따라서는 뷰의 상태 변경을 요청할 수 있다. 때로는 구조를 단순하게 하려고 Controller 로직을 제거하고 뷰에서 Controller 기능을 포함하는 MVC Document View 패턴도 사용된다. 플래시에서도 Controller를 View에 포함하는 경우를 흔히 볼 수 있다. 다만 Controller가 View에 포함되더라도 유연한 코드를 위해서는 View와 Model 간에 강한 결합을 피하고 View 내부에서도 Controller 로직을 따로 처리하는 것이 바람직하다.
[그림 3] MVC 패턴 순서도
1. 사용자는 뷰를 통해서만 소통한다.
뷰를 통해 사용자가 명령을 입력하면 입력된 명령을 컨트롤러에 전달하고 전달된 명령에 따라 컨트롤러에서는 모델에 상태 변경을 요청한다. 사용자는 뷰를 통해서만 의사소통을 한다.
2. 컨트롤러에서 모델에게 상태를 변경하라는 요청을 한다.
뷰에서 전달된 명령을 컨트롤러에서 받아 사용자의 행동을 파악한 후 모델의 상태를 변경하도록 요청한다. Controller에서는 Model의 참조를 가지고 있으며 Model의 메서드를 호출함으로써 모델의 상태를 변경할 수 있다.
3. 반대로 컨트롤러에서 뷰에 상태 변경 요청을 할 수도 있다.
최종적으로 모델에서 상태 변경을 뷰에 알리지만 컨트롤러에서 명령을 받았을 때 뷰의 화면 상태를 변경하도록 직접 요청할 수 있다. 컨트롤러에서 모델에 전달하는 데이터와 직접 관련이 없거나, 관련이 있더라도 모델을 통해 데이터 상태 변경과 별도로 처리해야 할 사항에 대해서는 컨트롤러에서 뷰에 직접 상태 변경을 요청할 수도 있다.
4. 모델에서 상태 변경이 완료되면 뷰에 알린다.
사용자의 명령이나 내부적인 변화에 따라 모델의 데이터가 변경되면 뷰에 알린다. 이때 느슨한 결합을 위해 브로드캐스트(broadcast)를 통해 전달하는데, 플래시에서는 이때 필요한 것이 EventListener다. EventListener는 이벤트를 디스패치(dispatch)하는 것으로 자신 이외의 관계에 대해서는 알 필요 없이 불특정 다수의 인스턴스 객체에 이벤트를 전달할 수 있다.
5. 뷰에서 모델에게 상태에 대한 데이터를 요청한다.
모델에서 변경된 데이터를 직접 가져온다. 모델에서는 자신의 상태가 변경되면 이벤트를 dispatch한다. 이때 뷰에서는 모델의 dispatch 이벤트를 addEventListener를 통해 청취하는 것으로 Model의 변경된 값을 참조한다.
2-3. MVC 패턴을 사용하는 이유
재사용할 가능성이 적거나 View와 Controller의 기능을 함께 포함해도 뷰의 코드가 복잡하지 않으면 Controller의 기능을 View에 직접 넣어도 되겠지만 대부분 화면 처리 부분과 모델을 제어하는 로직이 함께 있으면 자칫 모델과 분리할 수 없는 강한 결합으로 가는 경우가 비일비재하다. 또한 소프트웨어는 이것이 얼마나 오래도록 사용될 것이며 앞으로 어떻게 발전할지 미리 예측할 수 없는 경우가 대부분이기 때문에 뷰와 컨트롤러를 느슨하게 결합해 놓으면 유연하고 확장하기 좋은 구조를 설계할 수 있다.
때에 따라서는 컨트롤러가 모델에 상태 변경만을 요청하는 것이 아니다. 모델의 상태 변경에 따라 컨트롤러에서도 이벤트를 청취할 수 있는데 하나의 Controller에서 상태 변경을 Model에게 요청하는 것이 아닌, 다수의 Controller가 Model에게 상태 변경을 요청할 수 있을 경우에는 Model에서 변경되는 상태에 따라 다른 Controller의 상태를 변경해야 하는 경우도 생긴다. 이런 경우에는 Controller에서 Model에 요청하는 상태 변경과 Controller에서 수신하는 변경 완료 신호가 중첩되어 리미트 스코프(Limit Scope)에 빠지지 않도록 기능 구분에 신경을 써야 한다.
MVC 패턴을 사용하는 가장 큰 장점이라고 한다면 뷰와 모델간의 간섭을 피하고 Controller라는 중간 관리자를 두어 간접적으로 의사소통을 함으로써 유연한 구조를 설계할 수 있다는 것이다.
2-4. 예제를 통해 MVC 패턴 이해하기
그럼 예제를 통해 MVC 패턴이 어떻게 사용됐는지 알아보자. 예제로 설명할 코드는 참고자료(ActionScript 3.0 Design Patterns) 책에 실려있는 예제를 변경하여 사용하였다. 예제는 사용자가 키보드를 클릭하면 해당하는 키의 아스키(ascii) 코드 값을 출력(output) 창에 출력하는 기능을 한다. 일단 가장 기본적인 MVC 패턴 구조로 접근해 보자.
[그림 4] 예제 1 클래스 다이어그램
Main.as
package {
import flash.display.Sprite;
public class <st2:place w:st="on">Main</st2:place> extends Sprite {
private var _model:IModel;
private var _controller:Controller;
private var _view:View;
public function <st2:place w:st="on">Main</st2:place>() {
_model = new Model();
_controller = new Controller(_model);
_view = new View(_model, _controller, stage);
}
}
}
Main은 Document 클래스다. 기본적인 Model, View, Controller에 해당하는 클래스를 생성한다.
IModel.as
package {
import flash.events.IEventDispatcher;
public interface IModel extends IEventDispatcher {
function set key(inKeyCode:uint):void
function get key():uint
}
}
Model 클래스에서 범용적으로 사용되는 key 메서드를 getter, setter 메서드를 이용하여 정의했다. IModel 인터페이스에서 정의한 메서드는 Model 클래스에서 구현한다. IModel 인터페이스를 구현하는 이유는 다형성을 통해 확장을 쉽게 할 수 있기 때문이다.
Model.as
package {
import flash.events.Event;
import flash.events.EventDispatcher;
public class Model extends EventDispatcher implements IModel {
private var _lastKeyPressed:uint;
public function set key(inKeyCode:uint):void {
_lastKeyPressed = inKeyCode;
dispatchEvent(new Event(Event.CHANGE));
}
public function get key():uint {
return _lastKeyPressed;
}
}
}
Model.as는 실제 모델에 해당하는 클래스다. 이 객체에서는 EventDispatcher를 상속하여 자체 이벤트를 발생하도록 한다. Model의 key 메서드에 값이 할당되면 해당 속성(property) 값을 변경하고 이벤트를 dispatch해 준다. 이 모델 클래스에서 확인할 수 있듯이 다른 객체를 알 필요 없이 상태가 변경되면 이벤트를 불특정 객체에 알리는 역할만 한다.
Controller.as
package {
import flash.events.KeyboardEvent;
public class Controller {
private var _model:IModel;
public function Controller(inModel:IModel) {
_model = inModel;
}
internal function pressKey(e:KeyboardEvent):void {
_model.key = e.charCode;
}
}
}
Controller.as 클래스에서는 pressKey 메서드를 통해 Model의 상태 변경을 요청한다. 일단 사용자가 직접 Controller와 의사소통을 하지 않기 때문에 View를 통해 Controller의 pressKey 메서드를 호출하도록 되어 있다.
View.as
package {
import flash.display.Stage;
import flash.events.KeyboardEvent;
import flash.events.Event;
public class View {
private var _model:IModel;
private var _controller:Controller;
public function View(inModel:IModel, inController:Controller, inTarget:Stage) {
_model = inModel;
_controller = inController;
_model.addEventListener(Event.CHANGE, update);
inTarget.addEventListener(KeyboardEvent.KEY_DOWN, onKeyPressHandler);
}
private function update(e:Event):void {
trace(_model.key);
}
private function onKeyPressHandler(e:KeyboardEvent):void {
_controller.pressKey(e);
}
}
}
View.as 클래스에서는 사용자가 키보드의 키를 눌렀을 때 Controller에 정의된 pressKey 함수를 호출해 준다. 만약 View에서 직접 Model의 값을 변경하면 모델이 뷰와 강한 결합을 해 기능이 추가될수록 문제가 발생할 가능성이 크다. 또한 Controller에서는 Model의 상태를 변경하는 것 이외에 여러 가지 로직을 처리할 수 있는데 View에서 직접 Model의 상태를 변경하면 그러한 로직을 View나 Model에 포함해야 하기 때문에 코드가 길어질수록 재사용과 유지보수에 문제가 생긴다.
View.as에서는 사용자의 키보드로부터 키가 눌렸을 때 이벤트를 청취하고 이벤트가 발생하면 Controller에 이벤트를 전달한다. 전달된 이벤트는 Controller에서 Model의 상태를 변경하고 Model에서는 상태가 변경됐음을 알림으로써 Model로서 역할을 다 한다. 이때 View.as에서는 Model의 이벤트를 청취하여 Model의 상태가 변경된 시점에서 Model의 현재 상태 값을 참조하여 View에서 필요한 처리를 하는 것이다.
[그림 5] 예제 2 클래스 다이어그램
example2에서는 example1과 큰 차이는 없으나 Controller를 IKeyboardInput이라는 인터페이스를 통해 구현했다. 이렇게 한 이유는 서로 다른 처리를 하는 Controller 객체를 만들어 다형성을 토대로 손쉽게 Controller에 해당하는 로직을 변경할 수 있기 때문이다.
Main.as
package {
import flash.display.Sprite;
public class <st2:place w:st="on">Main</st2:place> extends Sprite {
private var _model:IModel;
private var _controller:IKeyboardInput;
private var _view:View;
public function <st2:place w:st="on">Main</st2:place>() {
_model = new Model();
_controller = new Controller(_model);
_view = new View(_model, _controller, stage);
}
}
}
Main.as 클래스에서는 기존 예제에서 Controller 객체 타입을 직접 사용한 것이 아닌 IKeyboardInput 타입으로 정의함으로써 IKeyboardInput을 구현하는 타입은 모두 담을 수 있도록 처리하였다.
IModel.as 클래스와 Model.as 클래스는 example1과 똑같다.
IKeyboardInput.as
package {
import flash.events.KeyboardEvent;
public interface IKeyboardInput {
function pressKey(e:KeyboardEvent):void
}
}
IKeyboardInput을 구현하는 객체에서 공통으로 쓰는 메서드를 정의한다. IKeyboardInput 형으로 담을 수 있는 것은 pressKey 메서드를 구현한 것으로 IKeyboardInput 타입으로 사용할 수 있다.
Controller.as
package {
import flash.events.KeyboardEvent;
public class Controller implements IKeyboardInput {
private var _model:IModel;
public function Controller(inModel:IModel) {
_model = inModel;
}
public function pressKey(e:KeyboardEvent):void {
_model.key = e.charCode;
}
}
}
example1과 같으나 example2에서는 IKeyboardInput 인터페이스와 IKeyboardInput에서 정의한pressKey 메서드를 구현한다. 인터페이스에서는 메서드 접근자 public만 가능하기 때문에 example1에서 internal로 정의했던 pressKey 메서드를 public으로 변경하였다. 이로써 Controller 객체는 IKeyboardInput 타입으로 대체할 수 있게 되는 것이다.
View.as
package {
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.display.Stage;
public class View {
private var _model:IModel;
private var _controller:IKeyboardInput;
public function View(inModel:IModel, inController:IKeyboardInput, inTarget:Stage) {
_model = inModel;
_controller = inController;
_model.addEventListener(Event.CHANGE, update);
inTarget.addEventListener(KeyboardEvent.KEY_DOWN, onKeyPressHandler);
}
private function update(e:Event):void {
trace(_model.key);
}
private function onKeyPressHandler(e:KeyboardEvent):void {
_controller.pressKey(e);
}
}
}
View.as에서는 생성 인자로 IKeyboardInput 타입을 전달 받는다. ControllerOther.as라는 클래스를 아래와 같이 정의한다고 가정하자.
ControllerOther.as
package {
import flash.events.KeyboardEvent;
public class ControllerOther implements IKeyboardInput {
private var _model:IModel;
public function ControllerOther(inModel:IModel) {
_model = inModel;
}
public function pressKey(e:KeyboardEvent):void {
_model.key = e.charCode;
trace("ControllerOther-pressKey");
}
}
}
그리고 Main.as에서 Controller 객체가 아닌 ControllerOther 객체를 생성하여 _controller 속성에대입해 보자.
Main.as
package {
import flash.display.Sprite;
public class <st2:place w:st="on">Main</st2:place> extends Sprite {
private var _model:IModel;
private var _controller:IKeyboardInput;
private var _view:View;
public function <st2:place w:st="on">Main</st2:place>() {
_model = new Model();
_controller = new ControllerOther(_model);
_view = new View(_model, _controller, stage);
}
}
}
이렇게 수정하고 실행해 보면 ControllerOther 객체의 pressKey 메서드에 정의한 기능(출력 창에 ControllerOther-pressKey 출력)이 실행됨을 확인할 수 있을 것이다. 이처럼 다형성을 통해 Controller가 처리하는 로직을 런타임에서 손 쉽게 변경할 수 있다.
[그림 6] 예제 3 클래스 다이어그램
example3은 약간 복잡해 보이지만 example2와 다른 부분은 View에 해당하는 부분, 그리고 View를 여러 개 추가할 수 있도록 Main에서 모델의 상태가 변경되었을 때 등록된 View들에 일괄적으로 이벤트를 전달하는 부분이 추가되었다. 그 외에는 example2와 똑같다.
example3에서는 View에 해당하는 서로 다른 처리를 하는 클래스들을 제작하여 Model에서 상태가 변경됐을 때 등록된 복수의 View 객체에 변경 정보를 알리고 변경된 상태에 따라 자체적으로 처리한다.
Main.as
package {
import flash.display.Sprite;
import flash.events.Event;
public class <st2:place w:st="on">Main</st2:place> extends Sprite {
private var _model:IModel;
private var _controller:IKeyboardInput;
private var _rootView:CompositeView;
public function <st2:place w:st="on">Main</st2:place>() {
_model = new Model();
_controller = new Controller(_model);
_rootView = new RootNodeView(_model, _controller, stage);
_rootView.add(new CharCodeLeafView(_model));
_rootView.add(new AsciiCharLeafView(_model));
_model.addEventListener(Event.CHANGE, onUpdateHandler);
}
private function onUpdateHandler(e:Event):void
{
_rootView.update(e);
}
}
}
Main.as에서는 RootNodeView가 여러 개의 View를 관리한다. RootNodeView의 add 메서드를 통해 RootNodeView에서는 등록된 모든 뷰에 이벤트를 전달하는 역할을 한다.
IModel.as, Model.as, IKeyboardInput.as, Controller.as는 example2와 동일하다.
ComponentView.as
package {
import flash.errors.IllegalOperation_error;
import flash.events.Event;
import flash.display.Sprite;
public class ComponentView extends Sprite {
protected var _model:IModel;
protected var _controller:IKeyboardInput;
public function ComponentView(inModel:IModel, inController:IKeyboardInput = null) {
_model = inModel;
_controller = inController;
}
public function add(inComponentView:ComponentView):void {
throw new IllegalOperation_error("add operation not supported");
}
public function remove(inComponentView:ComponentView):void {
throw new IllegalOperation_error("remove operation not supported");
}
public function getChild(inIndex:int):ComponentView {
throw new IllegalOperation_error("getChild operation not supported");
return null;
}
public function update(e:Event = null):void {}
}
}
ComponentView는 View에 관련된 모든 객체에서 상속하는 상위 클래스이며 Sprite를 상속한다. 이 클래스를 상속하는 하위 클래스에서는 각 역할에 따라 이 클래스에서 정의한 메서드를 오버라이드(override)하여 재정의한다.
CompositeView.as
package {
import flash.events.Event;
public class CompositeView extends ComponentView {
private var _children:Array;
public function CompositeView(inModel:IModel, inController:IKeyboardInput = null) {
super(inModel, inController);
_children = new Array();
}
override public function add(c:ComponentView):void {
_children.push(c);
}
override public function update(event:Event = null):void {
for each (var c:ComponentView in _children) {
c.update(event);
}
}
}
}
CompositeView 클래스는 실제 View 기능을 구현한 클래스들을 그룹화하려고 사용했다. Override한 add 메서드를 통해 서로 다른 기능을 하는 View 클래스들을 추가하고 CompositeView에 구현된 update를 통해 등록된 모든 View 객체에 모델의 상태 변경을 알리는 역할을 한다.
RootNodeView.as
package {
import flash.events.KeyboardEvent;
import flash.display.Stage;
public class RootNodeView extends CompositeView {
public function RootNodeView(inModel:IModel, inController:IKeyboardInput, inTarget:Stage) {
super(inModel, inController);
inTarget.addEventListener(KeyboardEvent.KEY_DOWN, onKeyPressHandler);
}
private function onKeyPressHandler(e:KeyboardEvent):void {
_controller.pressKey(e);
}
}
}
RootNodeView는 CompositeView를 상속하는 하위 클래스로 사용자가 키보드를 누르는 정보를 Controller에 전달한다. example2에서는 View에 해당하는 하나의 클래스 안에 포함하였지만 example3에서는 상속을 통해 각 기능적인 부분을 분리하였다. 이렇게 하면 기능 추가와 수정에 유연하게 대처할 수 있다.
AsciiCharLeafView.as
package {
import flash.events.Event;
public class AsciiCharLeafView extends ComponentView {
public function AsciiCharLeafView(inModel:IModel, inController:IKeyboardInput = null) {
super(inModel, inController);
}
override public function update(e:Event = null):void {
trace(String.fromCharCode(_model.key));
}
}
}
AsciiCharLeafView는 View의 기능을 하는 클래스로 ComponentView에서 정의한 update 메서드만을 override한다. CompositeView를 상속하는 RootNodeView에 등록된 모든 View 클래스는 모델의 상태 변경에 따라 실행된다. 기능은 아스키 코드에 해당하는 문자열(string)을 출력 창에 출력해 주는 것이다.
CharCodeLeafView.as
package {
import flash.events.Event;
public class CharCodeLeafView extends ComponentView {
public function CharCodeLeafView(inModel:IModel, inController:IKeyboardInput = null) {
super(inModel, inController);
}
override public function update(e:Event = null):void {
trace(_model.key);
}
}
}
AsciiCharLeafView.as와 형태가 같으며 update 메서드만 기능이 다르다. 이 클래스에서는 example1, example2와 같이 사용자가 누른 키의 아스키 코드 값을 출력 창에 출력한다.
이 예제를 실행해 보면 아스키 코드 값과 누른 키의 String이 출력 창에 출력됨을 확인할 수 있다. 순서를 보면 아스키 코드 값이 먼저 출력됨을 볼 수 있는데 이는 Main.as 클래스에서 RootNodeView의 add 메서드를 통해 CharCodeLeafView의 인스턴스 객체가 먼저 등록되었기 때문이다. 플래시의 내부 클래스 EventDispatcher의 addEventListener 메서드와 dispatchEvent 메서드에서도 이렇게 복수의 객체를 등록하고 일괄적으로 실행하는 비슷한 형태를 하고 있다.
위에서 세 가지 예제를 알아 보았다. MVC 패턴의 아주 기초적인 코드부터 확장해 가는 과정에서 느낄 수 있듯이 단순한 구조로(강한 결합) 모든 기능을 포함했다면 확장 과정에서 관련이 없는 클래스까지 수정해야 하는 문제가 발생하거나 기능 확장에 유연하게 대처할 수 없었을 것이다.
3. 정리
MVC 패턴의 장점
MVC 패턴을 사용하는 이유는 데이터를 화면상의 디자인과 분리할 수 있기 때문이라고 이미 언급했다. 데이터를 디자인과 분리하면 똑 같은 데이터를 사용하는 전혀 다른 디자인 형태를 빠르게 추가할 수 있고 디자인 요소를 제외한 다른 것에 영향을 덜 받기 때문에 수정도 쉽다. 즉 참조하는 데이터의 상태 변화에 따라 다른 결과를 화면에 출력할 수 있고, 서로 느슨한 결합을 통해 외부로부터 요구되는 구조 변경에도 융통성 있게 대응할 수 있다.
MVC 패턴의 단점
MVC 패턴의 단점은 기본 기능 설계를 위해 작성해야 할 클래스가 많아져 다소 복잡하다는 것이다. 또한 속도에 민감한 작업에서는 상속과 참조의 복잡도에 따라 단순한 구조로 제작하는 것보다 속도 최적화에 도움이 되지 않을 수도 있다.
사실 OOP를 처음 접하는 사람에게는 MVC 패턴의 구조가 복잡해 보일 수도 있다. 유지보수할 필요가 없고 기능 자체가 단순한 구조에 MVC 패턴을 사용하는 것은 비효율적일 것이다. 하지만 프로그래밍을 학습하는 과정에서 단순한 구조에서부터 디자인 패턴을 활용해 볼 필요가 있다. 예술가가 동일한 도구를 가지고 다양한 아이디어를 통해 예술 작품을 만들어 내듯이, 구조를 설계하는 프로그래머도 다양한 아이디어를 통해 구조를 설계한다. 하지만 잘못된 코딩 버릇을 되풀이해 사용하다 보면 바람직하게 설계할 수 있는 구조를 비효율적으로 개발하여 더 이상 발전할 수 없는 소프트웨어를 양산할 수 있다. 따라서 디자인 패턴을 공부하는 것으로 좀 더 효율적인 구조를 설계하는 방법을 학습하고 자신이 경험해 보지 않은 효율적인 설계를 통해 자신의 구조 설계 습관에 변화를 줄 수 있다.
MVC 패턴이 Model, View, Controller로 분리된 형태임을 이미 배웠지만 디자인 패턴에는 한 가지 방법만 존재하지는 않는다. 이미 많은 개발자를 통해 검증 받은 디자인 패턴이라도 궁극적으로는 소프트웨어를 효율적으로 제작하기 위한 방법에 불과하다. 프로젝트마다, 디자인마다(플래시는 유독 디자인에 영향을 많이 받는다) 기존 디자인 패턴에 완벽하게 적용되는 것은 그리 많지 않을 것이다. 디자인 패턴 공부는 효율적인 소프트웨어를 제작하려는 한 방법임을 아는 것이 중요하다. 디자인 패턴은 잘 사용하면 수십 배, 수백 배 효율을 낼 수 있지만 자칫 바람직하지 않은 방향으로 사용하면 오히려 프로젝트의 성패를 좌우하는 독약으로 사용될 수 있다는 점도 명심할 필요가 있다.
나 또한 디자인 패턴에 대해 공부하며 실제 작업에서 활용해 보려고 노력 중이다. 학습한 디자인 패턴을 실제 작업에서 활용하다 보면 그 프로젝트에 맞는 효율적인 설계를 하는 것이 아니라 디자인 패턴을 위한 설계를 하는 자신을 발견할 때가 많다. 이럴 때는 디자인 패턴에서 학습한 눈에 보이는 구조를 따라가지 말고 한 걸음 물러나서 프로젝트 전체를 보고 다시 한번 고민해 보는 것이 필요하다.
이렇게 MVC 패턴에 대한 이야기를 해보았다. 처음 접하는 독자들에게는 한 없이 어려워 보일 수도 있는 패턴이 아닌가 싶다. 하지만 반복적으로 MVC 패턴에 관련된 내용을 습득하고 실제 작업에서 자신이 아는 지식을 대입해 봄으로써 좀 더 MVC 패턴(유연하고 확장하기 쉬운 설계)에 가깝게 다가갈 수 있을 것이라 생각한다. 다음에는 우리가 쉽게 접할 수 있는 각각의 디자인 패턴을 알아보겠다.
참고 자료
ActionScript 3.0 Design Patterns / By William B. Sanders,Chandima Cumaranatunge / O'Reilly
Head First Design Patterns / Eric Freeman,Elisabeth Freeman,Kathy Sierra, Bert Bates / O’Reilly
(국문 : Head First Design Patterns / 서환수역 / 한빛미디어)
Head First Object-Oriented Analysis&Design / McLaughlin / O'Reilly
(국문 : Head First Object-Oriented Analysis&Design [세상을 설계하는 객체지향 방법론] / <st2:personname w:st="on">신광연</st2:personname>, <st2:personname w:st="on">박종걸</st2:personname> 역 / 한빛미디어
Advanced ActionScript 3 with Design Patterns / Joey Lott,Danny Patterson / Adobe
(국문 : 액션스크립트 3.0 디자인 패턴 / <st2:placetype w:st="on"><st2:placename w:st="on">정</st2:placename><st1:givenname w:st="on">호연</st1:givenname></st2:placetype>,양주일 / 에이콘)
출처 : 어도비RIA공식사이트
'■ 개발관련 ■ > Design Pattern' 카테고리의 다른 글
소프트웨어 SOLID (0) | 2022.03.04 |
---|---|
Flux 패턴 (0) | 2019.03.12 |