Không nên để ám ảnh với mẫu thiết kế (design pattern)
Những mẫu thiết kế có thể là trợ thủ đắc lực khi chúng ta sử dụng chúng một cách chính xác. Tuy nhiên, khi sử dụng không đúng thì lợi bất cập hại. Việc cấp thiết là làm thế nào chúng ta sử dụng chúng một cách chính xác. Bởi vì một mẫu thiết kế là một giải pháp được định sẵn cho một ...
Những mẫu thiết kế có thể là trợ thủ đắc lực khi chúng ta sử dụng chúng một cách chính xác.
Tuy nhiên, khi sử dụng không đúng thì lợi bất cập hại. Việc cấp thiết là làm thế nào chúng ta sử dụng chúng một cách chính xác.
Bởi vì một mẫu thiết kế là một giải pháp được định sẵn cho một vấn đề cụ thể, được kiểm chứng qua thời gian và được biết đến trong cộng đồng phần mềm, bạn có thể rất muốn áp dụng nó ngay khi bạn có cơ hội.
Khi bạn học một công nghệ mới, rất tốt để thử áp dụng nó ngay. Tuy nhiên, quan niệm “tìm cơ hội dù là nhỏ nhất để đưa mẫu thiết kế vào code” có vẻ là một ý tồi đối với tôi.
Làm sao để tôi biết khi nào là tốt nhất để sử dụng mẫu thiết kế? Chúng ta sẽ thấy ở bên dưới, việc đánh giá khả năng những thay đổi trong các phần khác nhau của mã nguồn và hiểu mục đích của mẫu thiết kế mà chúng ta đang sử dụng là quan trọng để đưa ra quyết định thích hợp.
Tôi đã đi đến kết luận này bằng việc xem xét 3 điều sau. Ta hãy điểm qua chúng.
Nếu bạn biết và áp dụng các nguyên tắc thiết kế hướng đối tượng, những mẫu thiết kế sẽ đến một cách tự nhiên.
Tôi tin rằng việc biết các nguyên tắc thiết kế hướng đối tượng và áp dụng những nguyên tắt như SOLID, KISS và YAGNI thì quan trọng hơn nhiều so với chính các mẫu thiết kế. Nếu bạn áp dụng những nguyên tắc này thì các mẫu thiết kế sẽ đến một cách tự nhiên.
Chúng ta hãy xem xét một ví dụ. Giả sử chúng ta đang làm một ứng dụng nhỏ về việc xuất một bản nhạc lên màn hình. Bây giờ, để tinh giản hóa vấn đề, chúng ta chỉ cần vẽ nốt trắng và nốt đen.
Trong số các lớp khác nhau của hệ thống, chúng ta có lớp sau:
Như bạn thấy, rõ rang có một sự trùng lập ở đây. Chỉ có khác biệt là tham số truyền vào của hàm drawNoteHead.
Sau khi xem xét, có lẽ bạn đi đến kết luận để loại bỏ sự trùng lặp ta sẽ làm như sau:
1- Tạo một phương thức chung drawNote và phần thân hàm được sao chép từ một trong hai hàm đã có trên.
2 – Tham số truyền vào của phương thức drawNoteHead sẽ được lấy bằng một phương thức mới. Và, mã nguồn của phương thức mới này có thể được viết bởi những lớp con, vì vậy lớp cha vẫn giữ được lôgic căn bản và các lớp con vẫn giữ được những khác biệt.
3 – Thay thế tất cả những phương thức gọi đến drawHalfNote và drawQuaterNote bằng phương thức drawNote của lớp con riêng biệt.
Được rồi, hãy bắt tay vô nào:
Có thể có giải pháp khác, nhưng chúng ta đã đạt được điều chúng ta muốn: loại bỏ sự trùng lặp của mã nguồn.
Đoán xem nào.
Bạn vừa áp dụng mẫu thiết kế Template đấy.
Chỉ như vậy thôi. Chẳng cần phải suy nghĩ. Đó là điểm hay của các nguyên tắc thiết kế hướng đối tượng.
Bạn có thể không biết tất cả mẫu thiết kế, nhưng chừng nào mà bạn hiểu rõ những nguyên tắc thiết kế hướng đối tượng, bạn chẳng cần phải biết hết làm gì.
Tất nhiên, nếu bạn đã hiểu rõ mẫu thiết kế Template, bạn có thể thấy nó ngay lập tức. Bạn sẽ nói “nó thì hoàn hảo để áp dụng mẫu thiết kế Template”. Cách đúng để áp dụng các mẫu thiết kế là: khi bạn thấy rõ, chúng phù hợp và chúng làm cho công việc của bạn dễ dàng hơn.
Điều này dẫn tôi đến quan điểm thứ hai.
Bạn có thể không cần nó
Đôi khi chúng ta nhìn vào một đoạn mã, chúng ta rùng mình trước sự xấu xí của nó, và chúng ta nhận thấy ngay lập tức cần áp dụng mẫu thiết kế để làm cho nó tốt hơn.
Nhưng chuyện gì sẽ xảy ra khi nó đang làm việc tốt và bạn không cần thay đổi nó?
Một ví dụ, chúng ta quay trở lại với chương trình xuất bản nhạc lên màn hình, nhưng với một kịch bản hơi khác. Tưởng tượng rằng chúng ta muốn tạo một lớp cho từng loại khóa nhạc. Ứng dụng của chúng ta sẽ hỗ trợ tất cả các khóa nhạc dựa trên 3 khóa chính: khóa C, khóa F và khóa G.
Chúng ta có một hàm trả về đúng khóa nhạc mà chúng ta muốn. Nó trông như sau:
Nó là một mã nguồn xấu xí. Có lẽ bạn muốn cải tiến(refactor) nó, phải không? Có lẽ bạn đang nghĩ đến việc áp dụng mô hình Factory Method, vâng, nó sẽ phù hợp và chắn chắn sẽ làm cho code của bạn sạch hơn.
Nhưng nếu tôi nói với bạn rằng mã nguồn này sẽ chẳng bao giờ thay đổi?
Tôi không phải là một nhạc sĩ, nên có thể tôi sai, nhưng dường như không thực tế khi nghĩ rằng sẽ có những nốt mới được phát minh trong tương lai. Vậy nếu không có nốt nào được thêm vào thì thật sự không cần thiết phải thay đổi mã nguồn trên. Dù cho mã nguồn đó có xấu xí như thế nào đi nửa nhưng nếu nó hoạt động tốt thì bạn không cần thay đổi nó, việc tái cấu trúc mã nguồn (với một mẫu thiết kế hoặc bất kì kỹ thuật nào) sẽ tốn một chi phí không cần thiết.
Việc này dẫn dắt tôi đến quan điểm thứ ba.
Chi phí cho việc áp dụng mẫu thiết kế
Có rất nhiều mẫu thiết kế khác nhau, nhưng hầu hết chúng đều có điểm chung: khi bạn áp dụng chúng, bạn cần đề ra một vài cấu trúc. Nói cách khác, bạn cần thêm một số lớp hoặc giao diện hoặc cả hai vào mã nguồn.
Trong ví dụ đầu tiên, cấu trúc này bao gồm một lớp trừu tượng được kế thừa bởi hai lớp con. Hơn nữa, để mã nguồn cũ sử dụng được những lớp mới này, bạn cũng cần thực hiện một vài thay đổi không liên quan trực tiếp đến bản thân mẫu thiết kế.
Bài học được rút ra là: nếu bạn đang nghĩ về việc áp dụng một mẫu thiết kế, hãy cân nhắc đến chi phí thực hiện và những lợi ích tiềm năng. Việc chỉ làm cho có sẽ làm cho mã nguồn của bạn phức tạp hơn.
Làm thế nào tôi có thể biết khi nào là tốt nhất để sử dụng một mẫu thiết kế?
Đó là câu hỏi triệu đô la. Nó phụ thuộc vào 2 điều: mã nguồn sẽ thay đổi như thế nào trong tương lai và mục đích của mẫu thiết kế.
Khả năng thay đổi trong các phần khác nhau của mã nguồn.
Những ví dụ trong bài viết này giới thiệu tầm quan trọng của việc xem xét các thay đổi để quyết định xem có nên áp dụng mẫu thiết kế hay không.
Trong ví dụ đầu tiên, áp dụng mẫu thiết kế hoàn toàn là cần thiết. Bởi vì, chúng ta không chỉ xuất bản nhạc mà chỉ với nốt trắng và nốt đen, phải không? Vì khi thêm một số nốt nữa sẽ làm cho vấn đề trùng lặp càng tệ hơn, khi đó cần một mô hình để tránh điều đó.
Ở ví dụ hai, nó hoàn toàn trái ngược. Vì chúng ta có thể bảo đảm 100% rằng không có khóa nhạc hoặc nốt nhạc nào được thêm vào, đoạn code không cần thiết phải thay đổi và vì vậy, không cần thiết phải áp dụng mẫu thiết kế.
Mục đích của mô hình
Thực sự là chưa đủ khi chỉ biết mẫu thiết kế được triển khai như thế nào; mà chúng ta cũng cần biết lý do phía sau sự tồn tại của nó.
Nói một cách khác: chúng ta cần biết mô hình được tạo ra để làm gì.
Chúng ta cần hiểu một cách thấu đáo loại vấn đề mà mẫu thiết kế giải quyết. Như thế, chúng ta sẽ dễ dàng biết được khi nào là thời gian thích hợp để áp dụng nó.
Khi thực hiện những loại quyết định như thế này, tôi nhớ đến 2 quyển sách:
– Head First Design Patterns: nếu bạn chưa bao giờ nghe nói đến các mẫu thiết kế trước đó, hoặc bạn chỉ mới bắt đầu tìm hiểu thì quyển sách này là thực sự cần thiết. Nó giải thích những mẫu thiết kế được sử dụng thường xuyên nhất theo một cách rất trực quan và dễ hiểu.
– Design Patterns: Elements of Reusable Object-Oriented Software: đây là một cuốn sách kinh điển mà bạn cần phải đọc. Nó được viết như bảng tổng hợp các mẫu thiết kế phổ biến nhất, được tổ chức thành 3 mục chính: khởi tạo, khởi dựng và hành vi.
Thật không may, lại có thêm một vấn đề khác mà chúng ta phải đối mặt đó là: việc nhận ra và áp dụng các mô hình trong các dự án.
Khi bạn viết code mới từ đầu, khá dễ dàng để nhận ra sự cần thiết phải có mẫu thiết kế. Tuy nhiên, việc áp dụng mẫu thiết kế cho code cũ thì khó khăn hơn.
Cần phải chắc chắn là bạn đã hiểu toàn bộ mã nguồn làm việc như thế nào trước khi đụng vào nó. Việc này có thể là dễ dàng hoặc là đau thương, phụ thuộc vào độ phức tạp của code.
Thật may cho chúng ta, có một quyển sách khác có thể giúp về vấn đề này: Refactoring to Patterns. Cuốn sách này chỉ cho bạn cách thực sự tích hợp mẫu thiết kế vào mã nguồn đã có sẵn.
Những quyển sách trên, cùng với nhiều quyển khác, đều nằm trong danh sách, sách về lập trình của John mà tôi khuyên bạn nên đọc.
Kết luận
Các mẫu thiết kế không phải là “Chén thánh” của lập trình. Trong thực tế tôi không nghĩ một điều như vậy tồn tại.
Chúng đơn giản chỉ là cơ chế làm cho code của chúng ta rõ ràng hơn và dễ dàng hơn để hiểu và bảo trì…khi áp dụng đúng.
Người viết: Lê Minh Thiện
Các bạn muốn chia sẻ bài viết cho Techtalk vui lòng gửi về contact@techtalk.vn