paint-brush
¡Domine los principios SOLID como la palma de su mano en solo 8 minutos!por@arulvalananto
26,411 lecturas
26,411 lecturas

¡Domine los principios SOLID como la palma de su mano en solo 8 minutos!

por Arul Valan Anto11m2023/07/07
Read on Terminal Reader

Demasiado Largo; Para Leer

En este blog, demostraré la implementación de los principios SOLID en una aplicación React. Al final de este artículo, comprenderá por completo los principios de SOLID.
featured image - ¡Domine los principios SOLID como la palma de su mano en solo 8 minutos!
Arul Valan Anto HackerNoon profile picture
0-item

En este blog, demostraré la implementación de los principios SOLID en una aplicación React. Al final de este artículo, comprenderá por completo los principios de SOLID. Antes de comenzar, déjame darte una breve introducción a esos principios.

¿Qué son los principios SOLID?

Los principios SOLID son cinco principios de diseño que nos ayudan a mantener nuestra aplicación reutilizable, mantenible, escalable y débilmente acoplada. Los principios SOLID son:


  • Principio de responsabilidad única
  • Principio abierto-cerrado
  • Principio de sustitución de Liskov
  • Principio de segregación de interfaz
  • Principio de inversión de dependencia


Bien, examinemos cada uno de estos principios individualmente. Uso React como ejemplo, pero los conceptos básicos son similares a otros lenguajes y marcos de programación.

Principio de responsabilidad única

“Un módulo debe ser responsable ante un solo actor”. —Wikipedia.


El principio de responsabilidad única establece que un componente debe tener un propósito o responsabilidad claros. Debe centrarse en una funcionalidad o comportamiento específico y evitar asumir tareas no relacionadas. Seguir SRP hace que los componentes sean más enfocados, modulares y fáciles de comprender y modificar. Veamos la implementación real.


 // ❌ 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> ); };


En el ejemplo anterior, el componente Products viola el Principio de responsabilidad única al asumir múltiples responsabilidades. Administra la iteración de productos y maneja la representación de la interfaz de usuario para cada producto. Esto puede hacer que el componente sea difícil de entender, mantener y probar en el futuro.

En su lugar, haga esto para adherirse a 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> ); };


Esta separación garantiza que cada componente tenga una sola responsabilidad, lo que facilita su comprensión, prueba y mantenimiento.

Principio abierto-cerrado

"Las entidades de software (clases, módulos, funciones, etc.) deben estar abiertas para la extensión, pero cerradas para la modificación". —Wikipedia.


El principio abierto-cerrado enfatiza que los componentes deben estar abiertos para la extensión (pueden agregar nuevos comportamientos o funcionalidades) pero cerrados para la modificación (el código existente debe permanecer sin cambios). Este principio fomenta la creación de código resistente al cambio, modular y fácil de mantener. Veamos la implementación real.


 // ❌ 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> ); }


En el ejemplo anterior, modificamos el componente Button existente agregando un accesorio icon . Alterar un componente existente para acomodar nuevos requisitos viola el Principio Abierto-Cerrado. Estos cambios hacen que el componente sea más frágil e introducen el riesgo de efectos secundarios no deseados cuando se usan en diferentes contextos.

En su lugar, haz esto:

 // ✅ 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> ); }


En el ejemplo anterior, creamos un componente funcional IconButton separado. El componente IconButton encapsula la representación de un botón de icono sin modificar el componente Button existente. Se adhiere al Principio Abierto-Cerrado al extender la funcionalidad a través de la composición en lugar de la modificación.

Principio de sustitución de Liskov

“Los objetos de subtipo deben ser sustituibles por objetos de supertipo” — Wikipedia.


El principio de sustitución de Liskov (LSP) es un principio fundamental de la programación orientada a objetos que enfatiza la necesidad de la sustituibilidad de los objetos dentro de una jerarquía. En el contexto de los componentes React, LSP promueve la idea de que los componentes derivados deberían poder sustituir a sus componentes base sin afectar la corrección o el comportamiento de la aplicación. Veamos la implementación real.


 // ⚠️ 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> );


En el ejemplo anterior, tenemos un componente BadCustomSelect destinado a servir como una entrada de selección personalizada en React. Sin embargo, viola el principio de sustitución de Liskov (LSP) porque restringe el comportamiento del elemento select base.

En su lugar, haz esto:

 // ✅ 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> ); };


En el código revisado, tenemos un componente CustomSelect destinado a ampliar la funcionalidad del elemento select estándar en React. El componente acepta accesorios como value , iconClassName , handleChange y accesorios adicionales mediante el operador de propagación ...props . Al permitir el uso de las características del elemento select y aceptar accesorios adicionales, el componente CustomSelect sigue el principio de sustitución de Liskov (LSP).

Principio de segregación de interfaz

“Ningún código debe verse obligado a depender de métodos que no utiliza”. —Wikipedia.


El Principio de Segregación de Interfaz (ISP) sugiere que las interfaces deben enfocarse y adaptarse a los requisitos específicos del cliente en lugar de ser demasiado amplias y obligar a los clientes a implementar funciones innecesarias. Veamos la implementación real.


 // ❌ 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> ); }


En el ejemplo anterior, pasamos todos los detalles del producto al componente ProductThumbnailURL , aunque no lo requiera. Agrega riesgos y complejidad innecesarios al componente y viola el Principio de segregación de interfaz (ISP).

Vamos a refactorizar para adherirnos al 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> ); };


En el código revisado, el componente ProductThumbnailURL solo recibe la información requerida en lugar de los detalles completos del producto. Previene riesgos innecesarios y fomenta el Principio de Segregación de Interfaz (ISP).

Principio de inversión de dependencia

“Una entidad debe depender de abstracciones, no de concreciones” — Wikipedia.


El Principio de Inversión de Dependencia (DIP) enfatiza que los componentes de alto nivel no deben depender de los componentes de bajo nivel. Este principio fomenta el bajo acoplamiento y la modularidad y facilita el mantenimiento de los sistemas de software. Veamos la implementación real.


 // ❌ 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> ); };


El componente CustomForm está estrechamente acoplado a sus elementos secundarios, lo que impide la flexibilidad y dificulta cambiar o ampliar su comportamiento.

En su lugar, haz esto:

 // ✅ 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> ); };


En el código revisado, presentamos el componente AbstractForm , que actúa como una abstracción del formulario. Recibe la función onSubmit como accesorio y maneja el envío del formulario. Este enfoque nos permite intercambiar o extender fácilmente el comportamiento del formulario sin modificar el componente de nivel superior.

Conclusión

Los principios SOLID brindan pautas que permiten a los desarrolladores crear soluciones de software bien diseñadas, fáciles de mantener y extensibles. Al adherirse a estos principios, los desarrolladores pueden lograr modularidad, reutilización de código, flexibilidad y complejidad de código reducida.


Espero que este blog haya brindado información valiosa y lo haya inspirado a aplicar estos principios en sus proyectos React existentes o futuros.


Mantente curioso; sigue codificando!


Referencia:


También publicado aquí .