티스토리 뷰
목차
디지인 패턴 중 상태패턴 (C# state pattern)을 활용한 응용 예제입니다. 실행 화면은 아래와 같습니다.
[디자인 패턴 예제 C#] 실행 화면
State Pattern 프로젝트 - TestImageButton(State).zip
스테이트 패턴 실행파일 - TestImageButton(State)exe.zip
전체 소스는 바로 위 링크를 참조하세요.
화면에서 위로 버튼을 누르면, 12가 증가하고, 아래로 버튼을 누르면, 12가 감소합니다. C# state pattern 상태 패턴 예제 소스 설명 시작합니다.
c# State Pattern 구현할 기능이 담긴 인터페이스를 하나 선언합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TestImageButton_State_ { interface IState { // 그림이랑 값을 바꾸는거 두 개 밖에 없음 // 추가하고 싶은 상태는 여기에 미리 정의 void ChangePicture(); void ChangeTestValue(int isIncrease); } } | cs |
이어서, c# 스테이트 패턴의 State를 제어할 컨트롤 클래스를 하나 선언합니다.
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.Drawing; namespace TestImageButton_State_ { class State { // 0~9까지의 버튼, 폼의 100, 10, 1자리 픽쳐 박스 // 픽쳐 박스의 경우엔 박싱/언박싱 문제가 있을 수 있음 public ImageList ilNo; public PictureBox[] pbValue = new PictureBox[3]; IState state; IState nullState; IState valueChange; private int _controlValue = 500; public int controlValue { get { return _controlValue; } set { _controlValue = value; } } public int StateOfObject { set { if (value == 0) state = nullState; else if (value == 1) state = valueChange; else state = nullState; } } public State() { nullState = new NullState(this); valueChange = new ValueChange(this); ilNo = new ImageList(); ilNo.ImageSize = new Size(33, 45); ilNo.Images.Add(Properties.Resources.no0); ilNo.Images.Add(Properties.Resources.no1); ilNo.Images.Add(Properties.Resources.no2); ilNo.Images.Add(Properties.Resources.no3); ilNo.Images.Add(Properties.Resources.no4); ilNo.Images.Add(Properties.Resources.no5); ilNo.Images.Add(Properties.Resources.no6); ilNo.Images.Add(Properties.Resources.no7); ilNo.Images.Add(Properties.Resources.no8); ilNo.Images.Add(Properties.Resources.no9); } public void ReleaseMemory() { // 이미지 리스트 메모리 해제 for (int ilCount = 0; ilCount < ilNo.Images.Count; ilCount++) { ilNo.Images[ilCount].Dispose(); } ilNo.Dispose(); // 픽쳐 박스 메모리 해제 pbValue[0].Dispose(); pbValue[1].Dispose(); pbValue[2].Dispose(); GC.Collect(); } public void ChangePicture() { state.ChangePicture(); } public void ChangeTestValue(int isIncrease) { state.ChangeTestValue(isIncrease); } } } | cs |
그리고, 각 상태에 따른 c# State Pattern 동작을 구현해야합니다.
스테이트 패턴의 특징이 바로 구현부를 따로 떼어낸다는 것입니다.
아래는 스테이트 상태가 Null일 경우입니다.
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 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; namespace TestImageButton_State_ { class NullState : IState { State st; public NullState(State _st) { this.st = _st; } public void ChangePicture() { MessageBox.Show("그림 못바꿔요"); } public void ChangeTestValue(int isIncrease) { MessageBox.Show("값도 못바꿔요"); } } } | cs |
다음은, 스테이트 패턴에서 실제로 값을 변경하는 경우입니다.
상속받은 State Pattern 인터페이스를 먼저 정의합니다. ValueChange() 함수에서 조작해야 할 State를 상황에 맞게 변경해 주죠.
ChangePicture() 함수가 호출된다면, 100 자리의 수를 1자리 씩 끊어 별도의 변수에 저장합니다. 세 자리를 한꺼번에 다루는 상황이 아니기에 부득이 저렇게 해야 합니다.
계산이 끝나면, 일단 픽처 박스의 이미지를 비웁니다.
Dispose() 함수를 이용해서요.
이 소스를 생략하고 바로 새 이미지로 덮어씌워도 되는데, 이러면 메모리 누수가 발생합니다. 개발자는 이미지 교체를 생각했지만, 컴파일러 입장에선 이미지 위에 다른 이미지를 올리는 것이니깐요.
이점만 주의하면 됩니다.
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 | using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TestImageButton_State_ { class ValueChange : IState { State st; public ValueChange(State _st) { this.st = _st; } public void ChangePicture() { int hundred, ten, one, toBeChange; toBeChange = st.controlValue; hundred = Convert.ToInt32(Math.Truncate(toBeChange * 0.01)); ten = Convert.ToInt32(Math.Truncate((toBeChange - (hundred * 100)) * 0.1)); one = Convert.ToInt32(toBeChange - (hundred * 100) - (ten * 10)); // 각각의 이미지에 대한 Dispose를 해주지 않으면 메모리 좔좔 샘 // 가비지 컬렉터는 완벽하지 않음 st.pbValue[0].Image.Dispose(); st.pbValue[1].Image.Dispose(); st.pbValue[2].Image.Dispose(); st.pbValue[0].Image = st.ilNo.Images[hundred]; st.pbValue[1].Image = st.ilNo.Images[ten]; st.pbValue[2].Image = st.ilNo.Images[one]; GC.Collect(); } public void ChangeTestValue(int tmpRefValue) { int tmp = 12 * tmpRefValue; st.controlValue += tmp; st.controlValue = (st.controlValue > 999) ? 999 : st.controlValue; st.controlValue = (st.controlValue < 100) ? 100 : st.controlValue; } } } | cs |
마지막으론, c# State Pattern 폼의 코드입니다.
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace TestImageButton_State_ { public partial class Form1 : Form { State stateController; readonly int stateIsNull = 0; readonly int stateIsChange = 1; PictureBox[] pbValue = new PictureBox[3]; public Form1() { InitializeComponent(); pbValue = new PictureBox[3]; pbValue[0] = pbCount1; pbValue[1] = pbCount2; pbValue[2] = pbCount3; stateController = new State { pbValue = this.pbValue }; // 처음엔 state 상태를 Null로 stateController.StateOfObject = stateIsNull; } private void pbControl_MouseUp(object sender, MouseEventArgs e) { string targetName = ((PictureBox)sender).Name.ToString(); // 마우스를 뗐으니 평상시 버튼으로 변경 if (targetName.CompareTo("pbUp") == 0) { pbUp.Image.Dispose(); pbUp.Image = Properties.Resources.SubForm___Home___ArrowUp; } if (targetName.CompareTo("pbDown") == 0) { pbDown.Image.Dispose(); pbDown.Image = Properties.Resources.SubForm___Home___ArrowDown; } GC.Collect(); // 마우스를 뗐으니 왼쪽에 값 변경 stateController.ChangePicture(); } private void pbControl_MouseDown(object sender, MouseEventArgs e) { string targetName = ((PictureBox)sender).Name.ToString(); // 선택된 버튼으로 교체 if (targetName.CompareTo("pbUp") == 0) { pbUp.Image.Dispose(); pbUp.Image = Properties.Resources.SubForm___Home___ArrowUp_sel; } if (targetName.CompareTo("pbDown") == 0) { pbDown.Image.Dispose(); pbDown.Image = Properties.Resources.SubForm___Home___ArrowDown_sel; } GC.Collect(); // state 설정 stateController.StateOfObject = stateIsChange; // + 또는 - 설정 int tmpRefValue = (targetName.CompareTo("pbUp")) == 0 ? 1 : -1; stateController.ChangeTestValue(tmpRefValue); } private void CreatePictureBox(string pbName) { } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { stateController.ReleaseMemory(); pbValue[0].Dispose(); pbValue[1].Dispose(); pbValue[2].Dispose(); GC.Collect(); } } } | cs |
pbControl_MouseUp() 함수와 pbControl_MouseDown() 함수는 화살표가 갖는 이벤트 함수가 되겠습니다.
종종, C#의 가비지 컬렉터를 지나치게 신뢰한 나머지, 메모리 해제의 필요성에 대해 아예 생각지도 않은채 넘어가는 경우가 많은데, 메모리 해제는 꼭 해줘야 합니다. 가비지 컬렉터는 똑똑할지언정 완벽한 존재는 아닙니다.
이것으로 C# 스테이트 패턴 예제 소개를 마칩니다.
State Pattern 프로젝트 - TestImageButton(State).zip
스테이트 패턴 실행파일 - TestImageButton(State)exe.zip
ps. 이미지는 절대 경로로 하세요. 리소스에서 불러오는 것보다 훨씬 빨라요.
관련 글
▷ [C# 디자인 패턴] 팩토리 메소드 패턴 예제 (Factory Pattern)
▷ C# 팩토리 패턴 예제 (추상 Abstract Factory Method Pattern)
▷ C++ 싱글톤 패턴 + Friend 키워드 예제, 디자인패턴 사용 방법
ⓒ written by vicddory