Mẫu thiết kế (Design Patterns) – Phần 1 : nhóm Structural – mẫu Decorator

Mẫu thiết kế (Design Patterns) là một chủ đề rất hay trong công nghệ phần mềm. Tuy nhiên sẽ rất khó khăn cho những người mới bước đầu tìm hiểu về nó. Để có thể hiểu và áp dụng được các mẫu thiết kế đòi hỏi người lập trình phải có kinh nghiệm về lập trình hướng đối tượng.

Mẫu thiết kế là một giải pháp tổng thể cho các vấn đề chung trong thiết kế phần mềm. Nó không phải là một chuẩn được quy định bởi một tổ chức nào cả mà chỉ là những thiết kế của các chuyên gia đã được sử dụng và được đánh giá tốt giúp giải quyết những vấn đề thiết kế thường gặp. Mẫu thiết kế chú trọng việc giúp phần mềm có tính uyển chuyển, dễ nâng cấp, thay đổi.

Mẫu thiết kế được phân loại ra thành 3 nhóm chính:

Structural Patterns: liên quan đến các vấn đề làm thế nào để các lớp và đối tượng kết hợp với nhau tạo thành các cấu trúc lớn hơn (gồm Adapter, Bridge, Composite, Decorator, …).

Creational Patterns: Khắc phục các vấn đề về khởi tạo đối tượng, hạn chế sự phụ thuộc nền tảng (gồm Factory, Abstract Factory, Singleton, Prototype, Builder…).

Behavioral Patterns: Mô tả cách thức để các lớp hoặc đối tượng có thể giao tiếp với nhau (gồm State, Observer, Strategy, Template, …).

Trong bài đầu tiên của loạt bài về Design Patterns này mình sẽ giới thiệu về mẫu Decorator của nhóm Structural.

Định nghĩa: Mẫu Decorator gắn kết thêm tính năng cho các đối tượng một cách linh hoạt. Nó cung cấp một phương pháp linh hoạt hơn là sử dụng các lớp con để mở rộng tính năng của đối tượng.

Tư tưởng của mẫu này là bạn sẽ bao bọc mã nguồn để mở rộng tính năng. Bạn không cần thiết phải chỉnh sửa mã nguồn cũ mà sẽ dùng những đối tượng mới bao bọc những đối tượng cũ để “trang trí” thêm cho đối tượng cũ.

Để minh họa, mình sẽ dùng một ví dụ đơn giản để các bạn dễ hình dung. Giả sử ban đầu bạn có 1 chiếc xe đạp:

Sau đó bạn muốn gắn thêm cho nó một chiếc tên lửa để nó chạy nhanh hơn:

Rồi sau đó bạn lại muốn gắn thêm cho nó một cái cánh quạt để nó có thể bay:

Dưới đây là đoạn mã nguồn minh họa cho ví dụ này. Bạn bắt đầu bằng việc viết mã nguồn cho lớp Bike.

class Bike : Component{

&nbsp&nbsp&nbsp&nbsp float load;

&nbsp&nbsp&nbsp&nbsp public Bike(float velocity, float load)

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.Velocity = velocity;

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.load = load;

&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public override void Run()

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp Console.WriteLine(“Van toc cua xe dap: ” + Velocity.ToString());

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp Console.WriteLine(“Tai trong cua xe dap: ” + load.ToString());

&nbsp&nbsp&nbsp&nbsp }

}

abstract class Component

{

&nbsp&nbsp&nbsp&nbsp private float velocity;

&nbsp&nbsp&nbsp&nbsp public float Velocity

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp get { return velocity; }

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp set { velocity = value; }

&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public abstract void Run();

}

Ở trên, lớp Bike kế thừa từ lớp Component. Có thể ngay từ đầu bạn đã xác định cho Bike kế thừa như vậy, hoặc cũng có thể sau này lúc tinh chỉnh mã nguồn bạn có nhu cầu gắn thêm “đồ chơi” cho Bike thì mới tạo ra interface để nó kế thừa như trên. Điều này hoàn toàn phụ thuộc vào bạn.

Bây giờ chúng ta sẽ viết thêm một số class để “trang trí” cho Bike. Chúng ta tạo ra một lớp trừu tượng kế thừa từ Component, lớp này sẽ được những lớp con (chính là những “đồ chơi” mà ta muốn gắn thêm) kế thừa lại.

abstract class Decorator : Component{

&nbsp&nbsp&nbsp&nbsp protected Component comp;

&nbsp&nbsp&nbsp&nbsp public Decorator(Component comp)

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.comp = comp;

&nbsp&nbsp&nbsp&nbsp }

}

Giờ chúng ta sẽ tạo ra lớp Rocket để gắn thêm tên lửa cho xe đạp.

class Rocket : Decorator

{

&nbsp&nbsp&nbsp&nbsp public Rocket(Component comp)
: base(comp)

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.comp.Velocity += 100;

&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public override void Run()

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.comp.Run();

&nbsp&nbsp&nbsp&nbsp }

}

Và cuối cùng là cánh quạt.

class Propeller : Decorator{

&nbsp&nbsp&nbsp&nbsp public Propeller(Component comp)
: base(comp)

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public override void Run()

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.comp.Run();

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp Console.WriteLine(“Chiec xe dap da duoc gan canh quat va co the bay!”);

&nbsp&nbsp&nbsp&nbsp }

}

Sau đó chúng ta sẽ lần lượt gắn những vật này cho Bike như sau:

//Tạo ra 1 chiếc xe đạp với vận tốc 20 và tải trọng 50Bike bike = new Bike(20, 50);

bike.Run();

//Gắn thêm tên lửa cho xe đạp

Console.WriteLine(“\n========Gan them ten lua cho xe dap:========”);

Rocket bikeWithRocket = new Rocket(bike);

bikeWithRocket.Run();

//Gắn thêm cánh quạt cho xe đạp

Console.WriteLine(“\n========Gan them canh quat cho xe dap:========”);

Propeller bikeWithRocketAndPropeller = new Propeller(bikeWithRocket);

bikeWithRocketAndPropeller.Run();

Bạn có thể chạy đoạn mã nguồn trên và kết quả chúng ta nhận được như sau:

Bài hướng dẫn này mình chỉ trình bày tư tưởng cơ bản của Design Patterns và của mẫu Decorator. Các bạn có thể biến thể mẫu này đôi chút để phù hợp với nhu cầu sử dụng của mình. Chúc các bạn thành công!
Nguồn: Projectviet

Mẫu thiết kế (Design Patterns) – Phần 2 : Mẫu Adapter (Nhóm Structural)

Tiếp tục với nhóm Structural, lần này mình xin giới thiệu mẫu Adapter. Mẫu này khá đơn giản và dễ hiểu. Trong thực tế có thể bạn đã nhiều lần áp dụng mẫu này mà không hề biết. Đối với những bài tập nhỏ ở trường thì tác dụng của nó là không nhiều, nhưng đối với những hệ thống lớn mà việc thay đổi những hành động hay cách giao tiếp của một lớp (interface) sẽ phát sinh nhiều rắc rối không đáng có thì việc áp dụng mẫu này là lựa chọn vô cùng sáng suốt.

Mẫu Adapter rất hữu ích trong những tình huống khi tồn tại sẵn một lớp cung cấp toàn bộ những hàm (thao tác, dịch vụ) mà bạn cần (gọi là lớp Adaptee) nhưng lại không có interface phù hợp với mong muốn của bạn. Khi đó mẫu Adapter sẽ được sử dụng để điều chỉnh interface của lớp Adaptee này theo như ý muốn mà không cần phải điều chỉnh nhiều ở những lớp có liên quan.

Tư tưởng của mẫu này có thể được thể hiện như sau:

– Giả sử ban đầu hệ thống của bạn gồm 2 phần như sau:

– Sau đó bạn muốn thay thế phần BackEnd này bằng một cái khác và vẫn muốn sử dụng tiếp phần Front End, và vấn đề nảy sinh, Front End cũ không tương thích với BackEnd mới:

– Lúc này bạn sẽ cần phải có một Adapter để kết nối 2 phần này lại với nhau:

Chúng ta sẽ thử làm 1 ví dụ. Giả sử chúng ta có 1 hệ thống quản lý sinh viên. Ban đầu hệ thống của chúng ta có một lớp Student tương tự như lớp FrontEnd ở trên. Lớp Student này có thể giao tiếp tốt với hệ thống cũ:

public class Student

{

&nbsp&nbsp&nbsp&nbsp private int Age;

&nbsp&nbsp&nbsp&nbsp private string FullName;

&nbsp&nbsp&nbsp&nbsp public int GetAge()

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp return this.Age;

&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public void SetAge(int Age)

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.Age = Age;

&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public string GetFullName()

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp return this.FullName;

&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public void SetFullName(string FullName)

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.FullName = FullName;

&nbsp&nbsp&nbsp&nbsp }

}

Tuy nhiên giờ chúng ta có nhu cầu phải thay đổi một phần của hệ thống quản lý sinh viên này (có thể là để cải tiến tốc độ hoặc thay đổi kiến trúc…). Và vấn đề nảy sinh khi lớp Teacher của hệ thống mới chỉ có thể giao tiếp với một lớp tuân theo Interface như bên dưới

public interface IStudent

{

&nbsp&nbsp&nbsp&nbsp string GetFirstName();

&nbsp&nbsp&nbsp&nbsp string GetLastName();

&nbsp&nbsp&nbsp&nbsp void SetFirstName(string FirstName);

&nbsp&nbsp&nbsp&nbsp void SetLastName(string LastName);

}

public class Teacher

{

&nbsp&nbsp&nbsp&nbsp private IStudent student;

&nbsp&nbsp&nbsp&nbsp public Teacher(IStudent student)

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.student = student;

&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public void ViewFirstName()

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp Console.WriteLine(“First name: ” + student.GetFirstName());

&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public void ViewLastName()

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp Console.WriteLine(“Last name: ” + student.GetLastName());

&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public void UpdateFirstName(string FirstName)

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.student.SetFirstName(FirstName);

&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public void UpdateLastName(string LastName)

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.student.SetLastName(LastName);

&nbsp&nbsp&nbsp&nbsp }

}

Do đó, để tận dụng được lớp Student có sẵn chúng ta cần một Adapter có thể giao tiếp được với cái lớp Student cũ và lớp Teacher mới. Adapter này sẽ hiện thực hóa cái phương thức của Interface IStudent bằng cách gọi lại những phương thức của Student và thêm vào các xử lý để đem lại kết quả mong muốn.

public class Adapter : IStudent

{

&nbsp&nbsp&nbsp&nbsp private string FirstName;

&nbsp&nbsp&nbsp&nbsp private string LastName;
&nbsp&nbsp&nbsp&nbsp Student student;

&nbsp&nbsp&nbsp&nbsp public Adapter(Student student)
&nbsp&nbsp&nbsp&nbsp {
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.student = student;
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.FirstName = student.GetFullName().Split(” ”)[0];
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.LastName = student.GetFullName().Split(” ”)[1];
&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public void SetFirstName(string FirstName)
&nbsp&nbsp&nbsp&nbsp {
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.FirstName = FirstName;
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.student.SetFullName(this.FirstName + ” ” + this.LastName);
&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public void SetLastName(string LastName)

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.LastName = LastName;

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp this.student.SetFullName(this.FirstName + ” ” + this.LastName);

&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public string GetFirstName()

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp return FirstName;

&nbsp&nbsp&nbsp&nbsp }

&nbsp&nbsp&nbsp&nbsp public string GetLastName()

&nbsp&nbsp&nbsp&nbsp {

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp return LastName;

&nbsp&nbsp&nbsp&nbsp }

}

Cuối cùng chúng ta viết một đoạn lệnh để kiểm tra kết quả đạt được:

Student student = new Student();

student.SetFullName(“Nguyen Hung”);

Adapter adapter = new Adapter(student);

Teacher teacher = new Teacher(adapter);

Console.WriteLine(“Truoc khi update:”);

teacher.ViewFirstName();

teacher.ViewLastName();

teacher.UpdateFirstName(“Le”);

teacher.UpdateLastName(“Thanh”);

Console.WriteLine();

Console.WriteLine(“========================”);

Console.WriteLine(“Sau khi update:”);

teacher.ViewFirstName();

teacher.ViewLastName();

Console.WriteLine();

Console.WriteLine(“========================”);

Console.WriteLine(“FullName hien tai:”);

Console.WriteLine(student.GetFullName());

Kết quả nhận được như sau:


Nguồn: Projectviet

Mẫu thiết kế (Design Patterns) – Phần 3 : Mẫu State (nhóm Behavioral)

Mẫu thiết kế state được sử dụng để làm gì, hãy xem xét vấn đề sau :

Trong một công ty có nhiều loại nhân viên. Mỗi loại nhân viên có một cách tính lương khác nhau.

Chương trình quản lý nhân viên của bạn sẽ tính toán lương cho một nhân viên như thế nào họ chuyển từ loại này sang loại khác(hoặc chuyển từ phòng ban này sang phòng ban khác,). Ví dụ: Khi bạn apply vào vị trí developer của một công ty phần mềm, khi bắt đầu làm thì bạn là junior, một bậc cao hơn là senior, và cao hơn nữa là vai trò leader.. mỗi lần “lên cấp” như vậy là một lần thay đổi công thức tính lương.

Cách thông thường:

Ta có mỗi loại nhân viên là một class kế thừa từ 1 class chính: class NhanVien.
Mỗi lần như vậy, ta lại tạo ra một đối tượng mới của loại nhân viên ở phòng ban khác và copy thông tin từ đối tượng cũ sang đối tượng mới này.

Đối với lập trình cách này có thể làm được, tuy nhiên khi so sánh với thực tế thì nó không đúng.

Đó là chưa kể đến việc copy toàn bộ thông tin của đối tượng này sang đối tượng mới là khó khăn ví dụ như Copy lịch sử công tác như thế nào?.. và còn một số vấn đề nữa..

Giải pháp:

Trong chương trình, ta tạo ra nhiều classs loại nhân viên mỗi loại có công thức tính lương riêng, nhưng không cho kế thừa từ class NhanVien.

Trong class NhanVien, ta có một thuộc tính là _loaiNV ( kiểu LoaiNhanVien). Khi class NhanVien gọi hàm tính lương thì. Ta sẽ gọi thuộc tính _loaiNV.TinhLuong().

Khi chuyển đổi qua một vai trò khác:

/*Junior : 0;

Senior : 1;

Leader : 2;

*/

switch(x)
{
case -1:
&nbsp&nbsp&nbsp&nbspreturn;
&nbsp&nbsp&nbsp break;
case 0:
&nbsp&nbsp&nbsp&nbspnv.LoaiNhanVien = new Junior();
&nbsp&nbsp&nbsp break;
case 1:
&nbsp&nbsp&nbsp&nbspnv.LoaiNhanVien = new Senior();
&nbsp&nbsp&nbsp break;
case 2:
&nbsp&nbsp&nbsp&nbspnv.LoaiNhanVien = new Leader();
&nbsp&nbsp&nbsp break;
}
labelLuong.Text = nv.TinhLuong().ToString();

Mẫu state:

Cấu trúc trên được gọi là mẫu state…

Mẫu state: Allow an object to alter its behavior when its internal state changes.The object will appear to change its class. ( cho phép một object có thể thay đổi hành vi bên ngoài khi có sự thay đổi bên trong).

Ví dụ trên là một ví dụ điển hình của mẫu state này.

Cấu trúc của mẫu State:

Áp dụng vào các trường hợp:

 

  • • Hành vi của Một object phụ thuộc vào trạng thái của nó, và nó phải thay đổi hành vi theo thời gian phụ thuộc vào trạng thái của nó.
  • • Có nhiều loại thao tác, trong đó, một số hàm có điều kiện phụ thuộc vào trạng thái của object. ( Các trạng thái này được chia ra làm nhiều nhánh và biểu diễn trong các lớp riêng biệt. Và một số hàm có điều kiện phụ thuộc vào trạng thái có cấu trúc tương tự nhau.)
  • • Thường được áp dụng vào game: Một nhân vật trong game có nhiều trạng thái:
    Trạng thái bình thường.
    Trạng thái phẫn nộ
    ….
    Mỗi trạng thái thì nhân vật có những thông số riêng, vd: ở trạng thái thịnh nộ thì nhân vật sẽ hóa to lên,biến hình, tăng sức tấn công, tăng năng lượng,tăng máu, đánh nhanh… mỗi cái là một chỉ số khác nhau… khi thay đổi trạng thái thì các thông số, hành vi của nhân vật cũng thay đổi.
    Nguồn: Projectviet

 

Mẫu thiết kế (Design Patterns) – Phần 4 : Mẫu Strategy (Nhóm Behavioral)

Vấn đề:

Chúng ta cần viết một chương trình mã hóa dữ liệu ( hoặc là lưu dữ liệu) với nhiều cách mã hóa và định dạng khác nhau.

Mẫu Strategy:

Ta định nghĩa sẵn các đối tượng, các thuật toán, các hàm…

Mẫu strategy cho phép người sử dụng các “chiến lược” có sẵn vào những vấn đề cụ thể… Ngoài ra còn cho phép người dùng dễ dàng thêm vào một “chiến lược” mới.

Các “chiến lược” được tách biệt ra khỏi chương trình. Người dùng chỉ cần biết cách sử dụng chúng, không cần biết chúng thực hiện như thế nào!

Áp dụng:

Để xây dựng một phần mềm mã hóa theo nhiều dạng như đã nói đầu tiên ta xây dựng các đối như sau:

  • • Ta xây dựng lớp Strategy là lớp cha và một số phương thức để sử dụng.
  • • Sau đó xây dựng các lớp con ( ConcreteStrategyA, ConcreteStrategyB, ConcreteStrategyC,…) với các phương thức đế sử dụng các lớp con ( được kế thừa từ lớp cha).

Cụ thể, đối với vấn đề trên,

    • • Ta xây dựng lớp MaHoa
    • • Sau đó tạo các lớp MaHoaDangA, MaHoaDangB, MaHoaDangC.
    • • Tiếp theo, định nghĩa hàm ThucThi cho lớp cha. Và rồi định nghĩa hàm đó ở các lớp con.

⇒ Ta đã định nghĩa xong các chiến lược để sử dụng.

public class MaHoaDangA : MaHoa{

override string Save()

{

s = ” Ma Hoa Theo Dang A”;

s;

}

}

public class MaHoaDangB : MaHoa

{

override string Save()

{

s = ” Ma Hoa Theo Dang B”;

s;

}

………

Vậy là kết thúc phần định nghĩa các “chiến lược”.

Để sử dụng các “chiến lược” này ta cần một lớp Context như sau:

public class Context{

Private MaHoa context;

public void SetMaHoaLoaiA()

{

context = new MaHoaDangA();

}

public void SetMaHoaLoaiB()

{

context = new MaHoaDangB();

}

public void SetMaHoaLoaiC()

{

context = new MaHoaDangC();

}

public string MaHoaDuLieu()

{

return context.Save();

}

}

Ta sử dụng các phương thức của các lớp mã hóa thông qua lớp Context. Khi cần sử dụng chiến lược nào chỉ cần gọi lớp Context để cài đặt chiến lược ấy, và gọi hàm cần sử dụng:

int loai = comboBox1.SelectedIndex;if (loai < 0 || loai >= n)

return;

switch (loai)

{

case 0:

Context.SetMaHoaLoaiA();

break;

case 1:

Context.SetMaHoaLoaiB();

break;

case 2:

Context.SetMaHoaLoaiC();

break;

}

this.label1.Text = Context.MaHoaDuLieu();

Tóm lại:

• Lớp Strategy ( lớp MaHoa): định nghĩa một interface thông dụng để cung cấp tất cả các giải thuật. Lớp Context sử dụng interface này để gọi các giải thuật đã được định nghĩa bởi một ConcreteStrategy.

• Lớp ConcreteStrategy ( Lớp MaHoaDangA, MaHoaDangB, MaHoaDangC): thực thi các thuật toán sử dụng interface của lớp cha.

• Lớp Context:

Liên kết với một đối tượng ConcreteStrategy .

Lớp được định nghĩa để sử dụng các “chiến lược” có sẵn.

Nguồn: Projectviet

Mẫu thiết kế (Design Patterns) – Phần 5 : Mẫu Composite (nhóm Structural )

Đây là một mẫu rất cơ bản trong lập trình hướng đối tượng. Một số bài toán điển hình có thể cho thấy được hiệu quả một cách rõ ràng của của mẫu này như là quản lý thư mục (trong thư mục có thể lại có nhiều thư mục con hoặc file), mạch điện (mạch song song, mạch nối tiếp, trong mạch song song lại có thể có nhiều mạch song song hoặc mạch nối tiếp), chi tiết đơn – chi tiết phức (trong chi tiết phức lại có thể có nhiều chi tiết phức và chi tiết đơn), vẽ hình (đường thẳng, hinh đa giác, trong hình đa giác sẽ có nhiều đường thẳng)…

Qua một số ví dụ đó chắc hẳn các bạn cũng đã thấy được đặc điểm chung của các bài toán có thể áp dụng mẫu này. Đó là khi chương trình của bạn phải thao tác với một cấu trúc dữ liệu dạng cây, trong đó các nhánh và các node lá của cây cần được đối xử như nhau.

Chẳng hạn như trong trường hợp của bài toán quản lý thư mục ta có một cấu trúc dạng cây bao gồm các nhánh là các folder và các node lá tương ứng với các file. Các bạn lưu ý folder là một dạng đối tượng “composite” (complex object), folder có thể chứa bên trong nó nhiều folder và file. Trong khi file chỉ là dạng simple object, không chứa các đối tượng con bên trong.

Tuy nhiên, folder và file lại có một số thao tác xử lý và thuộc tính giống nhau như getsize(), getname(), size, name… Nếu không áp dụng mẫu Composite bạn vẫn hoàn toàn có thể giải bài toán này như sau. Tạo 2 class File và Folder như sau (2 class này mình chỉ viết gọn để minh họa):

public class File

{

float _Size;

public float GetSize()

{

return _Size;

}

}

public class Folder

{

List _FolderList;

List _FileList;

public float GetSize()

{

float size = 0;

foreach (Folder folder in _FolderList)

size += folder.GetSize();

foreach (File file in _FileList)

size += file.GetSize();

return size;

}

}

Đoạn mã nguồn trên nhìn tương đối ổn. Tuy nhiên bạn có thể thấy vấn đề ở đây là nếu chương trình của chúng ta có nhiều đối tượng hơn (chứ không chỉ là 2 đối tượng File và Folder) thì không thể nào lại cho mỗi đối tượng lại có một danh sách (List) tương ứng được. Chẳng hạng như bài vẽ hình, có thể có đường thẳng, hình tam giác, hình vuông, hình lăng trụ,… thì khả năng sử dụng các danh sách như trên là không phải là lựa chọn sáng suốt.
Vấn đề trên sẽ được giải quyết một cách dễ dàng nếu chúng ta xem File và Folder là như nhau trong việc tính dung lượng (get size). Tức là chúng sẽ cùng kế thừa từ một kiểu tổng quát, như hình bên dưới:

Các class Item, File, Folder sẽ được code lại như sau:

Item.cs:

public abstract class Item

{

protected float _Size;

protected string _FileName;

public Item(string filename, float size)

{

this._FileName = filename;

this._Size = size;

}

public virtual void Add(Item item)

{

}

public virtual void Remove(Item item)

{

}

public virtual Item GetChild(int index)

{

return null;

}

public abstract float GetSize();

}

File.cs

public class File : Item

{

public File(string filename, float size) :
base(filename, size)

{

}

public override float GetSize()

{

return _Size;

}

}

Folder.cs

public class Folder : Item

{

List _ItemList;

public Folder(string foldername)
: base(foldername, 0)

{

_ItemList = new List();

}

public Item SubItem

{

get

{

throw new System.NotImplementedException();

}

set

{

}

}

public override void Add(Item item)

{

_ItemList.Add(item);

}

public override void Remove(Item item)

{

_ItemList.Remove(item);

}

public override Item GetChild(int index)

{

if (index >= 0 && index < _ItemList.Count)

return _ItemList[index];

return base.GetChild(index);

}

public override float GetSize()

{

float size = 0;

foreach (Item item in _ItemList)

{

size += item.GetSize();

}

return size;

}

}

Giờ thì kiểm tra kết quả mà chúng ta đạt được bằng đoạn code sau:

Item folder = new Folder(“Root”);

Item file1 = new File(“file1″, 10);

Item childFolder1 = new Folder(“childFolder1″);

Item file2 = new File(“file2″, 20);

folder.Add(file1);

folder.Add(childFolder1
);
childFolder1.Add(file2);

System.Console.WriteLine(“Folder: ” + folder.GetSize().ToString());

System.Console.WriteLine(“childFolder: ” + childFolder1.GetSize().ToString());

Item item1 = folder.GetChild(0);

if (item1 != null)

System.Console.WriteLine(“childItem1: ” + item1.GetSize().ToString());

Item item2 = folder.GetChild(1);

if (item2 != null)

System.Console.WriteLine(“childItem2: ” + item2.GetSize().ToString());

Đây là kết quả nhận được:

Nguồn : projectviet

Mẫu thiết kế (Design Patterns) – Phần 6 : Mẫu Observer (nhóm Behavioral)

Vấn đề:

Ta viết phần mềm hiển thị dữ liệu (giá vàng, cổ phiếu, biểu đồ…) trên các thiết bị, môi trường (window app, web, mobile,tablet….. ).

Khi có sự thay đổi số liệu ở cơ sở dữ liệu (CSDL), các thiết bị sẽ lần lượt cập nhật và hiển thị lại dữ liệu ngay lập tức.

Thêm một thiết bị mới mà không cần phải sửa mã nguồn hay thay đổi cấu trúc.

Mẫu Observer:

Xác định một sự phụ thuộc một-nhiều giữa các đối tượng để khi một đối tượng thay đổi trạng thái, tất cả các đối tượng phụ thuộc nó được thông báo và cập nhật tự động.

  • Sự thay đổi của một đối tượng ảnh hưởng đến sự thay đổi của những đối tượng khác và chúng ta không biết có bao nhiêu đối tượng cần phải cập nhật.
  • Một đối tượng có thể gửi thông điệp đến những đối tượng khác mà không cần biết chúng là ai.

Trong sơ đồ trên:

  • Subject: các đối tượng liên quan đến nhau ( trong ví dụ ở đầu bài là các môi trường hiển thị dữ liệu).
  • Observer: người quan sát, nhận các thông báo từ các đối tượng và gọi các đối tượng liên quan cập nhật.

Áp Dụng vào vấn đề đầu bài:

Trường hợp 1: có 1 Subject : dữ liệu giá vàng.

Ta có các class sau:

class GiaVang{

}

public class WindowApp

{

}

public class Web

{

}

public class Mobile

{

}

Ta xây dựng một abstract class cho các thiết bị, môi trường hiển thị:

public abstract class Observer{

protected Observer()

{

}

virtual public void Update()

{

}

}

Nếu ta code cứng vào lớp GiaVang có 3 thuộc tính kiểu WindowApp, Web, Mobile thì sẽ khó khăn khi ta thêm một thiết bị mới để hiển thị. Nên ở đây ta sử dụng List Observer.Và Khi giá vàng có sự thay đổi thì lớp GiaVang sẽ gọi các thiết bị, các ứng dụng hiển thị cập nhật.

class GiaVang{

public static List Observers;

public void Attach(Observer concreteObserver)

{

Observers.Add(concreteObserver);

}

public void Detach(Observer concreteObserver)

{

Observers.Remove(concreteObserver);

}

public void DataChange()

{

for (int i = 0; i < Observers.Count; i++)

Observers[i].Update();

}

}

public abstract class Observer

{

protected Observer()

{

GiaVang.Observers.Add(this);

}

virtual public void Update()

{

}

}

Trường hợp 2: có nhiều Subject: dữ liệu giá vàng, dữ liệu cổ phiếu,…
Nếu các thiết bị chỉ có thể hiển thị một số dữ liệu: ta tạo ra một lớp abstract và cho các lớp dữ liệu đó kế thừa. Và đẩy các hàm của lớp GiaVang lên lớp abstract này.

Ở đây, ta giả sử Mobile chỉ hiển thị được giá vàng, và 2 thiết bị còn lại hiển thị được cả 2 loại dữ liệu.

Ta có các lớp Observer được cài đặt như sau:

public abstract class Observer{

protected Observer()

{

}

virtual public void Update()

{

}

}

Và các lớp liên quan:

public class Mobile : Observer{

public Mobile()

{

GiaVang.Observers.Add(this);

}

}

public class Web : Observer

{

public Web()

{

GiaVang.Observers.Add(this);

CoPhieu.Observers.Add(this);

}

}

public class WindowApp : Observer

{

public WindowApp()

{

GiaVang.Observers.Add(this);

CoPhieu.Observers.Add(this);

}

}

Như vậy,ở ví dụ trên ta đã giải quyết được các trường hợp:

  • Dễ dàng thêm một thiết bị mới vào hệ thống.
  • Dễ dàng cho phép các thiết bị hiển thị các thông tin khác nhau.
  • Hệ thống được cập nhật thường xuyên,đảm bảo tính chính xác, nhất quán dữ liệu giữa các thiết bị hiển thị và cơ sở dữ liệu.
    Nguồn: projectviet

Mẫu thiết kế (Design Pattern) – Phần 7 : Mẫu Bridge (nhóm Structural)

Như chúng ta biết, có rất nhiều chuẩn hình ảnh như JPEG, BMP, PNG, … và các hệ điều hành khác nhau như Windows, Linux, MacOS đều có thể hiểu và hiển thị được các định dạng hình ảnh này. Mỗi định dạng ảnh có cấu trúc khác nhau và cách hiển thị cũng khác nhau tùy thuộc môi trường hệ điều hành.

Giả sử chúng ta phải viết một phần mềm xem ảnh JPEG cho hệ điều hành Windows. Sau đó chúng ta phải mở rộng nó để cho phép chương trình này có thể xem được các định dạng ảnh khác như BMP, PNG, … cũng trên Windows. Điều này có thể dễ dạng thực hiện khi chúng ta cho các class JPEGImage, BPMImage, PNGImage,… kế thừa từ một abstract class (hoặc implement interface) là Image.

Tuy nhiên sau đó chúng ta cần mở rộng tiếp chương trình để nó có thể hiển thị được các định dạng ảnh này trên các hệ điều hành khác như Linux, MacOS, … Chúng ta có thể cải tiến lại như sau:

Tuy nhiên, dễ dàng nhận thấy là cách làm này tạo ra sự lặp lại ý tưởng, lặp code, dài dòng và lại khó mở rộng về sau khi chúng ta có thêm các định dạng ảnh mới và các hệ điều hành mới.

Để khắc phục điều này, ý tưởng của mẫu Bridge là tách phần xử lý sang một lớp khác, nghĩa là tách phần xử lý hiển thị hình ảnh sang một lớp mới.

Mình sẽ không trình bày nhiều về code của mẫu này vì nó tương đối dễ hiểu, các bạn có thể download source code kèm theo bài viết để xem. Kết quả thực hiện đoạn code như sau:

Điều quan trọng hơn mình muốn đề cập đó là các bạn phải nắm rõ bản chất của mẫu này, cũng như là các mẫu thiết kế (Design Patterns) nói chung. Cần phải phân biệt được sự khác nhau giữa chúng, nếu không sẽ rất dễ nhầm lẫn các mẫu thiết kế với nhau (vì rất nhiều mẫu sẽ hao hao giống nhau nếu bạn chỉ nhìn vào mấy cái Diagram). Và sự thật là bạn phải làm việc nhiều với nó, tìm hiểu kỹ các tài liệu về nó mới phân biệt được đặc trưng của từng mẫu.

Chẳng hạn như với mẫu Bridge này bạn sẽ cảm giác nó hơi giống với mẫu Strategy (Nhóm Behavioral). Tuy nhiên có sự khác biệt giữa hai mẫu này, chủ yếu là ở mục đích sử dụng của chúng:

– Brige Pattern là để tách rời phần xử lý (giải thuật…) với phần chủ thể (nơi thực thi các giải thuật đó). Phần xử lý hầu như chỉ có tác dụng bên trong phần chủ thể và không bao giờ được sử dụng ở các nơi khác. Mẫu thiết kế Bridge thường được dùng như một cầu nối giữa 2 nhóm đối tượng khác nhau. Đúng như tên gọi của nó, Brige Pattern bao gồm phần Abstract định nghĩa 2 đầu của một cây cầu. Các Implementation triển khai các Abstract đó thành từng cặp.

– Strategy Pattern thì khác, một chủ thể có thể sự dụng linh hoạt khác “chiến lược” khác nhau. Điều đó giải thích cho tên gọi của nó, “Chiến lược”. Chiến lược gì? Chiến lược sử dụng các triển khai của Abstract. Lúc này tôi có rất nhiều dữ liệu tôi muốn sử dụng Quick Sort, lúc khác tôi chỉ có vài phần dữ liệu thôi nên tôi dùng Bubble Sort là đã ok rồi….
Nguồn: projectviet