paint-brush
Nắm vững các nguyên tắc CHẮC CHẮN như lòng bàn tay chỉ trong 8 phút!từ tác giả@arulvalananto
26,405 lượt đọc
26,405 lượt đọc

Nắm vững các nguyên tắc CHẮC CHẮN như lòng bàn tay chỉ trong 8 phút!

từ tác giả Arul Valan Anto11m2023/07/07
Read on Terminal Reader

dài quá đọc không nổi

Trong blog này, tôi sẽ chứng minh việc triển khai các nguyên tắc SOLID trong ứng dụng React. Đến cuối bài viết này, bạn sẽ nắm bắt đầy đủ các nguyên tắc RẮN.
featured image - Nắm vững các nguyên tắc CHẮC CHẮN như lòng bàn tay chỉ trong 8 phút!
Arul Valan Anto HackerNoon profile picture
0-item

Trong blog này, tôi sẽ chứng minh việc triển khai các nguyên tắc SOLID trong ứng dụng React. Đến cuối bài viết này, bạn sẽ nắm bắt đầy đủ các nguyên tắc RẮN. Trước khi chúng ta bắt đầu, hãy để tôi giới thiệu ngắn gọn cho bạn về các nguyên tắc đó.

Nguyên tắc SOLID là gì?

Nguyên tắc SOLID là năm nguyên tắc thiết kế giúp chúng tôi giữ cho ứng dụng của mình có thể tái sử dụng, có thể bảo trì, có thể mở rộng và liên kết lỏng lẻo. Các nguyên tắc RẮN là:


  • Nguyên tắc đơn trách nhiệm
  • Nguyên tắc đóng mở
  • Nguyên tắc thay thế Liskov
  • Nguyên tắc phân biệt giao diện
  • Nguyên tắc đảo ngược phụ thuộc


Được rồi, chúng ta hãy xem xét từng nguyên tắc riêng lẻ. Tôi sử dụng React làm ví dụ, nhưng các khái niệm cốt lõi cũng tương tự như các ngôn ngữ và khuôn khổ lập trình khác.

Nguyên tắc một trách nhiệm

“Một mô-đun phải chịu trách nhiệm cho một và chỉ một diễn viên.” —Wikipedia.


Nguyên tắc Trách nhiệm Đơn lẻ nêu rõ rằng một thành phần nên có một mục đích hoặc trách nhiệm rõ ràng. Nó nên tập trung vào chức năng hoặc hành vi cụ thể và tránh đảm nhận các nhiệm vụ không liên quan. Việc tuân theo SRP làm cho các thành phần trở nên tập trung hơn, có tính mô-đun hơn, dễ hiểu và dễ sửa đổi. Hãy xem triển khai thực tế.


 // ❌ Bad Practice: Component with Multiple Responsibilities const Products = () => { return ( <div className="products"> {products.map((product) => ( <div key={product?.id} className="product"> <h3>{product?.name}</h3> <p>${product?.price}</p> </div> ))} </div> ); };


Trong ví dụ trên, thành phần Products vi phạm Nguyên tắc Trách nhiệm Đơn lẻ bằng cách đảm nhận nhiều trách nhiệm. Nó quản lý việc lặp lại các sản phẩm và xử lý kết xuất giao diện người dùng cho từng sản phẩm. Điều này có thể làm cho thành phần khó hiểu, bảo trì và thử nghiệm trong tương lai.

Thay vào đó, hãy làm điều này để tuân thủ SRP:

 // ✅ Good Practice: Separating Responsibilities into Smaller Components import Product from './Product'; import products from '../../data/products.json'; const Products = () => { return ( <div className="products"> {products.map((product) => ( <Product key={product?.id} product={product} /> ))} </div> ); }; // Product.js // Separate component responsible for rendering the product details const Product = ({ product }) => { return ( <div className="product"> <h3>{product?.name}</h3> <p>${product?.price}</p> </div> ); };


Sự tách biệt này đảm bảo mỗi thành phần có một trách nhiệm duy nhất, giúp chúng dễ hiểu, kiểm tra và bảo trì hơn.

Nguyên tắc đóng mở

“Các thực thể phần mềm (lớp, mô-đun, chức năng, v.v.) phải được mở để mở rộng, nhưng đóng để sửa đổi.” —Wikipedia.


Nguyên tắc Mở-Đóng nhấn mạnh rằng các thành phần nên mở để mở rộng (có thể thêm các hành vi hoặc chức năng mới) nhưng đóng để sửa đổi (mã hiện có sẽ không thay đổi). Nguyên tắc này khuyến khích việc tạo mã có khả năng phục hồi để thay đổi, theo mô-đun và dễ bảo trì. Hãy xem triển khai thực tế.


 // ❌ Bad Practice: Violating the Open-Closed Principle // Button.js // Existing Button component const Button = ({ text, onClick }) => { return ( <button onClick={onClick}> {text} </button> ); } // Button.js // Modified Existing Button component with additional icon prop (modification) const Button = ({ text, onClick, icon }) => { return ( <button onClick={onClick}> <i className={icon} /> <span>{text}</span> </button> ); } // Home.js // 👇 Avoid: Modified existing component prop const Home = () => { const handleClick= () => {}; return ( <div> {/* ❌ Avoid this */} <Button text="Submit" onClick={handleClick} icon="fas fa-arrow-right" /> </div> ); }


Trong ví dụ trên, chúng tôi sửa đổi thành phần Button hiện có bằng cách thêm một icon chống đỡ. Việc thay đổi một thành phần hiện có để đáp ứng các yêu cầu mới vi phạm Nguyên tắc Đóng-Mở. Những thay đổi này làm cho thành phần này trở nên dễ vỡ hơn và có nguy cơ xảy ra các tác dụng phụ ngoài ý muốn khi được sử dụng trong các ngữ cảnh khác nhau.

Thay vào đó, hãy làm điều này:

 // ✅ Good Practice: Open-Closed Principle // Button.js // Existing Button functional component const Button = ({ text, onClick }) => { return ( <button onClick={onClick}> {text} </button> ); } // IconButton.js // IconButton component // ✅ Good: You have not modified anything here. const IconButton = ({ text, icon, onClick }) => { return ( <button onClick={onClick}> <i className={icon} /> <span>{text}</span> </button> ); } const Home = () => { const handleClick = () => { // Handle button click event } return ( <div> <Button text="Submit" onClick={handleClick} /> {/* <IconButton text="Submit" icon="fas fa-heart" onClick={handleClick} /> </div> ); }


Trong ví dụ trên, chúng tôi tạo một thành phần chức năng IconButton riêng biệt. Thành phần IconButton gói gọn việc hiển thị nút biểu tượng mà không sửa đổi thành phần Button hiện có. Nó tuân thủ Nguyên tắc Mở-Đóng bằng cách mở rộng chức năng thông qua thành phần hơn là sửa đổi.

Nguyên tắc thay thế Liskov

“Các đối tượng loại con nên được thay thế cho các đối tượng siêu loại” - Wikipedia.


Nguyên tắc thay thế Liskov (LSP) là một nguyên tắc cơ bản của lập trình hướng đối tượng, nhấn mạnh nhu cầu về khả năng thay thế của các đối tượng trong một hệ thống phân cấp. Trong ngữ cảnh của các thành phần React, LSP thúc đẩy ý tưởng rằng các thành phần dẫn xuất có thể thay thế các thành phần cơ sở của chúng mà không ảnh hưởng đến tính chính xác hoặc hành vi của ứng dụng. Hãy xem triển khai thực tế.


 // ⚠️ Bad Practice // This approach violates the Liskov Substitution Principle as it modifies // the behavior of the derived component, potentially resulting in unforeseen // problems when substituting it for the base Select component. const BadCustomSelect = ({ value, iconClassName, handleChange }) => { return ( <div> <i className={iconClassName}></i> <select value={value} onChange={handleChange}> <options value={1}>One</options> <options value={2}>Two</options> <options value={3}>Three</options> </select> </div> ); }; const LiskovSubstitutionPrinciple = () => { const [value, setValue] = useState(1); const handleChange = (event) => { setValue(event.target.value); }; return ( <div> {/** ❌ Avoid this */} {/** Below Custom Select doesn't have the characteristics of base `select` element */} <BadCustomSelect value={value} handleChange={handleChange} /> </div> );


Trong ví dụ trên, chúng ta có một thành phần BadCustomSelect nhằm phục vụ như một đầu vào lựa chọn tùy chỉnh trong React. Tuy nhiên, nó vi phạm Nguyên tắc thay thế Liskov (LSP) vì nó hạn chế hành vi của phần tử select cơ sở.

Thay vào đó, hãy làm điều này:

 // ✅ Good Practice // This component follows the Liskov Substitution Principle and allows the use of select's characteristics. const CustomSelect = ({ value, iconClassName, handleChange, ...props }) => { return ( <div> <i className={iconClassName}></i> <select value={value} onChange={handleChange} {...props}> <options value={1}>One</options> <options value={2}>Two</options> <options value={3}>Three</options> </select> </div> ); }; const LiskovSubstitutionPrinciple = () => { const [value, setValue] = useState(1); const handleChange = (event) => { setValue(event.target.value); }; return ( <div> {/* ✅ This CustomSelect component follows the Liskov Substitution Principle */} <CustomSelect value={value} handleChange={handleChange} defaultValue={1} /> </div> ); };


Trong mã sửa đổi, chúng ta có một thành phần CustomSelect nhằm mở rộng chức năng của thành phần select tiêu chuẩn trong React. Thành phần chấp nhận các đạo cụ như value , iconClassName , handleChange và các đạo cụ bổ sung bằng cách sử dụng toán tử trải rộng ...props . Bằng cách cho phép sử dụng các đặc điểm của phần tử select và chấp nhận các đạo cụ bổ sung, thành phần CustomSelect tuân theo Nguyên tắc thay thế Liskov (LSP).

Nguyên tắc phân tách giao diện

“Không có mã nào bị buộc phải phụ thuộc vào các phương thức mà nó không sử dụng.” —Wikipedia.


Nguyên tắc Phân tách Giao diện (ISP) gợi ý rằng các giao diện nên được tập trung và điều chỉnh theo yêu cầu cụ thể của khách hàng hơn là quá rộng và buộc khách hàng phải triển khai chức năng không cần thiết. Hãy xem triển khai thực tế.


 // ❌ Avoid: disclose unnecessary information for this component // This introduces unnecessary dependencies and complexity for the component const ProductThumbnailURL = ({ product }) => { return ( <div> <img src={product.imageURL} alt={product.name} /> </div> ); }; // ❌ Bad Practice const Products = ({ product }) => { return ( <div> <ProductThumbnailURL product={product} /> <h4>{product?.name}</h4> <p>{product?.description}</p> <p>{product?.price}</p> </div> ); }; const Products = () => { return ( <div> {products.map((product) => ( <Product key={product.id} product={product} /> ))} </div> ); }


Trong ví dụ trên, chúng tôi chuyển toàn bộ chi tiết sản phẩm tới thành phần ProductThumbnailURL , mặc dù thành phần này không yêu cầu. Nó bổ sung thêm rủi ro và độ phức tạp không cần thiết cho thành phần, đồng thời vi phạm Nguyên tắc phân tách giao diện (ISP).

Hãy cấu trúc lại để tuân thủ ISP:

 // ✅ Good: reducing unnecessary dependencies and making // the codebase more maintainable and scalable. const ProductThumbnailURL = ({ imageURL, alt }) => { return ( <div> <img src={imageURL} alt={alt} /> </div> ); }; // ✅ Good Practice const Products = ({ product }) => { return ( <div> <ProductThumbnailURL imageURL={product.imageURL} alt={product.name} /> <h4>{product?.name}</h4> <p>{product?.description}</p> <p>{product?.price}</p> </div> ); }; const Products = () => { return ( <div> {products.map((product) => ( <Product key={product.id} product={product} /> ))} </div> ); };


Trong mã sửa đổi, thành phần ProductThumbnailURL chỉ nhận thông tin bắt buộc thay vì toàn bộ chi tiết sản phẩm. Nó ngăn ngừa những rủi ro không cần thiết và thúc đẩy Nguyên tắc Phân chia Giao diện (ISP).

Nguyên tắc đảo ngược phụ thuộc

“Một thực thể nên phụ thuộc vào sự trừu tượng, không phải sự cụ thể hóa” - Wikipedia.


Nguyên tắc đảo ngược phụ thuộc (DIP) nhấn mạnh rằng các thành phần cấp cao không nên phụ thuộc vào các thành phần cấp thấp. Nguyên tắc này thúc đẩy khớp nối lỏng lẻo và mô đun hóa và tạo điều kiện bảo trì hệ thống phần mềm dễ dàng hơn. Hãy xem triển khai thực tế.


 // ❌ Bad Practice // This component follows concretion instead of abstraction and // breaks Dependency Inversion Principle const CustomForm = ({ children }) => { const handleSubmit = () => { // submit operations }; return <form onSubmit={handleSubmit}>{children}</form>; }; const DependencyInversionPrinciple = () => { const [email, setEmail] = useState(); const handleChange = (event) => { setEmail(event.target.value); }; const handleFormSubmit = (event) => { // submit business logic here }; return ( <div> {/** ❌ Avoid: tightly coupled and hard to change */} <BadCustomForm> <input type="email" value={email} onChange={handleChange} name="email" /> </BadCustomForm> </div> ); };


Thành phần CustomForm được liên kết chặt chẽ với các phần tử con của nó, ngăn cản tính linh hoạt và khiến nó khó thay đổi hoặc mở rộng hành vi của nó.

Thay vào đó, hãy làm điều này:

 // ✅ Good Practice // This component follows abstraction and promotes Dependency Inversion Principle const AbstractForm = ({ children, onSubmit }) => { const handleSubmit = (event) => { event.preventDefault(); onSubmit(); }; return <form onSubmit={handleSubmit}>{children}</form>; }; const DependencyInversionPrinciple = () => { const [email, setEmail] = useState(); const handleChange = (event) => { setEmail(event.target.value); }; const handleFormSubmit = () => { // submit business logic here }; return ( <div> {/** ✅ Use the abstraction instead */} <AbstractForm onSubmit={handleFormSubmit}> <input type="email" value={email} onChange={handleChange} name="email" /> <button type="submit">Submit</button> </AbstractForm> </div> ); };


Trong mã sửa đổi, chúng tôi giới thiệu thành phần AbstractForm , hoạt động như một phần trừu tượng cho biểu mẫu. Nó nhận chức năng onSubmit làm chỗ dựa và xử lý việc gửi biểu mẫu. Cách tiếp cận này cho phép chúng tôi dễ dàng trao đổi hoặc mở rộng hành vi của biểu mẫu mà không cần sửa đổi thành phần cấp cao hơn.

Phần kết luận

Các nguyên tắc SOLID cung cấp các hướng dẫn trao quyền cho các nhà phát triển tạo ra các giải pháp phần mềm được thiết kế tốt, có thể bảo trì và có thể mở rộng. Bằng cách tuân thủ các nguyên tắc này, các nhà phát triển có thể đạt được tính mô đun, khả năng sử dụng lại mã, tính linh hoạt và giảm độ phức tạp của mã.


Tôi hy vọng blog này đã cung cấp những hiểu biết có giá trị và truyền cảm hứng cho bạn để áp dụng những nguyên tắc này trong các dự án React hiện có hoặc sau này của bạn.


Hãy luôn tò mò; tiếp tục viết mã!


Thẩm quyền giải quyết:


Cũng được xuất bản ở đây .