Ưu điểm của mô hình chiến lược


15

Tại sao nó có lợi khi sử dụng mẫu chiến lược nếu bạn chỉ có thể viết mã của mình trong các trường hợp if / then?

Ví dụ: Tôi có một lớp TaxPayer và một trong các phương thức của nó tính toán các loại thuế bằng các thuật toán khác nhau. Vậy tại sao nó không thể có các trường hợp if / then và tìm ra thuật toán nào sẽ sử dụng trong phương thức đó, thay vì sử dụng mẫu chiến lược? Ngoài ra, tại sao bạn không thể thực hiện một phương thức riêng cho từng thuật toán trong lớp TaxPayer?

Ngoài ra, thuật toán có nghĩa gì khi thay đổi khi chạy?


2
Đây có phải là bài tập về nhà không? Nó là tốt hơn để nói rằng lên phía trước nếu có.
Fuhrmanator

2
@Fuhrmanator no it
isntnt

Câu trả lời:


20

Đối với một điều, các khối lớn của if/elsekhối không dễ kiểm tra . Mỗi "nhánh" mới thêm một đường dẫn thực thi khác và do đó làm tăng độ phức tạp chu kỳ . Nếu bạn muốn kiểm tra mã của mình kỹ lưỡng, bạn phải bao gồm tất cả các đường dẫn thực thi và mỗi điều kiện sẽ yêu cầu bạn viết ít nhất một bài kiểm tra nữa (giả sử bạn viết các bài kiểm tra nhỏ, tập trung). Mặt khác, các lớp thực hiện chiến lược thường chỉ hiển thị 1 phương thức công khai, rất dễ kiểm tra.

Vì vậy, với lồng nhau, if/elsebạn sẽ kết thúc với nhiều thử nghiệm cho một phần mã của mình, trong khi với Chiến lược, bạn sẽ có một vài thử nghiệm cho từng chiến lược đơn giản hơn. Với cái sau, thật dễ dàng để có phạm vi bảo hiểm tốt hơn, bởi vì khó bỏ lỡ các đường dẫn thực thi.

Về khả năng mở rộng , hãy tưởng tượng bạn đang viết một khung, trong đó người dùng được cho là có thể thực hiện hành vi của chính họ. Ví dụ: bạn muốn tạo một số loại khung tính thuế và muốn hỗ trợ các hệ thống thuế của các quốc gia khác nhau. Thay vì triển khai tất cả chúng, bạn chỉ muốn cho người dùng khung cơ hội cung cấp cách triển khai cách tính một số loại thuế cụ thể.

Đây là mô hình chiến lược:

  • Bạn xác định một giao diện, ví dụ TaxCalculation, và khung của bạn chấp nhận các thể hiện của loại này để tính thuế
  • Người dùng của khung công tác tạo ra một lớp thực hiện giao diện này và chuyển nó đến khung của bạn, do đó cung cấp một cách để thực hiện một số tính toán

Bạn không thể làm tương tự với if/else, bởi vì điều đó sẽ yêu cầu thay đổi mã của khung, trong trường hợp đó, nó sẽ không còn là khung nữa. Vì các khung thường được phân phối ở dạng biên dịch, đây có thể là lựa chọn duy nhất.

Tuy nhiên, ngay cả khi bạn chỉ viết một số mã thông thường, Chiến lược vẫn có lợi vì nó làm cho ý định của bạn rõ ràng hơn. Nó nói "logic này có thể cắm và có điều kiện", nghĩa là có thể có nhiều triển khai có thể thay đổi tùy thuộc vào hành động của người dùng, cấu hình hoặc thậm chí nền tảng.

Sử dụng mô hình chiến lược có thể cải thiện khả năng đọc bởi vì, trong khi một lớp mà thực hiện một số chiến lược đặc biệt thường nên có một tên mô tả, ví dụ USAIncomeTaxCalculator, if/elsekhối là "vô danh", trong trường hợp tốt nhất chỉ nhận xét và ý kiến có thể nói dối. Ngoài ra, theo sở thích cá nhân của tôi, chỉ có nhiều hơn 3 if/elsekhối liên tiếp là không thể đọc được và nó trở nên khá tệ với các khối lồng nhau.

Các nguyên tắc mở / đóng cũng rất phù hợp, bởi vì, như tôi đã mô tả trong ví dụ trên, Chiến lược cho phép bạn mở rộng một logic trong một số phần của mã của bạn ( "mở cho phần mở rộng") mà không cần viết lại những bộ phận ( "đóng cửa sửa đổi" ).


1
if/elsekhối cũng dễ đọc mã hơn. Đối với mô hình chiến lược, nguyên tắc Mở / Đóng đáng được đề cập đến IMO.
Maciej Chałapuk

1
Khả năng kiểm tra là lý do lớn. (Hầu hết) Mỗi ​​chi nhánh trong mã của bạn nên được kiểm tra. Bạn càng có nhiều if, các đường dẫn có thể tồn tại thông qua mã của bạn càng nhiều, bạn càng phải viết nhiều bài kiểm tra và nhiều cách để phương thức đó thất bại. Nếu tôi có thể trích dẫn Yogi Berra quá cố: "Nếu bạn đến một ngã ba trên đường hãy mang nó đi." Điều này áp dụng tuyệt vời để thử nghiệm đơn vị. Hơn nữa, nhiều iftuyên bố có nghĩa là bạn có khả năng lặp lại logic cho các điều kiện đó, tăng thêm tải thử nghiệm của bạn và tăng nguy cơ lỗi phát sinh.
Greg Burghardt

Cảm ơn bạn đã trả lời. Vậy tại sao tôi không thể sử dụng các phương thức riêng biệt cho các thuật toán khác nhau trong cùng một lớp?
Armon Safai

Bạn có thể, nhưng bạn vẫn sẽ cần một loạt các if/elsekhối để gọi chúng (hoặc bên trong chúng, để xác định xem nó có nên làm gì đó hay không), vì vậy nó không giúp ích nhiều, ngoại trừ mã có thể dễ đọc hơn. Và cũng không có khả năng mở rộng cho người dùng của khung giả thuyết của bạn sau đó.
scriptin

1
Bạn có thể rõ ràng hơn về lý do tại sao nó dễ kiểm tra hơn? Ví dụ để tái cấu trúc một câu lệnh tình huống (hoặc nếu / sau đó) thành một phương thức đa hình (cơ sở cho chiến lược) là khá dễ dàng để kiểm tra. refactoring.com/catalog/replaceConditableWithPolymorphism.html Nếu tôi biết tất cả các điều kiện để kiểm tra, tôi viết một bài kiểm tra cho mỗi. Nếu tôi có chiến lược, tôi phải khởi tạo và thực hiện từng cái cho từng cái. Cách tiếp cận chiến lược dễ kiểm tra hơn? Chúng ta không nói về ifs lồng nhau phức tạp khi bạn tái cấu trúc chiến lược.
Fuhrmanator

5

Tại sao nó có lợi khi sử dụng mẫu chiến lược nếu bạn chỉ có thể viết mã của mình trong các trường hợp if / then?

Đôi khi bạn chỉ nên sử dụng nếu / sau đó. Đó là mã đơn giản dễ đọc.

Hai vấn đề chính với mã if / then đơn giản là nó có thể vi phạm nguyên tắc đóng mở . Nếu bạn phải vào và thêm hoặc thay đổi một điều kiện, bạn đang sửa đổi mã này. Nếu bạn mong muốn có nhiều điều kiện hơn, chỉ cần thêm một chiến lược mới là đơn giản hơn / sạch hơn / ít có khả năng phá vỡ hơn.

Vấn đề còn lại là khớp nối. Bằng cách sử dụng if / then, tất cả các triển khai được gắn với triển khai đó, khiến chúng khó thay đổi hơn trong tương lai. Bằng cách sử dụng chiến lược, khớp nối duy nhất là giao diện của chiến lược.


Có gì sai khi sửa đổi mã trong mã if / then? bạn có phải sửa đổi mã trong mẫu chiến lược không nếu bạn quyết định thay đổi cách một trong các thuật toán hoạt động?
Armon Safai

@armonsafai - nếu bạn sửa đổi chiến lược, bạn chỉ cần kiểm tra chiến lược. Nếu bạn sửa đổi tất cả các thuật toán, bạn cần kiểm tra tất cả các thuật toán. Tệ hơn, nếu bạn thêm một chiến lược mới, bạn chỉ cần thử nghiệm chiến lược đó. Nếu bạn thêm một điều kiện mới, bạn cần kiểm tra tất cả các điều kiện.
Telastyn

4

Chiến lược rất hữu ích khi các if/thenđiều kiện dựa trên các loại , như được giải thích trong http://www.refactoring.com/catalog/replaceConditableWithPolymorphism.html

Các điều kiện kiểm tra kiểu thường không có độ phức tạp chu kỳ cao, vì vậy tôi không nói rằng Chiến lược cải thiện mọi thứ nhất thiết phải có.

Lý do chính cho Chiến lược được giải thích trong sách GoF p.316 đã giới thiệu mẫu này:

Sử dụng mẫu Chiến lược khi

...

  • một lớp định nghĩa nhiều hành vi và chúng xuất hiện dưới dạng nhiều câu điều kiện trong các hoạt động của nó. Thay vì nhiều điều kiện, hãy chuyển các nhánh có điều kiện liên quan vào lớp Chiến lược của riêng họ.

Như đã đề cập trong các câu trả lời khác, nếu được áp dụng một cách thích hợp, mẫu Chiến lược cho phép thêm các phần mở rộng mới (chiến lược cụ thể) mà không nhất thiết yêu cầu sửa đổi phần còn lại của mã. Đây được gọi là nguyên tắc Mở-Đóng hoặc Nguyên tắc Biến đổi được bảo vệ . Tất nhiên, bạn vẫn phải mã hóa chiến lược cụ thể mới và mã máy khách phải nhận ra các chiến lược là các trình cắm thêm (điều này không tầm thường).

Với các if/thenđiều kiện, cần phải thay đổi mã của lớp chứa logic điều kiện. Như đã đề cập trong các câu trả lời khác, đôi khi điều này là ổn khi bạn không muốn thêm độ phức tạp để hỗ trợ thêm chức năng mới (trình cắm) mà không cần biên dịch lại.


3

[...] Nếu bạn chỉ có thể viết mã của mình trong trường hợp if / then?

Đó chính xác là lợi ích lớn nhất của mô hình chiến lược. Không có điều kiện.

Bạn muốn các lớp / phương thức / hàm của bạn đơn giản và ngắn nhất có thể. Mã ngắn rất dễ kiểm tra và rất dễ đọc.

Các điều kiện ( if/ elseif/ else) làm cho các lớp / phương thức / hàm của bạn dài ra, bởi vì thông thường mã mà một quyết định đánh giá truelà khác với phần mà quyết định đánh giá false.


Một lợi ích tuyệt vời khác của mô hình chiến lược là, nó có thể tái sử dụng trong toàn bộ dự án của bạn.

Khi sử dụng mẫu thiết kế chiến lược, bạn rất có thể có một loại bộ chứa IoC nào đó, từ đó bạn có được việc triển khai giao diện mong muốn, có lẽ bằng một getById(int id)phương thức, trong đó idcó thể là một thành viên liệt kê.

Điều này có nghĩa, việc tạo ra việc thực hiện chỉ ở một nơi trong mã của bạn.

Nếu bạn muốn thêm nhiều triển khai, bạn thêm triển khai mới vào getByIdphương thức và thay đổi này được phản ánh ở mọi nơi trong mã nơi bạn gọi nó.

Với if/ elseif/ elseđiều này là không thể làm được. Bằng cách thêm một triển khai mới, bạn phải thêm một elseifkhối mới và thực hiện nó ở mọi nơi sử dụng các triển khai đó hoặc bạn có thể kết thúc bằng một mã không hợp lệ, vì bạn đã quên thêm việc triển khai vào cấu trúc của nó.


Ngoài ra, thuật toán có nghĩa gì khi thay đổi khi chạy?

Trong ví dụ của tôi, đó idcó thể là một biến được xác định dựa trên đầu vào của người dùng. Nếu người dùng nhấp vào nút A, thì id = 2nếu anh ta nhấp vào nút B, thì id = 8.

Do idgiá trị khác nhau , việc triển khai giao diện khác nhau được lấy từ bộ chứa IoC và mã thực hiện các hoạt động khác nhau.


Cảm ơn bạn đã trả lời. Vậy tại sao tôi không thể sử dụng các phương thức riêng biệt cho các thuật toán khác nhau trong cùng một lớp?
Armon Safai

@ArmonSafai Liệu các phương pháp riêng biệt có thực sự giải quyết được gì không? Tôi không nghĩ vậy. Bạn đang chuyển vấn đề từ nơi này sang nơi khác và quyết định gọi phương thức nào sẽ được đưa ra dựa trên kết quả của một điều kiện. Một lần nữa, một if/ elseif/ elsebang. Giống như trước đây, chỉ ở một nơi khác.
Andy

Vì vậy, các trường hợp if / then sẽ là chính phải không? Bạn có phải sử dụng các trường hợp if / then trong chính cho mẫu chiến lược không?
Armon Safai

1
@ArmonSafai Không, bạn sẽ không. Bạn sẽ có một công tắc trên idbiến trong getByIdphương thức, sẽ trả về việc thực hiện cụ thể. Mỗi khi bạn cần triển khai giao diện, bạn sẽ yêu cầu bộ chứa IoC cung cấp cho bạn.
Andy

1
@ArmonSafai Bạn cũng có thể có một phương thức getSortByEnumType(SortEnum type)trả về việc thực hiện Sortgiao diện, có một phương thức getSortTypetrả về một SortEnumbiến và lấy một bộ sưu tập làm tham số và getSortByEnumTypephương thức sẽ lại chứa một công tắc trên typetham số trả về cho bạn thuật toán sắp xếp chính xác. Nếu bạn cần thêm một thuật toán sắp xếp mới, bạn chỉ cần chỉnh sửa enum và một phương thức. Và bạn đã được thiết lập.
Andy

2

Tại sao nó có lợi khi sử dụng mẫu chiến lược nếu bạn chỉ có thể viết mã của mình trong các trường hợp if / then?

Mẫu chiến lược cho phép bạn tách các thuật toán của bạn (chi tiết) khỏi logic kinh doanh của bạn (chính sách cấp cao). Hai điều này không chỉ khó hiểu khi đọc, mà còn có những lý do rất khác nhau để thay đổi.

Ngoài ra còn có một yếu tố khả năng mở rộng làm việc nhóm lớn ở đây. Hãy tưởng tượng một nhóm lập trình lớn nơi có nhiều người đang làm việc với gói kế toán này. Nếu các thuật toán thuế là tất cả trong lớp hoặc mô-đun TaxPayer , thì có thể xảy ra xung đột hợp nhất. Hợp nhất xung đột là tốn thời gian và dễ bị lỗi để giải quyết. Thời gian này làm giảm năng suất từ ​​nhóm và các lỗi được đưa ra bởi sự hợp nhất xấu gây thiệt hại uy tín với khách hàng.

Ngoài ra, thuật toán có nghĩa gì khi thay đổi khi chạy?

Một thuật toán thay đổi trong thời gian chạy là một thuật toán có hành vi được xác định bởi cấu hình hoặc bối cảnh. Một cách tiếp cận if / then tại chỗ không cho phép điều này một cách hiệu quả vì nó liên quan đến việc tải lại các lớp được sử dụng tích cực hiện có. Với Mẫu chiến lược, các đối tượng chiến lược thực hiện từng thuật toán có thể được xây dựng khi sử dụng. Kết quả là những thay đổi đối với các thuật toán này (sửa lỗi hoặc cải tiến) có thể được thực hiện và tải lại khi chạy. Cách tiếp cận này có thể được sử dụng để cho phép phát hành liên tục và không có thời gian chết.


1

Không có gì sai với if/elsemỗi se. Trong nhiều trường hợp if/elselà cách đơn giản nhất và dễ đọc nhất để diễn đạt logic. Vì vậy, cách tiếp cận bạn mô tả là hoàn toàn hợp lệ trong nhiều trường hợp. (Nó cũng hoàn toàn có thể kiểm tra được, vì vậy đó không phải là vấn đề.)

Nhưng có một số trường hợp cụ thể trong đó một mẫu chiến lược có thể cải thiện khả năng duy trì của mã tổng thể. Ví dụ:

  • Nếu các thuật toán tính thuế cụ thể có thể thay đổi độc lập với nhau và logic logic. Trong trường hợp này, thật tốt khi tách chúng thành các lớp riêng biệt, vì các thay đổi sẽ được bản địa hóa.
  • Nếu các thuật toán mới có thể được thêm vào trong tương lai, mà không thay đổi logic cốt lõi.
  • Nếu nguyên nhân của sự khác biệt giữa hai thuật toán cũng ảnh hưởng đến các phần khác của mã. Giả sử bạn chọn giữa hai thuật toán dựa trên khung thu nhập của người nộp thuế. Nếu khung thu nhập này cũng khiến bạn chọn các chi nhánh khác trong mã, thì việc lập chiến lược tương ứng với khung thu nhập một lần sẽ tốt hơn và gọi khi cần, thay vì có nhiều nhánh if / khác phân tán trên mã.

Để mô hình chiến lược có ý nghĩa, giao diện giữa logic cốt lõi và thuật toán tính thuế phải ổn định hơn các thành phần riêng lẻ. Nếu có khả năng thay đổi yêu cầu sẽ khiến giao diện thay đổi, thì mẫu chiến lược thực sự có thể là một trách nhiệm pháp lý.

Tất cả bắt nguồn từ việc "thuật toán tính thuế" có thể được tách biệt rõ ràng khỏi logic cốt lõi gọi nó. Một mô hình chiến lược có một số chi phí so với một if/else, vì vậy bạn sẽ phải quyết định theo từng trường hợp nếu khoản đầu tư đó xứng đáng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.