Strategy

7/20/2021 OOPPatternBehavioral pattern

# Description:

Xác định một nhóm thuật toán, đặt mỗi thuật toán vào một lớp riêng biệt và làm cho các đối tượng của chúng có thể hoán đổi cho nhau.

# Example:

Xử lí tạo Post, sau khi tạo thành công thì gửi mail thông báo cho các member khác.

Post

class Post
{
    public function create()
    {
        // do something to create a post
        
        // Send mail
        $this->notify();
    }
    
    protected function notify()
    {
        // send mail
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Bây giờ có change request là thay gửi mail bằng slack. Có các cách sau:

  • Chỉnh sửa lại method notify: Sẽ vi phạm Open/Closed Principle và cũng dễ phát sinh bug khi hệ thống đã hoạt động ổn định.

  • Dùng kế thừa overide lại method notify:

    class SlackPost extends Post
    {  
        protected function notify()
        {
            // send notify by slack
        }
    }
    
    1
    2
    3
    4
    5
    6
    7

    Trường hợp có thể thay đổi nhiều hơn vd gửi notify bằng SMS hoặc cả bằng mail và slack. Với business logic đơn giản thì không ảnh hưởng gì nhưng với những business logic phức tạp lại phải tạo ra nhiều class và overide lại method. Làm phức tạp hóa xử lí và cần thực hiện test lại tất cả ảnh hưởng.

# Xử lí bằng Strategy Pattern:

Tách xử lí notify ra khỏi Post và tạo các class xử lí, có thể hoán đổi xử lí notify cho các trường hợp khác nhau.

Tạo Interface cho xử lí notify

interface NotifyStrategy
{
    public function send();
}
1
2
3
4

Tách xử lí notify ra khỏi post

class Post
{
    protected NotifyStrategy $notify;

    public function __construct(NotifyStrategy $notify)
    {
        $this->notify = $notify;
    }

    public function create()
    {
        // do something to create a post
        
        $this->notify();
    }

    public function notify()
    {
        $this->notify->send();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Tạo các class xử lí:

class MailNotify implements NotifyStrategy
{
    public function send()
    {
        // send mail
    }
}
1
2
3
4
5
6
7
class SlackNotify implements NotifyStrategy
{
    public function send()
    {
        // send slack notify
    }
}
1
2
3
4
5
6
7

Bây giờ, nếu muốn sủ dụng mail hay slack thì chỉ cần set trong constructor

$mailPost = new Post(new MailNotify());
$slackPost = new Post(new SlackNotify());
1
2

Kết hợp với DI container (như binding trong Laravel Service Container) thì cũng dễ dàng và linh động hơn.

$app->bind(NotifyStrategy::class, MailNotify::class)
1

Xử lí thêm các class xử lí khác thì cũng dễ dạng. Vd: thêm yêu cầu có thể send notify bằng cả slack và mail.

class SlackMailNotify implements NotifyStrategy
{
    protected NotifyStrategy $mailNotify;
    protected NotifyStrategy $slackNotify;

    public function __construct(NotifyStrategy $mailNotify, NotifyStrategy $slackNotify)
    {
        $this->mailNotify = $mailNotify;
        $this->slackNotify = $slackNotify;
    }

    public function send()
    {
        $this->mailNotify->send();
        $this->slackNotify->send();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17