아래의 글은 언제나 휴일 출판사의 장문석님의 "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