MVC패턴

 | JSP
2012. 5. 8. 18:16


그림 1 MVC Pattern Diagram


<그림 1>에서는 일반적인 MVC 패턴을 다이어그램으로 표현하고 있습니다.

MVC 패턴은 Model, View, Controller 세 개의 컴포넌트로 구성되는데 각 컴포넌트에서 담당하는 책임은 다음과 같습니다.


  • ViewUI 요소를 그려줍니다.
  • ControllerUI의 사용자 액션에 응답하고 데이터 흐름을 제어합니다.
  • ModelMVP 패턴의 Model과 마찬가지로 데이터와 상태를 유지하며 데이터 처리 로직을 포함합니다.


MVC 패턴이 MVP패턴과 다른 점은 Presenter 컴포넌트 대신 Controller 컴포넌트가 존재하는 것이죠. Controller는 사용자의 액션 - 예를 들면 버튼 컨트롤을 클릭하거나 키보드를 통해 텍스트를 입력 받는 행위들 - 에 반응하여 적절한 행동을 하는 코드를 포함합니다. 즉, 사용자의 액션에 반응하여 Model의 데이터를 변경하는 역할을 수행합니다.


MVC 패턴이 적용된 대표적인 사례는 MFC(CDocument <-> CView), ASP.NET(ASPX <-> Code Behind), WinForm.NET(Form & Control Design.cs<-> Form & Control .cs <-> Entity or Business Logic .cs) , JSP등이 있겠습니다. 특히 JSP에서는 (자바진영) View와 Controller가 합쳐진 Model 1과 View와 Controller를 명확히 구분하고 View와 Model 사이에 상호작용을 전담하는 역할로서 Controller를 정의합니다.


그림 2 JSP Model 1 architecture


그림 3 JSP Model 2 architecture


Model 1에서는 JSP에서 사용자 Request(Controller)와 Response(View)를 담당하고 Data source의 접근 처리는 JavaBean(Model)에서 담당하게 됩니다.

Model 2에서는 사용자 Request를 Servlet(Controller)에서 담당하고 Response는 JSP(View), Data source의 접근 처리는 JavaBean(Model)에서 담당하게 되죠.


이와 비슷한 모델로 최근 ASP.NET 3.5에서 발표될 MVC Framework를 들 수 있겠습니다. Request URL을 Controller가 분석해서 Model에 데이터를 쿼리(조회, 갱신)하고 적합한 View를 선택해서 Rendering하는 방식입니다.


문제는 View와 모델을 완전히 분리할 수 없다는 것이죠.

JSP의 Model2를 보셔도 JSP와 JavaBean과의 상호작용은 피할 수 없습니다. 역시 ASP.NET의 MVC Framework에서도 Controller에서 Model에 쿼리를 수행한 다음 반환된는 Entity(or Entity List)를 View에 바인딩할 때 Entity 자체를 넘겨주게 되므로 View에서 Model을 의존하게 되는 상황이 발생하게 되죠.


또한 MVC 패턴에서 Model에 연결된 View가 여럿일 경우 Model과 View간(또는 Model을 대신해서 Controller) Observer패턴을 적용하여 Model(or Controller)에서 View로 Notify하는 (Active Model) 방식이 사용되거나 반대로 View에서 Model을 polling(Passive Model)하는 방식이 수행됩니다.


그림 4 MVC passive model


그림 5 MVC active model


그림 6 Using observer pattern to decouple the model from view in the active model


이 프로세스를 살펴봐도 역시 View와 Model의 커플링은 제거하기 힘들어 보이는 군요.

(이런 논의도 있습니다. http://kldp.org/node/70219 )


애초에 MVC패턴이 나온 이유가 뭘까요?

바로 View와 Model의 분리입니다. 쉽게 말해 표현계층과 데이터( + 데이터 처리 로직)를 분리하여 데이터 처리 로직이 중복 코딩되는 것을 막고 로직과 엔티티(데이터)를 재 사용하는데 그 목적이 있습니다. 또한 GUI의 단위 테스트 코드를 작성할 수 있을지에 대한 기대감도 포함합니다.


하지만 MVC패턴에서는 View와 Model간의 의존성을 완벽히 분리할 수 없는데다 패턴의 모호함때문에 해석이 제각각 달라지며 여러가지 변형이 생기게 되었습니다.

그런 와중에 MVP패턴이 마틴 파울러에 의해서 발표되었는데 이 패턴에서는 View와 Model의 완벽한 분리가 이뤄지는 특징이 있습니다.

MVP 패턴에 대해서는 "MVP(Model-View-Presenter)패턴을 적용한 GUI Architecture 설계" 포스트를 참조하세요.

Trackback 0 Comment 0

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



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

이 글은 스프링노트에서 작성되었습니다.

'JSP' 카테고리의 다른 글

log4j구현환경제공.  (0) 2012.05.08
MyBatis (odbc프레임웍)  (0) 2012.05.08
Login&Logout 기본패턴  (0) 2012.05.08
log4j  (0) 2012.05.08
JSTL- 사용 하기  (0) 2012.05.08
Posted by 사라링
BLOG main image
.. by 사라링

카테고리

사라링님의 노트 (301)
JSP (31)
J-Query (41)
JAVA (24)
디자인패턴 (1)
스트러츠 (3)
안드로이드 (11)
오라클 (45)
우분투-오라클 (1)
이클립스메뉴얼 (6)
스프링3.0 (23)
자바스크립트 (10)
HTML5.0 (17)
정보처리기사 (1)
기타(컴퓨터 관련) (1)
문제점 해결 (3)
프로젝트 (2)
AJAX (4)
하이버네이트 (3)
트러스트폼 (11)
Jeus (2)
재무관리(회계) (5)
정규식 (5)
아이바티스 (8)
취미 (2)
소프트웨어 보안 관련모음 (0)
정보보안기사 (6)
C언어 베이직 및 프로그램 (3)
보안 관련 용어 정리 (2)
넥사크로 (6)
웹스퀘어_ (0)
Total :
Today : Yesterday :