برنامه نویس، علاقه مند به کامپیوتر و علم ?
سه نوع دیزاین پترن(الگوی طراحی) که همه توسعه دهنده ها باید بدونن! (با نمونه کد از هر کدوم)
سه نوع دیزاین پترن(الگوی طراحی) که همه توسعه دهنده ها باید بدونن! (با نمونه کد از هر کدوم)
دیزاین پترن چیه ؟
دیزاین پترن یک راه حله که در سطح طراحی به کار میره و برای دور زدن مشکل هایی که ما مهندسین نرم افزار باهاش مواجه میشیم مورد نیازمون میشه. کد نیست - تکرار میکنم، کد نیست ❌. دیزاین پترن مثل یک توضیح میمونه که چطور با مشکلات مواجه بشیم و یک راه حل طراحی کنیم.
استفاده از این الگوها تمرین خوبی به حساب میاد، به این دلیل که طراحی این الگو ها کاملا آزمایش شده که درنتیجه به خواناتر بودن کد نهایی منجر میشه. دیزاین پترن ها اغلب برای زبان های برنامه نویسی به کار میره که از شئ گرایی پشتیبانی میکنن، مثل Java که همه ی کد های نمونمون با این زبان نوشته شده.
انواع دیزاین پترن
تقریبا 26 نوع دیزاین پترن هست که کشف شده (که عمرا ما به همشون نمیپردازیم)
این 26 نوع به 3 دسته اصلی تقسیم بندی میشن:
1.ایجادی: این نوع از الگوها برای نمونه سازی طراحی میشن. فقط میتونن به شکل کلاس یا ابجکت استفاده بشنن.
2.ساختاری: این نوع الگو ها با توجه به ساختار و ترکیب یک کلاس ایجاد میشن. هدف اصلی این الگو اینه که عملکرد کلاس هایی که شاملش شدن رو افزایش بده بدون اینه که ترکیب اون کلاس رو خیلی تغییر بده
3.رفتاری: این الگو ها بسته به نوع ارتباط هر کلاس با یکی دیگه طراحی میشن
نوع 1: ایجادی - دیزاین پترن Singleton (یگانه)
دیزاین پترن Singleton یک پترن ایجادی به حساب میاد که که هدفش اینه که فقط یکبار نمونه ازش ایجاد بشه وفقط یک راه دسترسی سراسری بهش ایجاد شده باشه. یک نمونه مثال عمومی از این دیزاین پترن رو میشه به کلاس Calendar (تقویم) در جاوا اشاره کرد که نمیتونید ازش نمونه ای ایجاد کنید. این کلاس متد ()getInstance خودش رو هم داره که برای دسترسی به ابجکت استفاده میشه.
یک کلاس که از Singleton استفاده میکنه شامل میشه از:
1. یک متغیر خصوصی ایستا (Private Static variable) که دربرگیرنده فقط یک نمونه از کلاس هست.
2. یک سازنده خصوصی (Private Constructor) که این کانستراکتور جلوی دوباره ایجاد این کلاس رو میگیره.
3. یک متد عمومی ایستا (Public static method) که تنها نمونه ایجاد شده از کلاس رو برگردونه.
پیاده سازی های مختلفی از Singleton وجود داره. امروز درمورد پیاده سازی های زیر صحبت میکنیم:
1. نمونه سازی مشتاقانه (Eager Instantiation)
2. نمونه سازی تنبل (Lazy Instantiation)
3. نمونه سازی رشته ایمن (Thread-Safe)
بیش از حد مشتاق
public class EagerSingleton {
// create an instance of the class.
private static EagerSingleton instance = new EagerSingleton();
// private constructor, so it cannot be instantiated outside this class.
private EagerSingleton() { }
// get the only instance of the object created.
public static EagerSingleton getInstance() {
return instance;
}
}
این نوع نمونه سازی در هنگام لودشدن کلاس اتفاق می افته، همینطور که نمونه سازی نمونه متغیر خارج از هر متدی اتفاق می افته. اگر این کلاس به هیچ وجه توسط برنامه سرویس گیرنده استفاده نشه ، یک اشکال سنگین ایجاد می کنه. طرح احتمالی ، اگر از این کلاس استفاده نشود ، طرح نمونه سازی Lazy است.
روز های تبلی
public class LazySingleton {
// initialize the instance as null.
private static LazySingleton instance = null;
// private constructor, so it cannot be instantiated outside this class.
private LazySingleton() { }
// check if the instance is null, and if so, create the object.
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
تفاوت خیلی زیادی با اجرای بالا نداره. تفاوت اصلی اینه که متغیر استاتیک در ابتدا تهی خواهد بود، و فقط با متد ()getInstance نمونه سازی میشه، اگر و فقط اگر متغیر نمونه در زمان چک شدن تهی باشه!
این روش یک مشکل رو حل میکنه، ولی یک مشکل دیگه همچنان باقی میمونه. چی میشه اگر دو مشتری مختلف به طور همزمان به کلاس Singleton دسترسی پیدا کنن، درست در آن واحد؟ خوب ، آنها بررسی می کنن که نمونه به طور همزمان تهیه، و همینطور هم هست، بنابراین برای هر درخواست توسط دو مشتری دو نمونه از کلاس ایجاد می شه. برای رفع این مشکل روش Thread-Safe اجرا میشه.
امنیت رشته کلید اصلی
public class ThreadSafeSingleton {
// initialize the instance as null.
private static ThreadSafeSingleton instance = null;
// private constructor, so it cannot be instantiated outside this class.
private ThreadSafeSingleton() { }
// check if the instance is null, within a synchronized block. If so, create the object
public static ThreadSafeSingleton getInstance() {
synchronized (ThreadSafeSingleton.class) {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
return instance;
}
}
در زبان برنامه نویسی جاوا، کلمه کلیدی synchronized برای متد ها و آبجکت های Thread-Safe استفاده میشه، بنابراین فقط یک Thread به یک منبع خاص در یک زمان دسترسی داره. نمونه سازی کلاس در یک بلاک کد synchronized قرار داده میشه به همین دلیل متد فقط به یکی از کلاینت ها اجازه دسترسی میده.
سربار برای روش همگام سازی (synchronized) زیاده و عملکرد کل عملیات را کاهش می ده.
برای مثال، اگر نمونه متغیر در حال حاظر نمونه سازی شده باشه، هر زمانی که کلاینتی به متد ()getInstance دسترسی پیدا کنه، متد synchronized هم اجرا خواهد شد و به همین دلیل عملکرد هم کاهش پیدا میکنه. این اتفاق فقط به این خاطررخ میده که چک کنه مقدار نمونه متغیر تهی باشه. اگر بفهمه که تهی هست، متد رو اجرا نخواهد کرد.
// double locking is used to reduce the overhead of the synchronized method
public static ThreadSafeSingleton getInstanceDoubleLocking() {
if (instance == null) {
synchronized (ThreadSafeSingleton.class) {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
برای کاهش این سربار، از یک قفل دوتایی استفاده میشه. چک کردن قبل از همگام سازی اتفاق می افته، و اگر فقط مقدار تهی باشه، متد همگام سازی رو اجرا میکنه.
نوع 2: ساختاری - دیزاین پترن Decorator (تزئین گر)
برای درک بهتر این پتن دیزاین، چرایی و مکان استفاده از اون یک سناریو کوچک براتون ایجاد میکنم.
خب درنظر بگیرید شما صاحب یک کافی شاپ هستید، و مثل همه تازه وارد ها، شما فقط با دو نوع قهوه ساده شروع میکنید، قهوه House Blendو Dark Roast (نام دو نوع قهوه). در سیستم صورتحسابتون، یک کلاس برای ترکیب های مختلف قهوه وجود داره که کلاس انتزاعی نوشیدنی رو به ارث میبره. مردم شروع میکنن به اومدن به کافی شاپ شما و از اون قهوه های فوق العادتون (اما تلخ) سفارش میدن. مشتری های تازه واردی هستن که خدای نکرده با قهوشون شیر یا شکر میخوان. با این کار به قهوه توهین میکنن!!؟؟
بهرحال شما باید اون دوتا اضافه کردنی(شکر و شیر)رو هم به منو و هم متأسفانه روی سیستم صورتحساب داشته باشید. دراصل فردی که سیستم صورتحساب رو برای شما طراحی کرده باید یک زیرکلاس برای هر دو قهوه ایجاد کنه که یکی شامل شکر و دیگری شیر هست. بعد از اون به این خاطر که همیشه حق با مشتری هست یکی از این جملات مخوف گفته میشه:
مشتری: "امکانش هست برام یک شیرقهوه همراه با شکر بیارید لطفا ؟ "
؟؟؟
حالا سیستم صورتحساب شما دوباره تو روتون میخنده. خب، به قسمت طراحی بر میگردیم.
سپس فرد IT ، قهوه شیر با شکر رو به عنوان زیر کلاس دیگری به هر کلاس قهوه والد اضافه می کنه. بقیه این ماه هم به راحتی گذری میشه و مشتری ها هم صف میکشن برای قهوه شما. شما واقعا دارید پول درمیارید. ؟؟
ولی صبر کنید هنوز مونده!
دنیا دوباره در مقابلتون وایستاده. یک رقیب اون طرف خیابون مغازه باز میکنه، نه با 4 نوع قهوه، بلکه با 10 نوع اضافه کردنی به قهوه! ؟
شما اون 10 تا اضافه کردنی دیگرمیخرید که بتونید قهوه هاتون رو بفروشی،. و بعد از اون یادتون میاد که فراموش کردید که اون سیستم صورتحساب بی رمق رو به روز کنید. به احتمال زیاد نمی تونید تعداد نامحدود زیرکلاس را برای هر ترکیبی از همه اضافه کردنی ها و مخلوط های قهوه ایجاد کنید. اندازه سیستم نهایی رو هم که نگم براتون !
وقت سرمایه گذاری رو یک سیستم صورتحساب درست حسابیه. شما پرسنل جدید فناوری اطلاعات را پیدا می کنید که واقعا می دونه چیزی که میدونه چی میگه و چیکار میکنه.
پرسنل جدیده: "چرا، خیلی ساده تر و کوچیک تر میشد اگه از دیزاین پترن دکوراتور استفاده میکردید."
أصلا چی هست ؟
الگوی طراحی دکوراتور تو گروه ساختاری قرار می گیره، که با ساختار واقعی یک کلاس سرو کار داره، چه با ارث بری، ترکیب یا هردو. هدف از این طراحی اصلاح عملکرد اشیا در زمان اجرا هست. این یکی از پترن دیزاین های دیگست که از کلاسهای انتزاعی و رابطها با ترکیب استفاده می کنه تا نتیجه دلخواه رو بدست بیاره.
خب بیاید از منظر ریاضی به قضیه نگاه کنیم.
4 مخلوط قهوه و 10 افزودنی اضافه کنید. اگر در ایجاد زیرکلاس ها برای هر ترکیب متفاوت از اضافه کردنی های هر قهوه گیر کردیم، از این فرمول استفاده میکنیم:
(10–1)² = 9² = 81 زیرکلاس
خب در اینجا 1 رو از 10 کم میکنیم چون نمی تونید یک افزونه را با نوع مشابه دیگه ای ترکیب کنیم. ترکیب شکر با شکر مسخره به نظر میرسه. این فقط برای یک مخلوط قهوه هست. 81 به دست آورده رو با 4 ضرب کنید تا 324 تا زیرکلاس متفاوت بدست بیارید! این خودش یک عالم برنامه نویسی حساب میشه...
اما با الگوی دکوراتور در این سناریو فقط به 16 کلاس نیاز داریم. میخوای شرط ببندیم؟
دیاگرام دیزاین پترن دکوراتور
دیاگرام کلاس براساس سناریوی کافی شاپ
اگر سناریوی خود را مطابق نمودار کلاس بالا ترسیم کنیم، ما 4 کلاس برای 4 ترکیب قهوه داریم، 10تا برای افزدونی ها و یکی برای مولفه انتزاعی و یکی هم برای دکوراتور انتزاعی. دیدید! 16 تا! خب شرطو باختید 100 تومن بدید بیاد!
همونطور که در بالا مشاهده میکنید درست همونطور که مخلوط های قهوه زیرکلاس های کلاس انتزاعی نوشیدنی هستن، کلاس انتزاعی اضافه کردنی هم متدهای خودش رو از اون به ارث می بره. افزونه ها ، که زیر کلاس های اون هستن، به نوبت اگر نیاز بشه از هر متدی ارث میبرن تا عملکرد رو به شی پایه اضافه کنن.
خب بریم به سمت کد زنی تا این پترن رو در حین استفاده ببینیم.
اول کلاس انتزاعی ایجاد میکنیم تا همه مخلوط های قهوه از اون ارث ببرن:
بعد هردو کلاس مخلوط قهوه(HouseBlend و DarkRoast) رو اضافه میکنیم.
کلاس انتزاعی AddOn هم از کلاس انتزاعی نوشیدنی ارث میبره (کد زیر)
و حالا پیاده سازی این کلاس انتزاعی:
همونطور که در بالا مشاهده میکنید میتونیم هر زیرکلاسی از Beverage رو به هر زیرکلاسی از AddOn پاس بدیم و هزینه اضافه شده و همچنین توضیحات به روز شده را دریافت کنیم. و از اونجا که کلاس AddOn اساساً از نوع Beverage هست، می تونیم یک AddOn را به AddOn دیگری منتقل کنیم. به این ترتیب می ونیم هر تعداد افزودنی را به یک ترکیب خاص قهوه اضافه کنیم.
خب حالا یکم کد بزنیم تا تستش کنیم.
نتیجه نهایی:
جواب داد! ما تونستیم بیش از یک افزودنی به مخلوط قهوه اضافه کنیم و هزینه و توضیحات نهایی اون رو با موفقیت به روز کنیم، بدون نیاز به ایجاد بی نهایت زیر کلاس برای هر ترکیب افزودنی و مخلوط های قهوه.
خب نهایتا به آخرین دسته بندی رسیدیم.
نوع 3: رفتاری - دیزاین پترن Command(فرمان)
یک الگوی طراحی رفتاری بر نحوه ارتباط کلاس ها و اشیا با همدیگه تمرکز میکنه. تمرکز اصلی دیزاین پترن فرمان ، ایجاد درجه بالاتری از اتصال سست بین طرفین درگیر هست (کلاس بخونیدش)
اوووم...چی شد ؟؟
کاپلینگ (جفت سازی) راهی است که دو کلاس (یا بیشتر) با یکدیگر تعامل دارند. سناریوی ایده آل هنگام تعامل این کلاس ها اینه که وابستگی زیادی به یکدیگه ندارن. به این اتصال سست گفته میشه. بنابراین ، تعریف بهتری برای جفت سازی سست اینه که میتونن کلاسهایی باشن که بهم پیوسته اند و کمترین استفاده را از یکدیگر می کنن.
نیاز به این پترن وقتی بوجود میاد که درخواست ها باید طوری ارسال بشن که آگاهی در این وجود نداره که شما دررابطه با چی و اینکه دریافت کننده کی هست درخواست دارید.
در این پترن، کلاس فراخوانی شده از کلاسی که واقعاً عملی رو انجام میده جدا می شه. کلاس invoker فقط هنگامی که مشتری درخواست کنه، روش قابل فراخوانی رو اجرا می کنه که شامل دستور لازم برای اجرا هست.
خب بیاید یک مثال در دنیای واقعی بزنیم. سفارش غذا در یک رستوران شیک. همانطور که پیش میره، شما سفارش خودتون رو (فرمان) به پیشخدمت (دعوت کننده) می دید ، و سپس اون را به آشپز (گیرنده) تحویل میده تا بتونید غذاتون رو دریافت کنید. ممکنه ساده به نظر برسه... اما برای کدنویسی یکمی عجیب غریب هست.
ایده خیلی ساده به نظر میرسه ولی کد زنی اون خیلی دردسر داره.
دیاگرام دیزاین پترن فرمان
جریان عملیاتی در سمت فنی به این شکله که ، شما یک دستور رو یجاد می کنید ، که رابط Command را اجرا می کنه و از گیرنده می خواید یک عمل را انجام بده و دستور را به دعوت کننده ارسال می کنه. invoker شخصی هست که می دونه چه موقع این دستور رو بده. سرآشپز تنها کسی هست که می دونه وقتی دستور/سفارش خاصی داده می شه چه کاری انجام بده. بنابراین، هنگامی که روش اجرای فراخوان اجرا می شه ، به نوبه خود باعث می شه که روش اجرای اشیا command فرمان روی گیرنده اجرا بشه ، بنابراین اقدامات لازم انجام می شه.
چیزهایی که باید پیاده سازی کنیم:
1. رابط فرمان
2. یک کلاس سفارش که رابط فرمان رو پیاده سازی میکنه
3. کلاس پیشخدمت (Invoker)
4. کلاس سرآشپز ((Chef
خب، کدمون یه همچین چیزی میشه:
سرآشپز، در نقش دریافت کننده
فرمان، در نقش رابط
سفارش، در نقش فرمان
پیشخدمت، درنقش فراخوانی کننده
شما، درنقش مشتری
همونطور که در بالا مشاهده می کنید ، مشتری سفارش می ده و گیرنده رو به عنوان سرآشپز تعیین می کنه. سفارش به پیشخدمت ارسال می شه، که میدونه چه زمانی دستور رو اجرا کنه (به عنوان مثال وقتی دستور آشپزی را به آشپز میده). هنگامی که invoker اجرا می شه، متد execute در کلاس order بر روی گیرنده اجرا می شه (به عنوان مثال به آشپز دستور داده می شه که پاستا بپزن یا کیک بپزن؟).
جمع بندی سریع
در این پست ما به بررسی سه مورد زیر پرداختیم:
1. دیزاین پترن دقیقا چی هست
2. تفاوت دیزاین پترن های مختلف و چرا با هم تفاوت دارن
3. یک دیزاین پترن بیس یا معمولی مثال زده شده برای هرنوع
امیدوارم مورد استفاده قرار بگیره
ریپازیتوری کد رو هم از این لینک دریافت کنید
مطلبی دیگر از این انتشارات
آموزش کار با فایل ها در پایتون جلسه ۲
مطلبی دیگر از این انتشارات
طراحی یه سیستم کوتاه کننده لینک ساده [قسمت اول: تعریف قابلیت ها و دیاگرام دیتابیس و انتخاب DBMS]
مطلبی دیگر از این انتشارات
یه روش خوب برای استفاده از گیت