Wednesday, February 15, 2017

SOLID - Bài 5: Dependency Inversion Principle

List các bài đã viết:
Nguyên tắc cơ bản trong thiết kế OOP: Bài mở đầu Generalization và Specification
SOLID - Bài 1: Single Responsibility Principle (SRP)
SOLID - Bài 2: Open Closed Principle (OCP)
SOLID - Bài 3: Liskov's Substitution Principle (LSP)
SOLID - Bài 4: Interface Segregation Principle (ISP)
SOLID - Bài 5: Dependency Inversion Principle (DIP)

Bây giờ, chúng ta sẽ tìm hiểu nguyên tắc cuối cùng trong 5 nguyên tắc SOLID - Dependency Inversion Principle (DIP). Tôi dịch là "Hoán đổi sự phụ thuộc".
Nội dung của nguyên tắc được phát biểu chia làm 2 mệnh đề như sau:
"Những module tầng cao (high-level module) không nên phụ thuộc vào những module tầng thấp (low-level module). Cả hai đều chỉ nên phụ thuộc vào định nghĩa trừu tượng của chúng. (1)
Những định nghĩa trừu tượng không nên phụ thuộc vào chi tiết cụ thể mà ngược lại, chi tiết nên phụ thuộc vào định nghĩa trừu tượng. (2)"
Khá khó hiểu ... nhưng không sao, bạn đừng lo vì chúng ta sẽ mổ xẻ phát biểu này từng phần một. Tôi xin lấy phần lớn giải thích và các ví dụ từ chính nguồn của tác giả Robert C. Martin về nguyên tắc này trong bài viết của ông. Lý do là vì sau khi tham khảo rất nhiều nguồn trên internet, tôi nhận ra chẳng có giải thích nào tốt hơn bài viết của ông cả.
Đầu tiên ta sẽ tìm hiểu thế nào là các tầng của phần mềm. Theo Grady Booch(1): "... tất cả những kiến trúc hướng đối tượng tốt được chia làm nhiều tầng rõ ràng, mỗi tầng cung cấp một tập hợp các services chuyên biệt." . Thông thường, một phần mềm được cấu trúc gồm 3 tầng chính:



Tầng trên cùng là Presentation layer, nơi trực tiếp giao tiếp với người dùng bao gồm nhận và hiển thị dữ liệu. Tầng thứ nhì, business layer, là tầng xử lý các nghiệp vụ trên dữ liệu. Tầng cuối cùng, data layer, là tầng giao tiếp với database để thực hiện truy xuất và lưu trữ dữ liệu. Và đó chính là ý nghĩa tầng cao tầng thấp của module.
Trong lược đồ trên, tầng trên cùng Presentation sẽ dùng service cung cấp ở tầng Business tiếp theo Business dùng service ở tầng Data thấp nhất. Trông có vẻ ổn, nhưng thực tế mô hình này có điểm bất lợi vì sự phụ thuộc bị trải dài từ trên xuống dưới trên cả 3 tầng. Xin lấy ví dụ về chương trình Copy của tác giả:
Giả sử có một chương trình dùng để liên tục copy các chữ nhập vào từ bàn phím rồi gửi cho máy in như sau:

Mô hìnhCode

Giả sử nếu một lúc nào đó bạn muốn chương trình của bạn có thêm khả năng in ra file. Phương thức Copy này không thể sử dụng lại được. Do đó bạn có thể phải sửa lại phương thức Copy như sau:

Điều này sẽ trở nên rất phiền toái mỗi khi bạn muốn thêm vào một chức năng mới. Hơn nữa, việc sửa code làm bạn vi phạm nguyên tắc 2 Open/Closed. Để ý rằng nguyên nhân chính là do phương thức Copy phụ thuộc quá nhiều vào 2 phương thức của module tầng dưới: WriteToPrinter và ReadKeyBoard. Để giải quyết điều này, ta có thể tạo ra 2 lớp trừu tượng và Copy sẽ dùng 2 lớp này thay vì dùng trực tiếp.

Mô hìnhCode

Rõ ràng theo lược đồ trên, bạn đã thấy sự phụ thuộc không còn từ tầng cao xuống tầng thấp mà đã được giải quyết bằng cách tạo ra tầng trung gian abstract. Sau đó tầng sự phụ thuộc bị đổi ngược bằng cách tầng dưới phải dựa theo định nghĩa ở tầng trên mà implement. Đó chính là tại sao nguyên tắc này gọi là Dependency Inversion (hoán đổi phụ thuộc).



Cuối cùng, đây là một nguyên tắc khó nhất để hiện thực và tuân thủ. Nếu như Open/Closed là nguyên tắc khái quát quan trọng nhất thì đây chính là nguyên tắc chi tiết quan trọng nhất trong 5 nguyên tắc đã nêu. Trong thực tế, để hiện thực nguyên tắc này một cách triệt để, đôi khi bạn phải dùng khá nhiều bước design "cồng kềnh", thậm chí dùng đến những library để hiện thực nó.

Enjoy your coding!

References
(1) Object Solutions, Grady Booch, Addision Wesley 1996, p54
(2) The Dependency Inversion Principle, Robert C. Martin, C++ Report, May 1996

Monday, February 6, 2017

SOLID - Bài 4: Interface Segregation Principle

List các bài đã viết:
Nguyên tắc cơ bản trong thiết kế OOP: Bài mở đầu Generalization và Specification
SOLID - Bài 1: Single Responsibility Principle (SRP)
SOLID - Bài 2: Open Closed Principle (OCP)
SOLID - Bài 3: Liskov's Substitution Principle (LSP)
SOLID - Bài 4: Interface Segregation Principle (ISP)
SOLID - Bài 5: Dependency Inversion Principle (DIP)


Ở bài này ta sẽ tìm hiểu về nguyên tắc ứng với chữ I - chữ cái thứ tư trong SOLID - đó là Interface Segregation Principle (ISP). Tôi tạm dịch nguyên tắc này là "Tách Interface". Nội dung của nguyên tắc này phát biểu như sau:
"Các lớp thừa kế từ interface không nên bị ép buộc cài đặt những thứ nó không dùng đến từ interface đó"
Bạn có thể hiểu đơn giản thế này: "Lý do gì lại bán cái yên ngựa cho một gã không có  ngựa?" (What is the point in selling a horse saddle for one who does not own a horse?).
Thực ra tôi định sẽ viết một số bài riêng về interface và abstract, delegate ... nhưng vì tôi nhận ra điều đó hơi muộn, trong lúc viết cái series này, cho nên một số thứ các bạn tạm chấp nhận trước vậy (hoặc nếu bạn biết rồi thì càng tốt).
Interface giống như những ô cửa sổ. Bạn nhìn thế giới thông qua ô cửa sổ đó, và người ngoài kia nhìn vào nhà bạn cũng thông qua ô cửa sổ. Đứng ở ô cửa khác nhau, bạn thấy một góc mới của thế giới và người ở ngoài cũng vậy, nhìn vào ô cửa khác nhau, họ nhìn thấy những góc khác nhau của căn nhà của bạn. Việc gắn một cái cửa sổ vào nhà mình cũng là một việc cần suy nghĩ, vì chẳng ai gắn cả cái cửa sổ vào .. nhà vệ sinh cả hoặc vác cái cửa sổ quá to đến nỗi người ngoài nhìn thấy những góc mà bạn không muốn họ thấy.
Giả sử bạn gắn một cái cửa sổ không vừa với hơn cái tường nhà bạn, lúc đó sẽ có phần dư ra và trông sẽ rất dị hợm...
Sở dĩ tôi nói nhiều về "những cái cửa sổ" như vậy là để bạn hiểu tinh thần của nguyên tắc này. Đó là khi tạo ra một interface, nó cũng giống như việc lắp một cái cửa sổ vào nhà của bạn, bạn nên chọn cửa sổ vừa vặn với nhu cầu của bạn.
Ta xét ví dụ sau đây. Giả sử bạn xây dựng một module CMS (Content Management System). Hiện tại có 2 loại bài viết: Tin tức (News) và nghiên cứu (Research paper). Bạn định  nghĩa một bài viết bằng interface IArticle và 2 class News và Paper cài đặt lại interface IArticle:


Sau một thời gian, giả sử bạn thấy rằng các bài viết về tin tức độc giả có nhu cầu đóng góp ý kiến trong khi đối với các bài viết nghiên cứu thì độc giả không cần ý kiến mà muốn có 1 phần link tham khảo. Do đó, bạn sửa chương trình bạn lại thành:


Ví dụ trên là một ví dụ điển hình cho việc vi phạm nguyên tắc này (ISP). Bạn có thể thấy các class News và Paper đều phải implement những thứ thừa thãi mà nó ko dùng đến. Như vậy, làm sao để giải quyết tình trạng này? Câu trả lời là bạn nên tách interface IArticle thành các interface khác nhỏ hơn và chuyên biệt hơn (lại nhắc tôi nhớ đến GeneralizationSpecification). Xin xem một cách giải quyết dưới đây:


Sau đó cài đặt lại các interface chuyên biệt này nay vì interface gốc:


Như vậy, bạn có thể thấy rằng bằng việc tách ra thành các interface chuyên biệt hơn, bạn đã tránh được việc phải implement những thứ không cần thiết trong chương trình của bạn. Một lợi điểm của phương pháp này nữa là nó giúp bạn đối phó với các mở rộng trong tương lai. Giả sử bạn cần thêm một loại tin tức mới là Advertising, tin tức này không cần comment mà cũng ko cần references, bạn có thể tạo class IAdvertisement cài đặt trực tiếp IArticle. Hoặc một loại tin tức gì đó mà có thể có cả comment và references ví dụ: ITutorial và ITutorial cài đặt cả ICommentedArticle và IReferencedArticle. Tôi không đưa code mẫu vì nghĩ không cần thiết, bạn có thể tự làm điều này dễ dàng.
Tóm lại, thực ra nguyên tắc này là một trường hợp đặc biệt của nguyên tắc LSP ở bài trước, mà nghĩ rộng ra cũng liên quan rất nhiều đến nguyên tắc còn lại đã học OCP và SRP.

Enjoy your coding!

Sunday, February 5, 2017

GOF design patterns - Mô hình quan hệ các pattern


Dưới đây là mô hình quan hệ giữa các design pattern của Gang Of Four. Tôi sẽ có các bài viết chi tiết hơn về từng thành phần của mô hình này sau.




Mô hình trên được dịch và tham khảo từ cuốn sách nguyên thủy về design pattern của GOF: "Design Pattern".
Enjoy your coding!

Friday, February 3, 2017

SOLID - Bài 3: Liskov's Substitution Principle (LSP)

List các bài đã viết:
Nguyên tắc cơ bản trong thiết kế OOP: Bài mở đầu Generalization và Specification
SOLID - Bài 1: Single Responsibility Principle (SRP)
SOLID - Bài 2: Open Closed Principle (OCP)
SOLID - Bài 3: Liskov's Substitution Principle (LSP)
SOLID - Bài 4: Interface Segregation Principle (ISP)
SOLID - Bài 5: Dependency Inversion Principle (DIP)

Ta tiếp tục nguyên tắc thứ 3, ứng với chữ cái thứ 3 của SOLID - Liskov's Substitution Principle(LSP). Tôi tạm dịch là "Nguyên Tắc Đại Diện Cha-Con Liskov", nghe hơi kỳ dị và lệch nghĩa, nhưng vì tôi nhận thấy các bạn của tôi luôn gặp vấn đề về ghi nhớ nguyên tắc này nếu để nguyên mẫu. Vì thế tôi đã phải ngồi suy nghĩ cả nửa ngày để nghĩ ra một cái tên sao cho hình tượng nhất. Điều nữa là, tôi vẫn để lại chữ Liskov vì muốn giữ lại bản quyền tác giả, người đã đưa ra nó.
Bây giờ bạn sẽ tìm hiểu tại sao cái tên tôi đặt lại matched với nguyên tắc này. Nguyên văn của nguyên tắc này phát biểu như sau:
"Lớp con phải luôn có thể thay thế hoàn toàn bằng lớp cha của nó."
Có thể là tôi hiểu không tới, nhưng thành thực mà nói tôi không ưng ý với phát biểu này mấy. Vì vậy, tôi xin phép dịch lại theo ý mình cho dễ hiểu và dễ nhớ như sau, dĩ nhiên bạn có quyền thích kiểu nào cũng được miễn bạn cảm thấy thoải mái và không hiểu sai lệch là được:
"Lớp con khi đại diện cho lớp cha không được thay thế hành vi của của lớp cha"
Đây có thể hiểu một cách nôm na thế này: Những đứa con được quyền tự quyết định hành vi của nó miễn là không ảnh hưởng với truyền thống gia đình. Mỗi khi quyết định tạo ra một lớp thừa kế từ một lớp khác, bạn sẽ được thừa kế một số phương thức được cài đặt sẵn từ lớp cha. Bạn không nên điều chỉnh hành vi của các phương thức này tại các lớp con. Bạn chỉ nên cài đặt một số phương thức khác được đánh dấu là trừu tượng (abstract) trên lớp cha.
Vì sao?
- Trước tiên, bạn phải tôn trọng ý tưởng của người đi trước. Khi họ tạo ra 1 class, họ đã có một kế hoạc cụ thể chỉ ra phương thức nào được kế thừa không nên sửa, phương thức nào được quyền sửa (virtual), phương thức nào nên được cài đặt ở lớp con (abstract). Bạn nên làm theo.
- Ngược lại nếu không định nghĩa rõ điều này trong thiết kế, rất có thể người tạo ra lớp kế thừa sẽ vô tình thay đổi hành vi của cả chương trình mà không hề biết.
Xin xem ví dụ sau đây: Bạn tạo ra 1 lớp Dice (xúc sắc 6 mặt) như sau:
Người khác muốn tạo ra 1 lớp mới DiceEight (xúc sắc 8 mặt) thừa kế lại Dice để dùng lại property Rnd cho tiện. Nhưng vì xúc sắc mới 8 mặt nên anh ta sửa lại ThrowAValue cho phù hợp như sau:
Tuy nhiên, một tester viết phương thức test class dice của bạn, anh ta không hề biết rằng sẽ có người sẽ sửa vào phương thức ThrowAValue:
Rõ ràng nếu một ai đó dùng phương thức TestDice cho một object thuộc kiểu DiceEight anh ta sẽ gặp lỗi:
Nguyên tắc này thực chất làm cụ thể hơn nguyên tắc 2 Open Close Principle để ràng buộc các lớp con kế thừa lại từ lớp cha nhưng ko sửa đổi hành vi lớp cha. Như đã nói, nguyên tắc khác cũng đều xoay quanh nguyên tắc 2.

Enjoy your coding!

Wednesday, February 1, 2017

SOLID - Bài 2: Open Closed Principle

List các bài đã viết:
Nguyên tắc cơ bản trong thiết kế OOP: Bài mở đầu Generalization và Specification
SOLID - Bài 1: Single Responsibility Principle (SRP)
SOLID - Bài 2: Open Closed Principle (OCP)
SOLID - Bài 3: Liskov's Substitution Principle (LSP)
SOLID - Bài 4: Interface Segregation Principle (ISP)
SOLID - Bài 5: Dependency Inversion Principle (DIP)

Bài trước bạn đã làm quen với nguyên tắc đầu tiên - Single Responsibility Principle (Đơn Tác Vụ). Hôm nay ta tiếp tục nguyên tắc thứ 2, ứng với chữ cái thứ 2 của SOLID - Open Closed Principle. Tôi tạm dịch là "Nguyên Tắc Đóng và Mở".
Nguyên tắc này được phát biểu như sau:
"Các software entities (như class, modules, functions ...) nên được thiết kế để sẵn sàng mở rộng và hạn chế chỉnh sửa".
Có nghĩa là, chương trình của bạn khi tuân theo nguyên tắc này, một khi gặp yêu cầu mới, bạn sẽ chỉ cần mở rộng tính năng của nó ra thay vì mở code cũ ra để sửa.
Xin xem một ví dụ dưới đây cho rõ hơn.
Giả sử bạn cần thiết kế một chương trình để tính diện tích của một hình phẳng. Các hình cần tính bao gồm: hình tròn và hình vuông. Chương trình của bạn, ở version đầu sẽ như sau:

Vài hôm sau, gã sếp bảo hắn muốn thêm 1 tính năng mới đó là có thể tính diện tích hình chữ nhật nữa. Thế là bạn phải tạo thêm 1 class Rectangle, rồi lại tạo thêm 1 phương thức mới CalculateRectArea chẳng hạn.

Rồi lại một ngày đẹp trời, hắn lại bảo làm thêm cho hắn 1 chức năng tính tổng diện tích của một số hình bao gồm cả 3 loại trên. Thế là bạn lại sửa:

Và rồi, "hắn" lại muốn chương trình của bạn có thể tính thêm vài loại hình quái quỷ gì nữa, chẳng hạn như hình tam giác. Quá mệt mỏi vì lôi ra lôi vào sửa sửa xóa xóa. Bạn rủa gã sếp thậm tệ vì ko nói trước luôn 1 lần mà cứ chơi trò thêm bớt làm bạn cực nhọc.
Thế nhưng, nếu bạn cảm thấy cực nhọc trong vụ này, thì đó không hoàn toàn lỗi của gã kia mà còn một phần là do lỗi thiết kế của bạn. Tại sao ư? Xin hãy xem thiết kế dưới đây, tận dụng triệt để các tính chất của OOP và giải quyết bài toán một cách đẹp đẽ.

Như vậy, nếu có yêu cầu cần tính thêm diện tích cho 1 hình mới ví dụ như hình tam giác chẳng hạn, bạn ko cần phải sửa code hiện có nữa mà chỉ cần thêm 1 class mới Triangle như sau:

That's it. Có vậy thôi! Tới đây, chắc bạn đã hiểu tầm quan trọng của nguyên tắc này. Đây là nguyên tắc mà theo mình là quan trọng nhất trong 5 nguyên tắc, vì "sẵn sàng để mở rộng và hạn chế sửa chữa" là mục tiêu cốt yếu trong architectural design. Tuy nhiên, nguyên tắc này khá khái quát và trừu tượng so với các nguyên tắc còn lại. Thưc ra để hiện thực nguyên tắc này này bạn cần phải kết hợp tất cả các nguyên tắc còn lại một cách nhuần nhuyễn và khéo léo.

Mà thôi, chuyện đó để sau nha, còn bây giờ, enjoy your coding!

Monday, January 30, 2017

SOLID - Bài 1: Single Responsibility Principle

List các bài đã viết:
Nguyên tắc cơ bản trong thiết kế OOP: Bài mở đầu Generalization và Specification
SOLID - Bài 1: Single Responsibility Principle (SRP)
SOLID - Bài 2: Open Closed Principle (OCP)
SOLID - Bài 3: Liskov's Substitution Principle (LSP)
SOLID - Bài 4: Interface Segregation Principle (ISP)
SOLID - Bài 5: Dependency Inversion Principle (DIP)

SOLID là viết tắt của 5 chữ cái đầu trong 5 nguyên tắc(principle) cơ bản về thiết kế OOP được giới thiệu bởi Robert C. Martin:
- S: Single Responsibility Principle
- O: Open/Close Principle
- L: Liskov Substitution Principle
- I: Interface Segregation Principle
- D: Dependency Inversion Principle
Một chương trình một khi được thiết kế tuân thủ theo 5 nguyên tắc này sẽ trong sáng, dễ mở rộng và dễ bảo trì.
Để bắt đầu, chúng ta sẽ tìm hiểu nguyên tắc thứ nhất: Single Responsibility 
*Tôi tạm dịch nguyên tắc này là Đơn Tác Vụ cho các bạn dễ nhớ và dễ hình dung về nguyên tắc này.
Nội dung nguyên tắc này được phát biểu như sau: "Một class chỉ nên có một và chỉ một lý do để bị thay đổi."
Điều này có thể hiểu là mỗi class khi được tạo ra, nó chỉ gánh 1 trọng trách nhất định, không nên dồn nhiều trọng trách khác nhau cho cùng 1 class.
Ví dụ: Một chương trình gửi mail tự động bao gồm 3 tác vụ:
- Check giờ gửi
- Tạo nội dung email
- Gửi mail
Đầu tiên, ta có thể code như sau:
Thoạt tiên chương trình trên trông khá ổn. Cho đến một hôm ông sếp khó tính của bạn muốn sửa chương trình trên thay vì kiểm tra giờ gửi theo múi giờ hiện tại, ông ta muốn kiểm tra theo múi giờ ở US. Tiếp theo ông ta muốn mã hóa toàn bộ email trước khi gửi. Bạn đang lười và thực sự là bạn cũng có 1 thằng bạn đồng nghiệp đang ... ở không, hoàn toàn có thể nhờ nó. Tuy nhiên, khổ thay là vì hôm trước tất cả mấy cái method đó bạn nhét vô trong cùng 1 class cho nên 2 đứa phải làm cùng trên 1 file. Sau đó ngồi merge code oải gần chết... Vừa mới hì hục sửa xong, ông boss lại đòi gửi email thông qua server khác vì mail server bị hỏng, đồng thời check múi giờ với Thụy điển vì Email server ở Thụy điển... Đến đây chắc bạn sẽ phát điên ...
Đó, vấn đề là như thế đó. Bây giờ, ta xem cách giải quyết vấn đề như dưới đây:
- Ta tách ra thành nhiều class, mỗi class đúng một tác vụ chuyên biệt:

- Sau đó dùng lại trong chương trình chính:

Kể từ đây, mỗi lần thay đổi cái gì, bạn chỉ việc lôi cái class liên quan đến tác vụ ấy mà thay đổi. Không cần phải merge code nữa. Đỡ phải tẩy xóa chương trình chính của bạn...
Thực ra, code ở trên chưa hoàn hảo lắm. Nhưng mình ko muốn đưa vào nhiều thứ quá, sợ các bạn ngộp. Thôi cứ tạm hài lòng ở bước này đã rồi mình polish nó dần dần sau khi tìm hiểu các chương sau.

Còn bây giờ, enjoy your coding! :)

Wednesday, January 25, 2017

Nguyên tắc cơ bản trong thiết kế OOP: Bài mở đầu

List các bài đã viết:
Nguyên tắc cơ bản trong thiết kế OOP: Bài mở đầu Generalization và Specification
SOLID - Bài 1: Single Responsibility Principle (SRP)
SOLID - Bài 2: Open Closed Principle (OCP)
SOLID - Bài 3: Liskov's Substitution Principle (LSP)
SOLID - Bài 4: Interface Segregation Principle (ISP)
SOLID - Bài 5: Dependency Inversion Principle (DIP)

Lý do tôi viết loạt bài này là vì sau nhiều năm đi làm, gặp gỡ nhiều developer ở nhiều lứa tuổi và cấp bậc khác nhau, qua thảo luận, rất nhiều người trong số họ không cắt nghĩa được tại sao mình lại kiến trúc chương trình như vậy. Qua loạt bài viết này, tôi hy vọng cung cấp cho các bạn kiến thức cơ bản về architectural design bao gồm: SOLID, GRASP, GOF Design patterns ... và một số kinh nghiệm riêng của bản thân.
Đầu tiên có vài lời tôi muốn nói:
- Nếu các bạn không nằm trong số những người còn chưa nắm vững, chúc mừng bạn và mong bạn góp ý để bài viết tốt hơn.
- Tôi sẽ viết bằng tiếng Việt, rõ rồi, vì tôi đã nói là viết cho đa số các bạn mình mà! Tuy nhiên, tôi sẽ không dùng hoàn toàn 100% tiếng Việt, lý do là đôi khi tôi không tìm ra hoặc ko biết được từ nghĩa tương đương. Tuy nhiên tôi sẽ cố viết sao cho thật dễ hiểu và ngắn gọn.
- Có những thứ tôi chia sẻ từ kinh nghiệm thực tế của mình nên có thể bạn sẽ không tìm thấy từ bất cứ trong một nguồn tài liệu nào cả. 

Ok, giờ ta bắt đầu!

Nguyên tắc cơ bản trong lập trình nói chung, và trong OOP nói riêng, chúng ta chỉ cần code sao cho:
- Ngắn gọn và dễ hiểu. Nghĩa là sau khi code xong, người khác có thể nắm bắt ý tưởng của mình càng nhanh càng tốt. Ít nhất là cho chính bản thân, vài tháng sau quay lại đọc vẫn còn hiểu được mình đã làm cái gì.
- Code càng linh hoạt để đối phó với các sự thay đổi càng tốt. Đơn giản là vì requirement thay đổi liên tục lúc thêm cái này lúc bớt cái khác. Kể cả chính bản thân mình cũng thay đổi ý tưởng xoành xoạch.
Thực ra, giải pháp của chương trình là 1 luồng tư tưởng. Khi người khác đọc vào chương trình của mình, nếu họ năm được luồng tư tưởng này, họ sẽ rất dễ hiểu ý tưởng của mình. Có 2 tư tưởng chủ đạo trong thiết kế bao trùm tất cả mọi thứ ý tưởng khác. Đó là: Sự khái quát hóa (Generalization)sự chuyên biệt hóa (Specification). Hai tư tưởng này đối lập nhau nhưng lại hỗ trợ cho nhau.
- Một vấn đề càng khái quát càng dễ nắm bắt.
Ví dụ: Để lập trình giỏi tôi phải học nhiều và hiệu quả => nghe thì đơn giản, nhưng thế nào là nhiều? thế nào là hiệu quả? học cái gì??? Tuy nhiên người nghe sẽ nắm bắt được ngay ý đồ (muốn giỏi lập trình) và phương pháp (học nhiều và hiệu quả)
- Một giải pháp càng chi tiết càng dễ hình dung và cài đặt, do đó bảo trì và kiểm tra cũng sẽ dễ hơn. Cứ coi như tổng thể chương trình là 1 bài toán lớn. Bạn càng tách nhỏ nó bao nhiêu thì các bài toán nhỏ sẽ dễ giải hơn bấy nhiêu.
Như vậy làm sao để 2 khái niệm này tồn tại chung trong cùng một chỗ?
Đầu tiên, ta chia nhỏ vấn đề thành một chuỗi mắt xích nối tiếp nhau của các vấn đề con. Tại sao gọi là chuỗi mắt xích? Vì ngay sau khi kết thúc xử lý vấn đề này, ta có thể thực hiện ngay bước tiếp theo mà ko cần phải có công đoạn trung gian nào cả. Nếu vẫn còn thì có khả năng lớn là việc khái quát hóa của bạn cần xem lại. Lẽ dĩ nhiên là bạn phải dựa trên ngữ cảnh của bài toán lớn để đánh giá liệu giữa hai vấn đề trong mô hình của bạn còn chỗ hổng hay không.
Ví dụ: Quy trình của một website bán hàng online có thể phân ra thành các bước khái quát như sau:


Tiếp theo, đó là chuyên biệt hóa hay từng bước khái quát. Ví dụ theo mô hình trên, việc tiếp nhận khách hàng có thể chỉ là lấy IP người dùng, có thể là sẽ yêu cầu người dùng login. Phân loại khách hàng có thể phân loại dựa trên behavior của khách hàng rút ra từ hệ thống machine learning nào đó hoặc đơn giản chỉ là lấy dữ liệu từ database. Hoặc giao dịch có thể qua các payment gateway hoặc qua banking v.v... Như vậy chuyên biệt hóa gần giống với việc cung cấp các option cho từng bước khái quát bạn đã đặt ra.


Có thể thấy sau khi phân tích, chương trình của bạn đã có nhiều màu sắc hơn, nhiều lựa chọn hơn và vì thế dễ dàng thích ứng với các sự thay đổi hơn. Quan trọng là, nhìn vào mô hình, bạn biết được toàn bộ xử lý của chương trình, luôn biết phải làm gì và đang làm gì.
Sau này bạn sẽ có thể tìm thấy nhiều nguyên tắc về architectural design ở những nguồn khác, nhưng tất cả về cơ bản cũng chỉ là những biến thể của hai nguyên tắc trên.