Open – Close Principle – phần 1


Các bạn có thể xem loạt clip về SOLID tại đây: https://www.youtube.com/playlist?list=PLRLJQuuRRcFlRei0t0wbbCdzU8ujTD3jE

OCP là một trong những nguyên tắc quan trọng trong thiết kế phần mềm, mọi người thường nhớ đến nó như một trong 5 nguyên tắc SOLID. Tuy nhiên thực tế nó đã xuất hiện từ trước đó, được nhiều người thảo luận và sử dụng, ở nhiều cấp độ khác hơn là chỉ các class và interface… Trong bài này chúng ta sẽ cùng tìm hiểu OCP nhé.

Trước tiên, bạn hãy nhìn vào chương trình giải phương trình bậc 1 trong hình, đây là một chương trình rất đơn giản mà ai biết lập trình đều có thể hiểu được (kể cả khi bạn không biết C#). Chương trình này sẽ đọc các số a và b, sau đó trả về kết quả. Giờ ta hãy thử đưa chương trình này cho khách hàng, sau vài năm triển khai, liệu có thể có thêm những yêu cầu gì mới?

– Khách hàng muốn tăng năng suất lên 1 ngàn bài mỗi giờ, chương trình cần chuyển sang đọc dữ liệu từ bàn phím sang từ file.

– Kết quả cần được kết xuất ra file PDF thay vì màn hình, vì 1 ngàn kết quả mỗi giờ thì không ai đọc kịp.

– Một khách hàng đến từ Ả Rập (nghe là biết nhiều tiền rồi) yêu cầu tính kết quả của 1 triệu phương trình trong 1 giờ, vì không có máy tính nào xử lý nổi nên phải chia ra ra 5 máy, đồng thời nâng cấp thuật toán lên để tận dụng GPU. Dữ liệu phải kết xuất ra file text để tiếp tục xử lý trong chương trình khác.

– Phần mềm này sau đó được chuyển lên cloud thông qua một API, ai muốn dùng thì gửi dữ liệu đến và nhận về kết quả.

Vậy với mỗi thay đổi như trên, bạn cần sửa đổi những gì? Câu trả lời chính xác nhất là viết-lại-chương-trình-mới. Vì chương trình của bạn không có khả năng mở rộng nên bất kỳ thay đổi nào cũng sẽ cần đập đi xây lại.

Open – Close Principle là gì?

OCP được phát biểu là: “A software artifact should be open for extension but closed for modification.”

Một software artifact có thể là một class, một interface, một module, nhưng cũng có thể là một thành phần ở cấp độ cao hơn: một component (.dll, .jar files…), và thậm chí là một phần mềm (trong bài này ta sẽ nói đến class/interface cho dễ hiểu nhưng có thể áp dụng cho cả các cấp độ khác). Nó phải cho phép mở rộng các tính năng bằng cách thêm vào các thành phần mới (có thể thông qua thừa kế), nhưng cũng phải được thiết kế đủ tốt để không phải sửa lại code cũ (nôm na là khi thay đổi thì viết thêm code mới chứ không phải sửa code). Để làm điều này ta cần:

– Phân chia các class thành các class nhỏ hơn, đảm nhận duy nhất một mục đích cụ thể (Single Responsibility).

– Phân cấp chúng một cách hợp lý, các lớp chỉ phụ thuộc theo một chiều: chi tiết –> trừu tượng (Dependency Inversion).

Với class GiaiPhuongTrinh1, ta đã vi phạm Single Responsibility: nó vừa nhận nhiệm vụ nhập, vừa xuất và vừa tính kết quả, vì vậy khi cần thay đổi phương thức nhập dữ liệu, ta phải sửa lại toàn bộ (1). Ta cũng vi phạm Dependency Inversion, vì chương trình của chúng ta phụ thuộc vào một phương thức nhập dữ liệu cụ thể (từ bàn phím) thay vì một phương thức nhập liệu trừu tượng, do vậy cũng lại phải sửa chương trình mỗi khi cần hỗ trợ một phương thức nhập/xuất dữ liệu mới (2). Điều gì xảy ra nếu ta cần triển khai nhiều cấu hình khác nhau? Trên một số máy tính ta sẽ nhập từ bàn phím, trên một số máy khác ta đọc từ file,… Câu trả lời là ta sẽ phải viết 2 chương trình khác nhau. Gom cả (1) và (2), chương trình của ta hoàn toàn không có khả năng mở rộng.

Vậy ta sẽ thiết kế lại chương trình này như thế nào? Mời các bạn xem tiếp phần 2!

3 thoughts on “Open – Close Principle – phần 1

Leave a comment