Strategy Design Pattern
Strategy Design Pattern
Vấn đề
- Bạn đang phát triển một ứng dụng vẽ đồ họa.
- Trong class
GraphicsApp
, bạn có một phương thứcdrawShape(Shape shape)
. - Ban đầu, ứng dụng chỉ hỗ trợ vẽ hình vuông và hình tròn. Nhưng sau này, bạn muốn mở rộng ứng dụng để vẽ thêm nhiều hình khác như tam giác, ngũ giác, lục giác, vv. Mỗi lần thêm một hình mới, bạn phải sửa đổi phương thức
drawShape()
, điều này vi phạm nguyên tắc mở/đóng trong SOLID, làm cho mã nguồn trở nên khó bảo trì và dễ gây ra lỗi.
Giải pháp
Strategy pattern
có thể giải quyết vấn đề trên.- Thay vì viết mã để vẽ từng loại hình trong
drawShape()
, chúng ta có thể định nghĩa một interfaceDrawStrategy
với một phương thứcdraw()
. - Mỗi lớp cụ thể như
Square
,Circle
,Triangle
sẽ triển khai interface này theo cách riêng của nó. GraphicsApp
chỉ cần gọi phương thứcdraw()
củaDrawStrategy
, không cần biết chi tiết bên trong.- Khi cần thêm hình mới, chỉ cần thêm lớp mới triển khai
DrawStrategy
, không cần sửa đổi mã nguồn hiện có.
Một số ví dụ thực tế
- Trong ứng dụng vẽ đồ họa, chúng ta có thể dùng Strategy pattern để vẽ các loại hình khác nhau.
- Trong ứng dụng nén file, chúng ta có thể dùng Strategy pattern để chọn giữa các thuật toán nén khác nhau như ZIP, RAR, 7z.
- Trong ứng dụng di chuyển đồ vật, chúng ta có thể dùng Strategy pattern để chọn giữa các phương thức di chuyển khác nhau như bằng tay, bằng xe đẩy, bằng xe tải.
Khái niệm
Strategy pattern
bao gồm 3 thành phần chính:Strategy
: là interface định nghĩa một hành động cụ thể.ConcreteStrategy
: là các lớp cụ thể triển khai interfaceStrategy
.Context
: là lớp sử dụngStrategy
. Nó chứa một tham chiếu đến một đối tượngStrategy
và gọi phương thức củaStrategy
mà không cần biết chi tiết bên trong.
Sơ đồ UML biểu diễn
Strategy pattern
:classDiagram Context --|> Strategy : uses > Strategy <|.. ConcreteStrategy1 : implements > Strategy <|.. ConcreteStrategy2 : implements >
Code
- Dưới đây là mã nguồn minh họa bằng C++ và Golang.
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// C++
#include <iostream>
// Strategy
class DrawStrategy {
public:
virtual void draw() = 0;
};
// ConcreteStrategy
class Square : public DrawStrategy {
public:
void draw() override {
std::cout << "Draw square\n";
}
};
class Circle : public DrawStrategy {
public:
void draw() override {
std::cout << "Draw circle\n";
}
};
// Context
class GraphicsApp {
private:
DrawStrategy* strategy;
public:
void setStrategy(DrawStrategy* strategy) {
this->strategy = strategy;
}
void drawShape() {
strategy->draw();
}
};
int main() {
GraphicsApp app;
Square square;
Circle circle;
app.setStrategy(&square);
app.drawShape(); // Draw square
app.setStrategy(&circle);
app.drawShape(); // Draw circle
return 0;
}
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Golang
package main
import "fmt"
// Strategy
type DrawStrategy interface {
Draw()
}
// ConcreteStrategy
type Square struct{}
func (s Square) Draw() {
fmt.Println("Draw square")
}
type Circle struct{}
func (c Circle) Draw() {
fmt.Println("Draw circle")
}
// Context
type GraphicsApp struct {
strategy DrawStrategy
}
func (g *GraphicsApp) SetStrategy(s DrawStrategy) {
g.strategy = s
}
func (g *GraphicsApp) DrawShape() {
g.strategy.Draw()
}
func main() {
app := &GraphicsApp{}
square := &Square{}
circle := &Circle{}
app.SetStrategy(square)
app.DrawShape() // Draw square
app.SetStrategy(circle)
app.DrawShape() // Draw circle
}
Ưu nhược điểm
Ưu điểm
- Tăng khả năng mở rộng: khi cần thêm hành động mới, chỉ cần thêm lớp mới triển khai
Strategy
, không cần sửa đổi mã nguồn hiện có. - Tăng khả năng tái sử dụng: các
ConcreteStrategy
có thể được sử dụng lại ở nhiều nơi trong chương trình. - Tách biệt mã nguồn:
Context
không cần biết chi tiết bên trongStrategy
, giúp mã nguồn dễ đọc và dễ bảo dưỡng hơn.
Nhược điểm
- Tăng số lượng lớp: mỗi hành động cần một lớp
ConcreteStrategy
. - Khách hàng phải biết về các
ConcreteStrategy
để chọn đúngStrategy
.
So sánh với các design pattern khác
Strategy pattern
giốngState pattern
vì cả hai đều dựa trên composition để thay đổi hành vi của object tại runtime. Nhưng trongState pattern
, các state thường biết lẫn nhau và có thể thay đổi state của context. Trong khi đó, các strategy thường độc lập và không biết lẫn nhau.Strategy pattern
giốngTemplate Method pattern
vì cả hai đều cung cấp cách để thay đổi phần của thuật toán. NhưngTemplate Method pattern
sử dụng inheritance và cần override phương thức để thay đổi phần của thuật toán, trong khiStrategy pattern
sử dụng composition và cần thay đổi object để thay đổi phần của thuật toán.
This post is licensed under CC BY 4.0 by the author.