Command Pattern


1. 정의

- 요청을 캡슐화 해서 사용자가 보낸 요청에 대한 정보를 저장, 취소 등 할 수 있도록 하는 패턴


2. 설명

- 다양한 요청을 똑같은 함수의 호출로 처리 가능토록 인터페이스로 구현.

- 커맨드를 만들 때 해당 인터페이스를 상속받아 구현

- 커맨드를 저장하여 해당 커맨드의 로그를 남기거나 할 수 있다


3. 장/단점

1) 장점

- 작업 요청 로그를 남기거나 할 때 쓰면 쉽게 구현이 가능

2) 단점

- 객체의 캡슐화 하기 어려운 요청의 경우 구현이 힘들 수 있다.


4. 예제 코드

using System;
using System.Collections.Generic;

namespace CommandPattern
{
    // Command 인터페이스
    interface ICommand
    {
        // 실행
        int[,] Execute();
        // 되돌리기
        int[,] Undo();
    }

    class DrawSquareCommand : ICommand
    {
        // 왼쪽 위 좌표
        private int x1, y1;

        // 오른쪽 아래 좌표
        private int x2, y2;

        // 사각형 색깔
        private int color;

        // 이전의 캔버스를 저장
        private int[,] canvas;

        // 현재 캔버스와 사각형 좌표들을 받음
        public DrawSquareCommand(int[,] old, int x1, int y1, int x2, int y2, int color)
        {
            canvas = new int[ old.GetLength( 0 ), old.GetLength( 1 ) ];

            for(int i=0 ;i= x1 && i <= x2 && j >= y1 && j <= y2 )
                    {
                        resultCanvas[ i, j ] = color;
                    }
                    else
                        resultCanvas[ i, j ] = canvas[ i, j ];
                }
            }

            return resultCanvas;
        }

        // 사각형 그리기 되돌리기
        public int[,] Undo()
        {
            return canvas;
        }
    }

    class DrawCircleCommand : ICommand
    {
        // 중심 좌표
        private int x1, y1;

        // 원 반지름
        private int squareRadius;

        // 원 색깔
        private int color;

        // 이전의 캔버스를 저장
        private int[,] canvas;

        // 현재 캔버스와 사각형 좌표들을 받음
        public DrawCircleCommand(int[,] old, int x1, int y1, int radius, int color)
        {
            canvas = new int[ old.GetLength( 0 ), old.GetLength( 1 ) ];

            for ( int i = 0 ; i < canvas.GetLength( 0 ) ; i++ )
            {
                for ( int j = 0 ; j < canvas.GetLength( 1 ) ; j++ )
                {
                    canvas[ i, j ] = old[ i, j ];
                }

            }

            this.x1 = x1;
            this.y1 = y1;

            squareRadius = radius * radius;

            this.color = color;
        }

        // 사각형 그리기 실행
        public int[,] Execute()
        {
            // 결과 값 저장할 변수
            int[,] resultCanvas = new int[ canvas.GetLength( 0 ), canvas.GetLength( 1 ) ];

            // 원 그림
            for ( int i = 0 ; i < canvas.GetLength( 0 ) ; i++ )
            {
                for ( int j = 0 ; j < canvas.GetLength( 1 ) ; j++ )
                {
                    if ( ( i - x1 ) * ( i - x1 ) + ( j - y1 ) * ( j - y1 ) <= squareRadius ) 
                    {
                        resultCanvas[ i, j ] = color;
                    }
                    else
                        resultCanvas[ i, j ] = canvas[ i, j ];
                }
            }

            return resultCanvas;
        }

        // 원 그리기 되돌리기
        public int[,] Undo()
        {
            return canvas;
        }
    }

    class DrawLineCommand : ICommand
    {
        // 왼쪽 위 좌표
        private int x1, y1;

        // 오른쪽 아래 좌표
        private int x2, y2;

        // 사각형 색깔
        private int color;

        // 이전의 캔버스를 저장
        private int[,] canvas;

        // 현재 캔버스와 사각형 좌표들을 받음
        public DrawLineCommand(int[,] old, int x1, int y1, int x2, int y2, int color)
        {
            canvas = new int[ old.GetLength( 0 ), old.GetLength( 1 ) ];

            for ( int i = 0 ; i < canvas.GetLength( 0 ) ; i++ )
            {
                for ( int j = 0 ; j < canvas.GetLength( 1 ) ; j++ )
                {
                    canvas[ i, j ] = old[ i, j ];
                }
            }

            this.x1 = x1;
            this.y1 = y1;
            this.x2 = x2;
            this.y2 = y2;

            this.color = color;
        }

        // 사각형 그리기 실행
        public int[,] Execute()
        {
            // 결과 값 저장할 변수
            int[,] resultCanvas = new int[ canvas.GetLength( 0 ), canvas.GetLength( 1 ) ];

            // 기울기를 구할 수 없을 때
            if (x2 == x1)
            {
                // 선 그림
                for ( int i = 0 ; i < canvas.GetLength( 0 ) ; i++ )
                {
                    for ( int j = 0 ; j < canvas.GetLength( 1 ) ; j++ )
                    {
                        if ( i == x1 && j >= y1 && j <= y2 )
                        {
                            resultCanvas[ i, j ] = color;
                        }
                        else
                            resultCanvas[ i, j ] = canvas[ i, j ];
                    }
                }
            }
            else
            {
                // 기울기
                float slope = ( float ) ( y2 - y1 ) / ( x2 - x1 );

                // y 절편
                float yIntercept = y1 - ( x1 * slope );
                
                // 선 그림
                for ( int i = 0 ; i < canvas.GetLength( 0 ) ; i++ )
                {
                    for ( int j = 0 ; j < canvas.GetLength( 1 ) ; j++ )
                    {
                        if ( i >= x1 && i <= x2 && j >= y1 && j <= y2 )
                        {
                            if ( j == ( int ) ( slope * i + yIntercept ) )
                            {
                                resultCanvas[ i, j ] = color;
                            }
                            else
                                resultCanvas[ i, j ] = canvas[ i, j ];
                        }
                        else
                            resultCanvas[ i, j ] = canvas[ i, j ];
                    }
                }
            }

            return resultCanvas;
        }

        // 선 그리기 되돌리기
        public int[,] Undo()
        {
            return canvas;
        }
    }

    class CommandManager
    {
        // 실행취소 기록
        private Stack undoHistory;
        // 다시실행 기록
        private Stack redoHistory;

        public CommandManager()
        {
            undoHistory = new Stack();
            redoHistory = new Stack();
        }

        // 명령 실행
        public int[,] ExecuteCommand(ICommand com)
        {
            // 일반 명령 실행 시 다시 실행 기록은 삭제.
            redoHistory.Clear();

            // 실행할 명령을 실행취소 기록에 넣어줌.
            undoHistory.Push( com );
            
            return com.Execute();
        }

        // 실행 취소
        public int[,] UndoCommand()
        {
            ICommand undoCom = undoHistory.Pop();
            redoHistory.Push( undoCom );

            return undoCom.Undo();
        }

        // 다시 실행
        public int[,] RedoCommand()
        {
            ICommand redoCom = redoHistory.Pop();
            undoHistory.Push( redoCom );

            return redoCom.Execute();
        }
    }

    class Program
    {
        public static void PrintCanvas(int[,] canvas)
        {
            for ( int i = 0 ; i < canvas.GetLength( 0 ) ; i++ )
            {
                for ( int j = 0 ; j < canvas.GetLength( 1 ) ; j++ )
                {
                    Console.Write( canvas[ i, j ] );
                    Console.Write( " " );
                }
                Console.Write( "\n" );
            }
            Console.Write( "\n" );
        }

        public static void UpdateCanvas(int[,] canvas, int[,] newCanvas)
        {
            for ( int i = 0 ; i < canvas.GetLength( 0 ) ; i++ )
            {
                for ( int j = 0 ; j < canvas.GetLength( 1 ) ; j++ )
                {
                    canvas[ i, j ] = newCanvas[ i, j ];
                }
            }
        }

        static void Main(string[] args)
        {
            // canvas 생성
            int[,] canvas = new int[ 10, 10 ];

            // canvas 초기화
            for ( int i = 0 ; i < canvas.GetLength( 0 ) ; i++ )
            {
                for ( int j = 0 ; j < canvas.GetLength( 1 ) ; j++ )
                {
                    canvas[ i, j ] = 0;
                }
            }

            Console.WriteLine( "==================== 초기 캔버스 ====================" );
            PrintCanvas( canvas );

            // 명령 매니저 생성
            CommandManager cManager = new CommandManager();

            // 원 그리기 명령 생성 후 실행
            DrawCircleCommand dCircle = new DrawCircleCommand( canvas, 5, 5, 3, 5 );
            UpdateCanvas(canvas, cManager.ExecuteCommand( dCircle ));
            Console.WriteLine( "==================== 1.원 그리기 ====================" );
            PrintCanvas( canvas );

            // 사각형 그리기 명령 생성 후 실행
            DrawSquareCommand dSquare = new DrawSquareCommand( canvas, 4, 4, 6, 6, 3 );
            UpdateCanvas( canvas, cManager.ExecuteCommand( dSquare ) );
            Console.WriteLine( "================== 2.사각형 그리기 ==================" );
            PrintCanvas( canvas );

            // 선 그리기 명령 생성 후 실행
            DrawLineCommand dLine = new DrawLineCommand( canvas, 4, 4, 6, 6, 1 );
            UpdateCanvas( canvas, cManager.ExecuteCommand( dLine ) );
            Console.WriteLine( "==================== 3.선 그리기 ====================" );
            PrintCanvas( canvas );

            // 되돌리기 명령 실행
            UpdateCanvas( canvas, cManager.UndoCommand() );
            Console.WriteLine( "================== 2번으로 되돌리기 =================" );
            PrintCanvas( canvas );

            // 되돌리기 명령 실행
            UpdateCanvas( canvas, cManager.UndoCommand() );
            Console.WriteLine( "================== 1번으로 되돌리기 =================" );
            PrintCanvas( canvas );

            // 다시 실행 명령 실행
            UpdateCanvas( canvas, cManager.RedoCommand() );
            Console.WriteLine( "================== 2번으로 다시실행 =================" );
            PrintCanvas( canvas );
        }
    }
}


5. 출력 결과물


==================== 초기 캔버스 ====================
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0

==================== 1.원 그리기 ====================
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 5 0 0 0 0
0 0 0 5 5 5 5 5 0 0
0 0 0 5 5 5 5 5 0 0
0 0 5 5 5 5 5 5 5 0
0 0 0 5 5 5 5 5 0 0
0 0 0 5 5 5 5 5 0 0
0 0 0 0 0 5 0 0 0 0
0 0 0 0 0 0 0 0 0 0

================== 2.사각형 그리기 ==================
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 5 0 0 0 0
0 0 0 5 5 5 5 5 0 0
0 0 0 5 3 3 3 5 0 0
0 0 5 5 3 3 3 5 5 0
0 0 0 5 3 3 3 5 0 0
0 0 0 5 5 5 5 5 0 0
0 0 0 0 0 5 0 0 0 0
0 0 0 0 0 0 0 0 0 0

==================== 3.선 그리기 ====================
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 5 0 0 0 0
0 0 0 5 5 5 5 5 0 0
0 0 0 5 1 3 3 5 0 0
0 0 5 5 3 1 3 5 5 0
0 0 0 5 3 3 1 5 0 0
0 0 0 5 5 5 5 5 0 0
0 0 0 0 0 5 0 0 0 0
0 0 0 0 0 0 0 0 0 0

================== 2번으로 되돌리기 =================
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 5 0 0 0 0
0 0 0 5 5 5 5 5 0 0
0 0 0 5 3 3 3 5 0 0
0 0 5 5 3 3 3 5 5 0
0 0 0 5 3 3 3 5 0 0
0 0 0 5 5 5 5 5 0 0
0 0 0 0 0 5 0 0 0 0
0 0 0 0 0 0 0 0 0 0

================== 1번으로 되돌리기 =================
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 5 0 0 0 0
0 0 0 5 5 5 5 5 0 0
0 0 0 5 5 5 5 5 0 0
0 0 5 5 5 5 5 5 5 0
0 0 0 5 5 5 5 5 0 0
0 0 0 5 5 5 5 5 0 0
0 0 0 0 0 5 0 0 0 0
0 0 0 0 0 0 0 0 0 0

================== 2번으로 다시실행 =================
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 5 0 0 0 0
0 0 0 5 5 5 5 5 0 0
0 0 0 5 3 3 3 5 0 0
0 0 5 5 3 3 3 5 5 0
0 0 0 5 3 3 3 5 0 0
0 0 0 5 5 5 5 5 0 0
0 0 0 0 0 5 0 0 0 0
0 0 0 0 0 0 0 0 0 0


'C# > Design Pattern' 카테고리의 다른 글

[Design Pattern C#]Singleton Pattern  (0) 2016.11.26

Singleton Pattern


1. 정의

- 오직 단 하나의 인스턴스만 생성되는 클래스로 전역 범위에서 접근 가능함


2. 설명

- 생성자를 private로 선언해서 하나의 인스턴스 외에는 생성 자체가 불가능 하도록 함.

- 해당 클래스의 인스턴스를 저장하는 Instance란 멤버변수를 선언하고 이 멤버변수에 접근하는 방식으로만 인스턴스 접근

- Instance에 접근 할 때 만약 인스턴스가 생성되어 있지 않을 경우에만 생성자를 호출해 인스턴스를 생성.

- Instance 멤버변수를 static으로 선언하기 때문에 전역 범위에서 접근 가능.


3. 장/단점

1) 장점

- 인스턴스가 하나만 생성 되므로 메모리 관리가 효율적이다

- 인스턴스가 여러개 생성되면 안되는 경우에 사용하는 것이 좋다

2) 단점

- 전역변수나 마찬가지라 다양한 접근이 있을 경우 데이터 관리에 문제가 생길 수 있다

- 멀티 쓰레드 프로그램 일 경우 locking에 신경 써주지 않으면 문제가 발생한다.


4. 예제 코드

using System.IO;
using System.Text;

namespace CsharpTest
{
    class SingletonLog
    {
        // 로그 파일 이름
        private readonly string fileName = "log.txt";
        // 파일 입출력 위한 StreamWriter 변수
        private StreamWriter sw;
        // 로그 포멧 정리 위한 StreamBuilder 변수
        private StringBuilder sb;

        // 로그 종류
        private readonly int[] code = { 1000, 2000, 3000 };
        // 로그 중요도
        private readonly string[] grade = { "WARNING", " ERROR ", " INFOR " };
        
        // 하나 뿐인 인스턴스
        private static SingletonLog _instance;

        // 표면상 보이는 인스턴스
        public static SingletonLog Instance
        {
            get
            {
                // 인스턴스가 생성되지 않았을 경우 생성
                if ( _instance == null )
                    _instance = new SingletonLog();

                return _instance;
            }
        }

        // 생성자
        private SingletonLog()
        {
            // 파일 열기
            sw = new StreamWriter( fileName, true );
            // StringBuilder 생성
            sb = new StringBuilder();
        }

        // 로그 출력하는 함수
        public void WriteLog(int codeIndex, int gradeIndex, string log)
        {
            // 로그 포멧 지정
            sb.AppendFormat( "[{0}]{1}({2}) : {3}", System.DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss"), code[codeIndex], grade[gradeIndex], log );

            // 로그 출력
            sw.WriteLine( sb.ToString() );
            sw.Flush();

            // StringBuilder 초기화
            sb.Clear();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 특별한 생성자 없이 그냥 전역변수처럼 호출
            SingletonLog.Instance.WriteLog( 0, 0, "test0" );
            SingletonLog.Instance.WriteLog( 1, 1, "test1" );
            SingletonLog.Instance.WriteLog( 2, 2, "test2" );
        }
    }
}


5. 출력 결과물

<log.txt>

[2016-11-25 11:11:25]1000(WARNING) : test0

[2016-11-25 11:11:26]2000( ERROR ) : test1

[2016-11-25 11:11:26]3000( INFOR ) : test2



'C# > Design Pattern' 카테고리의 다른 글

[Design Pattern C#]Command Pattern  (0) 2016.11.28

+ Recent posts