You are on page 1of 20

Bài viết được tham khảo từ cuốn Design pattern for dummies

strategy

1. Giới thiệu về design pattern

Design Pattern là một kỹ thuật trong lập trình hướng đối tượng, nó khá quan trọng
và mọi lập trình viên muốn giỏi đều phải biết. Được sử dụng thường xuyên trong
các ngôn ngữ OOP. Nó sẽ cung cấp cho bạn các "mẫu thiết kế", giải pháp để giải
quyết các vấn đề chung, thường gặp trong lập trình. Các vấn đề mà bạn gặp phải có
thể bạn sẽ tự nghĩ ra cách giải quyết nhưng có thể nó chưa phải là tối ưu. Design
Pattern giúp bạn giải quyết vấn đề một cách tối ưu nhất, cung cấp cho bạn các giải
pháp trong lập trình OOP.

Trong Design Pattern có 3 nhóm bao gồm:

 Creational Pattern (nhóm khởi tạo) gồm: Abstract Factory, Factory Method,
Singleton, Builder, Prototype. Nó sẽ giúp bạn trong việt khởi tạo đối tượng,
như bạn biết để khởi tạo bạn phải sử dụng từ khóa new, nhóm Creational
Pattern sẽ sử dụng một số thủ thuật để khởi tạo đối tượng mà bạn sẽ không
nhìn thấy từ khóa này.

 Structural Pattern (nhóm cấu trúc) gồm: Adapter, Bridge, Composite,


Decorator, Facade, Proxy và Flyweight.. Nó dùng để thiết lập, định nghĩa
quan hệ giữa các đối tượng.

 Behavioral Pattern gồm: Interpreter, Template Method, Chain of


Responsibility, Command, Iterator, Mediator, Memento, Observer, State,
Strategy và Visitor. Nhóm này dùng trong thực hiện các hành vi của đối
tượng.

2. Tìm hiểu strategy pattern

Strategy pattern là gì? Strategy pattern (mẫu chiến lược): hiểu một cách đơn giản
thì đây là mẫu thiết kế giúp bạn trừu tượng hóa những hành vi (behavior, method,
function) của một đối tượng bằng cách đưa ra những cài đặt vào những lớp khác
nhau.

Bài toán thực tế. Bạn nhận được một hợp đồng thiết kế ô tô. Có rất nhiều mẫu ô tô
để bạn có thể làm. Bạn nghĩ ngay đến việc sử dụng OOP vào trong thiết kế ô tô của
mình. Đầu tiên, bạn tạo ra 1 lớp cơ sở có tên là Vehicle với một phương thức có tên
là go, phương thức này xuất hiện lên dòng chữ Now I’m driving.

abstract class Vehicle

public function go()

{
echo "Now I'm driving";
}

Sau đó, bạn tạo tiếp 1 lớp mới là lớp StreetRacer thừa kế từ lớp Vehicle như sau:

public class StreetRacer extends Vehicle


{

Tới đây, chương trình của bạn vẫn hoàn toàn tốt đẹp. Bạn có thể khai báo 1 đối
tượng StreetRacer và gọi tới hàm go:

$streetRacer = new StreetRacer;


$streetRacer->go();

Và kết quả trả về là: Now I’m driving. Kết quả hoàn toàn chính xác. Nhưng sau đó,
bạn nhận thêm 1 hợp đồng sản xuất máy bay trực thăng Helicopter. Bạn nhận thấy
máy bay trực thăng thì cũng là 1 phương tiện vận chuyển. Vì vậy bạn quyết định tạo
ra 1 lớp Helicopter thừa kế từ lớp Vehicle:

public class Helicopter extends Vehicle

Nhưng bạn chợt nhận ra vấn đề là khi sử dụng hàm go cho Helicopter, thì kết quả
trả về có vẻ không chính xác. Now I’m driving? Tại sao máy bay lại là driving?
Máy bay thì phải bay chứ nhỉ? Nhưng nó không phải là vấn đề lớn. Bạn quyết định
sẽ override hàm go cho lớp Helicopter như sau:

public class StreetRacer extends Vehicle


{

public function go()


{

echo "Now I'm flying";

}
}

Có vẻ vấn đề đã được giải quyết. Giờ thì máy bay đã là flying rồi. Nhưng vài tuần
sau, khách hàng yêu cầu phải chuyển từ Now, I’m flying sang Now, I’m flying
200mph và nhiều sự thay đổi kế tiếp. Có một vấn đề nảy sinh ở đây. Đó chưa phải
là một vấn đề lớn, nhưng nếu bạn phải xử lý các công việc này một cách khá
thường xuyên, thì việc cứ phải chỉnh sửa các lớp con như thế này trờ thành 1 vấn đề
bảo trì khá nghiêm trọng.

Bạn bắt đầu suy nghĩ. Có lẽ sự thừa kế không phải là cách giải quyết tốt cho tình
huống này. Nơi mà bạn cần phải thay đổi các chức năng thường xuyên ở các lớp
con. Và khi có càng nhiều lớp kế thừa liên quan, chúng cũng cần được bảo trì khi
có sự thay đổi và khi đó, bạn sẽ phải cập nhất phương thức go nhiều lần.

Vấn đề bạn cần phải giải quyết ở đây là làm sao để tránh được việc thay đổi ở các
lớp con, nếu không, bạn sẽ phải thay đổi code ở rất nhiều file để cập nhật được yêu
cầu của khách hàng.

Có lẽ là bạn cần một cách khác tốt hơn để xử lý vấn đề này thay vì sử dụng thừa
kế.

Nắm vững sự thay đổi từ “is-a” sang “has-a”

Với sự kế thừa, lớp cơ sở và các lớp con có một mối quan hệ “is-a”. Ví dụ , lớp
Helicopter có quan hệ “is-a” với lớp Vehicle, điều này có nghĩa Helicopter thừa kế
mọi thứ từ Vehicle, và nếu bạn phải chỉnh sửa các phương thức này, bạn sẽ gặp
phải vấn đề bảo trì nó trong tương lai. Lớp cơ sở xử lý phương thức theo một cách,
và lớp kế thừa lại thay đổi nó, và lớp kế tiếp lại thay đổi nó thêm một lần nữa. Và
cuối cùng bạn có một lô một lốc các biến thể của cùng 1 phương thức qua các lớp
con.

Mặc khác, nếu bạn có thể trích những đoạn code dễ thay đổi và đóng gói chúng vào
đối tượng, bạn có thể sử dụng các đối tượng này khi cần. Nhiệm vụ mới là xử lý
trên các đối tượng này. Bạn đã không để việc xử lý lây lan qua các lớp con. Làm
như vậy sẽ cho phép bạn chỉnh sửa code của bạn bằng việc tạo ra “sự kết hợp”
composites các đối tượng. Với composites “kết hợp” này, bạn có thể dễ dàng chọn
ra và sử dụng đối tượng cần thiết. Một quan hệ “has-a” mới được tạo ra. Một đối
tượng StreetRacer sẽ có một “has-a” cách để di chuyển (go), đã được đóng gói vào
đối tượng. Một Helicopter sẽ có một cách riêng để di chuyển (go), và cũng được
đóng gói vào đối tượng. Từng đối tượng sẽ thực hiện hành động của riêng nó. Một
đối tượng, một nhiệm vụ thường là có ý nghĩa hơn là việc kế thừa các lớp, và tạo ra
hàng tá các lớp con. Nói cách khác, chúng ta sắp xếp lại dựa trên nhiệm vụ của lớp,
chứ không phải trên sự kế thừa.

Cách tạo thuật toán:

Đầu tiên, bạn tạo 1 giao diện interface cho phương thức go như sau:

public interface goAlgorithm

{
public function go();

Trong giao diện goAlgorithm chỉ có duy nhất 1 phương thức là go. Sau đó bạn sẽ
tạo các lớp cụ thể cho từng thuật toán. Đầu tiên là lớp GoByDrivingAlgorithm, thực
hiện như sau:

class GoByDrivingAlgorithm implements goAlgorithm

{
public function go()
{

echo "Now I'm driving";

}
}

Ngoài ra, lớp GoByFlyingAlgorithm sẽ định nghĩa cho các phương tiện có thể bay.
class GoByFlyingAlgorithm implements goAlgorithm

public function go()


{

echo "Now I'm flying";

Và thậm chí bạn còn có thể định nghĩa phương thức go cho cả máy bay phản lực:

class GoByFlyingFastAlgorithm implements goAlgorithm


{

public function go()

echo "Now I'm flying very fast";


}

Vậy là bạn đã tách được các phương thức xử lý ra khỏi các lớp cụ thể như
StreetRacer hay Helicopter rồi đấy. Bây giờ bạn đã có thể đưa các thuật toán này
vào sử dụng được rồi đấy.

Sử dụng thuật toán

Sau khi bạn tạo một đối tượng từ một thuật toán, bạn cần phải lưu trữ đối tượng ở
đâu đó. Vì vậy hãy thêm vào lớp cơ sở Vehicle, một phương thức mới
SetGoAlgorithm. Phương thức này sẽ quyết định thuật toán mà bạn muốn sử dụng.

abstract class Vehicle

private $goAlgorithm

public function setGoAlgorithm(GoAlgorithm $__goAlgorithm )


{
$goAlgorithm = $__goAlgorithm;

Bây giờ khi bạn muốn sử dụng một thuật toán cụ thể nào đó ở lớp kế thừa, tất cả
việc cần làm là gọi phương thức setGoAlgorithm với một đối tượng thuật toán
đúng. Phương thức go trong lớp Vehicle có chút thay đổi.

public function go()


{

$goAlgorithm->go();
}

Bây giờ thì tất cả những gì bạn cần làm là chọn đúng thuật toán cho phương tiện. Ví
dụ như StreetRacer thì sẽ là GoByDrivingAlgorithm, cho Helicopter thì sẽ là
GoByFlyingAlgorithm,...

3. Tổng kết
Vậy là chúng ta vừa tìm hiểu về Strategy pattern (mẫu chiến lược).Ý nghĩa thực sự
của mẫu chiến lược là bạn tách rời phần xử lý một chức năng cụ thể ra khỏi đối
tượng của bạn. Sau đó tạo ra một tập hợp các thuật toán để xử lý chức năng đó và
lựa chọn thuật toán nào mà bạn thấy đúng đắn nhất khi thực thi chương trình. Mẫu
thiết kế này thường được sử dụng để thay thế cho sự kế thừa, khi bạn muốn chấm
dứt việc theo dõi và chỉnh sửa một chức năng qua nhiều lớp con.

Strategy pattern cho thấy đôi khi nó sẽ được áp dụng tốt cho mục đích hướng chức
năng. Và nó đặc biệt quan trọng khi bạn muốn thực hiện công việc nâng cấp, bảo trì
cho các đoạn mã dễ thay đổi của bạn một cách riêng biệt với toàn bộ mã của
chương trình, hoặc khi bạn muốn thay đổi thuật toán sử dụng khi chương trình được
thực thi.

Bạn nên sử dụng strategy pattern cho những trường hợp sau:

 Bạn có một đoạn code dễ thay đổi, và bạn tách chúng ra khỏi chương trình
chính để dễ dàng bảo trì
 Bạn muốn tránh sự rắc rối, khi phải hiện thực một chức năng nào đó qua quá
nhiều lớp con.
 Bạn muốn thay đổi thuật toán sử dụng khi chạy chương trình

Template Method

Định nghĩa
Xác định bộ xương của một thuật toán trong một hoạt động, trì hoãn một số bước
cho các lớp con. Phương thức mẫu cho phép các lớp con xác định lại các bước nhất
định của thuật toán mà không thay đổi cấu trúc của thuật toán.

Tần suất sử dụng:


Trung bình

Sơ đồ lớp UML

Các lớp và đối tượng tham gia:


AbstractClass (DataObject):

o định nghĩa các hoạt động nguyên thủy trừu tượng mà các lớp con cụ
thể xác định để thực hiện các bước của thuật toán
o thực hiện một phương thức mẫu xác định bộ xương của thuật
toán. Phương thức mẫu gọi các hoạt động nguyên thủy cũng như các
hoạt động được xác định trong AbstractClass hoặc các hoạt động của
các đối tượng khác.

ConcreteClass (CustomerDataObject):
thực hiện các hoạt động nguyên thủy khi thực hiện các bước cụ thể của lớp con của
thuật toán.

Mã cấu trúc trong C #


Mã cấu trúc này thể hiện phương thức Mẫu cung cấp một chuỗi các phương thức
gọi bộ xương. Một hoặc nhiều bước có thể được chuyển đến các lớp con thực hiện
các bước này mà không thay đổi trình tự gọi tổng thể.

1. using System;
2.
3. namespace DoFactory.GangOfFour.Template.Structural
4. {
5. /// <summary>
6. /// MainApp startup class for Real-World
7. /// Template Design Pattern.
8. /// </summary>
9. class MainApp
10. {
11. /// <summary>
12. /// Entry point into console application.
13. /// </summary>
14. static void Main()
15. {
16. AbstractClass aA = new ConcreteClassA();
17. aA.TemplateMethod();
18.
19. AbstractClass aB = new ConcreteClassB();
20. aB.TemplateMethod();
21.
22. // Wait for user
23. Console.ReadKey();
24. }
25. }
26.
27. /// <summary>
28. /// The 'AbstractClass' abstract class
29. /// </summary>
30. abstract class AbstractClass
31. {
32. public abstract void PrimitiveOperation1();
33. public abstract void PrimitiveOperation2();
34.
35. // The "Template method"
36. public void TemplateMethod()
37. {
38. PrimitiveOperation1();
39. PrimitiveOperation2();
40. Console.WriteLine("");
41. }
42. }
43.
44. /// <summary>
45. /// A 'ConcreteClass' class
46. /// </summary>
47. class ConcreteClassA : AbstractClass
48. {
49. public override void PrimitiveOperation1()
50. {
51. Console.WriteLine("ConcreteClassA.PrimitiveOperation1()");
52. }
53. public override void PrimitiveOperation2()
54. {
55. Console.WriteLine("ConcreteClassA.PrimitiveOperation2()");
56. }
57. }
58.
59. /// <summary>
60. /// A 'ConcreteClass' class
61. /// </summary>
62. class ConcreteClassB : AbstractClass
63. {
64. public override void PrimitiveOperation1()
65. {
66. Console.WriteLine("ConcreteClassB.PrimitiveOperation1()");
67. }
68. public override void PrimitiveOperation2()
69. {
70. Console.WriteLine("ConcreteClassB.PrimitiveOperation2()");
71. }
72. }
73.}
74.
75.
76.

Output
ConcreteClassA.PrimitiveOperation1()
ConcreteClassA.PrimitiveOperation2()

Mã thế giới thực trong C #

Mã trong thế giới thực này biểu thị một phương thức Mẫu có tên Run () cung cấp
một chuỗi các phương thức gọi bộ xương. Việc thực hiện các bước này được
chuyển đến lớp con CustomerDataObject, thực hiện các phương thức Kết nối,
Chọn, Xử lý và Ngắt kết nối.

1. using System;
2. using System.Data;
3. using System.Data.OleDb;
4.
5. namespace DoFactory.GangOfFour.Template.RealWorld
6. {
7. /// <summary>
8. /// MainApp startup class for Real-World
9. /// Template Design Pattern.
10. /// </summary>
11. class MainApp
12. {
13. /// <summary>
14. /// Entry point into console application.
15. /// </summary>
16. static void Main()
17. {
18. DataAccessObject daoCategories = new Categories();
19. daoCategories.Run();
20.
21. DataAccessObject daoProducts = new Products();
22. daoProducts.Run();
23.
24. // Wait for user
25. Console.ReadKey();
26. }
27. }
28.
29. /// <summary>
30. /// The 'AbstractClass' abstract class
31. /// </summary>
32. abstract class DataAccessObject
33. {
34. protected string connectionString;
35. protected DataSet dataSet;
36.
37. public virtual void Connect()
38. {
39. // Make sure mdb is available to app
40. connectionString =
41. "provider=Microsoft.JET.OLEDB.4.0; " +
42. "data source=..\\..\\..\\db1.mdb";
43. }
44.
45. public abstract void Select();
46. public abstract void Process();
47.
48. public virtual void Disconnect()
49. {
50. connectionString = "";
51. }
52.
53. // The 'Template Method'
54. public void Run()
55. {
56. Connect();
57. Select();
58. Process();
59. Disconnect();
60. }
61. }
62.
63. /// <summary>
64. /// A 'ConcreteClass' class
65. /// </summary>
66. class Categories : DataAccessObject
67. {
68. public override void Select()
69. {
70. string sql = "select CategoryName from Categories";
71. OleDbDataAdapter dataAdapter = new OleDbDataAdapter(
72. sql, connectionString);
73.
74. dataSet = new DataSet();
75. dataAdapter.Fill(dataSet, "Categories");
76. }
77.
78. public override void Process()
79. {
80. Console.WriteLine("Categories ---- ");
81.
82. DataTable dataTable = dataSet.Tables["Categories"];
83. foreach (DataRow row in dataTable.Rows)
84. {
85. Console.WriteLine(row["CategoryName"]);
86. }
87. Console.WriteLine();
88. }
89. }
90.
91. /// <summary>
92. /// A 'ConcreteClass' class
93. /// </summary>
94. class Products : DataAccessObject
95. {
96. public override void Select()
97. {
98. string sql = "select ProductName from Products";
99. OleDbDataAdapter dataAdapter = new OleDbDataAdapter(
100. sql, connectionString);
101.
102. dataSet = new DataSet();
103. dataAdapter.Fill(dataSet, "Products");
104. }
105.
106. public override void Process()
107. {
108. Console.WriteLine("Products ---- ");
109. DataTable dataTable = dataSet.Tables["Products"];
110. foreach (DataRow row in dataTable.Rows)
111. {
112. Console.WriteLine(row["ProductName"]);
113. }
114. Console.WriteLine();
115. }
116. }
117. }
118.
119.
120.

Output
Categories ----
Beverages
Condiments
Confections
Dairy Products
Grains/Cereals
Meat/Poultry
Produce
Seafood

Products ----
Chai
Chang
Aniseed Syrup
Chef Anton's Cajun Seasoning
Chef Anton's Gumbo Mix
Grandma's Boysenberry Spread
Uncle Bob's Organic Dried Pears
Northwoods Cranberry Sauce
Mishi Kobe Niku

1. Visitor Patern là gì
Trong thiết kế hướng đối tượng, Visitor là mẩu thiết kế(Design Patterns) cho phép
định nghĩa các thao tác(operations) trên một tập hợp các đối tượng (objects)
không đồng nhất (về kiểu) mà không làm thay đổi định nghĩa về lớp(classes) của
các đối tượng đó. Để đạt được điều này, trong mẩu thiết kế visitor ta định nghĩa
các thao tác trên các lớp tách biệt gọi các lớp visitors, các lớp này cho phép tách
rời các thao tác với các đối tượng mà nó tác động đến. Với mỗi thao tác được thêm
vào, một lớp visitor tương ứng được tạo ra.
Trong mẫu Khách truy cập, chúng tôi sử dụng lớp khách truy cập thay đổi thuật
toán thực thi của lớp phần tử. Bằng cách này, thuật toán thực hiện của phần tử có
thể thay đổi khi và khách truy cập thay đổi. Mẫu này thuộc danh mục mẫu hành
vi. Theo mẫu, đối tượng phần tử phải chấp nhận đối tượng khách truy cập để đối
tượng khách truy cập xử lý thao tác trên đối tượng phần tử.

Thực hiện
Chúng tôi sẽ tạo ra một giao diện ComputerPart xác định sự chấp nhận. Bàn
phím , Chuột , Màn hình và Máy tính là các lớp cụ thể thực hiện giao
diện ComputerPart . Chúng tôi sẽ định nghĩa một giao diện
khác ComputerPartVisitor sẽ xác định một hoạt động của lớp khách truy cập. Máy
tính sử dụng khách truy cập cụ thể để làm hành động tương ứng.
VisitorPatternDemo , lớp demo của chúng tôi, sẽ sử dụng máy
tính và ComputerPartVisitor lớp học để chứng minh sử dụng các mô hình khách
truy cập.

Bước 1
Xác định một giao diện để đại diện cho yếu tố.
Máy tínhPart.java
public interface ComputerPart {
public void accept(ComputerPartVisitor computerPartVisitor);
}
Bước 2
Tạo các lớp bê tông mở rộng lớp trên.
Bàn phím.java
public class Keyboard implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
Monitor.java
public class Monitor implements ComputerPart {

@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
Chuột.java
public class Mouse implements ComputerPart {

@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
Máy tính.java
public class Computer implements ComputerPart {

ComputerPart[] parts;

public Computer(){
parts = new ComputerPart[] {new Mouse(), new Keyboard(), new
Monitor()};
}

@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
for (int i = 0; i < parts.length; i++) {
parts[i].accept(computerPartVisitor);
}
computerPartVisitor.visit(this);
}
}
Bước 3
Xác định một giao diện để đại diện cho khách truy cập.
Máy tínhPartVisitor.java
public interface ComputerPartVisitor {
public void visit(Computer computer);
public void visit(Mouse mouse);
public void visit(Keyboard keyboard);
public void visit(Monitor monitor);
}
Bước 4
Tạo khách truy cập cụ thể thực hiện các lớp trên.
Máy tínhPartDisplayVisitor.java
public class ComputerPartDisplayVisitor implements ComputerPartVisitor {

@Override
public void visit(Computer computer) {
System.out.println("Displaying Computer.");
}

@Override
public void visit(Mouse mouse) {
System.out.println("Displaying Mouse.");
}

@Override
public void visit(Keyboard keyboard) {
System.out.println("Displaying Keyboard.");
}

@Override
public void visit(Monitor monitor) {
System.out.println("Displaying Monitor.");
}
}
Bước 5
Sử dụng ComputerPartDisplayVisitor để hiển thị các phần của Máy tính .
VisitorPotypeDemo.java
public class VisitorPatternDemo {
public static void main(String[] args) {

ComputerPart computer = new Computer();


computer.accept(new ComputerPartDisplayVisitor());
}
}
Bước 6
Xác nhận đầu ra.
Displaying Mouse.
Displaying Keyboard.
Displaying Monitor.
Displaying Computer.

You might also like