paint-brush
Beherrschen Sie SOLIDE Prinzipien wie Ihre Westentasche in nur 8 Minuten!von@arulvalananto
26,256 Lesungen
26,256 Lesungen

Beherrschen Sie SOLIDE Prinzipien wie Ihre Westentasche in nur 8 Minuten!

von Arul Valan Anto11m2023/07/07
Read on Terminal Reader
Read this story w/o Javascript

Zu lang; Lesen

In diesem Blog werde ich die Implementierung von SOLID-Prinzipien in einer React-Anwendung demonstrieren. Am Ende dieses Artikels werden Sie die SOLID-Prinzipien vollständig verstanden haben.
featured image - Beherrschen Sie SOLIDE Prinzipien wie Ihre Westentasche in nur 8 Minuten!
Arul Valan Anto HackerNoon profile picture
0-item

In diesem Blog werde ich die Implementierung von SOLID-Prinzipien in einer React-Anwendung demonstrieren. Am Ende dieses Artikels werden Sie die SOLID-Prinzipien vollständig verstanden haben. Bevor wir beginnen, möchte ich Ihnen eine kurze Einführung in diese Prinzipien geben.

Was sind SOLID-Prinzipien?

SOLID-Prinzipien sind fünf Designprinzipien, die uns helfen, unsere Anwendung wiederverwendbar, wartbar, skalierbar und lose gekoppelt zu halten. Die SOLID-Prinzipien sind:


  • Prinzip der Einzelverantwortung
  • Offen-Geschlossen-Prinzip
  • Liskov-Substitutionsprinzip
  • Prinzip der Schnittstellentrennung
  • Prinzip der Abhängigkeitsumkehr


Okay, lassen Sie uns jedes dieser Prinzipien einzeln untersuchen. Ich verwende React als Beispiel, aber die Kernkonzepte ähneln denen anderer Programmiersprachen und Frameworks.

Prinzip der Einzelverantwortung

„Ein Modul sollte einem und nur einem Akteur gegenüber verantwortlich sein.“ – Wikipedia.


Das Single-Responsibility-Prinzip besagt, dass eine Komponente einen klaren Zweck oder eine klare Verantwortung haben sollte. Es sollte sich auf bestimmte Funktionen oder Verhaltensweisen konzentrieren und die Übernahme unabhängiger Aufgaben vermeiden. Durch die Befolgung von SRP werden Komponenten fokussierter, modularer, leichter verständlich und modifizierbar. Sehen wir uns die tatsächliche Implementierung an.


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


Im obigen Beispiel verstößt die Products gegen das Prinzip der Einzelverantwortung, indem sie mehrere Verantwortlichkeiten übernimmt. Es verwaltet die Iteration von Produkten und kümmert sich um das UI-Rendering für jedes Produkt. Dies kann es schwierig machen, die Komponente in Zukunft zu verstehen, zu warten und zu testen.


Gehen Sie stattdessen wie folgt vor, um SRP einzuhalten:

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


Diese Trennung stellt sicher, dass jede Komponente eine einzige Verantwortung hat, wodurch sie einfacher zu verstehen, zu testen und zu warten ist.

Offen-Geschlossen-Prinzip

„Softwareeinheiten (Klassen, Module, Funktionen usw.) sollten für Erweiterungen offen, für Änderungen jedoch geschlossen sein.“ – Wikipedia.


Das Open-Closed-Prinzip betont, dass Komponenten für Erweiterungen offen sein sollten (können neue Verhaltensweisen oder Funktionalitäten hinzufügen), aber für Änderungen geschlossen sein sollten (bestehender Code sollte unverändert bleiben). Dieses Prinzip fördert die Erstellung von Code, der änderungsresistent, modular und leicht zu warten ist. Sehen wir uns die tatsächliche Implementierung an.


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


Im obigen Beispiel ändern wir die vorhandene Button Komponente, indem wir eine icon Requisite hinzufügen. Die Änderung einer vorhandenen Komponente zur Anpassung an neue Anforderungen verstößt gegen das Open-Closed-Prinzip. Diese Änderungen machen die Komponente anfälliger und bergen das Risiko unbeabsichtigter Nebenwirkungen bei der Verwendung in verschiedenen Kontexten.


Gehen Sie stattdessen wie folgt vor:

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


Im obigen Beispiel erstellen wir eine separate IconButton Funktionskomponente. Die IconButton Komponente kapselt die Darstellung einer Symbolschaltfläche, ohne die vorhandene Button Komponente zu ändern. Es folgt dem Open-Closed-Prinzip, indem es die Funktionalität durch Komposition und nicht durch Modifikation erweitert.


Liskov-Substitutionsprinzip

„Subtyp-Objekte sollten durch Supertyp-Objekte ersetzbar sein“ – Wikipedia.


Das Liskov-Substitutionsprinzip (LSP) ist ein grundlegendes Prinzip der objektorientierten Programmierung, das die Notwendigkeit der Ersetzbarkeit von Objekten innerhalb einer Hierarchie betont. Im Zusammenhang mit React-Komponenten vertritt LSP die Idee, dass abgeleitete Komponenten in der Lage sein sollten, ihre Basiskomponenten zu ersetzen, ohne die Korrektheit oder das Verhalten der Anwendung zu beeinträchtigen. Sehen wir uns die tatsächliche Implementierung an.


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


Im obigen Beispiel haben wir eine BadCustomSelect Komponente, die als benutzerdefinierte Auswahleingabe in React dienen soll. Es verstößt jedoch gegen das Liskov-Substitutionsprinzip (LSP), da es das Verhalten des select einschränkt.


Gehen Sie stattdessen wie folgt vor:

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


Im überarbeiteten Code haben wir eine CustomSelect Komponente, die die Funktionalität des Standard- select Elements in React erweitern soll. Die Komponente akzeptiert Requisiten wie value , iconClassName , handleChange und zusätzliche Requisiten mithilfe des Spread-Operators ...props . Indem die CustomSelect Komponente die Verwendung der Eigenschaften des select Elements zulässt und zusätzliche Requisiten akzeptiert, folgt sie dem Liskov-Substitutionsprinzip (LSP).


Prinzip der Schnittstellentrennung

„Kein Code sollte gezwungen werden, von Methoden abhängig zu sein, die er nicht verwendet.“ – Wikipedia.


Das Interface Segregation Principle (ISP) legt nahe, dass Schnittstellen fokussiert und auf spezifische Kundenanforderungen zugeschnitten sein sollten, anstatt zu weit gefasst zu sein und Kunden zur Implementierung unnötiger Funktionen zu zwingen. Sehen wir uns die tatsächliche Implementierung an.


 // ❌ 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 Product = ({ 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> ); }


Im obigen Beispiel übergeben wir die gesamten Produktdetails an die ProductThumbnailURL Komponente, obwohl diese nicht erforderlich ist. Es fügt der Komponente unnötige Risiken und Komplexität hinzu und verstößt gegen das Interface Segregation Principle (ISP).

Lassen Sie uns umgestalten, um dem ISP zu entsprechen:

 // ✅ 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 Product = ({ 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> ); };


Im überarbeiteten Code erhält die ProductThumbnailURL Komponente nur die erforderlichen Informationen statt der gesamten Produktdetails. Es verhindert unnötige Risiken und fördert das Interface Segregation Principle (ISP).

Abhängigkeitsinversionsprinzip

„Eine Entität sollte auf Abstraktionen beruhen, nicht auf Konkretionen“ – Wikipedia.


Das Dependency Inversion Principle (DIP) betont, dass High-Level-Komponenten nicht von Low-Level-Komponenten abhängen sollten. Dieses Prinzip fördert die lose Kopplung und Modularität und erleichtert die einfachere Wartung von Softwaresystemen. Sehen wir uns die tatsächliche Implementierung an.


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


Die CustomForm Komponente ist eng mit ihren untergeordneten Komponenten verknüpft, was Flexibilität verhindert und es schwierig macht, ihr Verhalten zu ändern oder zu erweitern.

Gehen Sie stattdessen wie folgt vor:

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


Im überarbeiteten Code führen wir die AbstractForm Komponente ein, die als Abstraktion für das Formular fungiert. Es empfängt die Funktion onSubmit als Requisite und übernimmt die Formularübermittlung. Dieser Ansatz ermöglicht es uns, das Formularverhalten einfach auszutauschen oder zu erweitern, ohne die übergeordnete Komponente zu ändern.

Abschluss

Die SOLID-Prinzipien stellen Richtlinien bereit, die Entwickler in die Lage versetzen, gut gestaltete, wartbare und erweiterbare Softwarelösungen zu erstellen. Durch die Einhaltung dieser Prinzipien können Entwickler Modularität, Wiederverwendbarkeit des Codes, Flexibilität und eine geringere Codekomplexität erreichen.


Ich hoffe, dieser Blog hat wertvolle Erkenntnisse geliefert und Sie dazu inspiriert, diese Prinzipien in Ihren bestehenden oder folgenden React-Projekten anzuwenden.


Bleib neugierig; Codieren Sie weiter!


Referenz:


Auch hier veröffentlicht.