Home Blog Page 14

[Tutorial] Design Pattern – Singleton Pattern

“A class of which only a single instance can exist”

The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance. It is named after the singleton set, which is defined to be a set containing one element. The office of the President of the United States is a Singleton. The United States Constitution specifies the means by which a president is elected, limits the term of office, and defines the order of succession. As a result, there can be at most one active president at any given time. Regardless of the personal identity of the active president, the title, “The President of the United States” is a global point of access that identifies the person in the office.

Design Pattern - Singleton Pattern

Rules of thumb

  • Abstract Factory, Builder, and Prototype can use Singleton in their implementation.
  • Facade objects are often Singletons because only one Facade object is required.
  • State objects are often Singletons.
  • The advantage of Singleton over global variables is that you are absolutely sure of the number of instances when you use Singleton, and you can change your mind and manage any number of instances.
  • The Singleton design pattern is one of the most inappropriately used patterns. Singletons are intended to be used when a class must have exactly one instance, no more, no less. Designers frequently use Singletons in a misguided attempt to replace global variables. A Singleton is, for intents and purposes, a global variable. The Singleton does not do away with the global; it merely renames it.
  • When is Singleton unnecessary? Short answer: most of the time. Long answer: when it’s simpler to pass an object resource as a reference to the objects that need it, rather than letting objects access the resource globally. The real problem with Singletons is that they give you such a good excuse not to think carefully about the appropriate visibility of an object. Finding the right balance of exposure and protection for an object is critical for maintaining flexibility.
  • Our group had a bad habit of using global data, so I did a study group on Singleton. The next thing I know Singletons appeared everywhere and none of the problems related to global data went away. The answer to the global data question is not, “Make it a Singleton.” The answer is, “Why in the hell are you using global data?” Changing the name doesn’t change the problem. In fact, it may make it worse because it gives you the opportunity to say, “Well I’m not doing that, I’m doing this” – even though this and that are the same thing.

Là một nhà tư vấn được trả lương hậu hĩnh tại MegaGigaco, bạn phải xử lý các sự cố về hiệu năng hệ thống. “Hệ thống hình như ngày càng chậm chạp hơn.” Các lập trình viên nói:

“Hmm,” bạn nói, “Tôi lưu ý các bạn rằng chúng ta đang có một đối tượng dữ liệu có kích thước khá lớn, khoảng 20Mb”
“Vâng”, họ nói.
“Cùng một thời điểm, các bạn sử dụng bao nhiêu đối tượng này?”
“Khoảng 219”, các lập trình viên nói
“Trời, vậy các bạn sử dụng 219 đối tượng 20Mb trong lúc chương trình hoạt động?” Bạn nói. “Chẳng lẽ không ai thấy được vấn đề ở đây à?”
“Không”, họ đồng thanh nói.
Bạn nói với họ “Các bạn sử dụng quá nhiều tài nguyên hệ thống. Các bạn có hàng trăm đối tượng to lớn mà máy tính phải xử lý. Các bạn có thật sự cần tất cả chúng?”
“Vâng…” họ nói.
“Tôi nghĩ là không,” bạn nói. “Tôi sẽ sửa chữa vấn đề này bằng cách sử dụng mẫu duy nhất Singleton.”
Chương này nói về việc kiểm soát số lượng đối tượng mà bạn phải tạo ra trong mã nguồn của mình. Có hai mẫu thiết kế đặc biệt giúp ích cho bạn: mẫu duy nhất Singleton và mẫu “hạng ruồi” flyweight.
Với mẫu duy nhất Singleton, bạn luôn đảm bảo rằng chỉ có duy nhất một đối tượng cho một lớp cụ thể trong suốt ứng dụng. Với mẫu “hạng ruồi” flyweight, bạn cũng có duy nhất một đối tượng cho một lớp, nhưng khi nhìn vào mã của bạn, ta có thể thấy giống như đang có nhiều đối tượng vậy. Đây là một thủ thuậ t khéo léo.

Tạo một đối tượng duy nhất với mẫu duy nhất Singleton.

Tôi bắt đầu với mẫu Singleton và xử lý rắc rối mà lập trình viên MegaGigaCo gặp phải. Họ muốn chắn chắc rằng chỉ tạo duy nhất một đối tượng cho một lớp cụ thể mặc cho người khác có cố gắng tạo bao nhiêu đối tượng đi nữa.
Các lập trình viên đang tạo ra hàng trăm đối tượng Database trong mã nguồn, và rắc rối là từng đối tượng này có kích thước rất lớn. Đâu là giải pháp? Mẫu duy nhất Singeton là câu trả lời.
Mẫu duy nhất Singleton chắc chắn rằng bạn có thể khởi tạo chỉ duy nhất một đối tượng cho một lớp. Nếu bạn không sử dụng mẫu thiết kế này, toán tử new như thường sử dụng, sẽ tạo ra liên tiếp nhiều đối tượng mới như sau:

Design Pattern - Singleton Pattern

Ghi nhớ: Để chắc chắn rằng bạn chỉ có duy nhất một đối tượng, mặc cho người khác có hiện thực bao nhiêu phiên bản đi nữa, hãy sử dụng mẫu duy nhất Singleton. Cuốn sách GoF nói rằng, mẫu Singleton “Đảm bảo rằng một lớp chỉ có duy nhất một thể hiện và cung cấp một biến toàn cục để truy cập nó”
Bạn sử dụng mẫu Singleton khi bạn muốn hạn chế việc sử dụng tài nguyên (thay vì việc tạo không hạn chế số lượng đối tượng) hoặc khi bạn cần phải xử lý một đối tượng nhạy cảm, mà dữ liệu của nó không thể chia sẻ cho mọi thể hiện, như registry của Windows chẳng hạn.
Gợi ý: Ngoài đối tượng bản ghi registry, bạn có thể sử dụng mẫu Singleton khi bạn muốn hạn chế số lượng các thể hiện được tạo bởi vì bạn muốn chia sẻ dữ liệu của các đối tượng này. Ví dụ như khi bạn có một đối tượng cửa sổ window hay hộp thoại dialog, cần phải hiện thị và thay đổi dữ liệu, bạn sẽ không muốn tạo nhiều thể hiện của đối tượng này, vì bạn sẽ bị bối rối trong việc phải truy cập dữ liệu của thể hiện nào.
Việc tạo một đối tượng duy nhất cũng rất quan trọng khi bạn sử dụng đa luồng và khi bạn không muốn sự đụng độ dữ liệu xảy ra. Ví dụ bạn đang làm việc với một đối tượng cơ sở dữ liệu, và các thể hiện khác cũng làm việc trên cùng cơ sở dữ liệu đó, việc đụng độ có thể gây ra các vấn đề nghiêm trọng. Tôi sẽ thảo luận cách làm việc với mẫu Singleton và đa luồng trong chương này.
Bất cứ khi nào bạn thật sự cần duy nhất một thể hiện của một lớp, hãy nghĩ tới mẫu Singleton ( thay vì dùng toán tử new).

Tạo lớp cơ sở dữ liệu Database dựa trên kiểu Singleton

Giờ là lúc bạn bắt tay vào viết mã nguồn. Bạn sẽ tạo một lớp tên Database mà các lập trình viên trong công ty sẽ sử dụng. Lớp này có một hàm khởi dựng đơn giản, như mã sau:

public class cDatabase
{
    private int _record;
    private string _name;   

    public cDatabase(string n)
    {
        _name = n;
        _record = 0;
    }
}

Bạn cần phải thêm vào hai hàm editRecord, cho phép bạn chỉnh sửa một bản ghi, và hàm getName, trả về tên gọi của Database.

public class cDatabase
{
    private int _record;
    private string _name;
  
    public cDatabase(string n)
    {
        _name = n;
        _record = 0;
    }
  
    public void editRecord(string operation)
    {
        Console.Writeln("Performing a: " + operation +
" opration on record: " + _record + " in a database: " + _name);
    }
  
    public string getName()
    {
        return _name;
    }
}

Tới giờ mọi việc vẫn tốt đẹp. Bất cứ khi nào bạn tạo một đối tượng bằng toán tử new, một đối tượng mới sẽ được tạo ra. Nếu bạn tạo 3 database, bạn sẽ có 3 đối tượng như sau:

cDatabase dataone = new cDatabase(“products”);
cDatabase dataone = new cDatabase(“products Also”);
cDatabase dataone = new cDatabase(“products Again”);

 Làm sao để bạn có thể tránh việc tạo một đối tượng mới khi sử dụng toán tử new? Đây là một giải pháp – làm cho hàm khởi dụng từ toàn cục (public) trở thành cục bộ (private)

    private cDatabase(string n)
    {
        _name = n;
        _record = 0;
    }

Điều này ngăn cản mọi người sử dụng hàm khởi dựng, ngoài trừ chính trong lớp này gọi tới. Nhưng đợi một chút, có gì không ổn ở đây? Ai ở trên trái đất này lại cần có một hàm khởi dựng riêng tư vậy? Làm sao bạn có thể tạo một đối tượng khi bạn không thể gọi hàm khởi tạo nó?
Bạn đã làm cho hàm khởi dựng trở nên riêng tư và cách duy nhất để phần còn lại của thế giới khởi tạo đối tượng đó là thêm vào một hàm tạo đối tượng, và gọi nó khi bạn chắn chắn muốn tạo một đối tượng duy nhất cho lớp này.

Hãy xem đoạn mã sau:

public class cDatabase
{
    private int _record;
    private string _name;
  
    private cDatabase(string n)
    {
        _name = n;
        _record = 0;
    }  
}
OK. Đầu tiên bạn ngăn chặn việc khởi tạo bằng toán tử new. Và bây giờ cách duy nhất là tạo một hàm để gọi việc khởi tạo đối tượng, thông thường hàm này có tên getInstance (hay createInstance hoặc một cái tên cụ thể như createDatabase cũng được).
Chú ý rằng hàm này được gán phạm vi công cộng và là một phương thức tĩnh để bạn có thể truy cập tới nó thông qua tên lớp (ví dụ như Database.getInstance()) (ND: public và static là hai khái niệm trong OOP. Public giúp hàm có thể được sử dụng từ bất kì lớp khác, static giúp ta có thể sử dụng hàm trực tiếp từ tên lớp, không cần thông qua một đối tượng lớp. )
public class cDatabase
{
    private int _record;
    private string _name;
  
    private cDatabase(string n)
    {
        _name = n;
        _record = 0;
    }  

public static cDatabase getInstance(string n)
{
}


}

Hàm này sẽ trả về một đối tượng Database, nhưng hàm chỉ hoạt động khi có ít nhất một đối tượng đã tồn tại. Vì thế đầu tiên ta cần kiểm tra đối tượng này, tôi gọi nó là singleObject, xem nó đã tồn tại chưa? Nếu chưa, tôi sẽ tạo nó. Và sau đó trả giá trị nó về cho hàm.

public class cDatabase
{

private static cDatabase _singleObj;
    private int _record;
    private string _name;
  
    private cDatabase(string n)
    {
        _name = n;
        _record = 0;
    }  

public static cDatabase getInstance(string n)
{
if (_singleObj == null)
_singleObj = new cDatabase(n);
return _singleObj;

}

}

Vấn đề đã được giải quyết. Bây giờ chỉ có duy nhất một đối tượng Database tồn tại trong cùng một thời điểm. (Vấn đề đa luồng ta sẽ giải quyết trong phần sau của chương). Việc gọi hàm getInstance sẽ cho ta một đối tượng như hình sau:
Design Pattern - Singleton Pattern

Khi bạn gọi getInstance lần nữa, bạn sẽ nhận được cùng một đối tượng như lần đầu.
Không quan tâm đến việc bạn gọi bao nhiêu lần getInstance, bạn luôn nhận được cùng một đối tượng. Đó chính là cách bạn phải làm với mẫu singleton.

Chạy thử ví dụ với mẫu Singleton

Bắt đầu bằng việc tạo một đối tượng Database với tên là products, sau đó gọi hàm getName:

public static void main()
{
cDatabase database;
database = cDatabase.getInstance("products");
Console.Writeline("This is the: " + cDatabase.getName() + " database");
}

Sau đó bạn tiếp tục tạo một đối tượng Database với tên là employees, và gọi lại hàm getName để kiểm tra:

public static void main()
{
cDatabase database;
database = cDatabase.getInstance("products")
Console.Writeline("This is the " + database.getName() + " database");
  
    database = cDatabase.getInstace("employees");
Console.Writeline("This is the " + database.getName() + " database");

}

Tuy nhiên đối tượng Database đã được tạo, vì vậy trong lần thứ hai, hàm getInstance vẫn trả về đối tượng Database cũ, và kết quả là bạn nhận được thông báo:
This is the products database
This is the products database
Quá rõ ràng. Bạn đã nhận được duy nhất một đối tượng cho dù đã thực hiện việc tạo hai lần. Cách thức bạn làm việc như sau: ngăn cản việc khởi tạo bằng toán tử new, và tạo một hàm mới để tạo đối tượng theo ý bạn. Đó chính là cách mẫu Singleton hoạt động.
Đối với vấn đề đa luồng!
Hãy xem lại hàm getInstance trong ví dụ trên:
Có một lỗ hổng tiềm tàng ở đây, tuy nhỏ nhưng là một lỗ hổng rõ ràng, đó là khi làm việc với đa luồng. Hãy nhớ rằng, bạn muốn đảm bảo rằng chỉ có duy nhất một đối tượng Database tồn tại. Nhưng khi bạn có nhiều luồng chương trình chạy cùng lúc, bạn sẽ gặp rắc rối. Cụ thể là, hãy chú ý đoạn mã kiểm tra sự tồn tại của đối tượng Database:
Nếu có hai luồng cùng thực hiện hàm kiểm tra này một lúc, hai luồng này đều thỏa điều kiện của hàm if ( tức chưa có đối tượng nào được tạo), và điều này có nghĩa là cả hai luồng đều tạo ra một đối tượng Database.
Làm sao để chỉnh sửa chỗ này? xem đoạn mã sau:
private static object _obj = new object();
public static cCustomer createInstance(string name)
{
// double check to avoid multiThreading
    if (_singleObj == null)
    {
    lock (_obj)
        {
        if (_singleObj == null)
            _singleObj = new cCustomer(name);
        }
    }
     return _singleObj;
}

sử dụng lock để khóa việc truy cập vào hàm getInstance, trong khi hàm getInstance được chạy. Bất cứ luồng nào muốn gọi hàm getInstance, đều phải đợi hàm này hoạt động xong. và kỹ thuật này đã giải quyết được vấn đề đa luồng.
Kể từ khi bạn sử dụng kỹ thuật đồng bộ hóa trên hàm getInstance, bạn không còn lo lắng về vấn đề đa luồng nữa. Chỉ duy nhất một luồng được gọi hàm getInstance. Nó ngăn chặn việc tạo đối tượng bằng một bức tường an toàn, việc kiểm tra ở trên cho thấy, nếu đối tượng muốn tạo đã tồn tại, hàm sẽ không tạo, ngược lại, sẽ tạo đối tượng cho lớp.

Ref:
https://sourcemaking.com/design_patterns/singleton
https://haihth.wordpress.com/2013/02/23/dp-chapter5/

[Tutorial] Design Pattern – Adapter Pattern

“Match interfaces of different classes”

The Adapter pattern allows otherwise incompatible classes to work together by converting the interface of one class into an interface expected by the clients. Socket wrenches provide an example of the Adapter. A socket attaches to a ratchet, provided that the size of the drive is the same. Typical drive sizes in the United States are 1/2″ and 1/4″. Obviously, a 1/2″ drive ratchet will not fit into a 1/4″ drive socket unless an adapter is used. A 1/2″ to 1/4″ adapter has a 1/2″ female connection to fit on the 1/2″ drive ratchet, and a 1/4″ male connection to fit in the 1/4″ drive socket.

Design Pattern - Adapater Pattern

Rules of thumb

  • Adapter makes things work after they’re designed; Bridge makes them work before they are.
  • Bridge is designed up-front to let the abstraction and the implementation vary independently. Adapter is retrofitted to make unrelated classes work together.
  • Adapter provides a different interface to its subject. Proxy provides the same interface. Decorator provides an enhanced interface.
  • Adapter is meant to change the interface of an existing object. Decorator enhances another object without changing its interface. Decorator is thus more transparent to the application than an adapter is. As a consequence, Decorator supports recursive composition, which isn’t possible with pure Adapters.
  • Facade defines a new interface, whereas Adapter reuses an old interface. Remember that Adapter makes two existing interfaces work together as opposed to defining an entirely new one.
Đôi khi một đối tượng không thật sự phù hợp với những gì ta mong muốn. Một lớp có thể thay đổi, hoặc một đối tượng trở nên khó khăn khi sử dụng. Chương này sẽ là giải pháp tốt cho vấn đề trên khi sử dụng hai mẫu thiết kế: mẫu chuyển đổi adapter và mẫu façade. Mẫu adapter cho phép bạn chuyển đổi một đối tượng để cung cấp cho một lớp khác có thể sử dụng chúng. Mẫu façade cũng tương tự vậy, nó thay đổi vẻ ngoài của một đối tượng, nhưng có một chút khác biệt: bạn sử dụng mẫu này để đơn giản hóa các chức năng của nó, làm cho nó dễ làm việc với đối tượng hay lớp khác.

Kịch bản của mẫu Adapter

“Được,” trưởng nhóm phát triển MegaGigaCo nói, khi đang bước vào phòng, “Thu dọn mọi thứ, quản lý đã ra lệnh rằng chúng ta phải chuyển đổi cơ sở hạ tầng sang một hệ thống mới, chúng ta vừa mua từ công ty của cháu Giám đốc”
“Hmm”, các lập trình viên nói, “Đó là một rắc rối. Giao diện khách hàng trực tuyến của chúng ta cho phép khách hàng sử dụng phần mềm từ công ty Ace và đóng gói chúng trong một lớp tên Ace. Làm sao để chúng ta có thể chuyển đổi chúng cho phù hợp với hệ thống mới?
“Chỉ có thể là một đối tượng Acme mới”, trưởng nhóm nói “không  phải là đối tượng Ace”
“Oh, không”, mọi người nói “Làm sao có thể như vậy được”
Bạn có thể thấy rắc rối ở đây. Hiện tại, hệ thống phù hợp với một đối tượng Ace như hình vẽ

Design Pattern - Adapater Pattern

Nhưng khi hệ thống thay đổi, nó yêu cầu một đối tượng Acme ( không phải Ace), vì vậy đối tượng Ace không thích hợp nữa. Xem hình sau:

Design Pattern - Adapater Pattern

 “Tôi có một giải pháp”. Bạn nói. Mọi người quay sang, và bạn nói tiếp ”Dĩ nhiên, với tư cách là một tư vấn viên, tôi sẽ tính chi phí cao cho việc này”

“OK”, trưởng nhóm nói.

“Bạn cần sử dụng mẫu chuyển đổi Adapter”, bạn giải thích, “Mẫu adapter cho phép bạn chuyển đổi một lớp hoặc một đối tượng sang một lớp hoặc đối tượng mới mà bạn mong muốn”. Bạn vẽ ra giải pháp lên tấm bảng như hình sau:

Design Pattern - Adapater Pattern
 “Ah” cả nhóm phát triển phần mềm nói “Chúng tôi đang hiểu ra vấn đề”
“Tốt” bạn nói “Vậy phải trả phí cho tôi”
Chỉnh sửa rắc rối khi kết nối với mẫu Adapter:
Mẫu thiết kế Adapter cho phép bạn sửa đổi một giao diện giữa đối tượng và một lớp mà không phải sửa đổi trực tiếp lên chúng. Khi bạn làm việc với một ứng dụng mua sẵn, sản phẩm bạn nhận được thường không tương thích với những gì bạn thật sự muốn.
Đây là phần đặc biệt quan trọng trong phát triển trực tuyến. Ngày càng nhiều các công ty tạo ra các sản phẩm cho những công ty lớn, họ đang bỏ qua các phần mềm cho những công ty nhỏ. Và điều đó thật đáng xấu hổ vì việc tương thích của hệ thống, phần mềm từ công ty nhỏ không thể giao tiếp với một hoặc nhiều thành phần khác trong toàn hệ thống. Nhưng việc chuyển sang một giải pháp đắt tiền thì không phải lúc nào cũng cần thiết. Thông thường, giải pháp có thể là một bộ chuyển đổi nhỏ.
Cách tốt nhất để xem mẫu Adapter làm việc là thông qua ví dụ. Hiện tại giao tiếp người dùng công ty MegaGigaCo mà tôi đã nhắc tới trong phần trước của chương này, dữ liệu người dùng được đóng gói trong lớp Ace. Lớp này quản lý tên của khách hàng, với hai hàm sau:
 
getName()
setName()
Nhưng theo bạn biết, công ty MegaGigaCo đang chuyển sang sử dụng phần mềm Acme, mà cách thức quản lý khách hàng có khác một chút. Vấn đề ở đây là phần mềm Acme cần một đối tượng Acme. Đối tượng này có tới bốn hàm, chứ không phải hai, dùng để quản lý tên khách hàng. Chúng là:
setFirstName()
setLastName()
getFirstName()
getLastName()
Vì vậy bạn cần một bộ chuyển đổi để chắc chắn rằng hệ thống mới Acme có thể xử lý được đối tượng Ace. Bộ chuyển đổi này gọi hai hàm của đối tượng Ace và mở rộng chúng thành bốn hàm mà đối tượng Acme yêu cầu, như hình sau:

Design Pattern - Adapater Pattern

Đây chính là cách thức làm việc của mẫu Adapter
Ghi nhớ: Sách của GoF định nghĩa mẫu Adapter như sau: “Chuyển đổi giao tiếp của một lớp sang một kiểu giao tiếp khác mà khách hàng mong muốn. Mẫu adapter cho phép các lớp có thể làm việc với nhau cho dù giao tiếp của chúng không tương thích nhau”
Thông qua định nghĩa chính thức của mẫu Adapter nói về các lớp, mẫu này bao gồm hai phần chính như sau: một cho đối tượng, một cho lớp. Chúng ta sẽ xem xét cả hai trong chương này.
Bạn sử dụng mẫu Adapter khi bạn cố gắng đưa một cái chốt hình vuông vào cái lỗ hình tròn. Nếu một lớp có giao tiếp không tương thích, bạn có thểm thêm vào một bộ chuyển đổi – giống như bộ chuyển đổi điện trong những chuyến du lịch toàn cầu – để có thể đạt được yêu cầu mong muốn
Mẫu này đặc biệt tốt trong trường hợp bạn đang làm việc với mã nguồn cũ mà yêu cầu là không được thay đổi mã cũ, trong khi phần mềm giao tiếp với mã nguồn cũ này lại thay đổi.

Xem source code đính kèm

Ref
https://sourcemaking.com/design_patterns/adapter
https://haihth.wordpress.com/2013/02/23/dp-chapter6/

[Tutorial] Design Pattern – Template Method

“Defer the exact steps of an algorithm to a subclass”

The Template Method defines a skeleton of an algorithm in an operation, and defers some steps to subclasses. Home builders use the Template Method when developing a new subdivision. A typical subdivision consists of a limited number of floor plans with different variations available for each. Within a floor plan, the foundation, framing, plumbing, and wiring will be identical for each house. Variation is introduced in the later stages of construction to produce a wider variety of models.
Another example: daily routine of a worker.

Design Pattern - Template Pattern

Rules of thumb

Download full source code here

“Tin tốt”, giám đốc GigundoCorp – một công ty mới mà bạn đang nhận trách nhiệm tư vấn –  vừa nói trong khi chạy vào phòng họp “Chúng ta đã nhận được hợp đồng đó”

“Hợp đồng nào?”, mọi người hỏi

“Hợp đồng về những con rô bốt tự động lắp ráp xe hơi”, vị giám đốc nói.

“Ồ, thì ra là hợp đồng đó” Mọi người nói.
“Giờ thì về phòng và viết chương trình thôi”, vị giám đốc vừa nói và xua đuổi mọi người ra khỏi phòng họp
“Chờ một lát”, bạn nói “Chúng ta có nên dành chút thời gian cho vấn đề thiết kế không? Ví dụ: có khả năng chúng ta sẽ tạo một loại khác của rô bốt trong tương lai chẳng hạn”
“Chắc chắn rồi”, vị giám đốc nói. “Chúng ta có một tá hồ sơ dự thầu ngoài đó. Nhưng không có thời gian nghĩ về nó đâu. Chúng ta cần phải bắt đầu tạo những con rô bốt tự động trước”
“Vâng”, các lập trình viên rên rỉ và mọi người trở về phòng của mình.
“Có điều gì đó mách bảo với tôi rằng họ đang mắc phải sai lầm”, bạn tự nhủ trong căn phòng trống rỗng, rải rác những ly Styrofoam trống rỗng lăn lóc khắp sàn.
Chương này nói này về hai mẫu thiết kế giúp bạn có một cách thức khéo léo hơn trong việc tạo dựng các đối tượng: mẫu Template Methodmẫu Builder. Mẫu Template Method cho phép các lớp con định nghĩa lại các bước tạo đối tượng, rất thích hợp cho việc tạo ra các chủng loại rô bốt khác nhau. Mẫu Builder giúp bạn uyển chuyển hơn trong việc tạo đối tượng vì nó tách rời quá trình khởi tạo ra khỏi bản thân đối tượng. Cả hai mẫu sẽ được thảo luận trong chương này

Tạo robo đầu tiên

Các lập trình viên của GigundoCorp đã xào nấu ra phần mềm của họ trong vài ngày và nó vừa đủ đơn giản. Lớp robot bắt đầu với một hàm khởi tạo và có một số hành động mà robot có thể thực hiện, ví dụ như, để khởi động robot, bạn gọi hàm bắt đầu Start, để robot làm việc, bạn gọi hàm lắp ráp assemble, để kiểm tra sản phẩm, bạn gọi hàm kiểm tra test,  ..

public class cRobo{

public cRobo()
{

}

public void go()
{
start();
getParts();
assemble();
test();
stop();
}

    public void start()
    {
        Console.WriteLine("staring ...");
    }

    public void getParts()
    {
        Console.WriteLine("Getting a carburetor ...");
    }

    public void assemble()
    {
        Console.WriteLine("assembling ...");
    }

    public void test()
    {
        Console.WriteLine("Testing ...");
    }

    public void stop()
    {
        Console.WriteLine("Stopping ...");
    }

}

Bạn có thể nhanh chóng viết chương trình kiểm tra. Đầu tiên tạo một robot và gọi hàm go như sau:

public static void main() {
cRobo robo = new cRobo();
robo.go();
}

khi chạy chương trình …

Staring ….
Getting a carburetor …
assembling …
Testing …
Stopping …

“Tuyệt vời”, giám đốc điều hành phấn khích. “Phần thưởng luôn ở xung quanh. Tôi đã nói với anh rằng họ không cần cái thứ mẫu thiết kế vớ vấn”. Các lập trình viên của công ty ném cho bạn một ánh nhìn dè bỉu.

Tạo Robot với Mẫu thiết kế Template Method

Ngày tiếp theo, “Tin tốt”, giám đốc điều hành của GigundoCorp la lớn, trong khi phóng vào phòng họp. “Chúng ta kí được hợp đồng khác!”
“Hợp đồng khác nào?” Mọi người hỏi
“Hợp đồng cho robot nướng bánh” Vị giám đốc nói “Giờ thì ra khỏi đây và viết phần mềm cho nó”
Các lập trình viên nhìn vào trong ly cà phê của họ “Chúng ta phải viết lại tất cả phần mềm từ đầu”, họ nói
Vị giám đốc liếc mắt nhìn bạn và hỏi “Có tốn nhiều chi phí không?”
“Rất nhiều”, các lập trình viên nói. Và bạn thì đang chống lại sự thúc giục để nói rằng “Tôi đã nói với các anh từ trước”
Đây là thời điểm thích hợp để nói về mẫu thiết kế Template Method. Có một rắc rối mà lập trình viên GigundoCopr đối mặt, họ có một con robot tự động như hình sau:
Design Pattern - Template Pattern

 
Nhưng bây giờ họ cần một con robot nướng bánh như hình sau, và thế là phải viết lại mã nguồn từ đầu

Design Pattern - Template Pattern

Con robot nướng bánh có một số chức năng giống như con robot lắp ráp ô tô, như là hàm start, stop, tuy nhiên nó có những sự khác biệt như lắp ráp getParts sẽ không hiển thị “Getting a carburetor” mà thay vào đó là “Getting flour and sugar…
Đó là nơi mà mẫu thiết kế Template Method được áp dụng. Mẫu này nói rằng, bạn có thể viết một phương thức, dùng để xác định một loạt các thuật toán, giống như hàm go mà bạn thấy trước đây, để chạy một loạt các chức năng cho robot như hình:
 public void go() 
{
start();
getParts();
assemble();
test();
stop();
}

Sau đó bạn đưa hàm này vào một bộ khuôn template bằng cách cho phép các lớp con định nghĩa lại các bước thuật toán theo cách cần thiết. Trong trường hợp này, để làm một con robot nướng bánh, bạn sẽ viết lại các hàm getParts, assemble, và test.
Theo định nghĩa chính thức của sách GoF, mẫu Template Method như sau: “Định nghĩa một bộ khung của một thuật toán trong một chức năng, chuyển giao việc thực hiện nó cho các lớp con. Mẫu Template Method cho phép lớp con định nghĩa lại cách thực hiện của một thuật toán, mà không phải thay đổi cấu trúc thuật toán.”
Điều này có nghĩa là bạn nên sử dụng mẫu Template Method khi bạn có một thuận toán được tạo bởi nhiều bước, và bạn muốn thể tùy chỉnh một số bước trong đó. Chú ý rằng nếu bạn muốn viết lại mọi thứ từ đầu – khi mọi bước đều phải tùy chỉnh lại – thì bạn không cần dùng template.

Tạo robot bằng bộ khuôn Template

Nếu bạn có một bộ khuôn Template dựa trên robot, bạn có thể cho nó kế thừa như hình sau:

Design Pattern - Template Pattern

Bằng cách gọi hàm go, tập hợp các thuật toán sẽ được thực hiện. Để tùy chỉnh trong lớp kế thừa, bạn chỉ cần viết lại một số bước nào bạn muốn, trong trường hợp robot nướng bánh sẽ như hình sau:

Design Pattern - Template Method

Đó là ý tưởng đằng sau mẫu thiết kế Template Method – Một chức năng bao gồm nhiều bước sẽ được tùy chỉnh bởi lớp con. Trong trường hợp bạn cần hai robot, một robot lắp ráp ô tô, một robot nướng bánh, mọi việc sẽ như thế nào?
Bạn bắt đầu bằng cách tạo một bộ khuôn Template trong một lớp trừu tượng abstract (để lớp khác có thể kế thừa nó), gọi là RobotTemplate
Xem source code đính kèm nhen các bạn 🙂

Ref
https://haihth.wordpress.com/2013/02/23/dp-chapter7/
https://sourcemaking.com/design_patterns/template_method

[Tutorial] Design Pattern – Builder Pattern

“Separates object construction from its representation”

The Builder pattern separates the construction of a complex object from its representation so that the same construction process can create different representations. This pattern is used by fast food restaurants to construct children’s meals. Children’s meals typically consist of a main item, a side item, a drink, and a toy (e.g., a hamburger, fries, Coke, and toy dinosaur). Note that there can be variation in the content of the children’s meal, but the construction process is the same. Whether a customer orders a hamburger, cheeseburger, or chicken, the process is the same. The employee at the counter directs the crew to assemble a main item, side item, and toy. These items are then placed in a bag. The drink is placed in a cup and remains outside of the bag. This same process is used at competing restaurants.
Design Pattern - Builder Pattern

Rules of thumb

  • Sometimes creational patterns are complementory: Builder can use one of the other patterns to implement which components get built. Abstract Factory, Builder, and Prototype can use Singleton in their implementations.
  • Builder focuses on constructing a complex object step by step. Abstract Factory emphasizes a family of product objects (either simple or complex). Builder returns the product as a final step, but as far as the Abstract Factory is concerned, the product gets returned immediately.
  • Builder often builds a Composite.
  • Often, designs start out using Factory Method (less complicated, more customizable, subclasses proliferate) and evolve toward Abstract Factory, Prototype, or Builder (more flexible, more complex) as the designer discovers where more flexibility is needed.

Download full source code here

Xây dựng Robots với mẫu Builder

“Tin tốt!” Giám đốc điều hành của GigundoCorp reo  lên, trong khi phóng như bay vào phòng họp.

“Khách hàng của chúng ta nói rằng họ muốn kiểm soát nhiều hơn tính năng của Robot, vì vậy chúng ta không thể sử dụng những bộ khuôn Template đã viết sẵn được nữa. Bây giờ họ muốn họ có thể chọn hành động mà robot sẽ thực hiện”
“Để tôi làm rõ chỗ này”, bạn nói “Đầu tiên, chúng ta thiết lập mọi thứ , robot khởi động, nhận nguyên liệu, lắp ráp, kiểm tra và dừng. Nhưng bây giờ khách hàng lại muốn kiểm soát trình tự này và chọn lựa những chức năng họ muốn? Có thể là robot khởi động, rồi kiểm tra, rồi lắp ráp, rồi dừng?”
“Đúng vậy”, Giám đốc nói
“Đây là thời điểm để sử dụng một mẫu thiết kế mới”, bạn nói
“Tôi e rằng phải làm như vậy”, Giám đốc nói.

Những quy định của khách hàng

Trong mẫu thiết kế Template Method, vấn đề chính là những thuật toán nhiều bước – bạn có thể cài đặt nó theo cách bạn muốn, và những lớp con sử dụng theo cách bạn đã thiết lập (Mặc dù bạn có thể viết lại một số bước, nhưng quy trình vẫn không thay đổi ). Nhưng bây giờ tình hình đã khác – khách hàng muốn họ thiết lập trình tự hoạt động và số lượng các bước của thuật toán. Vì vậy mã nguồn mà bạn đã phát triển không còn là trung tâm chính nữa, bạn phải đóng gói nó trong một lớp mới, lớp builder.
Mẫu Template Method mà ta đã được làm quen trong phần trước cho phép bạn tùy chỉnh các bước của một thuật toán bằng cách viết lại các bước trong thuật toán như hình sau:

Design Pattern - Builder Pattern

Mọi chức năng đều dựa trên khuôn mẫu Template trong mẫu thiết kế này, và bạn có thể tùy chỉnh template theo cách bạn muốn. Nhưng bây giờ bạn không còn điều khiển thuật toán nữa, thay vào đó chính khách hàng thực hiện. Họ tạo robot với những chức năng và trình tự họ muốn. Ví dụ để thêm hành động khởi động, khách hàng có thể gọi hàm addStart. Để thêm hành động kiểm tra, họ gọi hàm addTest và ….

Design Pattern - Builder Pattern

Để có thể đáp ứng yêu cầu kiếm soát hành động robot của khách hàng GigundoCorp, bạn phải chuyển mã nguồn cũ qua một lớp mới, lớp CookieRobotBuilder, lớp này hỗ trợ các hàm addStart, addTest, addAssemble và hàm addStop:
Design Pattern - Builder Pattern

Nhờ đó, khách hàng có thể sử dụng CookieRobotBuilder để tạo robot nướng bánh. Khi khách hàng tạo xong robot, mã nguồn sẽ gọi hàm getRobot của đối tượng CookieRobotBuilder để nhận về một robot mới,

Design Pattern - Builder Pattern

Và bây giờ khách hàng đã nắm quyền kiểm soát các thuật toán, bạn không phải kế thừa một template mẫu nữa.Thay vì vậy, để tạo một loại khác của robot, bạn cho phép khách hàng sử dụng những đối tượng builder khác nhau.
Ý tưởng chính như sau: bây giờ khách hàng có thể thiết lập trình tự và số lượng các bước trong thuật toán, và chọn lựa đúng đối tượng builder để tạo ra robot mà họ muốn.
Sách của GoF nói rằng, mẫu thiết kế Builder “Tách rời việc tạo dựng một đối tượng phức tạp ra khỏi bản thân đối tượng vì vậy cho phép cùng một quá trình tạo dựng có thể tạo ra nhiều loại đối tượng khác nhau”
Khác biệt lớn nhất giữa mẫu Template Method và mẫu Builder là ai sẽ tạo ra trình tự các bước trong thuật toán. Trong mẫu Template, bạn là người tạo ra trình tự, và các lớp con sẽ hiện thực chúng. Trong mẫu Builder, khách hàng sẽ thiết lập trình tự và số lượng các bước trong thuật toán, và hoán đổi giữa các builder mà phải cung cấp để tạo ra các đối tượng khác nhau thể hiện thuật toán đó.
Sử dụng mẫu thiết kế Builder khi bạn muốn khách hàng kiểm soát được quá trình tạo dựng. Ví dụ, đây là mẫu thiết kế mà bạn muốn khi bạn xây dựng robot sử dụng cùng một quá trình khởi tạo nhưng muốn có thể tạo ra những con robot khác nhau. Tất cả những gì khách hàng cần là gọi những builder khác nhau – quá trình xây dựng vẫn như cũ. Đây là một ví dụ khác, bạn muốn đọc một đoạn văn bản và xây dựng một tài liệu, nhưng bạn lại không biết định dạng chính xác của nó là RTF, Microsoft Word, hay văn bản đơn giản… Mặc dù quá trình tạo dựng là giống nhau cho từng tài liệu, bạn có thể sử dụng những builder khác nhau để tạo dựa vào kiểu của loại tài liệu.
Nói cách khác, khi khách hàng muốn kiểm soát quá trình tạo dựng, nhưng bạn vẫn muốn có thể tạo ra nhiều đối tượng khác nhau, mẫu Builder sẽ giúp bạn thực hiện điều đó.
Ghi nhớ: Mẫu thiết kế này tương tự với mẫu Factory, nhưng mẫu Factory là trung tâm trong quá trình khởi tạo một bước, chứ không cài đặt nhiều bước như ở đây.

Cho phép khách hàng tạo Robot

Khi bạn sử dụng mẫu Builder, khách hàng sẽ phụ trách quá trình tạo dựng. Khách hàng sử dụng đối tượng xây dựng builder của bạn để làm những gì họ muốn. Để cho phép khách hàng tạo robot thể hiện một loạt các hành động – khởi động, lắp ráp, ngừng… – Tôi tạo ra một giao diện interface Robot Builder hỗ trợ các hàm như sau: addStart, addGetParts, addAssemble, addTest và addStop:
Ví dụ để tạo một robot với các hành động start, test, assemble và sau đó là stop, khách hàng chỉ cần gọi hàm addStart, addTest, addAssemble và addStop của đối tượng xây dựng builder theo đúng trình tự đó. Khi robot đã được tạo xong, khách hàng chỉ cần gọi hàm getRobot của Builder để nhận về một robot mới. Và đối tượng robot mới này có hỗ trợ hàm go, hàm này sẽ thực hiện hàng loạt hành động mà bạn đã tạo dựng trước đó.
Bởi vì bạn có nhiều loại đối tượng builder để tạo nhiều loại robot khác nhau – ví dụ như builder xây dựng robot làm bánh, builder xây dựng robot lắp ráp ô tô – Tôi sẽ tạo một giao diện interface RobotBuilder mà tất cả các builder sẽ hiện thực giao diện này. Đây là những hàm mà các robot builder phải hiện thực, từ hàm addStart tới hàm addStop, kể cả hàm getRobot. Xem mã sau:
Bạn thấy đó:
Khách hàng có thể cài đặt các hành động cho robot như start, stop, test, assemble, getParts … theo trình tự bất kì. 

Design Pattern - Builder Pattern

Tuyệt vời. Bạn có thể đưa builder cho khách hàng, giúp khách hàng có thể kiểm soát quá trình tạo dựng đối tượng.

Ref
https://sourcemaking.com/design_patterns/builder
https://haihth.wordpress.com/2013/02/23/dp-chapter7/

[Tutorial] Design Pattern – Decorator Pattern

“Add responsibilities to objects dynamicall”

The Decorator attaches additional responsibilities to an object dynamically. The ornaments that are added to pine or fir trees are examples of Decorators. Lights, garland, candy canes, glass ornaments, etc., can be added to a tree to give it a festive look. The ornaments do not change the tree itself which is recognizable as a Christmas tree regardless of particular ornaments used. As an example of additional functionality, the addition of lights allows one to “light up” a Christmas tree. 
Another example: assault gun is a deadly weapon on it’s own. But you can apply certain “decorations” to make it more accurate, silent and devastating.
Design Pattern - Decorator Pattern

Rules of thumb

Adapter provides a different interface to its subject. Proxy provides the same interface. Decorator provides an enhanced interface.
Adapter changes an object’s interface, Decorator enhances an object’s responsibilities. Decorator is thus more transparent to the client. As a consequence, Decorator supports recursive composition, which isn’t possible with pure Adapters.
– A Decorator can be viewed as a degenerate Composite with only one component. However, a Decorator adds additional responsibilities – it isn’t intended for object aggregation.
Decorator is designed to let you add responsibilities to objects without subclassing. Composite’s focus is not on embellishment but on representation. These intents are distinct but complementary. Consequently, Composite and Decorator are often used in concert.
Decorator and Proxy have different purposes but similar structures. Both describe how to provide a level of indirection to another object, and the implementations keep a reference to the object to which they forward requests.
Decorator lets you change the skin of an object. Strategy lets you change the guts.

Download full source code use abstract class – CSharp

Download full source code use interface – CSharp

Giữ vững nguyên tắc viết mã “Open-Close” hay “Luôn mở cho việc mở rộng, nhưng đóng cho việc sửa đổi”

Bạn đang làm nhân viên tư vấn Thiết Kế Mẫu tại công ty GigantoComputer, với mức lương khá cao và bạn đang ở trong căn tin công ty.

“Hôm nay có món gì?” bạn hỏi tay đầu bếp khó chịu đang đứng sau bếp nướng.

“Cho một cái hamburger,” bạn nói và xoay xoay cái khay trong tay.

Người đầu bếp mang cái hamburger đến bàn tính tiền, không quên hỏi lại “Có thêm thịt rán không?”

“Chắc chắn rồi”, bạn nói.

Người đầu bếp xóa phiếu ăn cũ trên máy tính tiền và làm lại phiếu ăn.

“Hamburger và thịt rán”. Vừa nói anh ta vừa gõ vào máy tính tiền.

“Cho thêm một ít pho mát” Bạn nói.

Người đầu bếp ném một ánh nhìn khó chịu , xóa cái phiếu ăn, mổ mổ cái bàn phím và nói “Hamburger với pho mát và thịt nướng. Ok. Đủ rồi chứ?”

“Hmm”, bạn nói, nhìn quét qua cái thực đơn “Hay là thêm một chút thịt xông khói?”

Người đầu bếp nhìn chằm chằm vào bạn và dường như định văng ra một vài câu khó chịu gì đó nhưng vẫn nhập phiếu ăn vào máy.

“Hey”, bạn nói. “Anh chắc chắn là được lợi nhiều hơn từ việc sử dụng mẫu thiết kế trang trí Decorator chứ hả?”

“Vâng”, anh đầu bếp trả lời, tự hỏi về những gì bạn nói “Tôi đã nói điều  này cả ngàn lần rồi”

Bạn cầm cái Hamburger pho mát thịt xông khói với vẻ hạnh phúc và nói “Thêm một vài lát cà chua nữa thì tuyệt!”

Chương này nói về hai mẫu thiết kế quan trọng, nó sẽ lắp đầy những thiếu sót trong việc lập trình hướng đối tượng cơ bản, đặc biệt là ở khả năng kế thừa. Đây là hai mẫu trang trí Decorator và mẫu nhà máy Factory.

Mẫu trang trí Decorator là lựa chọn hoàn hảo cho tình huống tôi vừa nêu ở trên bởi vì ta đang nói về khả năng mở rộng chức năng cho một lớp có sẵn. Sau khi viết một lớp, bạn có thể thêm phần trang trí Decorator (các lớp mở rộng) để mở rộng lớp này. Khi đó bạn không phải sửa đổi lên lớp gốc. Kết quả là cái Hamburger của bạn trở thành Hamburger pho mát, rồi Hamburger pho mát thịt xông khói, mọi thứ thật dễ dàng.

Nguyên lý Open-Close – “Luôn Open cho việc mở rộng và Closed cho việc sửa đổi”  


Một trong những khía cạnh quan trọng nhất trong quá trình phát triển một ứng dụng là các nhà phát triển và lập trình viên phải đối đầu với sự thay đổi, và đó là lý do vì sao các mẫu thiết kế này lại được giới thiệu trước tiên. Có thể nói các Mẫu Thiết Kế sẽ giúp bạn giải quyết được các sự thay đổi, và bạn có thể dễ dàng chuyển đổi mã nguồn của mình cho các trường hợp mới và bất khả kháng. Như tôi đã nói qua trong suốt cuốn sách này,lập trình viên thường tiêu tốn thời gian cho việc mở rộng và thay đổi mã nguồn hơn là phát triển mã nguồn gốc.
Mẫu chiến lược Strategy đã được giới thiệu trước đây trong chương II, giúp bạn xử lý những sự thay đổi bằng cách cho phép bạn chọn lựa một thuật toán thích hợp từ một tập hợp thuật toán bên ngoài hơn là phải viết lại mã nguồn. Mẫu trang trí Decorator cũng tương tự vậy, nó cho phép bạn viết tiếp mã nguồn, tránh việc sửa đổi lên mã nguồn gốc, trong khi vẫn đáp ứng được yêu cầu thay đổi. Đó là điểm chính yếu tôi muốn nhấn mạnh.
Ghi nhớ: Hãy làm cho mã nguồn của bạn đáp ứng được nguyên tắc “Luôn đóng cho sự chỉnh sửa, và luôn mở cho việc mở rộng” càng nhiều càng tốt. Nói cách khác, hãy thiết kế mã nguồn sao cho không cần phải thay đổi gì nhiều nhưng luôn có thể mở rộng khi cần.
Đây là một ví dụ cho việc viết mã nguồn luôn đóng cho sự thay đổi.
Công ty mà bạn đang làm tư vấn, công ty GigantoComputer, quyết định làm một cái máy vi tính mới. Đây là mã nguồn của lớp Computer:

 

public class cComputer  
{
        public cComputer() { }
        public string description()
        {
            return "You're getting a computer";
        }
}

Khi một đối tượng computer được khởi tạo. Phương thức description sẽ trả về văn bản “You’re getting a computer.” . Tới giờ mọi việc vẫn tốt đẹp. Nhưng một số khách hàng quyết định rằng họ muốn có một cái đĩa cứng trong máy tính. “Không vấn đề gì cả” Các lập viên trong công ty đáp. “ Chúng ta chỉ cần chỉnh sửa mã nguồn lại một chút như sau:”
public class cComputer  
{
        public cComputer() { }
        public string description()
        {
            return "You're getting a computer and a disk";
        }
}

Bây giờ khi một đối tượng computer được tạo và bạn gọi phương thức description, bạn sẽ nhận được văn bản “You’re getting a computer and a disk.” Nhưng một vài khách hàng vẫn chưa hài lòng. Họ muốn thêm một cái màn hình nữa. Và thế là các lập trình viên phải chỉnh sửa tiếp như sau:

public class cComputer  
{
        public cComputer() { }
        public string description()
        {
            return "You're getting a computer and a disk and a monitor";
        }
}

Bây giờ, khi bạn tạo một computer và gọi phương thức description bạn sẽ thấy

"You're getting a computer and a disk and a monitor";
Bạn có thể thấy vấn đề ở đây: Các lập trình viên phải thay đổi mã nguồn mỗi khi khách hàng thay đổi yêu cầu của họ. Rõ ràng, đó là vấn đề chính.
Và bạn, với cương vị là tư vấn mẫu thiết kế, sẽ chỉnh sửa nó.
 
Tôi phải nhắc lại một lần nữa: càng nhiều càng tốt, hãy viết mã nguồn của bạn đóng cho việc sửa đổi, nhưng mở cho việc mở rộng. Trong chương II, bạn đã biết cách làm việc với mẫu chiến lược Strategy. Đó là, bạn đóng gói mã nguồn vào các thuật toán riêng biệt để sử dụng dễ dàng, hơn là việc xử lý chúng thông qua các lớp con.
Mẫu trang trí Decorator có một cách tiếp cận khác. Thay vì sử dụng một thuật toán bên ngoài, mẫu thiết kế này sử dụng một phương pháp “bao bọc” mã nguồn của bạn để mở rộng chúng.
Ghi nhớ: Định nghĩa chính thức của mẫu trang trí Decorator trong sách của GoF có viết: “Gắn kết thêm một số tính năng cho đối tượng một cách linh động. Mẫu trang trí Decorator cung cấp một phương pháp linh hoạt hơn là sử dụng lớp con để mở rộng chức năng cho đối tượng”
Mẫu thiết kế này được gọi là “Người trang trí” Decorator nhưng dường như đó là tên gọi rườm rà. Một cái tên tốt hơn cho mẫu này có thể là “Người tăng thêm” Augmentor hay “Người mở rộng” Extender bởi vì nó cho phép bạn: tăng thêm hay mở rộng một lớp một cách linh động khi chương trình được thực thi. Tuy nhiên, như bạn thấy trong chương này, thuật ngữ “Người trang trí” Decorator còn giúp bạn hiểu rõ hơn khái niệm “đóng cho việc chỉnh sửa, mở cho việc mở rộng”. Khi bạn làm hành động bao bọc mã nguồn để mở rộng thêm chức năng, bạn không cần thiết chỉnh sửa lại mã nguồn cũ, bạn chủ yếu tập trung vào việc trang trí nó.

Và đây là cách mà nó làm việc. Bạn bắt đầu với một cái máy tính computer đơn giản sau:

Design Pattern - Decorator Pattern
Khi bạn gọi phương thức description, bạn nhận được kết quả “You’re getting a computer”. Bây giờ bạn muốn thêm ít phần cứng, một ổ cứng mới chẳng hạn. Trong trường hợp này, bạn có thể thêm một lớp bao bọc wrapper như sau:
Design Pattern - Decorator Pattern
Bây giờ khi bạn gọi phương thức description của lớp bao bọc wrapper, nó sẽ gọi phương thức description của đối tượng computer để nhận được kết quả “You’re getting a computer “ và đối tượng ổ cứng disk sẽ trả về kết quả “and a disk”.  Kết quả bạn nhận được “You’re getting a computer and a disk”
Nếu bạn muốn thêm vài thứ nữa vào lớp máy tính Computer, bạn hãy đặt nó vào lớp bao bọc wrapper, ví dụ như thêm vào cái màn hình Monitor:
Design Pattern - Decorator Pattern
Bây giờ khi bạn gọi phương thức description, mọi việc sẽ xảy ra như sau:
–      Đối tượng computer, sẽ thực hiện phương thức description để tạo ra kết quả “You’re getting a computer”
–      Đối tượng disk, sẽ thực hiện tiếp phương thức trên để thêm vào “and a disk”
–      Đối tượng monitor, tiếp tục thực hiện phương thức description để thêm vào “and a monitor”
–      Kết quả là bạn nhận được “You’re getting a computer and a disk and a monitor”

VÍ DỤ VỀ MẪU TRANG TRÍ DECORATOR

Example:

Bạn bắt đầu viết một lớp máy tính Computer đơn giản, với một phương thức description trả về kết quả “computer” như sau:

public class cComputer
{
        public cComputer() { }

        public string description()
        {
            return "computer";
        }

}

OK. Bạn đã hoàn thành cái máy tính đơn giản. Bây giờ làm sao để tạo một lớp trang trí? Những lớp này hoạt động như là một lớp bao bọc cho lớp Computer, điều này có nghĩa là phải có một biến để lưu trữ một đối tượng computer. Một cách đơn giản để tạo lớp bao bọc wrapper là mở rộng lớp Computer.

Tạo dựng một Lớp trang trí Decorator

Bài này tôi dùng 2 cách, cách 1: là dùng abstract và cách 2 dùng interface trong CSharp, tôi có để lại source mẫu, bạn có thể xem và tham khảo 🙂 Trong khi trình bày tôi dùng interface vì nó sẽ giúp các bạn dễ hiểu hơn

Download full source code use abstract class – CSharp

Download full source code use interface – CSharp

public interface IComputer
{
        string description();
}

Thêm vào một đĩa cứng Disk

Đây là lớp bao bọc Disk , sẽ thêm một ổ cứng vào máy tính. Bởi vì đây là một lớp bao bọc, nó cần phải biết đang bao bọc thứ gì. Vì vậy bạn đưa cho nó một đối tượng computer ngay khi nó khởi tạo. Lớp bao bọc Disk sẽ lưu trữ một đối tượng tên computer

Bây giờ bạn cần hiện thực phương thức Description. Phương thức mới này sẽ gọi phương thức description của lớp computer và thêm vào dòng chữ “and a disk” như sau:

public class cDisk : IComputer
{
        IComputer _icomp;
        public cDisk(IComputer c)
        {
            _icomp = c;
        }

        public string description()
        {
            return _icomp.description() + " and a disk";

        }
}

Vậy là bạn đã bao bọc đối tượng computer, và khi bạn gọi phương thức description của đối tượng disk này, nó sẽ gọi phương thức description của lớp computer, đồng thời thêm vào dòng chữ “and a disk”. Kết quả bạn sẽ có “computer and a disk”

Thêm vào một màn hình monitor

Tương tự với monitor

public class cMonitor : IComputer
{
        IComputer _icomp;

        public cMonitor(IComputer c)
        {
            _icomp = c;
        }

        public string description()
        {
            return _icomp.description() + " and a Monitor";
        }
}


OK. Bạn đã có đầy đủ các lớp. Giờ là lúc chạy thử nghiệm chương trình.
Đầu tiên bạn tạo đối tượng computer như sau:

static void Main(string[] args)
{
            IComputer comp = new cComputer();   // implements interface IComputer           
            Console.ReadLine();
}

Sau đó bạn bao bọc đối tượng computer để thêm vào một Monitor

static void Main(string[] args)
{
            IComputer comp = new cComputer();   // implements interface IComputer
           
            // add 1 Monitor
            comp = new cMonitor(comp);
            Console.ReadLine();
}

Bây giờ hãy thêm vào 2 Disk, Cuối cùng bạn gọi phương thức description của lớp bao bọc để xem kết quả:

static void Main(string[] args)
{
            IComputer comp = new cComputer();   // implements interface IComputer
          
            // add 1 Monitor
            comp = new cMonitor(comp); 

            // and 2 Disk
            comp = new cDisk(comp);    
            comp = new cDisk(comp); 

Console.WriteLine(string.Format("you're getting: {0}" , comp.description()));
            Console.ReadLine();
}

OK. Khi chạy chương trình bạn nhận được kết quả.

you’re getting: a computer and a monitor and a disk and a disk


Không tồi. Bạn đã mở rộng một đối tượng gốc thật đơn giản bằng cách bao bọc nó trong nhiều lớp trang trí decorator khác nhau, tránh việc phải chỉnh sửa trong mã nguồn gốc. Và đó là Mẫu Thiết Kế Trang Trí Decorator.

Download full source code use abstract class – CSharp

Download full source code use interface – CSharp

Ref: 
https://haihth.wordpress.com/2013/02/21/dp-chapter3/
https://sourcemaking.com/design_patterns/decorator

[Tutorial] Design Pattern – Factory Pattern

 “Creates an instance of several families of classes”

The purpose of the Abstract Factory is to provide an interface for creating families of related objects, without specifying concrete classes. This pattern is found in the sheet metal stamping equipment used in the manufacture of Japanese automobiles. The stamping equipment is an Abstract Factory which creates auto body parts. The same machinery is used to stamp right hand doors, left hand doors, right front fenders, left front fenders, hoods, etc. for different models of cars.

Rules of thumb

  • Abstract Factory classes are often implemented with Factory Methods, but they can be implemented using Prototype.
  • Factory Methods are usually called within Template Methods.
  • Factory Method: creation through inheritance. Prototype: creation through delegation.
  • Often, designs start out using Factory Method (less complicated, more customizable, subclasses proliferate) and evolve toward AbstractFactory, Prototype, or Builder (more flexible, more complex) as the designer discovers where more flexibility is needed.
  • Prototype doesn’t require subclassing, but it does require an Initialize operation. Factory Method requires subclassing, but doesn’t require Initialize.
  • The advantage of a Factory Method is that it can return the same instance multiple times, or can return a subclass rather than an object of that exact type.
  • Some Factory Method advocates recommend that as a matter of language design (or failing that, as a matter of style) absolutely all constructors should be private or protected. It’s no one else’s business whether a class manufactures a new object or recycles an old one.
  • The new operator considered harmful. There is a difference between requesting an object and creating one. The newoperator always creates an object, and fails to encapsulate object creation. A Factory Method enforces that encapsulation, and allows an object to be requested without inextricable coupling to the act of creation.

Design Pattern - Factory Pattern

Download full source code here

Tại đây, công ty MegaGigaCo, bạn được trả lương cao cho kỹ năng thiết kế mẫu chuyên nghiệp của mình, bạn đang tạo một đối tượng kết nối cơ sở dữ liệu mới

cConnection conn = new cOracleConnection();
“Không tồi,” bạn nghĩ, sau khi hoàn thành đoạn mã việc lớp OracleConnection. Bây giờ bạn đã có thể kết nối với cơ sở dữ liệu Oracle.
“Nhưng,” Giám đốc điều hành la lên ,”làm thế nào để kết với máy chủ cơ sở dữ liệu Microsof SQL Server?”
“Được”, bạn nói “Bình tĩnh, để tôi suy nghĩ một lát”. Bạn ra khỏi phòng để ăn trưa và sau đó quay lại tìm giám đốc và ban quản trị. Mọi người nóng lòng chờ đợi và hỏi “Mọi việc đã xong chưa?”
Bạn trở lại làm việc và tạo ra một lớp mới dùng để kết nối cơ sở dữ liệu, lớp SQLServerConnection.

cConnection conn = new cSqlServerConnecton();

“Tốt lắm” Vị giám đốc nói. “Umh, vậy làm sao để kết nối với MySQL? Chúng ta muốn nó là kết nối mặc định”. “Woa”, bạn hơi bối rối. Tuy nhiên bạn vẫn làm thêm một kết nối với MySQL như sau:

cConnection conn = new cMySqlServerConnecton();

Hiện tại bạn đã có ba loại kết nối cơ sở dữ liệu như sau: Oracle, SQL Server và MySQL. Vì vậy bạn chỉnh sửa mã nguồn cho phù hợp với các biến như “Oracle”, “SQL Server” hay bất cứ biến nào khác như sau:

if (type.equals("Oracle"))
    conn = new cOracleConnection();
   

else if (type.equals("SQL Server"))
    conn = new cSqlServeverConnection();   

else
    conn = new cMySqlServerConnection();

Tuyệt, bạn nghĩ. Không có gì khó khăn ở đây. 
“Tin xấu” Vị giám đốc nói lớn trong khi chạy ào vào phòng làm việc của bạn. “Chúng ta cần phải chỉnh sửa lại mã nguồn để xử lý các kết nối an toàn cho tất cả máy chủ cơ sở dữ liệu. Hội đồng quản trị của khu vực Western yêu cầu như vậy” 
Bạn đưa vị giám đốc ra khỏi phòng và ngồi suy nghĩ. Tất cả mã nguồn chắc phải chỉnh sửa lại. Phương thức mới createConnection, phần chính của mã nguồn, sẽ phải chỉnh sửa lại.
Có lẽ đây là lúc nghĩ về việc tách rời phần mã nguồn dễ thay đổi ra khỏi chương trình chính, phần tạo kết nối cơ sở dữ liệu connection, và đóng gói nó vào một đối tượng. Và đối tượng đó chính là mẫu nhà máy Factory. Đối tượng là một nhà máy, được viết trong mã nguồn, nhằm tạo ra các đối tượng kết nối connection. 
Vì sao bạn nghĩ tới mẫu thiết kế nhà máy Factory. Đây là những gợi ý: 
– Bạn sử dụng toán tử new để tạo đối tượng OracleConnection 
– Sau đó lại sử dụng tiếp toán tử new để tạo đối tượng SQLServerConnection, và sau đó là MySQLConnection. Nói cách khác, bạn đã sử dụng toán tử new để tạo nhiều đối tượng thuộc các lớp khác nhau, điều này làm mã nguồn của bạn trở nên lớn hơn và bạn buộc phải lặp lại điều này nhiều lần trong toàn bộ mã nguồn. 
– Sau đó bạn đưa đoạn mã đó vào trong một phương thức 
– Bởi vì yêu cầu vẫn còn có thể thay đổi nhanh chóng, nên cách tốt nhất là đóng gói chúng vào một đối tượng nhà máy factory. Theo cách làm này, bạn đã tách phần mã dễ thay đổi riêng biệt ra và giúp phần mã nguồn còn lại giữ vững nguyên tắc “đóng cho việc sửa đổi” 
Chúng ta có thể nói rằng, toán tử new vẫn tốt trong mọi trường hợp, nhưng khi mã tạo dựng đối tượng bị liên tục thay đổi, ta nên nghĩ đến việc đóng gói chúng bằng mẫu thiết kế nhà máy factory.
Ghi nhớ: Theo sách GoF, mẫu thiết kế phương thức nhà máy Factory Method được định nghĩa “Định nghĩa một giao diện để tạo một đối tượng, nhưng cho phép các lớp con quyết định cách thức thể hiện nó. Phương thức nhà máy Factory cho phép một lớp trì hoãn việc hiện thực của nó qua các lớp con”

1st Exam:

abstract class Address
{
public abstract void Show();
}

abstract class Phone
{
public abstract void Show();
}

abstract class Factory
{
virtual public Address createAddress()
{
return null;
}
virtual public Phone createPhone()
{
return null;
}
}

class USAddress: Address
{
public override void Show()
{
Console.WriteLine("USA Address");
}
}

class USPhone: Phone
{
public override void Show()
{
Console.WriteLine("USA Phone");
}
}

class VNAddress: Address
{
public override void Show()
{
Console.WriteLine("Viet Nam address");
}
}

class VNPhone:Phone
{
public override void Show()
{
Console.WriteLine("Viet Nam phone");
}
}

class USFactory: Factory
{
public override Address createAddress()
{
return new USAddress();
}
public override Phone createPhone()
{
return new USPhone();
}
}

class VNFactory:Factory
{
public override Address createAddress()
{
return new VNAddress();
}
public override Phone createPhone()
{
return new VNPhone();
}
}

class Program
{
static void Main(string[] args)
{
Factory factory = new VNFactory();
Address address = factory.createAddress();
Phone phone = factory.createPhone();

Console.WriteLine("Create Object by VNFactory");
address.Show();
phone.Show();

factory = new USFactory();
address = factory.createAddress();
phone = factory.createPhone();

Console.WriteLine("Create Object by USFactory");
address.Show();
phone.Show();

Console.ReadKey();
}
}

2nd Exam:


Ref:
https://sourcemaking.com/design_patterns/factory_method
https://haihth.wordpress.com/2013/02/21/dp-chapter3/

[Tutorial] Design Pattern – Strategy Pattern

“Encapsulates an algorithm inside a class”

A Strategy defines a set of algorithms that can be used interchangeably. Modes of transportation to an airport is an example of a Strategy. Several options exist such as driving one’s own car, taking a taxi, an airport shuttle, a city bus, or a limousine service. For some airports, subways and helicopters are also available as a mode of transportation to the airport. Any of these modes of transportation will get a traveler to the airport, and they can be used interchangeably. The traveler must chose the Strategy based on trade offs between cost, convenience, and time.

Rules of thumb

Strategy is like Template Method except in its granularity.
– State is like Strategy except in its intent.
Strategy lets you change the guts of an object. Decorator lets you change the skin.
– State, Strategy, Bridge (and to some degree Adapter) have similar solution structures. They all share elements of the ‘handle/body’ idiom. They differ in intent – that is, they solve different problems.
Strategy has 2 different implementations, the first is similar to State. The difference is in binding times (Strategy is a bind-once pattern, whereas State is more dynamic).
Strategy objects often make good Flyweights.
Strategy Pattern

Download full source code here

Các lập trình viên tại MegaGigaCo (phần đầu chương) đều biết về sự “kế thừa” và họ bắt đầu việc thíêt kế xe hơi mới bất chấp lời cảnh báo của bạn cho đến khi bạn có cơ hội nói chuyện với họ. Họ biết họ đang phải thiết kế một loạt xe, vì vậy họ bắt đầu tạo ra một lớp cơ sở tên Vehicle với một phương thức tên là go, phương thức này xuất hiện lên dòng chữ Now I’m driving.

 
public abstract class clsVehicle
{
    public clsVehicle()
    {
    }

    public void go()
    {
        Console.WriteLine("Now, I'm driving");
    }
}

Sau đó họ tao tiếp một lớp mới, như là lớp StreetRacer, sử dụng Vehicle làm lớp cơ sở như sau:

public class clsStreetRacer:clsVehicle
{
public clsStreetRacer()
{
}
}

Chương trình tới đây vẫn tốt đẹp. Bạn có thể cho chạy chương trình với lớp StreetRacer như sau:

static void Main(string[] args)
{
clsStreetRacer streetRacer = new clsStreetRacer();
streetRacer.go();

Console.ReadLine();
}

Kết quả nhận được
=> Now I’m driving

Bạn cũng có thể chạy cùng lúc street racer và formula one racer với cùng một cách như sau:

static void Main(string[] args)
{
    clsStreetRacer streetRacer = new clsStreetRacer();
    clsFormulaOne formulaone = new clsFormulaOne();
    clsHelicopter helicopter = new clsHelicopter();
                   
    streetRacer.go();
    formulaone.go();
    helicopter.go();
    Console.ReadLine();
}

Kết quả nhận được
=> Now I’m driving
=> Now I’m driving
=> Now I’m driving

“Không tồi”. Giám đốc và ban điều hành nói. “Vậy cần gì phải sử dụng mẫu thiết kế” Họ hỏi mà mắt nhìn chằm chằm vào bạn. Nhưng sau đó họ nhận được một hợp đồng sản xuất máy bay trực thăng Helicopter. Máy bay trực thăng à? Họ lý luận, thì cũng là một phương tiện vận chuyển. Vì vậy họ tạo một lớp Helicopter , được mở rộng ra từ lớp Vehicle :

Trên kia bạn có sử dụng thêm lớp helicopter. Nhưng lại xuất hiện một vấn đề. Nếu như bạn sử dụng helicopter trong cùng một điều kiện như xe hơi:

Bạn sẽ nhận được 3 phương tiện như sau: một xe street racer, một xe Formula One, một máy bay helicopter như sau:

Kết quả nhận được
=> Now I’m driving
=> Now I’m driving
=> Now I’m driving

Có gì đó không ổn, Giám đốc nói một cách hồ nghi. Tại sao helicopter (máy bay trực thăng) mà lại đang chạy? Hình như nó đang bay thì mới đúng? Tuy nhiên vấn đề thực sự tồi tệ khi công ty MegaGigaCo nhận được một hợp đồng chế tạo máy bay phản lực Jet, khi đó chúng cũng được kế thừa từ lớp Vehicle :
Khi bạn cho chạy bốn phương tiện trên: một xe street racer, một xe formula one, một máy bay trực thăng helicopter, một máy bay phản lực jet, bạn nhận được kết quả sau:

Kết quả nhận được
=> Now I’m driving
=> Now I’m driving
=> Now I’m driving
=> Now I’m driving 

“Chắc chắn là đã có sai sót ở đây” Vị giám đốc lên tiếng. Máy bay phản lực Jet thì không chạy trên đường, chúng ở trên không. Chúng bay và rất nhanh. Không vấn đề gì, các lập trình viên trong công ty đáp. Chúng tôi sẽ override lên phương thức go của lớp Helicopter và lớp Jet để sửa chữa chúng. Họ chỉnh sửa lại như sau:

public class clsHelicopter:clsVehicle
{
    public clsHelicopter()
    {
        setGoAlgorithm(new clsGoByFlyingAlgorithm());
    }   

    public void go()
    {
        Console.Writeln("Now I'm flying!");
    }            
}

Giờ lớp máy bay trực thăng Helicopter đã bay được.
“OK”. Giám đốc nói “Tuy nhiên vào tuần sau, ban giám đốc họp và quyết định phải chuyển từ “Now I’m flying” sang “Now, I’m flying 200mph” và nhiều sự thay đổi tồi tệ kế tiếp…
Có một vấn đề nảy sinh ở đây, bạn giải thích. Các lập trình viên đã thể hiện một chức năng đơn giản – là lái một chiếc xe hay một chiếc phi cơ – qua nhiều lớp con. Đó có thể chưa là một vấn đề lớn nhưng nếu bạn xử lý các công việc này một cách khá thường xuyên, thì việc phải chỉnh sửa mọi lớp con như vậy sẽ trở thành một vấn đề bảo trì khá nghiêm trọng.
Bạn nói tiếp: có thể là “sự kế thừa” không phải là câu trả lời cho tình huống này. Nơi mà bạn cần phải thay đổi chức năng thường xuyên ở các lớp con. Bạn cần phải chỉnh sửa, bảo trì phần lớn các đoạn mã ở các lớp con khi có sự thay đổi. Và khi có càng nhiều lớp kế thừa liên quan, chúng cũng cần được phải bảo trì khi có sự thay đổi, và khi đó bạn phải cập nhật các phương thức go mãi mãi.
Vấn đề bạ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 bạn không tránh được điều này, bạn sẽ phải chỉnh sửa rất nhiều file để cập nhật mã của bạn.
Có lẽ có một cách tốt hơn để xử lý vấn đề này hơn là sử dụng sự “kế thừa”.
“Hey” một lập trình viên nói, “Sao anh không sử dụng giao diện interface thay cho sự kế thừa inheritance? Anh có thể cài đặt một giao diện IFly và cho giao diện đó một phương thức go và để cho lớp Helicopter hiện thực giao diện đó như sau:

public class clsHelicopter : IFly
{
    public clsHelicopter()
    {
    }
   
    public void go()
    {
        Console.Writeln("Now I'm flying!");
    }     
      
}

“Không tốt” bạn nói. Anh vẫn chưa giải quyết ổn thỏa vấn đề. Mỗi lớp và lớp con vẫn phải hiện thực cho riêng nó một giao diện, cũng giống như trường hợp của sự kế thừa. Bởi vì giao diện thì không cài đặt nội dung, bạn vẫn phải viết code cho từng lớp, điều này có nghĩa là chẳng có sử dụng lại được một đoạn code nào cả.

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

Mọi việc đều thay đổi. Trong thời buổi thương mại phát triển, mọi thứ thay đổi nhanh chóng. Vì vậy việc lập kế hoạch cho sự thay đổi là rất đáng giá. Nếu bạn có một vấn đề nhỏ cần phải có một giải pháp nhỏ, bạn có thể không cần phải lập một kế hoạch lớn lao cho sự thay đổi. Nhưng nếu bạn làm việc trong một dự án nghiêm túc, với một khối lượng công việc đáng kể, thì đúng là lúc bạn nên nhìn lại về một kế hoạch nghiêm túc khi có sự thay đổi. Các đoạn mã mà bạn viết hôm nay, sẽ phải chỉnh sửa lại để phù hợp với những yêu cầu phát triển trong tương lai. Hầu hết các nhà phát triển không chú ý tới vấn đề này, và sau đó họ luôn luôn hối tiếc. Vậy câu hỏi đặt ra là dự án phải lớn tới đâu, để bạn quan tâm đến vấn đề thay đổi. Đó là sự đánh giá của riêng bạn, một phần của nghệ thuật lập trình. Bằng cách nắm vững phương pháp xử lý sự thay đổi, bạn sẽ biết rõ hơn khi nào thì nên thực hiện nó.
Có một dấu hiệu đáng chú ý ở đây: Phân chia các đoạn mã dễ thay đổi trong chương trình riêng biệt với phần còn lại. Và làm cho chúng càng độc lập càng tốt cho sự bảo trì nâng cấp. Bạn cũng nên cố gắng tái sử dụng những phần này càng nhiều càng tốt.
Điều này có nghĩa là nếu ứng dụng của bạn có một phần bị thay đổi, bạn có thể đem nó riêng ra, sau đó thay đổi từng phần riêng biệt một cách dễ dàng trong khi vẫn không bị ảnh hưởng bởi những tác dụng phụ của nó.
Và đây là cách để lập kế hoạch cho sự thay đổi, và vì sao “kế thừa” lại không thể giải quyết tốt các sự thay đổi này. 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 mã 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 chiếc xe street racer sẽ có một “has-a” cách để di chuyển, đã được đóng gói vào đối tượng. Một máy bay trực thăng sẽ có một cách riêng để di chuyển, 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.
Sử dụng kế thừa sẽ tự động cài đặt mọi thuộc tính một cách nghiêm ngặt, bao gồm cả quan hệ “is-a”, là thứ gây ra các rắc rối khi bảo trì cũng như khi mở rộng. Nếu bạn đặt kế hoạch cho sự thay đổi, bạn nên nghĩ tới quan hệ “has-a” , nơi mà mã của bạn bao gồm nhiều đối tượng mà có thể dễ dàng cập nhật khi có sự thay đổi xảy ra.
Gợi ý: Khi có kế hoạch cho sự thay đổi, hãy thay thế quan hệ “is-a” thành quan hệ “has-a” và đặt các đoạn mã dễ thay đổi vào các đối tượng trong ứng dụng này hơn là kế thừa chúng.

Kế hoạch chỉnh sửa code

Làm thế nào mà ý tưởng phân chia các đoạn mã dễ thay đổi sẽ hoạt động trong ví dụ Vehicle/StreetRacer/Helicopter đã nhắc trước đây. Theo ý kiến của giám đốc điều hành, phần được thay đổi nhiều nhất là phương thức go , do đó chúng ta sẽ tách nó ra. Trong thuật ngữ về thiết kế mẫu, mỗi cách hiện thực một phương thức được gọi là 1 thuật toán(algorithm) hay có thể gọi là 1 chiến lược (strategy). Vì vậy bạn có thể tạo một tập hợp các giải thuật để sử dụng cho các biến của bạn như StreetRacer, FormulaOne, Helicopter, và Jet . Làm như thế để phân chia các đoạn mã dễ thay đổi vào trong thuật toán. Từng thuật toán sẽ hoàn thành 1 nhiệm vụ.

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

Để chắc chắn mọi thuật toán đều hiện thực cùng một phương thức (phương thức go ở trên). Bạn cần phải tạo một giao diện interface cho nó như sau:

public interface IGoAlgorithm
{
    void go();
}

Giao diện GoAlgorithm có một phương thức duy nhất go. Để chắc chắn rằng mọi thuận toán có thể được sử dụng bởi bất kì lớp Vehicle nào, ta cần phải hiện thực interface này. Thuật toán đầu tiên GoByDrivingAlgorithm , sẽ hiển thị văn bản “Now I’m driving”. Và đây là mã của thuật toán:

public class clsGoByDrivingAlgorithm:IGoAlgorithm
{
    public void go()
    {
        Console.WriteLine("Now, I'm driving!");
    }
}

Ngoài ra, thuật toán GoByFlying, sẽ hiển thị văn bản Now I’m flying. Mã như sau:

public class clsGoByFlyingAlgorithm:IGoAlgorithm{

        public void go()
        {
            Console.WriteLine("Now, I'm Flying!");
        }
}

Tương tự với GoByFlyingFast

 Tuyệt vời. Bạn vừa phân chia các thuật toán của mình ra khỏi phần mã. Bạn đang thực hiện thao tác thực thi quan hệ “has-a” hơn là quan hệ “is-a”. Bây giờ bạn đã có thể đưa các thuật toán này vào sử dụng.

Sử dụng chúng như thế nào?

Bạn đang có một số thuật toán, bạn có thể tạo các đối tượng và sử dụng quan hệ “has-a” thay cho “is-a”. 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ẽ lưu trữ thuật toán mà bạn muốn sử dụng. Mã như sau:


public abstract class clsVehicle
{
    private IGoAlgorithm _goAlgorithm;

    public clsVehicle()
    {
    }

   
    public void setGoAlgorithm(IGoAlgorithm algorithm)
    {
        _goAlgorithm = algorithm;
    }

}

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, theo cách như sau:

public class clsStreetRacer:clsVehicle
{
public clsStreetRacer()
{
setGoAlgorithm(new clsGoByDrivingAlgorithm());
}
}

Phương thức go của lớp Vehicle có chút thay đổi. Trước đây là:

public void go()
{
     // Phương thức go của lớp Vehicle có chút thay đổi. Trước đây là:
     // Console.WriteLine("Now, I'm driving");

     // Tuy nhiên, bây giờ nó phải gọi phương thức đã được định nghĩa ở các lớp thuật toán. Mã mới như sau:
     _goAlgorithm.go();
}

Bây giờ thì tất cả những gì phải làm là chọn đúng thuật toán mà bạn muốn sử dụng cho phương tiện nào đó. Ví dụ với street racer sẽ là thuật toán GoByDrivingAlgorithm,
Formula One sẽ là thuật toán GoByDrivingAlgorithm Helocopter dùng GoByFlyingAlgorithm, phản lực Jet sẽ dùng: GoByFlyingFastAlgorithm, code như sau

public class clsFormulaOne:clsVehicle
{
    public clsFormulaOne()
    {
        setGoAlgorithm(new clsGoByDrivingAlgorithm());
    }
}

public class clsHelicopter:clsVehicle
{
    public clsHelicopter()
    {
        setGoAlgorithm(new clsGoByFlyingAlgorithm());
    }      
}   
   
public class clsJet:clsVehicle
{
    public clsJet()
    {
        setGoAlgorithm(new clsGoByFlyingFastAlgorithm());
    }
}

Đã đến lúc chạy thử chương trình. Biên dịch và chạy thử chương trình như sau:

static void Main(string[] args)
{
    // Strategy Patterns
    clsStreetRacer streetRacer = new clsStreetRacer();
    clsHelicopter helicopter = new clsHelicopter();
    clsFormulaOne formulaone = new clsFormulaOne();
    clsJet jet = new clsJet();
               
    streetRacer.go();
    formulaone.go();
    helicopter.go();   

    Console.ReadLine();

}

Kết quả đúng như mong đợi. Tuy nhiên bây giờ bạn đã sử dụng mối quan hệ “has-a” thay vì quan hệ kế thừa “is-a”. Từ lúc này bạn có thể sử dụng các thuật toán xuyên suốt chương trình, bất cứ đâu, vì nó đã không còn nằm trong các lớp StreetRacer hay Helicopter nữa.
Kỹ thuật này thay thế cho cách tạo các lớp con và sử dụng kế thừa. Nếu bạn sử dụng một quan hệ kế thừa “is-a”, bạn sẽ bắt đầu sự rắc rối cho việc kiểm soát được các phương thức trong lớp cơ sở và các lớp con – trong ví dụ là bạn phải nạp đè lên phương thức go cho lớp HelicopterJet. Nếu bạn sử dụng mô hình “has-a”, bạn có thể tạo ra một dòng họ các thuật toán một cách rõ ràng, và sau đó bạn chọn một thuật toán thích hợp để sử dụng.
Theo cách này, bạn đã có thể khắc phục được vấn đề mà sự kế thừa đã gây ra cho hầu hết các lập trình viên: nếu bạn phải giải quyết một chức năng cụ thể nào đó qua nhiều thế hệ của một lớp, và chức năng này liên tục thay đổi, bạn sẽ phải chỉnh sửa rất nhiều mã của mình. Mặt khác, khi bạn tập trung chức năng đó vào một thuật toán duy nhất, việc thay đổi nó sẽ dễ dàng hơn rất nhiều.
Quay lại ví dụ trên, khi ban giám đốc muốn thay đổi từ “Now I’m flying” sang “Now I’m flying at 20 mph”. Đơn giản, bạn chỉ cần chỉnh sửa thuật toán GoByFlying:
public class clsGoByFlyingAlgorithm:IGoAlgorithm
{
        public void go()
        {
            Console.WriteLine("Now, I'm Flying at 200 mph!");
        }
 }

Và bây giờ tất cả mã của bạn đã tự động được cập nhật, bạn không cần thiết phải đi tìm và chỉnh sửa từng lớp con như trước nữa. Theo cách này, bạn đã tập trung sự xử lý một chức năng vào một đối tượng thuật toán duy nhất, bạn sẽ dễ dàng quản lý đối tượng này trong trường hợp yêu cầu chức năng bị thay đổi.
“Đợi một chút,” Giám đốc MegaGigaCo nói. “Có việc xảy ra, máy bay phản lực không chỉ bay nhanh, đầu tiên nó chạy trên đường băng một lúc, và khi đáp xuống mặt đất, nó lại tiếp tục chạy trên đường băng nữa. Vì vậy chúng ta phải chỉnh sửa chức năng cho nó lại : đầu tiên là chạy trên đường băng, rồi bay, rồi chạy tiếp?”
“Đó là về mặt lý thuyết,” các lập trình viên rên rỉ “Nhưng điều đó làm chúng ta phải viết thêm nhiều đoạn mã nữa”
“Không sao cả” Bạn nói. “Đó là một trong những điểm kỳ diệu của việc sử dụng một đối tượng thuật toán bên ngoài. Bạn có thể thay đổi nó khi bạn thực thi chương trình”
Khi bạn viết mã cho một chức năng trong một lớp, bạn không thể thay đổi nó khi thực thi chương trình. Tuy nhiên khi bạn sử dụng một đối tượng thuật toán bên ngoài với mối quan hệ “has-a”, bạn dễ dàng thay đổi chức năng đó lúc chương trình hoạt động. Nói cách khác một quan hệ “has-a” cho phép bạn dễ dàng thay đổi hơn một quan hệ “is-a” đặc biệt khi chương trình đang hoạt động.
Và đây là ví dụ cho việc sử dụng linh hoạt các thuật toán, cũng như việc thay đổi nó khi chương trình đang chạy. Bạn có thể tạo một máy ban phản lực, có thể chạy trên đường băng với thuật toán GoByDrivingAlgorithm,
Bạn có thể cài đặt thuật toán mới setGoAlgorithm cho máy bay phản lực, để thay đổi phương thức go một cách linh động, và sau đó gọi lại phương thức go để thấy sự khác biệt.
static void Main(string[] args)
{
    // Strategy Patterns
    clsStreetRacer streetRacer = new clsStreetRacer();
    clsHelicopter helicopter = new clsHelicopter();
    clsFormulaOne formulaone = new clsFormulaOne();
    clsJet jet = new clsJet();
               
    streetRacer.go();
    formulaone.go();
    helicopter.go();


    jet.setGoAlgorithm(new clsGoByDrivingAlgorithm());
    jet.go();
    jet.setGoAlgorithm(new clsGoByFlyingFastAlgorithm());
    jet.go();
    jet.setGoAlgorithm(new clsGoByDrivingAlgorithm());
    jet.go();
   

    Console.ReadLine();

}

Bạn thấy đó, việc chuyển đổi một thuật toán lúc thực thi chương trình rất dễ dàng. Nói cách khác, nếu bạn để việc xử lý thuật toán vào nội tại một lớp, bạn sẽ không thể thay đổi nó lúc chạy chương trình. Nhưng khi bạn cài đặt một chiến lược “Strategy”, bạn sẽ dễ dàng thay đổi nó khi chạy chương trình. Tất cả những điều trên mang chúng ta đến một mẫu thiết kế “Strategy”, hay được gọi là mẫu “chiến lược”.

Kết luận

MẪU “STRATEGY” – 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.
Chúng ta có thể nhìn thấy vấn đề tổng quát như sau. Đầu tiên mọi việc đều ổn, bạn có một đối tượng, một chức năng

Strategy Pattern

Một thời gian sau đó, do yêu cầu đặc biệt, bạn cần có thêm một lớp mới, bạn kế thừa lớp cũ, và ghi đè lên phương thức đã được thừa hưởng. Bạn đang dàn trải việc xử lý chức năng qua nhiều lớp con như hình:

Strategy Pattern

Mẫu “Strategy”, mẫu chiến lược nói rằng: bạn cần phải tách những phần dễ thay đổi và đóng gói chúng vào các đối tượng và bạn có thể sử dụng các đối tượng này khi cần. Bây giờ bạn có thể chỉnh sửa mã của mình thông qua việc tạo sự “kết hợp” các đối tượng. Khi chương trình thực thi, bạn chỉ cần sử dụng đúng đối tượng mà bạn cần. Như hình sau:

Strategy Pattern

Gợi ý: Bạn nên sử dụng mẫu Strategy khi có những tình huống sau:

  • Bạn có một đoạn mã 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

ref: 
https://sourcemaking.com/design_patterns/strategy
https://haihth.wordpress.com/2013/02/21/dp-chapter2/

[Tutorial] Design Patterns – Overview

In software engineering, a design pattern is a general repeatable solution to a commonly occurring problem in software design. A design pattern isn’t  a finished design that can be transformed directly into code. It is a description or template for how to solve a problem that can be used in many different situations
Design Patterns

Used of Design Patterns

Design patterns can speed up the development process by providing tested, proven development paradigms. Effective software design requires considering issues that may not become visible until later in the implementation. Reusing design patterns helps to prevent subtle issues that can cause major problems and improves code readability for coders and architects familiar with the patterns.
often, open only understand how to apply certain software design techniques to certain problems. These techniques are difficult to apply to a broader range of problems. Design patterns provide general solutions, documented in a format that doesn’t require specifics tied to a particular problem.
In addition, patterns allow developers to communicate using well-known, well understood names for software interactions. Common design patterns can be improved over time, making them more robust than ad-hoc designs

Creational design patterns

These design patterns are all about class instantiation. This pattern can be further divided into class-creation and object-creational patterns. While class-creation patterns use inheritance effectively in the instantiation process

Creational design patterns

Abstract Factory

Builder

Factory Method

Object Pool

Prototype

Singleton

Design Patterns

Structural design patterns

These design patterns are all about class and object composition. Structural class -creation patterns use inheritance to compose interfaces. Structural object-patterns define ways to compose objects to obtain view functionality

Structural design patterns

Adapter

Bridge

Composite

Decorator

Facade

Flyweight

Private Class Data

Proxy

Behavioral design patterns

These design patterns are all about Class’s objects communication. Behavioral patterns are those patterns that are most specifically concerned with communication between objects
Chain of responsibility

Behavioral design patterns

Command

Interpreter

Iterator

Mediator

Memento

Null Object

Observer

State

Strategy (Encapsulates an algorithm inside a class)

Template method

Visitor

Mẫu Thiết Kế Design Patterns

Là một lập trình viên, bạn biết rằng thật khó khăn để nhớ chi tiết những việc bạn đang thực hiện. Và khi bạn không nắm bắt được tổng quát công việc, bạn có thể dễ dàng bỏ lỡ những việc quan trọng. Khi đó, mã nguồn bạn đang viết có thể vẫn còn làm việc tốt đẹp, nhưng trừ khi bạn bao quát được bức tranh lớn hơn, lúc đó mã nguồn bạn viết mới thực sự hoàn hảo.

Những vấn đề nghiêm trọng thực sự thường xuất hiện sau khi bạn đã chỉnh sửa chúng ít nhất một lần. Những nhà phát triển thường tự mình xử lý bằng cách viết lại mã nguồn và sửa các lỗi. Tuy nhiên trong môi trường công việc, những nhà lập trình thường bỏ phần lớn thời gian để bảo trì, chỉnh sửa những công việc cũ hơn là tập trung vào những sản phẩm mới.

Bạn thấy rằng thật vô lý khi cứ phải làm, rồi sửa, làm lại, sửa tiếp… Giải pháp hợp lý nhất là bạn đưa ra được một quy trình tổng quan cho việc thiết kế và bảo trì, có như vậy, bạn mới tránh được các rắc rối phát sinh khi môi trường ứng dụng thay đổi, hoặc ít nhất bạn cũng giúp cho việc bảo trì, chỉnh sữa dễ dàng hơn khi có phát sinh.

Ý tưởng đằng sau của bài viết này là: Bạn sẽ sử dụng một tập hợp các mẫu thiết kế Design Patterns để làm đơn giản hóa quá trình trên. Kế hoạch này sẽ giúp bạn có một cái nhìn tổng quát. Một mẫu thiết kế Design Pattern là một giải pháp đã được kiểm nghiệm thành công khi đối diện một vấn đề lập trình phát sinh cụ thể. Khi bạn quen thuộc hết các mẫu thiết kế trong sách này, bạn nhìn vào một chương trình. Bam! Một giải pháp đúng đắn sẽ xuất hiện trong tâm trí bạn, thay vì bạn phải đập đầu vào tường trong vô vọng, giờ bạn có thể ung dung nói “Ở đây, tôi sẽ sử dụng mẫu Factory, mẫu Observer, hay mẫu Adapter…”

Đó là chưa nói, một số sách về thiết kế khuyên bạn nên dành phần lớn thời gian để phân tích và lên kế hoạch cho một đề án. Vẻ đẹp thật sự ở đây là một người nào đó đã đối mặt với vấn đề bạn đang gặp phải, họ đã có giải pháp đúng đắn cho nó. Và giờ khi bạn nhuần nhuyễn mẫu thiết kế, bạn có thể áp dụng các thiết kế đó một cách dễ dàng.

Làm sao để trở thành chuyên gia thiết kế trong lĩnh vực phần mềm, điều mà ai cũng thèm muốn? Thật dễ dàng, hãy xem nội dung của các phần này, nghiền ngẫm những mẫu thiết kế mà tôi dành nhiều tâm huyết để viết. Bạn không cần phải nhớ mọi thứ, bạn chỉ cần biết là có những mẫu thiết kế đó. Và khi bạn đối diện với một vấn đề thực tế, sâu thẳm trong bạn tự nhiên thốt lên “À, có vẻ chỗ này có thể dùng mẫu Iterator…” Sau đó bạn chỉ cần tìm kiếm mẫu thiết kế đó trong cuốn sách này, duyệt qua các ví dụ để biết phải làm gì. Và vì vậy, chương này sẽ là một tour du lịch nho nhỏ, giúp bạn đi qua một số mẫu thiết kế tiện dụng và hữu ích.


<Trích – dịch từ  Gang of Four – Erich Gamma, Richard Helm, Ralph Johnson và John Vlissides, trong một nghiên cứu của họ năm 1995. Với tựa đề gốc là “Design Patterns: Elements of Reusable Object-Oriented Software>

Ref
https://haihth.wordpress.com/2013/02/20/dp-chapter1/
https://sourcemaking.com/design_patterns

[Knownledge] OOP – Object-oriented programming, abstract class vs Interface

Object-oriented programming (OOP) is a programming language model organized around objects rather than “actions” and data rather than logic. Historically, a program has been viewed as a logical procedure that takes input data, processes it, and produces output data.

What’s different about Abstract class and Interface?

– Abstract class: is a parent class for all the classes of the same nature. (relative Is-a). We can use extends keyword in C#, php, java code
– Interface: is a function that you can add and any class. (relative can do). We can use implements keyword in C#, php, java code.
 
– Cùng một loại, nhóm chúng ta có thể gom thành một abstract class (Quan hệ Is – a)
– Cùng một chức năng, chúng ta có thể gom thành một interface (Quan hệ can do)
Xem hình bên dưới,
– Ta có các subclass thuộc abstract class Animals and Machines
– Ta có các Interface:Swimable, Runnable và Flyable (như ta thấy hai subclass Bolt và MCQueen là hai abstract class khác nhau, nhưng vẫn có chung interface)

How to used Abstract class and Interface:

OOP - Object-oriented programming, abstract class vs Interface

OOP - Object-oriented programming, abstract class vs Interface

Have a nice day!
Zidane
http://learn-tech-tips.blogspot.com/

[Tips] C# TextBox only accept number not character

You can use below function. This function can help you just allow input number not characters.

C# TextBox only accept number not character
// Developer: VịLH / Zidane (huuvi168@gmail.com)
// Last Modified: 2016-07-01

private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
     if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar) && (e.KeyChar != '.'))
           e.Handled = true;

      // only allow one decimal point
      if ((e.KeyChar == '.') && ((sender as TextBox).Text.IndexOf('.') > -1))
           e.Handled = true;
}

Any feedback or question, leave your comment, we can disccuss about it!
Zidane
http://learn-tech-tips.blogspot.com/