Hey! I am a Software Engineer who has crafting robust and scalable web applications for years.
The code in this story is for educational purposes. The readers are solely responsible for whatever they build with it.
Neste blog, demonstrarei a implementação dos princípios SOLID em um aplicativo React. No final deste artigo, você compreenderá totalmente os princípios do SOLID. Antes de começarmos, deixe-me fazer uma breve introdução a esses princípios.
Os princípios SOLID são cinco princípios de design que nos ajudam a manter nosso aplicativo reutilizável, sustentável, escalável e fracamente acoplado. Os princípios SOLID são:
Ok, vamos examinar cada um desses princípios individualmente. Eu uso o React como exemplo, mas os conceitos principais são semelhantes a outras linguagens de programação e estruturas.
“Um módulo deve ser responsável por um, e apenas um, ator.” - Wikipédia.
O Princípio da Responsabilidade Única afirma que um componente deve ter um propósito ou responsabilidade claro. Ele deve se concentrar em funcionalidades ou comportamentos específicos e evitar assumir tarefas não relacionadas. Seguir o SRP torna os componentes mais focados, modulares e facilmente compreendidos e modificados. Vamos ver a implementação 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> ); };
No exemplo acima, o componente Products
viola o Princípio de Responsabilidade Única ao assumir múltiplas responsabilidades. Ele gerencia a iteração de produtos e lida com a renderização da interface do usuário para cada produto. Isso pode tornar o componente difícil de entender, manter e testar no futuro.
// ✅ 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> ); };
Essa separação garante que cada componente tenha uma única responsabilidade, tornando-os mais fáceis de entender, testar e manter.
“entidades de software (classes, módulos, funções, etc.) devem ser abertas para extensão, mas fechadas para modificação.” - Wikipédia.
O Princípio Aberto-Fechado enfatiza que os componentes devem ser abertos para extensão (podem adicionar novos comportamentos ou funcionalidades), mas fechados para modificação (o código existente deve permanecer inalterado). Esse princípio incentiva a criação de código resiliente a mudanças, modular e de fácil manutenção. Vamos ver a implementação 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> ); }
No exemplo acima, modificamos o componente Button
existente adicionando uma propriedade icon
. Alterar um componente existente para acomodar novos requisitos viola o Princípio Aberto-Fechado. Essas mudanças tornam o componente mais frágil e introduzem o risco de efeitos colaterais não intencionais quando usados em diferentes contextos.
// ✅ 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> ); }
No exemplo acima, criamos um componente funcional IconButton
separado. O componente IconButton
encapsula a renderização de um botão de ícone sem modificar o componente Button
existente. Ele adere ao Princípio Aberto-Fechado, estendendo a funcionalidade por meio da composição em vez da modificação.
“Objetos de subtipo devem ser substituíveis por objetos de supertipo” — Wikipedia.
O Princípio de Substituição de Liskov (LSP) é um princípio fundamental da programação orientada a objetos que enfatiza a necessidade de substituição de objetos dentro de uma hierarquia. No contexto dos componentes React, o LSP promove a ideia de que os componentes derivados devem ser capazes de substituir seus componentes básicos sem afetar a correção ou o comportamento do aplicativo. Vamos ver a implementação 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> );
No exemplo acima, temos um componente BadCustomSelect
destinado a servir como uma entrada de seleção personalizada no React. No entanto, ele viola o Princípio de Substituição de Liskov (LSP) porque restringe o comportamento do elemento select
base.
// ✅ 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> ); };
No código revisado, temos um componente CustomSelect
destinado a estender a funcionalidade do elemento select
padrão no React. O componente aceita props como value
, iconClassName
, handleChange
e props adicionais usando o operador spread ...props
. Ao permitir o uso das características do elemento select
e aceitar props adicionais, o componente CustomSelect
segue o Princípio de Substituição de Liskov (LSP).
“Nenhum código deve ser forçado a depender de métodos que não usa.” - Wikipédia.
O Princípio de Segregação de Interface (ISP) sugere que as interfaces devem ser focadas e adaptadas aos requisitos específicos do cliente, em vez de serem excessivamente amplas e forçar os clientes a implementar funcionalidades desnecessárias. Vamos ver a implementação 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> ); }
No exemplo acima, passamos todos os detalhes do produto para o componente ProductThumbnailURL
, mesmo que isso não seja necessário. Acrescenta riscos e complexidade desnecessários ao componente e viola o Princípio de Segregação de Interface (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> ); };
No código revisado, o componente ProductThumbnailURL
recebe apenas as informações necessárias em vez de todos os detalhes do produto. Previne riscos desnecessários e promove o Princípio de Segregação de Interface (ISP).
“Uma entidade deve depender de abstrações, não de concreções” – Wikipedia.
O Princípio de Inversão de Dependência (DIP) enfatiza que componentes de alto nível não devem depender de componentes de baixo nível. Este princípio promove acoplamento fraco e modularidade e facilita a manutenção de sistemas de software. Vamos ver a implementação 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> ); };
O componente CustomForm
é fortemente acoplado a seus filhos, impedindo a flexibilidade e dificultando a alteração ou extensão de seu comportamento.
// ✅ 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> ); };
No código revisado, introduzimos o componente AbstractForm
, que atua como uma abstração para o formulário. Ele recebe a função onSubmit
como prop e manipula o envio do formulário. Essa abordagem nos permite trocar ou estender facilmente o comportamento do formulário sem modificar o componente de nível superior.
Os princípios SOLID fornecem diretrizes que capacitam os desenvolvedores a criar soluções de software bem projetadas, sustentáveis e extensíveis. Aderindo a esses princípios, os desenvolvedores podem obter modularidade, reutilização de código, flexibilidade e complexidade de código reduzida.
Espero que este blog tenha fornecido informações valiosas e inspirado você a aplicar esses princípios em seus projetos React existentes ou futuros.
Fique curioso; continue codificando!
Referência:
Publicado também aqui .