طراحی سایت|طراحی اپلیکیشن

آشنایی با اصل دوم از اصول SOLID

SOLID یکی از محبوب ترین مجموعه های اصول طراحی در توسعه نرم افزار شی گرا است. این کلمه مخفف 5 اصل طراحی زیر است:

ابداع کننده ی اصول SOLID کیست؟

پنج اصل SOLID توسط Robert C.Martin برای اولین بار مطرح شد.

هدف اصول SOLID چیست؟

هدف این اصول توسعه کدی با خوانایی بالا و قابل آزمایش و توسعه پذیر همچین ساخت یک استاندارد بین تمامی توسعه دهنده ها است.

همه آنها بسیار مورد استفاده قرار می گیرند و ارزش شناختن را دارند. اما در دومین پست از مجموعه اصول SOLID ، من روی دومین مورد تمرکز خواهم کرد: اصل باز / بسته (Open/Closed Principle).

تعریف اصل باز / بسته (Open/Closed Principle)

رابرت سی. مارتین این اصل را “مهمترین اصل طراحی شی گرا” دانست. اما او اولین کسی نبود که آن را تعریف کرد. برتراند مایر در سال 1988 در كتاب ساخت نرم افزار شی گرا درباره ی آن نوشت. وی اصل باز / بسته را چنین توضیح داد:
نهادهای نرم افزاری (کلاس ها ، ماژول ها ، توابع و غیره) باید برای پسوند باز باشند ، اما برای تغییر بسته هستند. ”

ایده کلی این اصل عالی است. به شما می گوید که کد خود را بنویسید تا بتوانید بدون تغییر کد موجود ، قابلیت جدیدی اضافه کنید. این امر از شرایطی که برای تغییر در یکی از کلاسهای شما نیز لازم است از همه کلاسهای وابسته استفاده شود، جلوگیری می کند. متاسفانه ، برتراند مایر پیشنهاد می کند که برای رسیدن به این هدف از ارث استفاده کنید:

“یک کلاس بسته است ، زیرا ممکن است در کلاسها گردآوری شود ، در کتابخانه ذخیره شود ، در خط مبنا قرار گیرد و توسط کلاسهای مشتری استفاده شود. اما همچنین باز است ، زیرا هر کلاس جدیدی ممکن است از آن به عنوان پدر استفاده کند و ویژگی های جدیدی به آن اضافه کند. وقتی یک کلاس فرزند تعریف می شود ، دیگر نیازی به تغییر اصل یا ایجاد مزاحمت برای مشتری های آن نیست. ”

اما همانطور که در طول سالها آموخته ایم و سایر نویسندگان با جزئیات کامل توضیح داده اند ، به عنوان مثال ، رابرت سی. مارتین در مقالات خود در مورد اصول SOLID یا جوشوا بلوک در کتاب خود با عنوان جاوا موثر ،در صورتی که زیر کلاس ها به جزئیات پیاده سازی کلاس اصلی آنها بستگی داشته باشد ، ارث بری اتصال جفت و سخت ایجاد می کند.

به همین دلیل رابرت سی. مارتین و دیگران اصل باز / بسته را به اصل باز / بسته چند شکلی بازتعریف کردند. این برنامه از رابط ها به جای ابر کلاس ها استفاده می کند تا پیاده سازی های مختلفی را امکان پذیر کند که بدون تغییر کدی که از آنها استفاده می کنید به راحتی می توانید جایگزین آنها شوید. رابط ها برای تغییر بسته هستند و شما می توانید پیاده سازی های جدیدی را برای افزایش عملکرد نرم افزار خود ارائه دهید.
مزیت اصلی این روش این است که یک رابط سطح اضافی از مفهوم و خلاصه را ارائه می دهد که اتصال شل را امکان پذیر می کند. پیاده سازی های یک رابط مستقل از یکدیگر هستند و نیازی به اشتراک کدی ندارند.

بیایید نگاهی به مثالی بیندازیم که از اصل باز / بسته (Open/Closed Principle) استفاده می کند.

دم کردن قهوه با اصل باز / بسته (Open/Closed Principle)
شما می توانید بسیاری از دستگاه های مختلف برای اماده سازی قهوه را خریداری کنید. بعضی از آنها قهوه را از فیلتر رد میکنند و برخی آن را آسیاب می کننند. همه آنها به یک منظور عمل می کنند: آنها قهوه خوشمزه ای دم می کنند که صبح ما را بیدار می کند.

تنها مشکل این است که برای روشن کردن دستگاه قهوه باید از رختخواب بلند شوید. بنابراین ، چرا همه چالش های دنیای فیزیکی ، مانند چگونگی قرار دادن آب و قهوه آسیاب شده در دستگاه یا چگونگی قرار دادن لیوان زیر آن بدون بلند شدن از رختخواب ، را نادیده نگیرید و یک برنامه ساده را اجرا کنید که برای دم کردن قهوه برای شما طراحی شده باشد؟

برای نشان دادن مزایای اصل باز / بسته ، من یک برنامه ساده نوشتم که یک قهوه ساز اساسی را کنترل می کند تا صبح یک قهوه خوشمزه برای شما دم کند.
کلاس BasicCoffeeMachine
اجرای کلاس BasicCoffeeMachine نسبتاً ساده است.فقط یک روش عمومی برای افزودن قهوه آسیاب شده و یک روش تولید قهوه فیلتر دارد.

import java.util.HashMap; import java.util.Map; public class BasicCoffeeMachine { private Map<CoffeeSelection, Configuration> configMap; private Map<CoffeeSelection, GroundCoffee>; groundCoffee; private BrewingUnit brewingUnit; public BasicCoffeeMachine(Map<CoffeeSelection, GroundCoffee> coffee) { this.groundCoffee = coffee; this.brewingUnit = new BrewingUnit(); this.configMap = new HashMap<>(); this.configMap.put(CoffeeSelection.FILTER_COFFEE, new Configuration(30, 480)); } public Coffee brewCoffee(CoffeeSelection selection) { Configuration config = configMap.get(CoffeeSelection.FILTER_COFFEE); // get the coffee GroundCoffee groundCoffee = this.groundCoffee.get(CoffeeSelection.FILTER_COFFEE); // brew a filter coffee return this.brewingUnit.brew(CoffeeSelection.FILTER_COFFEE, groundCoffee, config.getQuantityWater()); } public void addGroundCoffee(CoffeeSelection sel, GroundCoffee newCoffee) throws CoffeeException { GroundCoffee existingCoffee = this.groundCoffee.get(sel); if (existingCoffee != null) { if (existingCoffee.getName().equals(newCoffee.getName())) { existingCoffee.setQuantity(existingCoffee.getQuantity() + newCoffee.getQuantity()); } else { throw new CoffeeException(“Only one kind of coffee supported for each CoffeeSelection.”); } } else { this.groundCoffee.put(sel, newCoffee); } } }

به راحتی می توانید از طریق اپلیکیشن چنین دستگاه ساده قهوه ای را کنترل کنید ، درست است؟ بنابراین ، بیایید این کار را انجام دهیم

کلاس BasicCoffeeApp

روش اصلی BasicCoffeeApp یک نقشه با قهوه آسیاب شده تهیه می کند ، یک شی BasicCoffeeMachine را نمونه سازی می کند و برای تهیه قهوه روش آماده سازی قهوه را فرا می خواند.

public class BasicCoffeeApp { private BasicCoffeeMachine coffeeMachine; public BasicCoffeeApp(BasicCoffeeMachine coffeeMachine) { this.coffeeMachine = coffeeMachine; } public Coffee prepareCoffee(CoffeeSelection selection) throws CoffeeException { Coffee coffee = this.coffeeMachine.brewCoffee(selection); System.out.println(“Coffee is ready!”); return coffee; } public static void main(String[] args) { // create a Map of available coffee beans Map<CoffeeSelection, GroundCoffee> beans = new HashMap<CoffeeSelection, GroundCoffee>(); beans.put(CoffeeSelection.FILTER_COFFEE, new GroundCoffee( “My favorite filter coffee bean”, 1000)); // get a new CoffeeMachine object BasicCoffeeMachine machine = new BasicCoffeeMachine(beans); // Instantiate CoffeeApp BasicCoffeeApp app = new BasicCoffeeApp(machine); // brew a fresh coffee try { app.prepareCoffee(CoffeeSelection.FILTER_COFFEE); } catch (CoffeeException e) { e.printStackTrace(); } } // end main } // end CoffeeApp

خودشه!

از این به بعد ، می توانید در رختخواب بمانید تا زمانی که قهوه تازه تهیه شده توسط BasicCoffeeApp خود را حس کنید.

استفاده از اصل باز / بسته (Open/Closed Principle)

بسیار خوب خواهد بود اگر برنامه شما بتواند هر دو نوع دستگاه قهوه را کنترل کند. اما این به چند تغییر کد نیاز دارد. و چون قبلاً روی آن کار کرده اید ، چرا آن را تغییر ندهید تا نیازی به سازگاری آن با دستگاه های قهوه ساز آینده نداشته باشید.

استخراج رابط CoffeeMachine

طبق اصل Open / Closed ، باید رابطی را استخراج کنید که به شما امکان کنترل دستگاه قهوه را بدهد. این اغلب قسمت مهم بازسازی است. شما باید روشهایی را که برای کنترل دستگاه قهوه اجباری است ، قرار دهید ، اما هیچ یک از روشهای اختیاری که انعطاف پذیری پیاده سازی ها را محدود می کند ، نیست.

در این مثال ، این فقط روش brewCoffee است. بنابراین ، رابط CoffeeMachine فقط یک روش را مشخص می کند که باید توسط همه کلاس هایی که آن را پیاده سازی می کنند ، پیاده سازی شود.

public interface CoffeeMachine { Coffee brewCoffee(CoffeeSelection selection) throws CoffeeException; }

تطبیق کلاس BasicCoffeeMachine

در مرحله بعدی ، شما باید کلاس BasicCoffeeMachine را متناسب کنید. در حال حاضر روش brewCoffee را پیاده سازی می کند و کلیه قابلیت های مورد نیاز را فراهم می کند. بنابراین ، شما فقط باید اعلام کنید که کلاس BasicCoffeeMachine رابط CoffeeMachine را پیاده سازی می کند.

public class BasicCoffeeMachine implements CoffeeMachine { }

پیاده سازی های بیشتر را اضافه کنید

اکنون می توانید پیاده سازی های جدید رابط کاربری CoffeeMachine را اضافه کنید.

اجرای کلاس PremiumCoffeeMachine پیچیده تر از کلاس BasicCoffeeMachine است. روش brewCoffee آن ، که توسط رابط CoffeeMachine تعریف شده است ، از دو گزینه مختلف CoffeeSelections پشتیبانی می کند. بر اساس CoffeeSelection ارائه شده ، این روش یک روش خصوصی و جداگانه را می خواند که قهوه انتخاب شده را دم می کند. همانطور که در اجرای این روش ها مشاهده می کنید ، کلاس همچنین از ترکیبات برای ارجاع به یک Grinder استفاده می کند که قبل از دم کشیدن قهوه ، دانه های قهوه را آسیاب می کند.

import java.util.HashMap; import java.util.Map; public class PremiumCoffeeMachine implements CoffeeMachine { private Map<CoffeeSelection, Configuration> configMap; private Map<CoffeeSelection, CoffeeBean> beans; private Grinder grinder; private BrewingUnit brewingUnit; public PremiumCoffeeMachine(Map<CoffeeSelection, CoffeeBean> beans) { this.beans = beans; this.grinder = new Grinder(); this.brewingUnit = new BrewingUnit(); this.configMap = new HashMap<>(); this.configMap.put(CoffeeSelection.FILTER_COFFEE, new Configuration(30, 480)); this.configMap.put(CoffeeSelection.ESPRESSO, new Configuration(8, 28)); } @Override public Coffee brewCoffee(CoffeeSelection selection) throws CoffeeException { switch(selection) { case ESPRESSO: return brewEspresso(); case FILTER_COFFEE: return brewFilterCoffee(); default: throw new CoffeeException(“CoffeeSelection [“ + selection + “] not supported!”); } } private Coffee brewEspresso() { Configuration config = configMap.get(CoffeeSelection.ESPRESSO); // grind the coffee beans GroundCoffee groundCoffee = this.grinder.grind( this.beans.get(CoffeeSelection.ESPRESSO), config.getQuantityCoffee()); // brew an espresso return this.brewingUnit.brew(CoffeeSelection.ESPRESSO, groundCoffee, config.getQuantityWater()); } private Coffee brewFilterCoffee() { Configuration config = configMap.get(CoffeeSelection.FILTER_COFFEE); // grind the coffee beans GroundCoffee groundCoffee = this.grinder.grind( this.beans.get(CoffeeSelection.FILTER_COFFEE), config.getQuantityCoffee()); // brew a filter coffee return this.brewingUnit.brew(CoffeeSelection.FILTER_COFFEE, groundCoffee, config.getQuantityWater()); } public void addCoffeeBeans(CoffeeSelection sel, CoffeeBean newBeans) throws CoffeeException { CoffeeBean existingBeans = this.beans.get(sel); if (existingBeans != null) { if (existingBeans.getName().equals(newBeans.getName())) { existingBeans.setQuantity(existingBeans.getQuantity() + newBeans.getQuantity()); } else { throw new CoffeeException(“Only one kind of coffee supported for each CoffeeSelection.”); } } else { this.beans.put(sel, newBeans); } } }

تنها موردی که در برنامه باقی مانده استفاده از پیاده سازی های مختلف آن رابط است.

تطبیق CoffeeApp

کلاس CoffeeApp از 2 قسمت تشکیل شده است:

کلاس CoffeeApp و
روش اصلی
شما باید پیاده سازی خاص CoffeeMachine را در روش اصلی انجام دهید. بنابراین ، اگر قهوه ساز فعلی خود را جایگزین کنید ، همیشه باید این روش را در پیش بگیرید. اما تا زمانی که کلاس CoffeeApp از رابط CoffeeMachine استفاده کند ، نیازی به سازگاری آن نخواهید داشت.

import java.util.HashMap; import java.util.Map; public class CoffeeApp { private CoffeeMachine coffeeMachine; public CoffeeApp(CoffeeMachine coffeeMachine) { this.coffeeMachine = coffeeMachine; } public Coffee prepareCoffee(CoffeeSelection selection) throws CoffeeException { Coffee coffee = this.coffeeMachine.brewCoffee(selection); System.out.println(“Coffee is ready!”); return coffee; } public static void main(String[] args) { // create a Map of available coffee beans Map<CoffeeSelection, CoffeeBean>; beans = new HashMap<CoffeeSelection, CoffeeBean>(); beans.put(CoffeeSelection.ESPRESSO, new CoffeeBean( “My favorite espresso bean”, 1000)); beans.put(CoffeeSelection.FILTER_COFFEE, new CoffeeBean( “My favorite filter coffee bean”, 1000)); // get a new CoffeeMachine object PremiumCoffeeMachine machine = new PremiumCoffeeMachine(beans); // Instantiate CoffeeApp CoffeeApp app = new CoffeeApp(machine); // brew a fresh coffee try { app.prepareCoffee(CoffeeSelection.ESPRESSO); } catch (CoffeeException e) { e.printStackTrace(); } } // end main } // end CoffeeApp

خلاصه

پس از نگاهی دقیق به اصل مسئولیت منفرد در پست قبلی این مجموعه ، اکنون در مورد اصل باز / بسته بحث کردیم. این یکی از پنج اصل طراحی SOLID است که توسط رابرت سی. مارتین توصیف شده است. این اصل استفاده از رابط ها را ترویج می دهد تا شما را قادر سازد تا عملکرد برنامه خود را بدون تغییر کد موجود تطبیق دهید.

ما از این اصل در برنامه مثال برای کنترل انواع دستگاههای قهوه از طریق CoffeeApp خود استفاده کردیم. تا زمانی که یک دستگاه قهوه رابط CoffeeMachine را پیاده سازی می کند ، می توانید از طریق برنامه آن را کنترل کنید. تنها کاری که باید هنگام تعویض دستگاه قهوه موجود خود انجام دهید ارائه یک پیاده سازی جدید از رابط کاربری و تغییر روش اصلی است که اجرای خاص را ایجاد می کند. اگر می خواهید یک قدم جلو بروید ، می توانید از تزریق وابستگی ، انعکاس یا API لودر سرویس برای جایگزینی نمونه سازی یک کلاس خاص استفاده کنید.