알맞은 조건에 맞는 객체만을 생성해서 사용하는 방법에는 어떤 것이 있을까?

문제
해당 장비에 맞는 제어 프로그램을 만들어야 한다. 이 제어 프로그램에는 데이터를 송/수신 하는 부분과 송수신된 데이터를 파싱(parsing)하는 부분, 그리고 파싱된 데이터를 처리하는 부분으로 나뉜다. 현재에는 A장비와, B장비만 제어한다. 하지만 추후에 제어 되는 장비가 더 추가되거나 제거될 수 가 있다.
DataSender : 데이터를 송신하는 객체
DataReceiver : 데이터를 수신하는 객체
DataParser : 데이터를 파싱하는 객체
DataProc : 파싱된 데이터를 처리하는 객체  


해결1
프로그램 곳곳에 조건 비교 문장을 넣어 조건에 해당하는 객체를 생성한다(조건 비교 방식).

if(A장비인가?)
{
EquipADataSender eaSender = new EquipADataSender();
EquipADataReceiver eaReceiver = new EquipADataReceiver();
EquipADataParser eaParser = EquipADataParser();
EquipADataProc eaProc = EquipADataProc();
}
else if(B 장비인가?)
{
EquipBDataSender ebSender = new EquipBDataSender ();
EquipBDataReceiver ebReceiver = new EquipBDataReceiver ();
EquipBDataParser ebParser = EquipBDataParser ();
EquipBDataProc ebProc = EquipBDataProc ();
}
// 장비 C , 장비 B 가 추가 된다면? 계속해서 조건 문을 이어 나가야 한다.
// 또한 소스 코드 여러 부분에 걸쳐 이런 조건 문을 사용할 가능성이 매우 높다. 

 해결1에 대한 문제점
프로그램 곳곳에 조건 비교 문장이 존재하기 때문에, 새로운 조건을 추가할 일이 발생하면 프로그램 전체를 찾아 다니며 일일이 수정해야 하는 커다란 문제가 생긴다. 이는 곧 프로그램 전체를 다시 개발하는 것과 비슷한 노력이나 비용이 들 수 있음을 의미한다. 프로그램 전체를 찾아 다니며 비교 문장이 존재하는 곳을 모두 수정해야 할 것이다. 조건 비교 방식의 가장 큰 단점은 비교 문장이 프로그램 전역에 퍼져 있을 경우, 이에 대한 변경 비용이 많이 든다는 것이다. 문제의 원인은 변경될 가능성이 높은 코드(공통된 기능을 하는 코드)들이 프로그램 전역 곳곳에 퍼져 있다는 것이다. 이 문제를 해결하기 위해서는 변경될 가능성이 높은 코드들을 한 곳에 모아, 변경이 필요할 경우 그 부분만 고치면 원하는 작업을 수행할 수 있게 만들어 주는 것이다. 이처럼 객체 지향 설계의 핵심적인 접근 방법은 바로 변경이 예상되는 부분을 한 곳에 모아 관리 하도록 하는 것이다. 


해결2(Factory Pattern)
 프로그램 전역에 조건 비교 문장 없이 원하는 객체를 생성하기 위해서는 객체 생성을 전담하는 클래스(Factory class)를 활용하면 된다. 이는 시스템 환경에 따른 조건 비교 문장이 Factory 클래스 내부에만 존재하기 때문에 새로운 조건이 추가되거나 이전 조건을 변경시켜야 할 경우 프로그램 전체를 찾아 다니며 수정 해야 하는 부담을 덜 수 있다. 이렇게 객체 생성을 Factory class를 활용하는 방식을 Factory Pattern이라고 한다.





샘플 소스 (C#)

        static void Main(string[] args)
        {
            EquipFactory equipFactory = new EquipFactory();  // 팩토리 클래스 생성
            equipFactory.EEquipType = EEquipType.EquipA;   // 장비 선택

// Factory에서 해당 객체 생성
// 장비 종류에 관계 없이 동일한 방법으로 생성한다.

IDataSender sender = equipFactory.CreateDataSender();
            IDataReceiver receiver = equipFactory.CreateDataReceiver();
            IDataParser parser = equipFactory.CreateDataParser();
            IDataProc proc = equipFactory.CreateDataProc();

sender.Send();
            receiver.Receive();
            parser.Parse();
            proc.Proc();
        }

Visual Studio 2008, C#

해결2에 대한 문제점
 여전히 Factory 클래스 내의 소스코드에는 비교 문장이 존재한다는 것이다. 새로운 조건을 추가할 경우 이전 소스 코드를 면밀히 분석해서 수정해야 한다. 이는 새로운 조건의 추가를 이전 소스 코드와 무관하게 독립적으로 수행할 수 없다는 의미이다(이
전 소스 코드와 독립적으로 새로운 부분을 추가, 수정하기가 힘들다).


해결 3 (Abstract Factory Pattern)
 해결2에서 EquipFactory 클래스 내부에는 장비 종류에 따는 객체를 생성할 수 있게 조건문이 존재한다.
ex) EquipFactory의 CreateDataSender 메소드
public IDataSender CreateDataSender()
{
      switch (_eEquipType) // 장비에 따른 객체 생성
      {
            case EEquipType.EquipA: return new EquipADataSender();
            case EEquipType.EquipB: return new EquipBDataSender();
       }

       return null;
 }

 프로그램이 커지면, 이런 조건문 때문에 이전 코드와 독립적으로 새로운 부분을 추가, 수정하기가 힘들어 진다. 이를 해결 하기 위해서는 장비 별로 Factory 클래스를 생성한다. 그 후 장비 별 Factory 클래스에서 공통 메소드들을 뽑아내어 공통 인터페이스(IEquipFactory)로 만든다. 장비 별 Factory 클래스에서 이 공통 인터페이스를 상속 받아 구현하면 조건문을 사용하지 않고도 문제를 해결할 수 있다. 장비를 추가하거나 삭제할 경우, 이전 소스 코드를 수정하지 않고도 독립적으로 변경이 가능하다. 이런 방식을 Abstract Factory Pattern이라고 한다.






샘플 소스 (C#)

          IEquipFactory equipFactory = null;
            EEquipType equipType = EEquipType.EquipB;

// 처음 한번은 어떤 장비를 생성할 것인지 구별하기 위해서 조건문이 사용되었다.
// 하지만 Factory 클래스들은 장비 별로 독립적으로 구현되어 있다.
            switch (equipType)
            {
                case EEquipType.EquipA: equipFactory = new EquipAFactory(); break;
                case EEquipType.EquipB: equipFactory = new EquipBFactory(); break;
            }

            if (equipFactory == null) return;
           
            IDataSender sender = equipFactory.CreateDataSender();
            IDataReceiver receiver = equipFactory.CreateDataReceiver();
            IDataParser parser = equipFactory.CreateDataParser();
            IDataProc proc = equipFactory.CreateDataProc();

            sender.Send();
            receiver.Receive();
            parser.Parse();
            proc.Proc();


Visual Studio 2008, C#

1. 상위 클래스가 여러 하위 클래스들의 공통된 데이터를 추출해서 관리하도록 만든다.
2. 상위 클래스 전반적인 자료형을 대표하면서 외부에 공통된 인터페이스를 제공하고, 하위 클래스들은 각기 다른 구현 내용을 가지도록 만들어 준다.

 이 때 2번의 목적으로 클래스 상속을 이용하게 되면 상속 구조 내의 클래스를 사용하는 입장에서는 자료형과 인터페이스는 최상위 클래스를 참조하면서 구체적으로 실행되는 구현 내용은 다형성에 따라 결정되도록 만들 수 있다. 따라서 새로운 구현 내용이 추가된다 하더라도 새로운 하위 클래스만 정의해서 사용하면 기존 소스코드의 변경을 최소화하면서 새로운 구현 내용을 적용할 수 있게 되는 것이다.

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

Abstract Factory Pattern  (0) 2009.06.09
Template Method Pattern [C#]  (0) 2009.05.28

 전반적인 처리 과정이 동일하고, 세부적인 구현만 다소 다른 경우 서로 독립된 클래스 형태로 프로그램을 구현하게 되면 똑같은 프로그램을 반복해서 구현해야 하는 문제가 발생한다. 이렇게 구현된 프로그램은 유지, 보수 시에도 동일한 수정 사항을 반복 적용해야 되는 문제가 있다.

해결 : 동일한 처리 과정에 해당하는 모듈은 하나로 합쳐서 작성, 관리하고 서로 다른 부분은 각자의 자식 클래스에서 구현하게 함으로써 구현에 따른 부담도 줄이고, 동일한 모듈을 반복해서 수정해야 하는 문제도 없앨 수 있다(클래스 상속 관계를 이용).

클래스에 상관없이 동일한 처리 과정을 수행하는 맴버 함수는 상위 클래스에만 정의한다. 이는 클래스 상속 관계의 특성 상 상위 클래스에 정의된 맴버 함수는 하위 클래스에서도 사용할 수 있기 때문이다. 클래스 마다 구현 내용이 달라져야 하는 모듈의 경우에는 클래스 상속 관계에서 Overriding을 활용해서 상위 클래스에서 정의한 모듈을 하위 클래스에서 재정의 한다. 이 경우 하위 클래스 객체에 대해 해당 모듈이 호출되면 다형성 규칙에 의해 상위 클래스에서 정의한 모듈이 아닌 하위 클래스에서 정의한 모듈이 불려지게 된다.

이처럼 클래스 상속 관계를 이용하면 구체적인 구현은 다르나 큰 틀에서 알고리즘의 기본 골격이 동일한 경우, 동일한 기본 골격을 상위 클래스에서 하나의 모듈로 작성, 관리할 수 있게 되는데 이를 Template Method라고 한다. Template Method를 포함하는 클래스 구조를 Template Method 패턴이라고 한다.

사용 예제
요구사항 : 오피스 프로그램을 제작하는데 한글 문서(hwp), MSWORD 문서(doc)를 저장하는 기능을 구현해야 한다. 추후에 저장을 할 수 있는 문서 포맷이 늘어 날 수 있다.

문서를 저장하는 순서
step1. 문서를 연다.
step2. 문서를 저장한다.
step3. 문서를 닫는다.

소스 코드

using System;
using System.Collections.Generic;
using System.Text;
namespace TemplateMethodPattern
{
    class Document
    {
        // template method
        // 문서 파일 저장
        public bool DocSave()
        {
            // step1. 문서를 연다.
            if (!DocFileOpen()) return false;
            // step2. 문서를 저장한다.
            if (!DocFileSave()) return false;
            // step3. 문서를 닫는다.
            if (!DocFileClose()) return false;
            // 저장 성공
            return true;
        }
        protected virtual bool DocFileOpen() { return true; }
        protected virtual bool DocFileSave() { return true; }
        protected virtual bool DocFileClose() { return true; }
    }
    class HWPDocument : Document
    {
        protected override bool DocFileOpen()
        {
            // todo : HWP 문서 열기 처리 로직
            Console.WriteLine("한글 문서 열기.");
            return true;
        }
        protected override bool DocFileSave()
        {
            // todo : HWP 문서 저장 처리 로직
            Console.WriteLine("한글 문서 저장.");
            return true;
        }
        protected override bool DocFileClose()
        {
            // todo : HWP 문서 닫기 처리 로직
            Console.WriteLine("한글 문서 닫기.");
            return true;
        }
    }
    class MSWORDDocument : Document
    {
        protected override bool DocFileOpen()
        {
            // todo : MS Word 문서 열기 처리 로직
            Console.WriteLine("MS word 문서 열기.");
            return true;
        }
        protected override bool DocFileSave()
        {
            // todo : MS Word 문서 저장 처리 로직
            Console.WriteLine("MS word 문서 저장.");
            return true;
        }
        protected override bool DocFileClose()
        {
            // todo : MS Word 문서 닫기 처리 로직
            Console.WriteLine("MS word 문서 닫기.");
            return true;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // MS Word 문서 저장
            MSWORDDocument msword = new MSWORDDocument();
            msword.DocSave();
            // HWP 문서 저장
            HWPDocument hwp = new HWPDocument();
            hwp.DocSave();
        }
    }
}

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

Abstract Factory Pattern  (0) 2009.06.09
객체지향 설계에서 클래스 상속의 두가지 목적  (0) 2009.06.08

+ Recent posts