Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. PHP
Code

SOLID: Phần 4 - Quy tắc Dependency Inversion

by
Difficulty:BeginnerLength:LongLanguages:
This post is part of a series called The SOLID Principles.
SOLID: Part 3 - Liskov Substitution & Interface Segregation Principles

Vietnamese (Tiếng Việt) translation by Andrea Ho (you can also view the original English article)

Single Responsibility (SRP)Open/Closed (OCP)Liskov Substitution, Interface Segregation, and Dependency Inversion. Năm quy tắc agile sẽ định hướng cho bạn mỗi khi bạn viết code.

Sẽ không công bằng khi bảo bạn rằng bất cứ quy tắc SOLID nào quan trọng hơn số còn lại. Tuy nhiên, không quy tắc nào có thể tác động lớn và ngay lập tức vào code của bạn hơn Dependency Inversion, viết tắt là DIP. Nếu bạn thấy những quy tắc khác khó áp dụng, thì hãy bắt đầu bằng quy tắc này và áp dụng những cái còn lại vào phần code đã tuân thủ DIP.

Định nghĩa

A. Các module cấp cao không nên phụ thuộc vào các module cấp thấp hơn Cả hai loại đều nên phụ thuộc vào những abstraction.
B. Abstraction không nên phụ thuộc vào các chi tiết. Chi tiết nên phụ thuộc vào abstraction.

Quy tắc này được Robert C. Martin định nghĩa trong cuốn sách của ông Agile Software Development, Principles, Patterns and Practices và sau đó được tái bản trong phiên bản C# của cuốn sách Agile Principles, Patterns, and Practices in C#, và đó là quyển cuối cùng của 5 quy tắc SOLID agile.

DIP trong thực tiễn

Trước khi viết mã, tôi sẽ kể bạn nghe một câu chuyện. Ở Syneto, chúng tôi từng không quá chăm chút cho code của chúng tôi, Vài năm trước, chúng tôi biết không nhiều và thậm chí dù chúng tôi nỗ lực hết sức, nhưng không phải tất cả dự án đều tốt. Chúng tôi đã trải qua thời kỳ khó khăn và đã khắc phục và học được nhiều điều thông qua thử nghiệm và mắc lỗi.

Những quy tắc SOLID và kiến trúc rõ ràng của Uncle Bob (Robert C. Martin) trở thành những yếu tố quan trong và thay đổi cách viết mã của chúng tôi sang những cách thật khó để diễn tả. Tôi sẽ cố gắng minh hoạ, súc tích, một vài quyết định về kiến trúc tuân thủ DIP và chúng đã có ảnh hưởng tuyệt vời đến dự án của chúng tôi.

Đa số các dự án web gồm 3 công nghệ chính: HTML, PHP và SQL. Phiên bản cụ thể của những ứng dụng mà chúng ta đang nói đến hoặc kiểu triển khai của SQL mà bạn sử dụng không có liên quan với nhau. Thông tin từ một biểu mẫu HTML phải kết thúc theo cách này hoặc cách khác trong cơ sở dữ liệu. PHP có thể mang đến sự kết dinh cho cả hai.

Điều chủ chốt để từ bỏ điều này là làm sao 3 công nghệ này đại diện cho lớp kiến trúc khác nhau: user interface, business logic, và persistence. Chúng ta sẽ nhanh chóng bàn về ẩn ý của những lớp này. Từ giờ, hãy tập trung vào vái thứ kỳ dị nhưng chúng là giải pháp thông dụng sử dụng để giúp 3 công nghệ này làm việc cùng nhau.

Nhiều lúc tôi thấy các dự án sử dụng SQL trong một thẻ PHP trong một file HTML, hoặc mã PHP in ra các trang và các trang HTML, và biên dịch trực tiếp các biến toàn cục $_LEFT hoặc $_POST. Nhưng sao điều này lại không tốt?

html-php-sql-cross-dependencies

Hình ảnh phía trên hiển thị một bản nháp của điều chúng ta mô tả trong đoạn văn trước.‘  Các mũi tên đại diện những phần phụ thuộc khác nhau, và ta có thể kết luận, cơ bản thì mọi thứ đều phụ thuộc lẫn nhau. Nếu chúng ta cần thay đổi một bảng dữ liệu, thì chúng sẽ phải điều chỉnh file HTML. Hoặc nếu chúng ta thay đội một trường trong HTML, thì chúng ta phải thay đổi tên cột trong câu SQL. Hoặc nếu chúng ta xem xét hình mẫu thứ hai. thì có lẽ chúng ta cần điều chỉnh mã PHP khi HTML có thay đổi, hoặc trong tình huống xấu, khi tạo ra các nội dung HTML từ file PHP thì chắc chắn ta cần thay đổi file PHP để thay đổi nội dung HTML. Vì thế không còn nghi ngờ gì phần phụ thuộc đan xen giữa các class và module. Nhưng việc này chưa kết thúc ở đây. Bạn có thể lưu PHP code trong các bảng SQL.

html-php-sql-stored-procedures

Trong mô hình bên trên, các truy vấn đến các cơ sở dữ liệu SQL trả về mã PHP được tạo ra chứa dữ liệu từ các bảng. Những hàm và class PHP này đang thực hiện những câu truy vấn SQL khác để trả về những mã PHP khác, và vòng xoay này tiếp tục diễn ra đến khi lấy được và trả về tất cả thông tin... có thể đưa đến UI.

Tôi biết điều này nghe kỳ quặc với nhiều bạn, nhưng nếu bạn chưa làm việc với một dự dán triển khai theo cách này, thì chắc chắn trong tương lai bạn sẽ gặp nó. Đa số dự án hiện tại, bất kể ngôn ngữ lập trình nào, được xây dựng theo tư duy của những quy tắc xưa cũ, bởi những lập trình viên không thực sự quan tâm hoặc biết đủ nhiều để thực hành tốt hơn. Nếu bạn đang đọc những bài hướng dẫn này, có thể bạn ở một trình độ cao hơn như thế. Bạn đã sẵn sàng hoặc chuẩn bị để tôn trọng nghề nghiệp của bạn, bám chặt vào kỹ thuật của bạn, và thực hiện tốt hơn.

Một lựa chọn khác là tiếp tục sai lầm do những người tiền nhiệm gây ra và sống cùng với những hậu quả đó. Tại Syneto, sau khi một dự án của chúng tôi gần như không thể bảo trì bởi kiến trúc xưa cũ và phụ thuộc lẫn nhau và chúng tôi phải loại bỏ nó mãi mãi, chúng tôi đã quyết định không quay lại con đường đó nữa. Kể thừ đó, chúng tôi phấn đấu để có một kiến trúc gọn gàng và tuân thủ đúng đắn các yếu tố SOLID, và quan trong nhất là quy tắc Dependency Inversion. 

HighLevelDesign

Điều tuyệt với về kiến trúc này là cách các phần phụ thuộc đang hướng tới:

  • Giao diện người dùng (trong đa số trường hợp là một framework MVC) hoặc bất cứ gì những cơ chế khác cho dự án của bạn sẽ phụ thuộc vào business logic. Business logic hoàn toàn trừu tượng. Giao diện người dùng thì cụ thể.  UI chỉ là một chi tiết cho dự án, và cũng rất bị can thiệp. Mọi thứ không nên phụ thuộc vào UI và framework MVC của bạn.
  • Những quan sát thú vị khác mà chúng ta có thể làm là sự ổn định, cơ sở dữ liệu, MySQL hoặc PostgeSQL của bạn, phụ thuộc vào business logic.  Business logic của bạn là cơ sở dữ liệu. Điều này giúp mang đến sự bền vững như bạn mong muốn. Nếu ngày mai bạn muốn thay đổi MySQL bằng PostgreSQL, hoặc file văn bản thuần tuý, thì bạn có thể làm điều đó. DĨ nhiên, bạn sẽ cần triển khai một persistence layer cụ thể cho phương thức persistence mới, nhưng sẽ không phải điều chỉnh một dòng code nào trong business logic của bạn. Có giải thích chi tiết hơn về chủ đề persistence trong bài hướng dẫn Evolving Toward a Persistence Layer.
  • Cuối cùng, bên phải của phần business logic trong hình, chúng ta có tất cả các class đang tạo ra những class của business logic. Đó là những factory của các class được tạo ra bởi điểm bắt đầu của ứng dụng. Nhiều người sẽ nghĩ rằng những cái đó thuộc về business logic, nhưng khi họ tạo những đối tượng business, lý do duy nhất của họ là để làm điều này. Đó là chững class chỉ giúp chúng ta tạo ra những class khác. Các đối tượng business và logic chúng mang lại trở thành tách biệt độc lập với factory của chúng. Chúng ta có thể dùng những pattern khác nhau, như Simple Factory, Abstract Factory, Builder hoặc việc tạo ra một đối tượng theo cách thuần tuý để cung cấp business logic. Điều đó chẳng là gì. Một khi đối tượng business được tạo ra, chúng có thể thực hiện nhiệm vụ của chúng.

Cho tôi xem code

Vệc áp dụng quy tắc Dependency Inversion trong tầng kiến trúc khá dễ nếu bạn tuân theo design pattern cổ điển Agile. Tập luyện và minh hoạ nó bên trong business logic khá dễ và thậm chí có thể khá vui. Chúng ta sẽ hình dung một ứng dụng đọc sách điện tử.

Chúng ta phát triển e-reader thành một PDF-reader (chuyên đọc PDF)æ Mọi thứ đền ổn đến thời điểm này. Chúng ta có class PDFReader và sử dụng PDFBook. Hàm read() trên reader uỷ quyền cho phương thức read() của cuốn sách. Chúng ta chỉ xác thực điều này bằng các kiểm tra bằng regex sau phần quan trọng của String; kết quả trả về từ phương thức reader() của  PDFBook:

Hãy ghi nhớ rằng đây chỉ là một ví dụ: Chúng tôi sẽ không triển khai logic đọc file PDF hoặc những định dạng file khác. Điều này lý giải vì sao các thử nghiệm của chúng ta chỉ đơn giản kiểm tra vài chuỗi cơ bản. Nếu chúng ta xây dựng một ứng dụng thật sự, khác biệt duy nhất sẽ là cách chúng ta kiểm tra những định dạng file khác nhau. Cấu trúc phụ thuộc sẽ tương tự với ứng dụng của chúng ta.

pdfreader-pdfbook

Có một trình đọc PDF dùng sách PDF có lẽ là một giải pháp phù hợp cho một ứng dụng bị hạn chế. Nếu phạm vi triển khai là viết một trình đọc PDF và chỉ thế thôi, thì đây thực sự là một giải pháp có thể chấp nhận. Nhưng chúng ta muốn một trình đọc sách điện tử nói chung, hỗ trợ nhiều định dạng. Hãy đặt tên khác cho class của trịnh đọc sách.

Việc đặt tên không có ảnh hưởng đến chức năng. Những thử nghiệm vẫn thông qua.

Thử nghiệm bắt đầu lúc 1:04 PM...
PHPUnit 3.7.28 phát triển bởi Sebastian Bergmann.
Thời gian: 13 ms, Bộ nhớ: 2.50Mb
OK (1 test, 1 assertion)
Quá trình kết thúc với exit code 0

Nhưng có một ảnh hưởng nghiêm trọng về thiết kế.

ebookreader-pdfbook

Trình đọc sách đã trở nên trừu tượng hơn. Tổng quá hơn rất nhiều. Chúng ta có một EbookReader tổng quát sử dụng một loại sách cụ thể PDFBook.  Môt abstraction phụ thuộc vào một chi tiết. Thực tế sách của chúng ta thuộc loại PDF, chỉ nên là một chi tiết và không nên phụ thuộc vào nó.

Giải pháp hổ biến nhất và thường dược sử dụng nhất để thay đổi sự phụ thuộc là bổ sung thêm một module trừu tượng hơn trong thiết kế của chúng ta. "Thành phần trừu tượng nhất trong OOP là một interface. Do đó, bất kỳ class nào cũng có thể phụ thuộc vào một interface và vẫn tuân thủ DIP."

Chúng tôi đã tạo interface cho trình đọc sách này. Interface có tên gọi là Ebook và đại diện cho yêu cầu của EBookReader. Đây là kết quả trực tiếp của việc tuân thủ quy tắc Interface Segregation (ISP), nó minh chứng cho khái niệm các interface nên phản ánh yêu cầu của client. Các interface thuộc về các client, do vậy chúng được đăt tên để phản ánh các kiểu và đối tượng mà client cần có và sẽ chứa những phương thức mà client cần dùng. Sẽ hoàn toàn dễ hiểu khi một EBookReader sử dụng Ebooks và có phương thức read() của chính nó.

ebookreader-ebookinterface-pdfbook

Thay vì có một phụ thuộc, chúng ta có đến 2 phụ thuộc tại đây.

  • Phụ thuộc đầu tiên từ EBookReader hướng đến interface Ebook. EBookReader sử dụng Ebooks
  • Phụ thuộc thứ hai có khác biệt. Nó chỉ từ PDFBook đến cùng interface Ebook nhưng nó một kiểu triển khai. PDFBook chỉ là một dạng cú thể của EBook, và do đó khai triển interface sẽ thoã mản cho client.

Không có ngạc nhiên nào khi giải pháp này cũng cho phép chúng ta dùng với những loại ebook khác nhau trong trình đọc sách này. Điều kiện duy nhất cho tất cả những cuốn sách này là thoã mãn interface Ebook và triển khai chúng.

Điều này lần lượt dẫn lối chúng ta đến các quy tắc Open/Closed. và quy trình được khép lại..

Quy tắc dependency inversion dẫn dắt hoặc giúp chúng ta tuân theo tất cả các quy tắc khác. Việc tuân thủ DIP sẽ:

  • Hầu như thúc ép bạn tuân thủ OOP.
  • Cho phép bạn tách biệt những nhiêm vụ, trách nhiệm.
  • Sử dụng các kiểu thứ cấp đúng đắn.
  • Đề xuất cơ hôi tách biệt các interface của bạn.

Tổng kết

Thế đấy. Chúng ta đã hoàn tất. Tất cả hướng dẫn về các quy tắc SOLID đã hoàn thành. Với cá nhân tôi mà nói việc khám phá những quy tắc này và triển khai các dự án với chúng là một thay đổi cực lớn. Tôi hoàn toàn thay đổi cách nghĩ về thiết kế và kiến trúc và tôi có thể nói tất cả dự án tôi đang thực hiện thật dễ để quản lý và hiểu rõ.

Tôi xem các quy tắc SOILD là một trong những khái niệm quan trọng nhất của thiết kế hướng đối tượng. Những khái niệm này hướng dẫn chúng ta viết code tốt hơn và cuộc sống của những lập trình viên dễ hơn rất nhiều. Những code được thiết kế tốt giúp lập trình viên dễ hiểu hơn. Máy tính rất thông minh, chúng có thể hiểu code dù phức tạp thế nào. Mặt khác con người cũng có những giới hạn họ có thể duy trì suy nghĩ năng động và tập trung. Đặc biệt hơn, con số những điều như vậy là Magical Number Seven, Plus or Minus Two.

Chúng ta nên nỗ lực cấu trúc code của chúng ta với những con số này và có vài kỹ thuật có thể giúp chúng ta làm điều này. Chức năng tối đa có 4 dòng (tính luôn dòng định nghĩa là 5) vì thế chúng ta có thể trong một lần nhớ chúng. Những phần thụt lề không vượt quá 5 lần. Các class không có nhiều hơn 9 phương thức. Các design pattern thường có từ 5 đến 9 class. Thiết kế cấp cao trong những schema phía trên ứng dụng 4 đến 5 khái niệm. Có 5 quy tắc SOLID, mỗi quy tắc được minh hoạ từ 5 đến 9 khái niệm thứ cập hoặc module hoặc class. Một nhóm lập trình lý tưởng có khoảng 5 đến 9 người. Một công ty lý tưởng nên có từ 5 đến 9 đội ngũ.

Như bạn thấy, con số 7 kỳ diệu, cộng hoặc trừ 2, luôn xoay quanh ta, vậy sao code của bạn phải khác đi?

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.