알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다.
스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경 할 수 있다.

 

 

 

 

 

 

 

 

 

static void Main(string[] args)

        {

            Man badGuy = CreateBadGuy();

 

            Man superMan = CreateSuperMan();

 

            badGuy.Attack();

            superMan.Attack();

 

            Console.ReadKey();

        }

 

        private static Man CreateSuperMan()

        {

            SuperMan superMan = new SuperMan();

            superMan.AttackBehavior = new SuperManAttackBehavior();

            return superMan;

        }

 

        private static Man CreateBadGuy()

        {

            BadGuy badGuy = new BadGuy();

            badGuy.AttackBehavior = new BadGuyBehavior();

            return badGuy;

        }

  

 

 

 

 

AboutStrategyPattern.zip

 

아래의 글은 언제나 휴일 출판사의 장문석님의 "Escort GoF의 디자인 패턴" 도서의 내용을 정리한 것 입니다. (http://cafe.daum.net/ehclub.net/S8l6/11)

 

원형 패턴(Prototype Pattern)

 

1.1 개요

 

원형 패턴은 미리 만들어진 개체를 복사하여 개체를 생성하는 패턴을 말합니다. 그리고, 이 때 미리 만들어진 개체를 원형 개체라 부릅니다. 일반화 관계로 표현을 할 때 파생클래스의 개수가 과도히 많아지고 각 클래스의 메서드에서 수행하는 알고리즘에 차이가 없고 생성 시에 개체의 속성에 차이만 있다면 원형 패턴을 사용하는 것이 효과적입니다.

 

1.2 시나리오

 

다음 주에는 거제도와 통영으로 2박 3일 여행을 가기로 하였습니다. 그 곳의 해안 지역은 리아시스식 해안 형태를 지니고 있어 줌 렌즈를 갖고 가는 것이 좋을 것 같다는 생각을 하고 있습니다. 이번 기회에 괜찮은 줌 렌즈를 하나 구입을 하기 위해 EHLand 쇼핑몰을 검색을 해 보았습니다.

 

EHLand 쇼핑몰에는 다양한 형태의 줌 렌즈들이 있는데 이들 중에 무엇을 사는 것이 좋을지 생각하다보니 어떠한 원리로 만들어져 있는지 궁금해 졌습니다. EHLand의 예릭플 씨한테 물어보면 궁금증이 풀릴 것 같아 이메일을 보냈습니다.

 

 

안녕하세요, 예릭플 씨.

지난 번에 EHLand 사의 렌즈 개발 과정에 대해 친절히 소개해 하셔서 감사드립니다. 이번에 남해안에 여행을 가게 되어 줌 렌즈를 하나 구입을 하려고 합니다. '어떤 렌즈를 구입을 할까?' 고민을 하다보니 각 렌즈들이 만들어지는 과정이 궁금해졌습니다.

 

이메일을 보내고 난 다음 날 바로 예릭플 씨의 답변 메일을 받게 되었고 궁금증이 풀리게 되었습니다.

 

 

혁재 아버지, 안녕하세요.

가족끼리 남해에 여행간다고 하니 부럽네요. 혁재가 많이 좋아할 거 같군요.

이번에는 줌 렌즈를 만드는 공정이 궁금하신가 보네요. 줌 렌즈를 만드는 생산 라인은 복사기와 비슷한 원리로 만들어집니다. EHLand의 모든 줌 렌즈는 모두 같은 구조로 되어 있고 들어가는 모듈도 크게 다르지 않습니다. 각 생산 라인마다 견본 줌 렌즈가 있는데 이들은 기본 구조는 동일하게 제작되어 있습니다. 물론, 각 견본 줌 렌즈들은 서로 거리 조절링에 설정되어 있는 최소 거리와 최대 거리나 조리개 조절링에 설정된 최소치와 최대치가 다릅니다. 생산 라인의 기계는 견본 줌 렌즈의 이들 속성을 확인하여 동일하게 설정된 렌즈를 생산하고 있지요.

 

1.3 디자인

 

여기에서는 줌 렌즈와 줌 렌즈 생산 라인 두 개의 형식으로 구성된 예를 들고자 합니다. 줌 렌즈는 자신을 복제할 수 있는 메소드를 제공하고 있어야 합니다. 클래스 이름은 ZoomLens라 정합시다. 줌 렌즈 생산 라인은 견본 줌 렌즈를 하나 갖고 있게 할 것입니다. 그리고, 제품 생산요청이 들어오면 견본을 복제한 줌 렌즈를 반환하면 될 것입니다. 줌 렌즈 생산 라인에 대한 클래스 이름은 ProLine이라 하겠습니다.

 

이번에는 정상적으로 작성되었는지를 확인하기 위한 클래스나 코드에 대해서는 논하지 않겠습니다. 여러분이 창의적으로 생각을 하셔서 작성해 보세요.

 

[그림 1] 원형 패턴 설계 예

 

1. 4 구현

 

이제 원형 패턴에 대한 예제 프로그램을 구현해 봅시다. 구현 순서는 줌 렌즈, 줌 렌즈 생산 라인 순으로 구현 하겠습니다.

 

2.4.1 줌 렌즈

 

여기에서는 다양한 줌 렌즈를 일반화 관계로 사용하지 않고 하나의 ZoomLens 클래스에서 속성만 다르게 주어 이를 기반으로 개체를 복사하여 사용하기로 하였습니다. 이에 줌 렌즈의 생성자 메서드에서는 최소 줌 레벨과 최대 줌 레벨, 최소 초점 거리, 최대 초점 거리를 입력 인자로 전달받아 생성하도록 하겠습니다. 그리고, 복제를 하기 위한 메서드가 필요할 것입니다. 자신을 복제한 개체를 반환하는 메서드 이름은 Clone이라고 정하겠습니다. 이 외에 줌 인, 줌 아웃, 초점을 가까운 곳으로 이동, 먼 곳으로 이동시키는 기능 및 피사체를 사진을로 변환하는 기능을 추가하도록 하겠습니다.

 

 

namespace AboutPrototypePattern
{
    class ZoomLens
    {
        public enum TypeZoomLens
        {
            NM_NM,
            NM_NF,
            NF_NF,
            MF_NF,
            MF_MF
        }

        readonly int minZoomLevel;
        readonly int maxZoomLevel;
        readonly int minFocus;
        readonly int maxFocus;
        int zoomLevel;
        int focus;

        public ZoomLens(int _minZoomLevel, int _maxZoomLevel, int _minFocus, int _maxFocus)
        {
            this.minZoomLevel = _minZoomLevel;
            this.maxZoomLevel = _maxZoomLevel;
            this.minFocus = _minFocus;
            this.maxFocus = _maxFocus;
        }

        public void Take()
        {
            Console.WriteLine("zoomLevel : {0}, focus : {1}", zoomLevel, focus);
        }

        public int ZoomIn()
        {
            if (zoomLevel < maxZoomLevel)
            {
                zoomLevel++;
            }
            return zoomLevel;
        }

        public int ZoomOut()
        {
            if (zoomLevel < minZoomLevel)
            {
                zoomLevel--;
            }
            return zoomLevel;
        }

        public int NearFocus()
        {
            if (focus > minFocus)
            {
                focus--;
            }
            return focus;
        }

        public int FacFocus()
        {
            if (focus > maxFocus)
            {
                focus++;
            }
            return focus;
        }

        public ZoomLens Clone()
        {
            return new ZoomLens(this.minZoomLevel, this.maxZoomLevel, this.minFocus, this.maxFocus);
        }
    }

 

 

 

2.4.2 줌 렌즈 생산 라인

 

생산 라인은 하나의 줌 렌즈를 견본으로 갖고 있고 이를 기반으로 복사된 렌즈를 생산하기로 하였습니다. 이를 위해 생산 라인의 생성자는 어떠한 견본 렌즈를 갖고 있을 것인지를 입력 인자로 전달받기로 하겠습니다. 그리고, 생산 요청을 받아 견본 렌즈와 동일한 속성 값을 갖는 복제된 제품을 반환하는 메서드를 만들기로 합시다. 메서드 이름은 MakeLens라 하겠습니다.

namespace AboutPrototypePattern
{
    class ProLine
    {
        ZoomLens prototype;
        

        public ProLine(AboutPrototypePattern.ZoomLens.TypeZoomLens typeZoomLens)
        {
            switch (typeZoomLens)
            {
                case ZoomLens.TypeZoomLens.NM_NM: prototype = new ZoomLens(20, 70, 1, 100); break;
                case ZoomLens.TypeZoomLens.NM_NF: prototype = new ZoomLens(20, 70, 1, 200); break;
                case ZoomLens.TypeZoomLens.NF_NF: prototype = new ZoomLens(20, 300, 1, 200); break;
                case ZoomLens.TypeZoomLens.MF_NF: prototype = new ZoomLens(70, 300, 1, 200); break;
                case ZoomLens.TypeZoomLens.MF_MF: prototype = new ZoomLens(70, 300, 10, 200); break;
            }
        }

        public ZoomLens MakeLens()
        {
            ZoomLens product = prototype.Clone();
            return product;
        }
    }
}


 

아래의 글은 언제나 휴일 출판사의 장문석님의 "Escort GoF의 디자인 패턴" 도서의 내용을 정리한 것 입니다. (http://cafe.daum.net/ehclub.net/S8l6/11)

 

빌더 패턴(Builder Pattern)

 

1.1 개요

 

프로그래밍을 하다 보면 개체의 형식 변환을 해야 하는 경우가 발생합니다. 경우에 따라서는 소유 개체에서 피 소유 개체를 목적에 따라 변환 알고리즘을 다르게 적용하여 형식 변환을 해야 되는 경우가 있습니다. 이러한 경우에 빌더 패턴을 사용하던 소유 개체에 대한 클래스와 알고리즘에 대한 클래스를 분리하여 정의할 수 있습니다. 'GoF의 디자인 패턴'에서는 소유 개체를 디렉터라 부르고 알고리즘이 구현된 개체를 빌더라 부르고 있습니다.

 

빌더 패턴에서 빌더는 추상 클래스로 표현하여 개체를 생성하는 메서드에 대한 약속을 합니다. 파생 클래스에서는 약속된 메서드를 목적에 맞는 알고리즘을 사용하여 구체적으로 구현을 해야합니다.

소유 개체를 구현함에 있어 피 소유 개체를 생성하는 알고리즘이 다양할 경우 빌더 패턴을 사용하면 효과적입니다. 차후에 다른 형태의 알고리즘을 적용하여 피 소유 개체를 생성해야 하는 경우에 빌더 클래스에서 파생된 구체화 된 클래스만 추가하면 될 것입니다.

 

1.2 시나리오

 

우리 가족은 매주 토요일에는 여행을 다녀옵니다. 여러분들도 아시다시피 우리 가족은 사진을 찍는 것을 좋아합니다. 이러한 생활을 하다보니 사랑하는 아들 혁재는 자연스럽게 카메라와 사진에 대해 많은 호기심이 생기나 봅니다.

 

"아빠, 어떻게 해서 카메라로 사진을 찍을 수 있는거야?"

 

아들에게 렌즈에 대한 얘기와, 초점 빛에 대한 얘기를 하다보니 지겨운지 자기 방으로 들어가더군요. 이제껏 대부분의 아들의 호기심을 해소시켰다고 자부했었는데 내가 좋아하는 카메라에 대해 설명을 못해 안타까웠어요. 밤 새 고민을 하다 근처에 있는 EHCamera 회사를 견학하면 좋겠다는 생각을 하게 되었고 견학에 필요한 것이 무엇인지 확인하고 견학 신청을 하였습니다.

 

몇 일 뒤에 우리 가족은 EHCamera 회사를 방문했어요. 회사 입구에서부터 안내원이 반갑게 맞아주었고 회사에 대한 간단한 소개와 생산 라인을 견학할 수 있었어요. 그리고, 엔지니어와의 만남의 시간이 주어졌는데 그가 건네준 명함에는 '이 매핑'이라는 이름이 적혀있더군요.

 

"매핑님, 카메라에 상이 맺혀 사진이 되는 과정이 어떻게 되나요?"

"음, 카메라로 사진을 찍을 수 있는 원리는 사람의 눈으로 사물들을 보는 원리와 흡사합니다. 우리가 사물을 볼 수 있는 것은 빛이 사물에 부딪혀 반사되는 것이 눈에 그려지는 것입니다. 쉽게 얘기하면 빛이 그림을 그리는 것이지요."

"아빠, 학교에서 배웠어. 빛은 여러 가지 색을 지니고 있다고 배웠어. 그리고, 실험 시간에 스펙트럼을 통해 빛의 반사에 대해 알게 되었어. 카메라도 사람의 눈과 마찬가지구나."

매핑씨는 혁재가 하는 말을 듣고 좀 더 자세한 얘기를 해주었어요. 조리개를 열어 빛을 얻어오는 것과 반사경을 통해 상이 맺히는 것에 대하여 알게 되었습니다. 그리고, 설정에 따라 얻어온 상을 이미지로 변환하는 원리도 얘기해 줬어요. 그리고, 이 부분은 소프트웨어적으로 처리를 한다고 하더군요.

 

"아빠, 그럼 아빠도 저런 거 만드는 거야?"

"음. 비슷해"

 

그리고, 집에 돌아와서 카메라 효과에 관련된 자료를 조사 했어요. 조명에 의해 적목 현상을 어떻게 개선을 시키는 지도 알게 되었고 피사체가 사람인 경우에는 윤곽선을 부드럽게 해 주는 원리에 대해서도 알게 되었죠. 그리고, 새로운 카메라에 기존의 효과 외에 다른 효과들을 손쉽게 하기 위한 방법을 알게 되었어요. 이는 각각의 효과마다 별도의 모듈을 사용하고 각각의 모듈은 동일한 인자를 입력 받아 그림을 만들어 내는 공통적인 인터페이스를 갖게 하면 됩니다.

 

1.3 디자인

 

이번에는 카메라와 사진, 사진 빌더로 빌더 패턴에 대한 예를 들려고 합니다. 카메라는 사진을 찍는 기능이 있고 효과를 줄 수 있게 하려고 합니다. 대신 효과를 주는 부분을 사진 빌더를 통해 수행하게 할 것입니다.

 

카메라는 디렉터 역할을 하며 Camera 이름의 클래스로 구현을 하겠습니다.

 

사진은 카메라를 통해 사용자가 얻고자 하는 개체로 Picture라고 정하겠습니다.

 

사진 빌더는 추상클래스로 PictureBuilder라 하겠습니다. 사진 빌더에서 파생된 클래스는 피사체의 날카로운 부분을 매끈하게 바꾸어 사진을 생성하는 SmoothBuilder가 있습니다. 그리고, 피사체의 적목 현상이 발생하는 것을 보정하여 사진을 생성하는 REPeventBuilder도 추가하겠습니다.

[그림 1] 빌더 패턴 설계 예

 

1. 4 구현

 

이제 빌더 패턴에 대한 예제 프로그램을 구현해 봅시다. 구현 순서는 사진, 사진 빌더, 카메라 순으로 하겠습니다.

 

2.4.1 사진

 

사진은 사진의 이름을 가지고 있는 클래스로 생성합니다.

namespace AboutBuilderPattern

{

class Pictrure

{

string Name;

public Pictrure(string _name)

{

this.Name = _name;

}

public override string ToString()

{

return this.Name;

}

}

}

 

2.4.2 사진 빌더

 

사진 빌더는 피사체를 입력 매개변수로 받아 특정 효과를 거쳐 사진을 생성하는 역할을 수행합니다. 추상 클래스 PictureBuilder에서는 이에 대한 약속을 해야합니다. 효과를 주어 사진을 생성하는 기능을 Effect라 하겠습니다. 그리고, 입력 인자로 전달 받을 피사체의 형식을 string 형식을 사용하겠습니다.

 

namespace AboutBuilderPattern

{

abstract class PictureBuilder

{

public abstract Pictrure Effect(string orgin);

}

}

 

PictureBuilder로 부터 파생된 클래스 SmoothBuilder에서는 기반 클래스에서 약속한 Effect 메서드를 구현해야 합니다. 그리고, Effect 메서드에서는 날카로운 부분을 찾는 과정과 매끈하게 보정하는 작업이 필요할 것 입니다. 이들 기능에 대한 이름은 FindSharp, SharpToSmooth라고 정하겠습니다.

 

namespace AboutBuilderPattern

{

class SmoothBuilder:PictureBuilder

{

const string SMOOTH = "Smooth";

const string SHARP = "Sharp";

 

int FindSharp(string origin)

{

return origin.IndexOf(SHARP);

}

Pictrure SharpToSmooth(string origin, int index)

{

int flen = SHARP.Length;

string front = origin.Substring(0, index);

string last = origin.Substring(index + flen, origin.Length - index - flen);

return new Pictrure(front + SMOOTH + last);

}

 

public override Pictrure Effect(string orgin)

{

int index = FindSharp(orgin);

return SharpToSmooth(orgin,index);

}

}

}

 

PictureBuilder에서 파생된 또 다른 클래스 REPreventBuilder에서도 Effect 메서드를 구현해야 할 것입니다. 그리고, Effect 메서드에서는 적목 현상이 있는 눈의 위치를 보정하는 작업이 필요합니다. 이들 기능에 대한 이름은 FindRedEye, RedEyeToNormalEye라고 정하겠습니다.

 

namespace AboutBuilderPattern

{

class REPreventBuilder:PictureBuilder

{

const string RED_EYE = "RedEye";

const string NORMAL_EYE = "NormalEye";

 

public override Pictrure Effect(string orgin)

{

int index = FindRedEye(orgin);

return RedEyeToNormalEye(orgin, index);

}

 

int FindRedEye(string orgin)

{

return orgin.IndexOf(RED_EYE);

}

Pictrure RedEyeToNormalEye(string origin, int index)

{

int flen = RED_EYE.Length;

string front = origin.Substring(0, index);

string last = origin.Substring(index + flen, origin.Length - index - flen);

return new Pictrure(front + NORMAL_EYE + last);

}

}

}

 

2.4.3 카메라

 

카메라에는 셔터를 누르면 피사체를 사진으로 만들어주는 기능이 있어야 할 것입니다. 이러한 기능을 Effect Normal이라고 하겠습니다. 그리고, 찍은 사진을 필요할 때 얻어오는 기능도 있으면 사용자는 편리할 것입니다. 이러한 기능은 GetPicture 라고 합시다. 이 외에도 카메라에 줄 수 있는 효과들에 대한 열거형을 정의하여 사용을 한다면 좀 더 효과적으로 사용 할 수 있을 것입니다.

 

namespace AboutBuilderPattern

{

enum EffectType

{

EF_PREVENTRE=0,

EF_SMOOTH=1,

EF_NORMAL = 2,

EF_SMOOTH_PRERE=3

}

 

 

class Camera

{

PictureBuilder []pbs;

Pictrure picture;

 

public Camera()

{

pbs = new PictureBuilder[2];

pbs[0] = new REPreventBuilder();

pbs[1] = new SmoothBuilder();

picture = new Pictrure(string.Empty);

}

 

public Pictrure PressAShutter(string origin, EffectType effect)

{

switch (effect)

{

case EffectType.EF_NORMAL: EffectNormal(origin); break;

case EffectType.EF_PREVENTRE: PreventRedEye(origin); break;

case EffectType.EF_SMOOTH: Smoothing(origin); break;

case EffectType.EF_SMOOTH_PRERE: SmoothPrere(origin); break;

}

return GetPicture();

}

 

public Pictrure GetPicture()

{

return picture;

}

 

private void SmoothPrere(string origin)

{

Smoothing(origin);

PreventRedEye(origin);

}

 

private void Smoothing(string origin)

{

this.picture = pbs[(int)EffectType.EF_SMOOTH].Effect(origin);

}

 

private void PreventRedEye(string origin)

{

this.picture = pbs[(int)EffectType.EF_PREVENTRE].Effect(origin);

}

 

private void EffectNormal(string origin)

{

this.picture = new Pictrure(origin);

}

}

}

 

그리고, 정상적으로 구현된 것을 확인하기 위한 코드는 진입점인 main에 구현하기로 하겠습니다. 테스트는 카메라에 적목 현상이 있는 눈과 날카로운 몸체를 피사체로 전달하여 사진을 얻어오는 것으로 하겠습니다. 물론, 여러 효과를 주어 결과 사진을 확인 해야겠습니다.

 

namespace AboutBuilderPattern

{

class Program

{

static void Main(string[] args)

{

Camera camera = new Camera();

camera.PressAShutter("RedEyeSharpBody", EffectType.EF_NORMAL);

Console.WriteLine(camera.GetPicture());

 

camera.PressAShutter("RedEyeSharpBody", EffectType.EF_PREVENTRE);

Console.WriteLine(camera.GetPicture());

 

camera.PressAShutter("RedEyeSharpBody", EffectType.EF_SMOOTH);

Console.WriteLine(camera.GetPicture());

 

camera.PressAShutter("RedEyeSharpBody", EffectType.EF_SMOOTH_PRERE);

Console.WriteLine(camera.GetPicture());

}

}

}

 

 

[그림 2] 빌더 패턴 예제 실행 화면

 

 

AboutBuilderPattern.zip

아래의 글은 언제나 휴일 출판사의 장문석님의 "Escort GoF의 디자인패턴" 도서의 내용을 정리한 것 입니다. (http://cafe.daum.net/ehclub.net/S8l6/11)

 

추상 팩토리 패턴(Abstract Factory Pattern)

 

1.1 개요

 

프로그래밍을 하다 보면 특정 목적에 따라 사용해야 하는 개체들이 서로 호환성이 있어야 하는 경우가 발생합니다. 목적에 따라 사용해야 하는 개체군이 다른 경우에 특정 목적에 맞게 호환성 있는 개체가 무엇인지를 조사하고 주의 깊게 사용하는 비용이 발생할 수 있습니다. 이러한 경우에 추상 팩토리 패턴을 사용하면 효과적으로 비용을 줄일 수 있을 것 입니다.

 

추상 팩토리 패턴에서는 서로 호환성 있는 개체들을 생성하는 부분만 담당하는 개체를 제공하는 것입니다. 이와 같은 개체를 팩토리 개체라 부릅니다. 그리고, 구체화 된 팩토리 개체에서 약속된 개체들을 생성하는 인터페이스에 대한 추상화 된 형식을 제공을 합니다. 이와 같은 형태로 설계하면 사용자는 특정 목적에 맞게 호환성 있는 개체가 무엇인지를 조사하는 비용을 줄일 수 있습니다. 또한, 목적이 바뀌어도 구체화 된 팩토리 개체만 교체하면 되기 때문에 유지 보수 비용을 줄일 수 있게 됩니다.

 

 

1.2 시나리오

 

EHWorld(Every Holiday World)에는 Everyday 카메라와 Holiday 카메라가 있습니다. 두 종류의 카메라 모두 렌즈 교환식 카메라입니다. 저는 사람이나 건물 등을 촬영할 때에는 부드럽게 표현해 주는 Everyday 카메라를 선호합니다. 그리고, 여행을 가서 아름다운 풍경을 찍을 때에는 자연스럽게 표현해 주는 Holiday 카메라를 선호합니다. 그런데, 저에게 고민이 하나 있습니다. 사진에 취미를 갖고 렌즈를 하나 하나 구입하다 보니 이제는 여행을 갈 때마다 Holiday 카메라와 호환되는 렌즈를 찾는데 너무 많은 시간이 소요됩니다. 물론, 여행을 다녀오고 난 후에 다시 Everyday 카메라와 호환되는 렌즈를 찾을 때도 너무 많은 시간이 소요되네요.

 

어떻게 하면 제 고민을 해결할 수 있을까요?

 

어느 날 저의 아내가 저에게 선물을 가지고 왔습니다. 두 개의 라면 박스였습니다. 저는 순간 짜증이 밀려왔지만 아내의 말을 듣고 나니 짜증은 고마움과 기쁨으로 전이되었습니다.

 

"E 라면 박스에는 Everyday 렌즈들을 넣고 H라면 박스에는 Holiday 렌즈들을 넣으면 좋지 않을까?"

 

예전보다 렌즈를 찾는데 드는 비용은 줄어들었습니다. 하지만, 여전히 많은 시간을 소요해야 원하는 렌즈를 찾을 수 있었습니다. 강 건너 풍경을 찍을 때에는 줌 렌즈를 사용을 해야 원하는 피사체를 효과적으로 표현할 수 있구요. 사랑하는 이들을 찍을 때에는 35mm 단 렌즈를 선호하고 영원한 동반자인 사랑하는 나의 아내를 찍을 때에는 45mm 단 렌즈를 선호합니다. 그리고, 제가 강의했던 학생들이 교육 과정을 마치고 나면 수료식을 수행을 합니다. 이 때는 70mm 단 렌즈를 사용하곤 합니다. 가끔 아들이나 아내가 저의 카메라와 렌즈를 사용할 일이 있게 되면 어떠한 렌즈를 사용하는 것이 좋은지를 몰라서 대충 골라가곤 합니다. 그리고는 촬영된 사진이 이상하다고 투덜대곤 합니다.

 

어떻게 하면 우리 가족의 고민을 해결할 수 있을까요?

 

이 문제를 해결하기 위해 라면 박스 안에 칸막이를 만들었습니다. 3 X 3 형태로 만들어 우측부터 단 렌즈, 표준 렌즈, 줌 렌즈를 배치하였습니다. 그리고, 앞에는 가까운 피사체를 찍을 수 있는 렌즈를 배치하고 맨 뒤에는 먼 곳에 있는 피사체를 찍을 수 있는 렌즈를 배치하였습니다. 그리고, 박스 앞에 각각의 카메라를 배치할 수 있는 공간을 별도로 만들었습니다.

 

그 후로 우리 가족은 렌즈를 찾는 비용을 줄일 수 있게 되었고 아내와 아들도 자신들이 찍은 사진에 만족해합니다. 그런데, 이제 아내와 아들이 저와 같이 여행을 가면 서로 자신이 사진을 찍어야 한다고 실랑이를 벌이는 행복한 고민에 빠졌습니다.

 

1.3 디자인

 

여기에서는 렌즈의 종류는 각 기종 별로 하나의 렌즈만 제공하는 수준으로 디자인하려고 합니다. 여기에서 보여드리고자 하는 프로그램에는 카메라, 렌즈, 특정 기종의 카메라와 렌즈를 생성하는 역할을 하는 팩토리, 테스터로 분류를 할 것입니다.

 

카메라와 렌즈, 팩토리는 Everyday에 관련된 것과 Holiday에 관련된 구체화 클래스와 일반화 된 추상 클래스 구조로 만들어야겠지요. 여기에서 Everyday에 관련된 것들은 접두사 Ev를 사용하고 Holiday에 관련된 것들은 접두사 Ho를 사용할 것입니다.

 

카메라에는 렌즈를 장차하고 탈착하는 기능과 사진을 찍는 기능에 대해 구현 약속을 합시다. 렌즈에는 상을 얻어오는 기능에 대한 구현 약속을 하겠습니다. Ev 렌즈의 경우 손 쉽게 초점을 맞출 수 있는 기능을 제공하고 있어서 Ev카메라로 셔터를 누르면 자동으로 초점을 맞춘 후에 사진을 찍을 수 있을 것입니다. Ho렌즈의 경우에는 사용자에 의해 초점을 맞출 수 있는 기능을 제공합시다. Ho카메라의 경우에는 초점을 맞추고 사진을 찍는 것으로 구현을 해 봅시다.

 

테스터에서는 EvDayFactory 개체와 HoDayFactory 개체를 통해 각각의 카메라와 렌즈 개체를 생성을 하고 교차로 사용했을 때 호환성이 위배되는 지에 대한 테스트를 수행을 합니다. 그리고, Ev 카메라에 Ev 렌즈를 장착하여 사진을 찍고 Ho카메라와 Ho렌즈를 장착하여 사진을 찍는 테스트를 해 봅시다.

 

 

[그림 1] 추상 팩토리 패턴 설계 예

 

1. 4 구현

 

이제 구체적으로 구현을 해 보기로 합시다.

 

먼저, 카메라와 렌즈에 대한 구현을 하겠습니다. 그리고, 팩토리 부분을 구현을 합시다. 마지막으로 테스터를 구현을 하려고 합니다.

 

1.4.1 렌즈

 

렌즈의 경우는 추상 클래스인 Lens와 이를 기반으로 파생된 EvLens, HoLens가 있습니다. 그리고, 추상 클래스인 Lens에는 상을 맺히는 기능에 대해 Take 이름의 메서드로 구현 약속하겠습니다.

 

namespace AboutFactoryPattern

{

abstract class Lens

{

public abstract void Take();

}

}

 

EvLens의 경우 Lens를 기반의 파생 클래스로 만들기로 하였기 때문에 기반 클래스 Lens에서 약속한 Take 메서드를 구현하여야 합니다. 그리고, EvLens에는 자동 초점을 맞춰주는 기능을 별도로 제공하겠습니다. 자동 초점을 맞춰주는 기능에 대한 명칭은 AutoFocus라 정하겠습니다.

namespace AboutFactoryPattern

{

class EvLens:Lens

{

public override void Take()

{

Console.WriteLine("부드럽다.");

}

public void AutoFocus()

{

Console.WriteLine("AutoFocus.....");

}

}

}

 

HoLens도 Lens를 구체화 시킨 클래스 형태로 만들게 디자인 되어 있습니다. 이를 위해서는 Take 메서드를 구현하여야 할 것 입니다. 그리고, HoLens에는 사용자에 의해 초점을 맞추는 기능을 제공하기로 했었습니다. 해당 기능에 대한 명칭은 ManualFocus라 정하겠습니다.

 

namespace AboutFactoryPattern

{

class HoLens : Lens

{

public override void Take()

{

Console.WriteLine("자연스럽다.");

}

public void ManualFocus()

{

Console.WriteLine("사용자의 명령대로 초점을 잡다.");

}

}

}

 

1.4.2 카메라

 

카메라의 경우는 추상 클래스인 Camera와 파생 클래스 EvCamera, HoCamera가 있습니다. 그리고, 추상 클래스인 Camera에는 사진을 찍는 메서드를 제공합시다. 이에 대한 부분은 공통적으로 렌즈를 통해 상을 맺히는 부분이 있지만 세부적인 사항은 카메라의 종류에 따라 다를 수 있으므로 가상 메서드로 구현을 하겠습니다. 그리고 렌즈를 장착하는 기능과 탈착하는 기능이 필요할 것입니다. 렌즈를 장착하는 기능은 카메라에 따라 상이하기 때문에 추상 메서드로 만들겠습니다. 렌즈를 탈착하는 기능은 카메라에 상관이 없을 것 같습니니다.

 

메서드

기능

virtual bool TakeAPicture();

사진 찍다.

virtual bool PutInLens(Lens lens);

렌즈를 장착하다.

Lens GetOutLens();

렌즈를 탈착하다.

 

그리고, 카메라에는 장착한 렌즈를 위한 멤버 변수(멤버 필드라고도 함)가 필요할 것입니다. 이 외에 파생 클래스 개체에서 접근을 위해 렌즈를 설정하는 멤버 메서드와 설정된 렌즈를 얻어오는 멤버 메서드를 추가하겠습니다. 가급적 멤버 변수는 노출 수준을 private로 지정하여 정보 은닉하시기 바랍니다. 잘못된 값이 왔을 때에 대한 필터링 작업을 한 곳에서 수행을 함으로써 전체 유지 보수 비용을 줄일 수 있습니다. 여기에서도 장착된 렌즈를 관리하는 멤버 변수를 private로 지정을 할 것입니다. 파생 클래스 개체에서 이에 대한 접근이 가능하게 하기 위한 도구로 노출 수준이 protected로 지정된 멤버 메서드를 제공한다는 것은 이미 알고 있을 것이라 생각합니다.

 

 

namespace AboutFactoryPattern

{

abstract class Camera

{

Lens lens;

 

protected Camera()

{

lens = null;

}

 

public abstract bool PutInLens(Lens lens);

 

 

public virtual bool TakeAPicture()

{

if (lens == null)

{

return false;

}

lens.Take();

return true;

}

 

public Lens GetOutLens()

{

Lens re = lens;

lens = null;

return re;

}

 

protected void SetLens(Lens _lens)

{

this.lens = _lens;

}

 

protected Lens GetLens()

{

return this.lens;

}

 

}

}

 

EvCamera는 기반 클래스 Camera에서 파생된 클래스입니다. 기반 클래스 Camera에 구현 약속된 메서드인 PutInLens를 구현 해야합니다. 그리고, 사진을 찍는 TakeAPicture 메서드의 경우도 자동 초점을 하는 부분이 있으므로 재정의 해야 합니다.

 

namespace AboutFactoryPattern

{

class EvCamera:Camera

{

public override bool TakeAPicture()

{

Lens lens = base.GetLens();

EvLens evLens = lens as EvLens;

if (evLens == null)

{

return false;

}

 

evLens.AutoFocus();

return base.TakeAPicture();

}

 

public override bool PutInLens(Lens lens)

{

EvLens evLens = lens as EvLens;

if (evLens == null)

{

return false;

}

 

base.SetLens(lens);

return true;

}

}

}

 

 

HoCamera의 경우도 EvCamera와 동일한 이유로 TakeAPicture 메서드와 PutInLens를 구현하여야 합니다.

 

namespace AboutFactoryPattern

{

class HoCamera:Camera

{

public override bool TakeAPicture()

{

Lens lens = base.GetLens();

HoLens hoLens = lens as HoLens;

if (hoLens == null)

{

return false;

}

hoLens.ManualFocus();

return base.TakeAPicture();

}

 

public override bool PutInLens(Lens lens)

{

HoLens hoLens = lens as HoLens;

if (hoLens == null)

{

return false;

}

 

base.SetLens(hoLens);

return true;

}

}

}

 

 

1.4.4 팩토리

 

팩토리의 경우는 추상 클래스인 DayFactory와 파생 클래스 EvDayFactory, HoDayFactory가 있습니다. 그리고, 추상 클래스인 DayFactory에는 카메라를 생성하는 메서드와 렌즈를 생성하는 메서드를 구현 약속하였습니다.

 

 

namespace AboutFactoryPattern

{

abstract class DayFactory

{

List<Camera> cameras;

List<Lens> lenses;

 

public abstract Camera CreateCamera();

public abstract Lens CreateLens();

 

protected DayFactory()

{

cameras = new List<Camera>();

lenses = new List<Lens>();

}

 

protected void PutCamera(Camera camera)

{

this.cameras.Add(camera);

}

protected void PutLens(Lens lens)

{

this.lenses.Add(lens);

}

}

}

 

 

EvDayFactory와 HoDayFactory에서는 구현 약속되어 있는 두 개의 메서드를 구현해야 할 것입니다.

namespace AboutFactoryPattern

{

class EvDayFactory:DayFactory

{

 

public override Camera CreateCamera()

{

return new EvCamera();

}

 

public override Lens CreateLens()

{

return new EvLens();

}

}

}

 

namespace AboutFactoryPattern

{

class HoDayFactory:DayFactory

{

public override Camera CreateCamera()

{

return new HoCamera();

}

 

public override Lens CreateLens()

{

return new HoLens();

}

}

}

 

1.4.5 테스터

 

테스터는 팩토리를 통해 생성된 카메라와 렌즈를 이용하는 클라이언트 클래스입니다. 여기에서는 Tester라고 정하겠습니다.

 

테스터의 생성자에서는 EvDayFactory 개체와 HoDayFactory 개체를 생성하겠습니다. 그리고, 이들을 통해 카메라와 렌즈를 생성하는 작업을 수행하기로 하겠습니다.

그리고, Test메서드를 정의하기로 하겠습니다. 여기에서는 호환성을 테스트하는 작업을 수행하기로 하겠습니다.

 

namespace AboutFactoryPattern

{

class Tester

{

DayFactory []factories;

Camera []cameras;

Lens []lenses;

 

public Tester()

{

Init();

}

 

private void Init()

{

this.factories = new DayFactory[2];

this.cameras = new Camera[2];

this.lenses = new Lens[2];

 

this.factories[0] = new EvDayFactory();

this.factories[1] = new HoDayFactory();

 

MakeCameraSet(0);

MakeCameraSet(1);

}

 

private void MakeCameraSet(int index)

{

DayFactory factory = factories[index];

this.cameras[index] = factory.CreateCamera();

this.lenses[index] = factory.CreateLens();

}

public void Test()

{

Test(this.cameras[0], this.lenses[1]);

Test(this.cameras[0], this.lenses[0]);

Test(this.cameras[1], this.lenses[1]);

}

 

private void Test(Camera camera, Lens lens)

{

Console.WriteLine("테스트");

if (camera.PutInLens(lens) == false)

{

Console.WriteLine("카메라가 렌즈에 장착이 되지 않았음");

}

if (camera.TakeAPicture() == false)

{

Console.WriteLine("사진이 찍히지 않았습니다");

}

camera.GetOutLens();

}

}

}

 

 

namespace AboutFactoryPattern

{

class Program

{

static void Main(string[] args)

{

Tester tester = new Tester();

tester.Test();

}

}

}

 

 

[그림 2] 추상 팩토리 패턴 예제 실행 화면

 

 

 

AboutAbstractFactoryPattern.zip

'.Net > 디자인패턴' 카테고리의 다른 글

C# Strategy Pattern  (0) 2013.10.22
Escort GoF의 디자인 패턴- Prototype Pattern  (0) 2012.07.06
Escort GoF의 디자인 패턴- Builder Pattern  (0) 2012.07.06

+ Recent posts