Bu blogda SOLID ilkelerinin bir React uygulamasında uygulanmasını göstereceğim. Bu makalenin sonunda SOLID ilkelerini tam olarak kavrayacaksınız. Başlamadan önce, size bu ilkelerle ilgili kısa bir giriş yapmama izin verin.
SOLID ilkeleri, uygulamamızı yeniden kullanılabilir, bakımı yapılabilir, ölçeklenebilir ve gevşek bağlı tutmamıza yardımcı olan beş tasarım ilkesidir. SOLID ilkeleri şunlardır:
Tamam, gelin bu ilkelerin her birini ayrı ayrı inceleyelim. Örnek olarak React'ı kullanıyorum ancak temel kavramlar diğer programlama dilleri ve çerçevelerine benzer.
“Bir modül tek ve tek bir aktöre karşı sorumlu olmalıdır.” — Vikipedi.
Tek Sorumluluk İlkesi, bir bileşenin net bir amacı veya sorumluluğu olması gerektiğini belirtir. Belirli işlevsellik veya davranışlara odaklanmalı ve ilgisiz görevleri üstlenmekten kaçınmalıdır. SRP'yi takip etmek bileşenleri daha odaklı, modüler, kolay anlaşılır ve değiştirilebilir hale getirir. Gerçek uygulamaya bakalım.
// ❌ 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> ); };
Yukarıdaki örnekte Products
bileşeni, birden fazla sorumluluk üstlenerek Tek Sorumluluk İlkesini ihlal etmektedir. Ürünlerin yinelenmesini yönetir ve her ürün için kullanıcı arayüzü oluşturma işlemini gerçekleştirir. Bu, bileşenin gelecekte anlaşılmasını, bakımını ve test edilmesini zorlaştırabilir.
// ✅ 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> ); };
Bu ayırma, her bileşenin tek bir sorumluluğa sahip olmasını sağlayarak bunların anlaşılmasını, test edilmesini ve bakımını kolaylaştırır.
“Yazılım varlıkları (sınıflar, modüller, işlevler vb.) genişletmeye açık, değişiklik yapmaya kapalı olmalıdır.” — Vikipedi.
Açık-Kapalı Prensibi, bileşenlerin genişlemeye açık (yeni davranışlar veya işlevler eklenebilir) ancak değişiklik için kapalı olması (mevcut kod değişmeden kalmalıdır) gerektiğini vurgular. Bu ilke, değişime dayanıklı, modüler ve bakımı kolay kodun oluşturulmasını teşvik eder. Gerçek uygulamaya bakalım.
// ❌ 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> ); }
Yukarıdaki örnekte, mevcut Button
bileşenini bir icon
prop ekleyerek değiştiriyoruz. Mevcut bir bileşeni yeni gereksinimleri karşılayacak şekilde değiştirmek Açık-Kapalı İlkesini ihlal eder. Bu değişiklikler bileşeni daha kırılgan hale getirir ve farklı bağlamlarda kullanıldığında istenmeyen yan etki riskini ortaya çıkarır.
// ✅ 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> ); }
Yukarıdaki örnekte ayrı bir IconButton
fonksiyonel bileşeni oluşturuyoruz. IconButton
bileşeni, mevcut Button
bileşenini değiştirmeden bir simge düğmesinin oluşturulmasını kapsar. İşlevselliği değişiklik yerine kompozisyon yoluyla genişleterek Açık-Kapalı Prensibine bağlı kalır.
"Alt tip nesneler, süper tip nesnelerin yerine geçebilmelidir" - Vikipedi.
Liskov Değiştirme İlkesi (LSP), bir hiyerarşi içindeki nesnelerin ikame edilebilirliği ihtiyacını vurgulayan, nesne yönelimli programlamanın temel bir ilkesidir. React bileşenleri bağlamında LSP, türetilmiş bileşenlerin, uygulamanın doğruluğunu veya davranışını etkilemeden temel bileşenlerinin yerini alabilmesi gerektiği fikrini destekler. Gerçek uygulamaya bakalım.
// ⚠️ 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> );
Yukarıdaki örnekte, React'te özel seçim girişi olarak hizmet etmesi amaçlanan bir BadCustomSelect
bileşenimiz var. Ancak temel select
öğesinin davranışını kısıtladığı için Liskov Değiştirme İlkesini (LSP) ihlal eder.
// ✅ 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> ); };
Revize edilen kodda, React'teki standart select
öğesinin işlevselliğini genişletmeyi amaçlayan bir CustomSelect
bileşenimiz var. Bileşen, value
, iconClassName
, handleChange
gibi destekleri ve yayılma operatörünü ...props
kullanan ek destekleri kabul eder. CustomSelect
bileşeni, select
öğesinin özelliklerinin kullanılmasına izin vererek ve ek destekleri kabul ederek, Liskov Değiştirme İlkesini (LSP) izler.
“Hiçbir kod kullanmadığı yöntemlere bağımlı olmaya zorlanmamalıdır.” — Vikipedi.
Arayüz Ayrıştırma Prensibi (ISP), arayüzlerin aşırı geniş olması ve müşterileri gereksiz işlevler uygulamaya zorlaması yerine, spesifik müşteri gereksinimlerine odaklanması ve uyarlanması gerektiğini önermektedir. Gerçek uygulamaya bakalım.
// ❌ 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> ); }
Yukarıdaki örnekte, ürün detaylarının tamamını, gerektirmese de ProductThumbnailURL
bileşenine aktarıyoruz. Bileşene gereksiz riskler ve karmaşıklık katar ve Arayüz Ayrıştırma İlkesini (ISP) ihlal eder.
// ✅ 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> ); };
Revize edilen kodda ProductThumbnailURL
bileşeni, ürün ayrıntılarının tamamı yerine yalnızca gerekli bilgileri alıyor. Gereksiz riskleri önler ve Arayüz Ayrıştırma Prensibini (ISP) destekler.
"Bir varlık somutlaştırmalara değil, soyutlamalara dayanmalıdır" - Vikipedi.
Bağımlılığı Ters Çevirme İlkesi (DIP), yüksek seviyeli bileşenlerin düşük seviyeli bileşenlere bağlı olmaması gerektiğini vurgular. Bu prensip gevşek bağlantıyı ve modülerliği teşvik eder ve yazılım sistemlerinin bakımını kolaylaştırır. Gerçek uygulamaya bakalım.
// ❌ 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> ); };
CustomForm
bileşeni alt öğelerine sıkı bir şekilde bağlı olduğundan esnekliği engeller ve davranışını değiştirmeyi veya genişletmeyi zorlaştırır.
// ✅ 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> ); };
Revize edilen kodda formun soyutlaması görevi gören AbstractForm
bileşenini tanıtıyoruz. onSubmit
işlevini bir destek olarak alır ve form gönderimini yönetir. Bu yaklaşım, üst düzey bileşeni değiştirmeden form davranışını kolayca değiştirmemize veya genişletmemize olanak tanır.
SOLID ilkeleri, geliştiricilere iyi tasarlanmış, bakımı yapılabilir ve genişletilebilir yazılım çözümleri oluşturma yetkisi veren yönergeler sağlar. Geliştiriciler bu ilkelere bağlı kalarak modülerlik, kodun yeniden kullanılabilirliği, esneklik ve azaltılmış kod karmaşıklığı elde edebilirler.
Umarım bu blog değerli bilgiler sunmuştur ve bu ilkeleri mevcut veya takip eden React projelerinizde uygulamanız için size ilham vermiştir.
Meraklı kal; kodlamaya devam edin!
Referans:
Burada da yayınlandı.