[문제점] 가령 이름, 나이, 주소 등을 입력하는 EditBox가 있고, 각 EditBox는 입력 목적에 따라 허용 문자를 달리 할 수 있도록(정책) EditBox를 구현해보자. 다음의 코드는 이에 대한 예가 될 수 있겠다.
#include <iostream>
#include <string>
#include <conio.h>
using namespace std;
class Edit
{
string data;
public:
virtual bool validate(char c)
{
return isdigit(c);
}
string getData()
{
data.clear();
while (1)
{
// 한글자 입력 받기
char c = _getch();
// Enter Key > 입력 완료
if (c == 13) break;
// 입력 유효성 검사 (정책)
if (validate(c))
{
// 유효한 문자만 저장
data.push_back(c);
cout << c;
}
}
// 개행하고 끝내기
cout << endl;
return data;
}
};
int main()
{
Edit edit;
while (1)
{
string s = addEdit.getData();
cout << s << endl;
}
}
예를 들어 이름에 대한 EditBox는 알파뱃만 입력을 허용하고, 전화번호에 대한 EditBox는 숫자만 입력을 허용하는 정책을 만들어가려면 어떻게 해야할까? 본 코드에서는 validate 함수의 구현을 다르게 하면 되겠다.
이러한 getData 같은 코드들은 라이브러리 내부에 있을 것이다. 그렇다면 코드가 직접적으로 변경되면 안될 것이고, 정책만 변경하여 적용할 수 있어야 한다. 이러한 Validateion 정책을 교채, 적용할 수 있는 두 가지의 Pattern이 존재한다.
두 Pattern의 큰 방향은 변하지 않는 코드(전체 흐름) 안에 변해야하는 부분(정책)을 분리해야 하는 것이다. 변해야 하는 부분은 별도의 가상 함수로 분리하여 구현한다. 이것이 바로 Template Method 기법이다.
* 사용자 입력(getData)의 과정을 정리해보면 다음과 같다.
1. 기존 입력된 것이 있으면 지우고
2. 입력된 값이 Enter(13)이면 종료하고,
3. 값이 정책에 따라 유효한지 검사
4. 출력
5. 개행하고 입력된 값을 리턴
그렇다면 위 과정에서 (3)만 정책에 따라 다른 코드가 만들어질 수 있고, (3)을 제외한 모든 과정은 변할 필요가 없겠다. 변하지 않는 코드(전체 흐름) 안에 있는 변해야하는 부분(정책)은 분리하는 것이 좋다.
1. Template Method Design Pattern
변해야 하는 부분(정책)을 다음의 Edit Class와 같이 별도의 가상 함수로 분리한다.
class Edit
{
string data;
public:
virtual bool validate(char c)
{
return isdigit(c);
}
string getData()
{
data.clear();
while (1)
{
char c = _getch();
if (c == 13)
break;
// 입력 유효성 검사 (정책)
if (pVal->validate(data, c) )
{
data.push_back(c);
cout << c;
}
}
cout << endl;
return data;
}
};
이렇게 validate 함수를 가상화 하면 Edit 클래스의 파생 클래스를 만들고 validate()를 재정의 하면 되겠다.
class AddressEdit : public Edit
{
public:
// 주소는 모든 문자를 허용해야함.
virtual bool validate(char c)
{
return true;
}
};
int main()
{
AddressEdit addEdit;
while (1)
{
string s = addEdit.getData();
cout << s << endl;
}
}
이것은 가상의 라이브러리로부터 상속받은 클래스이며 코드의 수정이 아닌 확장이라고 볼 수 있다. 즉, 변하는 것과 변하지 않는 것을 구분하고, 변하는 것을 가상화하여 상속을 통해 가상화된 함수를 확장시키는 것을 Template Method Design Pattern라고 한다.
2. 전략 패턴 (Strategy Pattern)
변해야 하는 부분(정책)을 다른 클래스로 만든다.
이때 교체가 가능해야하는데, 이를 위해서 약한 결합. 즉, 인터페이스 기반 통신이 필요하다. 여기서는 IValidator라는 인터페이스를 만든다.
// Validation을 위한 클래스
struct IValidator
{
virtual bool validate(string s, char c) = 0;
// 현재까지 입력된 내용이 완결되었는가? 확인버튼 활성화
virtual bool isComplete(string s) { return true; }
// 모든 기반 클래스는 가상 소멸자가 필요하다.
virtual ~IValidator() {}
};
이제 Editor에 IValidator를 포함시킨다. 즉, 유효성 검사를 IValidator에게 "위임"한다.
class Edit
{
string data;
IValidator* pVal = 0;
public:
void setValidator(IValidator* p) { pVal = p; }
virtual bool validate(char c)
{
return isdigit(c);
}
string getData()
{
data.clear();
while (1)
{
char c = _getch();
if (c == 13 &&
pVal == 0 || pVal->isComplete(data))
break; // ENTER KEY를 눌렀더라도 정책에 부합하지 않으면 계속 입력
// 입력 유효성 검사 (정책)
if (pVal == 0 || pVal->validate(data, c) )
{
data.push_back(c);
cout << c;
}
}
cout << endl;
return data;
}
};
새로운 정책을 적용하려면 어떻게 하면 될까? 다섯 자리의 숫자만 허용하는 정책을 새로 만들어본다. 다음과 같이 IValidator를 상속 받은 새로운 클래스를 작성한다.
class LimitDigitValidator : public IValidator
{
int value;
public:
LimitDigitValidator(int n) : value(n) {}
virtual bool validate(string s, char c)
{
// 생성자를 통해 정해진 Value 크기보다는 작고, 숫자만 입력 허용
return s.size() < value&& isdigit(c);
}
// 초기화된 value크기만큼 입력이 되어야 완결(Enter) 될 수 있다.
virtual bool isComplete(string s)
{
return s.size() >= value;
}
};
이제 이 LimitDigitValidator를 사용한다.
int main()
{
Edit edit;
LimitDigitValidator v(5); // 정책 -> 동적으로 변경할 수 있다.
edit.setValidator(&v);
while (1)
{
string s = edit.getData();
cout << s << endl;
}
}
Template method와 Strategy Pattern의 장단점 비교
비교 | |
Template method (상속 기반) |
실행시간에 교채 불가능 정책 재사용 불가능 |
Strategy pattern (포함 기반-Composition) |
실행 시간에 교채 가능. 정책 재사용 가능. |
Strategy pattern은 정책을 실행시간에 교채 가능하다. 정책을 재사용 할 수 있다. 항상 상속 기반의 패턴들은 유연성이 떨어지고 실행시간에 교체할 수 없다.
디자인 패턴의 큰 흐름은 상속보다는 포함 기법을 주로 한다.
소스코드
'프로그래밍 이야기 > C++ 기초' 카테고리의 다른 글
[C++]Design Pattern - Application Framework (0) | 2020.12.07 |
---|---|
[C++] Design Pattern - Policy Base Design (0) | 2020.12.05 |
[C++]Design Pattern - 변하는 것을 분리하기 (0) | 2020.12.01 |
[C++] 예제로 배우는 객체 지향(Prototype Pattern) (0) | 2020.11.25 |
[C++] 예제로 배우는 객체 지향 #1 (0) | 2020.11.24 |