17. 해석자 패턴(Interpreter Pattern) 구현

17.4 구현

해석자 패턴에 대한 예제 프로그램을 구현하는 순서는 Picture, Expression 군, Macro와 데모 코드 순으로 하겠습니다.

17.4.1 Picture

Picture에서는 단순히 사진의 이름과 색조, 명도, 채도의 값을 갖는 클래스로 구현을 하겠습니다. 시나리오를 위해 필요한 클래스이며 해석자 패턴과 관련되는 형식은 아닙니다.

▶ Picture.cs

using System;
namespace Interpreter
{
    class Picture
    {
        string name;
        int tone;
        int brightness;
        int saturation;
        public Picture(string name,int tone,int brightness,int saturation)
        {
            this.name = name;
            this.tone = tone;
            this.brightness = brightness;
            this.saturation = saturation;
        }
        public void ChangeTone(int tone)
        {
            this.tone += tone;
        }
        public void ChangeBrightness(int brightness)
        {
            this.brightness += brightness;
        }
        public void ChangeSaturation(int saturation)
        {
            this.saturation += saturation;
        }
        public void View()
        {
            Console.WriteLine("사진 파일명:{0}",name);
            Console.WriteLine("    색조:{0} 명도:{1} 채도:{2}", 
                tone, brightness, saturation);
        }
    }
}

17.4.2 Expression 군

Expression은 입력 구문을 해석하는 메서드에 대한 추상적인 약속을 동반합니다.

public abstract string Interpret(string context);

여기에서는 Expression 개체를 책임 연쇄 패턴처럼 Expression 개체는 다음 Expresion 개체의 위치 정보를 알고 있어서 입력 구문에 대한 해석을 요청하면 자신이 해석하고 연쇄적으로 다음 개체에게 해석을 요청할 것입니다.

protected string NextInterpret(string context)
{
    ...
}

그리고 사진을 보정해 달라는 요청이 오면 자신이 이미 해석한 정보에 맞게 사진을 보정한 후에 연결된 다음 Expression 개체에게 보정 명령을 요청하도록 할 것입니다. 책임 연쇄 패턴을 적용하였다고 볼 수 있을 것입니다.

public Expression Next
{
    get;
    set;
}
public virtual void DoItWithPicture(Picture picture)
{
}

Expresion 형식을 기반의 파생 클래스에는 ToneExpression, BrightExpression, SatuExpression 클래스를 구현할 것입니다. 이들은 입력 구문 중에 색조 변환, 명도 변환, 채도 변환에 대한 구문을 해석하여 보정할 수치 값을 유지하도록 구현합시다. 물론, 해석에 대한 부분도 책임 연쇄 패턴과 같은 형태로 수행하게 설계하여 자신이 해석한 후에 다음 개체를 통해 해석을 요청을 할 것입니다. 또한, 사진에 대한 보정 명령에 대한 부분도 해석하는 것처럼 자신이 사진 개체에 대한 보정 작업을 수행 후에 다음 개체를 통해 보정을 요청합시다.

▶ Expression.cs

namespace Interpreter
{
    abstract class Expression
    {
        public Expression Next
        {
            get;
            set;
        }
        public Expression()
        {
            Next = null;
        }
        public abstract string Interpret(string context);
        public virtual void DoItWithPicture(Picture picture)
        {
            if(Next!=null)
            {
                Next.DoItWithPicture(picture);
            }
        }
        protected string NextInterpret(string context)
        {
            if(Next!=null)
            {
                return Next.Interpret(context);
            }
            return context;
        }
        protected int GetNumber(string context, int index)
        {
            int index2 = -1;
            index2 = context.IndexOf(";", index);
            string re = context.Substring(index + 1, index2 - index - 1);
            try
            {
                return int.Parse(re);
            }
            catch{    }
            return 0;
        }
    }
}

▶ ToneExpression.cs 

namespace Interpreter
{
    class ToneExpression:Expression
    {
        int value;
        public override string Interpret(string context)
        {
            value = 0;
            int index = -1;            
            string be = "";
            string af = "";            
            while ((index = context.IndexOf("T")) != -1)
            {                
                value += GetNumber(context, index);                
                if (index > 0)
                {
                    be = context.Substring(0, index);
                }
                else
                {
                    be = "";
                }
                index = context.IndexOf(";", index);
                af = context.Substring(index + 1);
                context = be + af;
            }
            return NextInterpret(context);
        }
        public override void DoItWithPicture(Picture picture)
        {
            picture.ChangeTone(value);
            base.DoItWithPicture(picture);
        }
    }
}

▶ BrightExpression.cs  

namespace Interpreter
{
    class BrighExpression:Expression
    {
        int value;
        public override string Interpret(string context)
        {
            value = 0;
            int index = -1;            
            string be = "";
            string af = "";
            while ((index = context.IndexOf("B")) != -1)
            {                
                value += GetNumber(context, index);
                if (index > 0)
                {
                    be = context.Substring(0, index);
                }
                else
                {
                    be = "";
                }
                index = context.IndexOf(";", index);
                af = context.Substring(index + 1);
                context = be + af;
            }
            return NextInterpret(context);
        }
        public override void DoItWithPicture(Picture picture)
        {
            picture.ChangeBrightness(value);
            base.DoItWithPicture(picture);
        }
    }
}

▶ SatuExpression.cs   

namespace Interpreter
{
    class SatuExpression : Expression
    {
        int value;
        public override string Interpret(string context)
        {
            value = 0;
            int index = -1;            
            string be = "";
            string af = "";            
            while ((index = context.IndexOf("S")) != -1)
            {                
                value += GetNumber(context, index);
                if (index > 0)
                {
                    be = context.Substring(0, index);
                }
                else
                {
                    be = "";
                }
                index = context.IndexOf(";", index);
                af = context.Substring(index + 1);
                context = be + af;
            }
            return NextInterpret(context);
        }
        public override void DoItWithPicture(Picture picture)
        {
            picture.ChangeSaturation(value);
            base.DoItWithPicture(picture);
        }
    }
}

17.4.3 Macro와 데모 코드

Macro에서는 Expression 개체들을 리스트 형태로 관리를 하기로 하였습니다. 이에 Expression 개체를 리스트에 추가하는 메서드를 제공해야겠지요. 그리고, 사진 보정에 대한 명령 구문을 추가를 하는 기능을 제공해야 할 것입니다. 이 기능에서는 내부 리스트의 맨 앞에 있는 Expression 개체에게 구문 분석을 요청합니다. 물론, 앞에서 Expression 형식에서는 자신에게 구문 분석 요청이 오면 자신이 해석한 후에 다음 개체에게 남은 부분에 대한 해석을 연쇄적으로 수행하게 구현한 것을 기억하시죠. 그리고 사진 개체를 입력 인자로 받아 매크로에 설정된 값으로 보정을 할 수 있어야 합니다.

▶ Macro.cs    

namespace Interpreter
{
    class Macro
    {
        Expression head=null;
        Expression tail=null;
        public void AddExpression(Expression expression)
        {
            if(head!=null)
            {
                tail.Next=expression;
                tail = expression;
            }
            else
            {
                head = tail = expression;
            }
        }
        public void ChangePicture(Picture picture)
        {	
            head.DoItWithPicture(picture);
        }
        public void AddContext(string context)
        {
            head.Interpret(context);	
        }
    }
}

데모에서는 Expression 개체들을 생성하여 Macro 개체에게 등록한 후에 사진 보정 구문을 입력을 하고 사진 개체를 입력인자로 전달하여 보정 명령을 내린 후에 사진 개체의 정보를 출력하는 것으로 작성해 보았습니다.

▶ Program.cs   

namespace Interpreter
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression ex1 = new ToneExpression();
            Expression ex2 = new BrighExpression();
            Expression ex3 = new SatuExpression();
            Macro macro = new Macro();
            macro.AddExpression(ex1);
            macro.AddExpression(ex2);
            macro.AddExpression(ex3);
            macro.AddContext("B 20 ;");
            macro.AddContext("B 20 ;T-12 ; S 10 ; B10 ;");
            Picture picture = new Picture("현충사의 봄", 100, 100, 100);
            macro.ChangePicture(picture);
            picture.View();
        }
    }
}

▶ 실행 결과  

사진 파일 명:현충사의 봄
    색조:88 명도:130 채도:110