일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- vector capacity
- increment operator
- member function pointer
- dynamic_cast
- this call
- std::vector
- discord bot
- constructor
- delete function
- virtual function table
- operator overloading
- return by reference
- std::cout
- virtual inheritance
- placement new
- 더 지니어스 양면포커
- base from member
- virtual destructor
- c++ basic practice
- new&delete
- vector size
- std::ostream
- pointer to member data
- conversion constructor
- diamond inheritance
- std::endl
- c++ multi chatting room
- virtual function
- suffix return type
- C++
- Today
- Total
I'm FanJae.
[C++ Design Pattern] Strategy Pattern 본문
1. Strategy Pattern의 정의
다양한 알고리즘이 존재 하면 이들 각각을 하나의 "클래스로 캡슐화하여 알고리즘의 대체가 가능"하도록 한다. Strategy 패턴을 이용하면 클라이언트와 독립적인 다양한 알고리즘으로 변형할 수 있다. 알고리즘을 바꾸더라도 클라이언트는 아무런 변경을 할 필요가 없다.
1-1. 예제를 통한 Strategy Pattern의 필요성 이해
#include <iostream>
#include <string>
#include <conio.h>
class Edit
{
std::string data;
public:
std::string get_text()
{
std::cin >> data;
return data;
}
};
int main()
{
Edit edit;
while (1)
{
std::string s = edit.get_text();
std::cout << s << std::endl;
}
}
- Edit 클래스는 사용자에게 입력을 받을때 사용하는 GUI Widget으로 가정해보자.
- 만약 Edit을 사용해서 나이를 입력받고 싶다고 가정하자.
※ 이 경우, 숫자만 입력되도록 제한(validation)해야 한다.
#include <iostream>
#include <string>
#include <conio.h>
class Edit
{
std::string data;
public:
std::string get_text()
{
data.clear();
while(1)
{
char c = _getch();
if (c == 13) break; // enter 키
if (isdigit(c))
{
data.push_back(c);
std::cout << c;
}
}
std::cout << "\n";
return data;
}
};
int main()
{
Edit edit;
while (1)
{
std::string s = edit.get_text();
std::cout << s << std::endl;
}
}
- 이와 같이 구현 가능하다. 이번에는 만약 주소를 입력받고 싶었다고 가정해보자.
- 그러면, Edit의 Validation 정책은 변경될 수 있어야 한다.
- Edit과 같은 Class는 라이브러리 내부이다. 내부 코드를 원할때마다 바꾸는건 어려운 일이다.
- Edit을 만드는 사람은 내부 코드를 수정하지 않고도 다른 방법으로 Validation 정책을 바꿀 수 있도록 해야 한다.
1-2. Validation 정책 변경 방법
- 변하는 것을 분리할 때 사용하는 2가지 방법이 존재한다.
- 변하는 코드를 가상함수로 분리하는 것과 변하는 코드를 다른 클래스로 분리하는 방법이다.
#include <iostream>
#include <string>
#include <conio.h>
class Edit
{
std::string data;
public:
std::string get_text()
{
data.clear();
while(1)
{
char c = _getch();
if (c == 13) break; // enter 키
if (isdigit(c))
{
data.push_back(c);
std::cout << c;
}
}
std::cout << "\n";
return data;
}
};
int main()
{
Edit edit;
while (1)
{
std::string s = edit.get_text();
std::cout << s << std::endl;
}
}
① 변하는 것을 가상함수로 분리 (template method 패턴)
#include <iostream>
#include <string>
#include <conio.h>
class Edit
{
std::string data;
public:
std::string get_text()
{
data.clear();
while (1)
{
char c = _getch();
if (c == 13 && iscomplete(data) ) break;
if (validate(data,c))
{
data.push_back(c);
std::cout << c;
}
}
std::cout << "\n";
return data;
}
virtual bool validate(const std::string& data, char c)
{
return true;
}
virtual bool iscomplete(const std::string& data)
{
return true;
}
};
int main()
{
Edit edit;
while (1)
{
std::string s = edit.get_text();
std::cout << s << std::endl;
}
}
- iscomplete(data) 를 통해서 입력 값이 완성되었는지도 체크한다.
① 파생 클래스 적용
#include <iostream>
#include <string>
#include <conio.h>
class Edit
{
std::string data;
public:
std::string get_text()
{
data.clear();
while (1)
{
char c = _getch();
if (c == 13 && iscomplete(data) ) break;
if (validate(data,c))
{
data.push_back(c);
std::cout << c;
}
}
std::cout << "\n";
return data;
}
virtual bool validate(const std::string& data, char c)
{
return true;
}
virtual bool iscomplete(const std::string& data)
{
return true;
}
};
class NumEdit : public Edit
{
int count;
public:
NumEdit(int count = 9999) : count(count) {}
bool validate(const std::string& data, char c) override
{
return data.size() < count && isdigit(c);
}
bool iscomplete(const std::string& data) override
{
return count != 9999 && data.size() == count;
}
};
int main()
{
NumEdit edit(5); // 5자리 숫자만, 5자리 입력 되어야만 enter 가능
while (1)
{
std::string s = edit.get_text();
std::cout << s << std::endl;
}
}
- template method가 과연 좋은 방법일까..?
② 다른 클래스로 분리하는 방법
- 인터페이스를 먼저 만들고 Edit에서 약한 결합으로 다양한 Validation 정책 클래스 사용
#include <iostream>
#include <string>
#include <conio.h>
struct IValidator
{
virtual bool validate(const std::string& data, char c) = 0;
virtual bool iscomplete(const std::string& data) { return true; }
virtual ~IValidator() { }
};
class Edit
{
std::string data;
IValidator* val = nullptr;
public:
void set_vailidator(IValidator* p) { val = p; }
std::string get_text()
{
data.clear();
while(1)
{
char c = _getch();
if (c == 13 && (val == nullptr || val->iscomplete(data) ) ) break; // enter 키
if (val == nullptr || val->validate(data,c))
{
data.push_back(c);
std::cout << c;
}
}
std::cout << "\n";
return data;
}
};
int main()
{
Edit edit;
while (1)
{
std::string s = edit.get_text();
std::cout << s << std::endl;
}
}
- Edit이 값의 유효성 여부를 체크하기 위해서 다른 클래스에 의존하고 있음을 알 수 있다.
- 이를 보통 위임한다고 한다.
1-2-1. Validator 구현
#include <iostream>
#include <string>
#include <conio.h>
struct IValidator
{
virtual bool validate(const std::string& data, char c) = 0;
virtual bool iscomplete(const std::string& data) { return true; }
virtual ~IValidator() { }
};
class DigitValidator : public IValidator
{
int count;
public:
DigitValidator(int count = 9999) : count(count) { }
bool validate(const std::string& data, char c) override
{
return data.size() < count && isdigit(c);
}
bool iscomplete(const std::string& data) override
{
return != 9999 && data.size() == count;
}
};
class Edit
{
std::string data;
IValidator* val = nullptr;
public:
void set_validator(IValidator* p) { val = p; }
std::string get_text()
{
data.clear();
while(1)
{
char c = _getch();
if (c == 13 && (val == nullptr || val->iscomplete(data) ) ) break; // enter 키
if (val == nullptr || val->validate(data,c))
{
data.push_back(c);
std::cout << c;
}
}
std::cout << "\n";
return data;
}
};
int main()
{
Edit edit;
DigitValidator v(5);
edit.set_validator(&v);
while (1)
{
std::string s = edit.get_text();
std::cout << s << std::endl;
}
}
- 다음과 같이 다른 클래스로 구현해서 사용할 수 있다.
2. Strategy(전략 패턴) 정리
① Validation 정책을 가상함수로 분리
- NumEdit이 Validation 정책을 소유하게 된다.
- 다른 클래스에서 Validation 정책을 사용할 수 없다.
- 실행시간에 Validation 정책을 교체할 수 없다.
② Validation 정책을 다른 클래스로 분리
- Edit와 Validation 정책이 서로 다른 클래스로 분리되어 있다.
- 다른 클래스에서 Validation 정책을 사용할 수 있다.
- 실행시간에 Validation 정책을 교체할 수 있다.
※ Edit 예제의 경우는 strategy가 더 적합하지만 template method 자체가 나쁜 것은 절대 아니다.
- 앞서 다뤘던, 도형 편집기에서 사각형 그리는 방법은 다른 클래스에서 사용해야 될 일이 없다.
- 또한 실행 시간에 교체할 이유도 없으며, 가상함수로 구현되면 멤버 함수라서 멤버 데이터 접근도 편리하다.
'C++ > Design Pattern' 카테고리의 다른 글
[C++ Design Pattern] Template Method Pattern (0) | 2024.09.18 |
---|