일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- operator overloading
- member function pointer
- delete function
- increment operator
- placement new
- std::cout
- std::ostream
- c++ basic practice
- virtual function table
- std::endl
- conversion constructor
- 더 지니어스 양면포커
- virtual destructor
- c++ multi chatting room
- constructor
- virtual function
- new&delete
- suffix return type
- discord bot
- diamond inheritance
- C++
- pointer to member data
- std::vector
- vector size
- base from member
- vector capacity
- virtual inheritance
- this call
- dynamic_cast
- return by reference
- Today
- Total
I'm FanJae.
[C++ Design Pattern] Template Method Pattern 본문
1. Template Method Pattern의 정의
오퍼레이션에는 알고리즘의 처리 과정만을 정의하고 각 단계에서 수행할 구체적인 처리는 서브클래스에서 정의한다. Template Method 패턴은 "알고리즘 처리과정은 변경하지 않고 알고리즘 각 단계의 처리를 서브클래스에서 재정의" 할 수 있게 한다.
1-1. 예제를 통한 Template Method Pattern의 필요성 이해 (코드의 중복)
#include <iostream>
#include "Painter.h"
class Shape
{
public:
virtual ~Shape() {}
virtual void draw() = 0;
};
class Rect : public Shape
{
public:
void draw() override
{
std::cout << "draw rect" << std::endl;
}
};
class Circle : public Shape
{
public:
void draw() override
{
std::cout << "draw circle" << std::endl;
}
};
int main()
{
Shape* s1 = new Rect();
Shape* s2 = new Circle();
s1->draw();
s2->draw();
}
// painter.h
#pragma once
#include <iostream>
class PainterPath
{
public:
void begin() {}
void end() {}
void draw_rect() {
std::cout << "draw rect" << std::endl;
}
void draw_circle() {
std::cout << "draw circle" << std::endl;
}
};
class Painter
{
public:
void draw_path(const PainterPath & path) {}
};
- GUI 환경에서 윈도우에 그림을 그린다면, 대부분의 라이브러리에는 그림을 그리기 위한 클래스를 제공할 것임.
- 화면 깜빡임 등을 방지(flicker free)하기 위해 다양한 방법을 제공한다.
- 라이브러리가 이런 형태로 동작하는 것이지 실제로 이 함수가 그림을 그려주는 것은 절대 아니다.
PainterPath path;
path.begin();
// path 객체를 사용해서
// 원하는 그림을 그린다.
path.end();
Painter surface;
surface.draw_path(path);
#include <iostream>
#include "Painter.h"
class Shape
{
public:
virtual ~Shape() {}
virtual void draw() =
};
class Rect : public Shape
{
public:
void draw() override
{
PainterPath path;
path.begin();
// path 멤버 함수로 그림을 그린다.
path.draw_rect();
path.end();
Painter surface;
surface.draw_path(path);
}
};
class Circle : public Shape
{
public:
void draw() override
{
PainterPath path;
path.begin();
// path 멤버 함수로 그림을 그린다.
path.draw_circle();
path.end();
Painter surface;
surface.draw_path(path);
}
};
int main()
{
Shape* s1 = new Rect();
Shape* s2 = new Circle();
s1->draw();
s2->draw();
}
- 이와 같이 만들 수 있다. 다만, 문제점이 있다면 이 코드의 문제는 코드의 중복이 너무 많다.
- 잘 보면, 모든 도형을 그리는 함수가 거의 일치하다.
1-2. 중복 제거
PainterPath path;
path.begin();
// path 멤버 함수로 그림을 그린다.
// path.draw_rect();
path.end();
Painter surface;
surface.draw_path(path);
- 중복이 되는 부분은 위와 같다. 이를 기반 클래스인 Shape의 draw()에서 제공하면 모든 도형이 물려받을 수 있다.
#include <iostream>
#include "Painter.h"
class Shape
{
public:
virtual ~Shape() {}
virtual void draw()
{
PainterPath path;
path.begin();
// path 객체를 사용해서 원하는 그림을 그린다.
path.end();
Painter surface;
surface.draw_path(path);
}
};
- 이 부분을 자세히 보면 전체적인 구조 자체(알고리즘의 처리 과정)은 변하지 않는다.
- 이 부분은 파생 클래스 (도형)마다 변경되어야 한다.
① 핵심
- 변하지 않은 코드 내부에 변해야 하는 코드를 찾는다.
- 변해야 하는 코드는 가상함수로 분리한다.
- 파생 클래스는 알고리즘 처리 과정을 물려 받으면서 가상함수 재정의를 통해서 변경이 필요한 부분만 다시 만들 수 있다.
② 반영 방법
#include <iostream>
#include "Painter.h"
class Shape
{
public:
virtual ~Shape() {}
virtual void draw()
{
PainterPath path;
path.begin();
// path 객체를 사용해서 원하는 그림을 그린다.
draw_imp(path);
path.end();
Painter surface;
surface.draw_path(path);
}
virtual void draw_imp(PainterPath& path) = 0;
};
- 이와 같이 변하지 않는 부분(전체적인 알고리즘)은 물려 받는다.
- 변해야 되는 부분(그림을 그리는 부분)만 최소화 해서 정의 한다.
③ 코드의 수정
#include <iostream>
#include "Painter.h"
class Shape
{
public:
virtual ~Shape() {}
virtual void draw()
{
PainterPath path;
path.begin();
// path 객체를 사용해서 원하는 그림을 그린다.
draw_imp(path);
path.end();
Painter surface;
surface.draw_path(path);
}
virtual void draw_imp(PainterPath& path) = 0;
};
class Rect : public Shape
{
public:
void draw_imp(PainterPath& path) override
{
path.draw_rect();
}
};
class Circle : public Shape
{
public:
void draw_imp(PainterPath& path) override
{
path.draw_circle();
}
};
int main()
{
Shape* s1 = new Rect();
Shape* s2 = new Circle();
s1->draw();
s2->draw();
}
- 이와 같이 줄일 수 있다.
④ 코드의 2차 수정
#include <iostream>
#include "Painter.h"
class Shape
{
public:
virtual ~Shape() {}
void draw()
{
PainterPath path;
path.begin();
// path 객체를 사용해서 원하는 그림을 그린다.
draw_imp(path);
path.end();
Painter surface;
surface.draw_path(path);
}
protected:
virtual void draw_imp(PainterPath& path) = 0;
};
class Rect : public Shape
{
public:
void draw_imp(PainterPath& path) override
{
path.draw_rect();
}
};
class Circle : public Shape
{
public:
void draw_imp(PainterPath& path) override
{
path.draw_circle();
}
};
int main()
{
Shape* s1 = new Rect();
Shape* s2 = new Circle();
s1->draw();
s2->draw();
}
① draw_imp()의 protected 처리
- 사용자 관점에서 보면 draw_imp()는 호출할 일이 없다. draw()만 사용하기 때문에 말이다.
- 따라서, 멤버 함수 내부적으로만 호출이 가능하도록 protected()로 해주는 것이 좋다.
- 외부에서 직접 부를일이 없기 때문이다.
② draw()의 가상함수 처리 삭제
- draw()는 파생 클래스에서 재정의 될 일이 없다.
- 따라서, draw()를 가상함수로 둘 이유가 없다.
2. 정리
class Shape
{
public:
void draw()
{
~~~
~~~
~~~
}
virtual method1() = 0;
virtual method2() = 0;
};
- 알고리즘의 기본적인 처리 과정은 변경되지 않는다. 즉, 전체적인 틀(Template)은 변화가 없다.
- 하지만 파생 클래스에 따라서 변화가 필요한 부분(변경할 기회를 주고 싶은 코드)는 가상함수로 분리한다.
'C++ > Design Pattern' 카테고리의 다른 글
[C++ Design Pattern] Strategy Pattern (0) | 2024.09.22 |
---|