Design Patternها (دیزاین پترن) از شهرت بالایی میان برنامه‌نویسان برخوردارند. دیزاین پترن، یک راه‌حل خوب‌ طراحی‌شده برای یک مسئله‌ی رایج نرم‌افزاری است. در ادامه با آموزش ۰ تا ۱۰۰ الگوهای طراحی یا همان Design Pattern ها در جاوا با ما همراه باشید.

 

Design Patternهای جاوا

در ادامه، برخی از مزایای استفاده از دیزاین پترن‌ها را عنوان می‌کنیم:

  1. دیزاین پترن‌ها از قبل تعریف‌شده‌اند و از یک رویکرد استاندارد برنامه‌نوسی برای حل یک مسئله‌ی رایج استفاده می‌کنند. درنتیجه با استفاده‌ی مناسب از دیزاین‌ پترن‌ها می‌توانیم در زمان صرفه‌جویی کنیم. جاوا دیزاین پترن‌های زیادی دارد که می‌توانیم از آنها در پروژه‌های جاوا استفاده کنیم.
  2. با استفاده از دیزاین پترن‌ها قابلیت استفاده‌ی مجدد (Reusability) کد بالاتر‌می‌رود. قابلیت استفاده‌ی مجدد بالاتر نیز منجر به کدی قوی‌تر با هزینه‌های نگه‌داری کمتر می‌شود. در نهایت، محصول نرم‌افزاری حاصل، هزینه‌ی کل مالکیت (TCO) کمتری دارد.
  3. دیزاین پترن‌ها از قبل تعریف‌شده‌اند و به همین دلیل فهم و دیباگ کد را ساده‌تر می‌کنند. سرعت توسعه‌ی کد بالاتر می‌رود و اعضای جدید تیم راحت‌تر می‌توانند کدهای نوشته‌شده را بفهمند.

Design pattern جاوا

Design Patternهای جاوا به سه دسته تقسیم می‌شوند. دیزاین پترن‌های سازنده (Creationalساختاری (Structural) و رفتاری (Behavioral). در این مقاله به معرفی تمام دیزاین پترن‌های موجود در هر دسته می‌پردازیم.

دوره های مرتبط در فرانش

 

Design Patternهای سازنده

دیزاین پترن‌های سازنده، بهترین راهکارِ موجودِ نمونه‌سازی از یک شئ (Instantiate) را در شرایطی خاص ارائه می‌دهند.

  1. Singleton (سینگلتون)

این پترن نمونه‌سازی از یک کلاس را محدود می‌کند. با Singleton، تنها یک نمونه از یک کلاس می‌تواند در ماشین مجازی جاوا وجود داشته باشد. دیزاین پترن ساده‌ای به‌نظر می‌رسد، اما نگرانی‌های پیاده‌سازی بسیاری را هنگام پیاده‌سازی به همراه دارد. برنامه‌نویسان همیشه درباره‌ی استفاده از پترن Singleton با هم اختلاف نظر داشته‌اند.
Singleton یکی از شناخته‌شده‌ترین دیزاین پترن‌های جاواست.

  1. Factory (فکتوری)

از این Design Pattern در شرایطی استفاده می‌شود که یک کلاس پدر داریم که چند کلاس فرزند دارد و می‌خواهیم بر اساس ورودی یکی از کلاس‌های فرزند را به عنوان خروجی بفرستیم.
با این پترن، مسئولیت ساخت یک نمونه از کلاس برعهده‌ی برنامه‌ی کلاینت نیست و کلاس Factory این مسئولیت را به‌عهده می‌گیرد.
می‌توانیم پترن Singleton را روی کلاس Factory اعمال کنیم یا متد Factory را استاتیک تعریف کنیم.

  1. Abstract Factory (فکتوری انتزاعی)

این Design Pattern شبیه Factory است. اگر با Design Pattern Factory جاوا آشنا باشید، می‌دانید که ما یک کلاس Factory داریم که بر اساس ورودی، کلاس‌های فرزند مختلفی را برمی‌گرداند. کلاس Factory برای انجام این کار از دستور if-else یا Switch استفاده می‌کند.

در Abstract Factory، بلوک if-else را حذف و برای هر کلاس فرزند از یک کلاس Factory استفاده می‌کنیم. یک کلاس Abstract Factory بر اساس کلاس Factory ورودی، یک کلاس فرزند را برمی‌گرداند.

  1. Builder (بیلدر)

وقتی Object در پترن‌های Factory و Abstract Factory دارای خصیصه‌های (Attribute) زیادی باشد، مشکلاتی رخ می‌دهد. Builder برای حل این مشکلات معرفی شد.
Builder شئ را مرحله به مرحله می‌سازد و متدی برای برگرداندن شئ نهایی ارائه می‌کند. به این ترتیب مشکلات مرتبط با داشتنِ پارامترهای اختیاری زیاد و Inconsistent State را برطرف می‌کند.

  1. Prototype (پروتوتایپ)

از این پترن زمانی استفاده می‌کنیم که ساخت یک شئ جدید هزینه، زمان و منابع زیادی می‌گیرد و یک شئ آماده‌ی مشابه آن نیز داریم.
Prototype مکانیزمی دارد که شئ اصلی را در یک شئ جدید کپی می‌کنیم و بعد آن را طبق نیازمان تغییر می‌دهیم. این Design Pattern از Cloning جاوا برای کپی شی استفاده می‌کند.

در پترن Prototype ضروری است شئ‌ای که قصد کپی کردن آن را داریم، قابلیت کپی را در اختیارمان قرار دهد. هیچ کلاس دیگری نباید این کار را انجام دهد.
کپی سطحی (Shallow Copy) یا عمیق (Deep Copy (Propertyهای شئ هم بسته به شرایط است و یک تصمیم طراحی محسوب می‌شود.

 

Design Patternهای ساختاری

پترن‌های ساختاری روش‌هایی مختلف را برای ساختِ ساختار یک کلاس ارائه می‌کنند. مثلاً استفاده از شئ‌گرایی (Inheritance) و ترکیب (Composition) برای ساخت اشیاء بزرگ‌تر با استفاده از اشیاء کوچک‌تر.

پیشنهاد فرانش به شما
آموزش گام به گام برنامه نویسی شی گرا (OOP)
  1. Adapter (آداپتور)

این پترن نوعی Design Pattern ساختاری است و زمانی از آن استفاده می‌کنیم که دو واسط غیرمرتبط بخواهند با هم همکاری کنند.
شئ‌ای که این دو واسط غیرمرتبط را به هم وصل می‌کند، Adapter نامیده می‌شود. یک مثال از دنیای واقعی شاید به درک بهتر این پترن کمک کند.
یک شارژر موبایل یک Adapter است، چون باتری به ۳ ولت نیاز دارد، اما برق شهری ۱۲۰ (ایالات متحده) یا ۲۴۰ ولت (هند) است. بنابراین شارژر موبایل یک Adapter بین برق شهری و باتری است.

  1. Composite

این پترن یکی از Design Patternهای ساختاری است و زمانی از آن استفاده می‌کنیم که قصد نمایش یک سلسله‌مراتب جزء-کل را داشته باشیم. وقتی می‌خواهیم ساختاری بسازیم که با همه‌ی اشیاء موجود در آن به گونه‌ای یکسان رفتار شود، از Composite استفاده می‌کنیم.

یک مثال از دنیای واقعی شاید به درک بهتر این پترن کمک کند. یک دیاگرام شامل اشیائی مثل دایره، خط، مثلث و … است و وقتی طرح را رنگ می‌کنیم (مثلا قرمز)، اشیاء درون آن نیز قرمز می‌شوند.
در اینجا طرح از قسمت‌های مختلفی تشکیل شده است و همه‌ی آنها وظایفی یکسان دارند.

  1. Proxy (پروکسی)

هدف Proxy این است که “جایگزین یا نماینده‌ای برای شئ دیگری ارائه کند تا دسترسی به آن کنترل شود”. تعریف بالا همه‌چیز را به خوبی عنوان می‌کند و از این Design Pattern برای کنترل دسترسی به یک عملکرد استفاده می‌کنیم.

کلاسی را درنظر بگیرید که دستوراتی را روی سیستم اجرا می‌کند. حالا اگر خودمان بخواهیم از این کلاس استفاده کنیم، مسئله‌ای پیش نمی‌آید، اما اگر بخواهیم آن را به یک اپلیکیشن کلاینت بدهیم، ممکن است مشکلاتی بسیار بد رخ دهد.
مثلاً برنامه‌ی کلاینت می‌تواند دستوراتی اجرا کند که برخی فایل‌های سیستمی پاک شوند یا تنظیمات سیستم را به‌گونه‌ای تغییر دهند که ما نمی‌خواهیم.

  1. Flyweight

وقتی قصد ساخت تعداد زیادی شئ از یک کلاس را داریم، از این Design Pattern استفاده می‌کنیم.
هر شئ فضای حافظه می‌گیرد و این مسئله می‌تواند برای دستگاه‌هایی با حافظه‌ی محدود، مثل گوشی‌های موبایل یا سیستم‌های تعبیه‌شده، مشکل‌ساز باشد.
با استفاده از Flyweight می‌توانیم با به اشتراک گذاشتن اشیاء، بار حافظه را کم کنیم. پیاده‌سازی String Pool در جاوا یکی از بهترین مثال‌های دیزاین پترن Flyweight است.

  1. Façade

این Design Pattern برای تعامل ساده‌تر اپلیکیشن‌های کلاینت با سیستم طراحی شده است. فرض کنید یک اپلیکیشن با مجموعه‌ای از واسط‌ها را داریم.
این اپلیکیشن قصد استفاده از پایگاه داده‌ی MySql/Oracle را دارد و می‌خواهد انواع مختلف گزارش مثل HTML، PDF و … را تولید کند.
پس مجموعه‌ای دیگر از واسط‌ها را برای کار با پایگاه داده‌های مختلف خواهیم داشت. حالا یک اپلیکیشن کلاینت می‌تواند از این واسط‌ها برای دریافت اتصال (Connection) لازم به پایگاه داده استفاده و یک گزارش تولید کند.
اما وقتی پیچیدگی برنامه بیشتر یا اسامی رفتارهای واسط گیج‌کننده می‌شوند، اپلیکیشن کلاینت برای مدیریت آنها به مشکل برمی‌خورد.
برای حل این مشکل می‌توانیم از دیزاین پترن Facade استفاده کنیم. در این صورت یک واسط Wrapper روی تمام واسط‌های موجود می‌نویسیم تا به اپلیکیشن کلاینت کمک کنیم.

پیشنهاد فرانش به شما
آموزش SQL برای مبتدی‌ها
  1. Bridge

وقتی هم در واسط‌ها و هم در پیاده‌سازی دارای سلسله‌مراتب هستیم، از این Design Pattern برای جداسازی واسط از پیاده‌سازی استفاده می‌کنیم.
در این حالت جزئیات پیاده‌سازی را از برنامه‌های کلاینت مخفی نگه‌ می‌داریم. این پترن نیز، مثل ۵ پترن قبلی، یک دیزاین پترن ساختاری است.

پیاده‌سازی دیزاین پترن Bridge نشان‌دهنده‌ی این موضوع است که برنامه‌نویس ترکیب را به ارث‌بری ترجیح می‌دهد.

  1. Decorator

وقتی قصد تغییر عملکرد یک شئ را در زمان اجرا داریم، از این Design Pattern استفاده می‌کنیم. نمونه‌های دیگر کلاس با این روش تغییر نمی‌کنند و تنها رفتار همان شئ تغییر می‌یابد.
Decorator نیز یکی از دیزاین پترن‌های ساختاری است و از کلاس یا واسط Abstract و ترکیب برای پیاده‌سازی استفاده می‌کند.

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

 

Design Patternهای رفتاری

پترن‌های رفتاری راهکارهایی را برای ارتباط بهتر اشیاء با هم ارائه می‌کنند. این پترن‌ها برای Extend بهتر، روش‌هایی را برای Lose Coupling و انعطاف عرضه می‌کنند.

  1. Template Method

Template یک Design Pattern رفتاری است و از آن برای ساخت یک استاب متد (Stub) و انجام برخی از مراحل پیاده‌سازی در کلاس فرزند استفاده می‌شود.
متد Template مراحل اجرای یک الگوریتم را تعیین می‌کند و می‌تواند یک روش پیاده‌سازی پیش‌فرضِ رایج را برای همه یا تعدادی از کلاس‌های فرزند ارائه کند.

فرض کنید می‌خواهیم یک الگوریتم برای ساخت یک ساختمان داشته باشیم. مراحل ساخت یک ساختمان به صورت زیرند:

ساخت پِی، ساخت ستون‌ها، ساخت دیوارها و پنجره‌ها.

نکته‌ی مهم این است که نمی‌توانیم ترتیب اجرای مراحل را تغییر دهیم. مثلاً نمی‌توانیم قبل از ساخت پی، پنجره بسازیم. در این شرایط، از یک متد Template استفاده می‌کنیم که از متدهای مختلفی برای ساخت ساختمان استفاده می‌کند.

  1. Mediator

از این Design Pattern برای ارائه‌ی یک واسط ارتباطی متمرکز بین اشیاء سیستم استفاده می‌شود. در شرایطی که در یک اپلیکیشن سازمانی، اشیاء مختلفی داریم که با هم تعامل دارند، این دیزاین پترن بسیار کاربرد دارد.
اگر اشیاء مستقیماً با هم تعامل داشته باشند، اجزای سیستم Tightly Coupled می‌شوند و این موضوع هزینه‌ی نگه‌داری را افزایش می‌دهد و توسعه‌ی نرم‌افزار را سخت می‌کند.
دیزاین پترن Mediator تلاش می‌کند یک واسط (Mediator) بین اشیاء قرار دهد تا از طریق آن با هم ارتباط داشته باشند و به پیاده‌سازی loose Coupling بین اشیاء کمک می‌کند.

برجِ کنترلِ ترافیکِ هوایی، یکی از بهترین مثال‌ها برای دیزاین پترن Mediator است، که در آن برج کنترل به عنوان واسطی بین پروازهای مختلف عمل می‌کند.
Mediator مسیری بین اشیاء مختلف است و می‌تواند منطق خود را در روش‌های ارتباط داشته باشد.

  1. Chain of Responsibility (زنجیره‌ی مسئولیت)

وقتی درخواست کلاینت برای پردازش به زنجیره‌ای از اشیاء داده می‌شود، برای داشتن Loose Coupling از این Design Pattern استفاده می‌شود.
سپس خود اشیاء موجود در زنجیره تصمیم می‌گیرند که چه شئ‌ای درخواست را پردازش کند و آیا لازم است که درخواست به شئ بعدی در زنجیره فرستاده شود یا خیر.

می‌دانیم که در یک دستور try-catch می‌توانیم چند بلوک Catch داشته باشیم. در اینجا هر بلوک Catch نوعی پردازنده برای پردازش Exception موردنظر است.
پس وقتی یک Exception در بلوک Try رخ داد، آن Exception برای پردازش به اولین بلوک Catch فرستاده می‌شود.
اگر بلوک Catch نتواند آن را پردازش کند، آن را به شئ بعدی در زنجیره می‌فرستد، که در اینجا یک بلوک Catch است.
در نهایت اگر آخرین بلوک Catch هم نتوانست آن را پردازش کند، Exception بیرون انداخته و به برنامه‌ی اجرا کننده‌ی کد فرستاده می‌شود.

  1. Observer

وقتی State یک شئ برایمان اهمیت دارد و می‌خواهیم از هر تغییری در آن مطلع شویم، از این Design Pattern استفاده می‌کنیم.
در دیزاین پترن Observer، آن شئ‌ای که مراقب State یک شئ دیگر است، Observer نامیده می‌شود و به شئ‌ای که مراقب State آن هستیم، Subject می‌گوییم.

در جاوا می‌توانیم با استفاده از یک پلتفرم داخلی و از طریق کلاس java.util.Observable و واسط java.util.Observer پترن Observer را پیاده‌سازی کنیم.
اگرچه از این گزینه‌ها استفاده‌ی چندانی نمی‌شود، چون بسیار ساده هستند و اغلب اوقات نمی‌خواهیم فقط به هدف پیاده‌سازی پترن Observer از یک کلاس ارث‌بری داشته باشیم، چون جاوا از ارث‌بری چندگانه پشتیبانی نمی‌کند.

(Java Message Service (JMS از پترن‌های Mediator و Observer استفاده می‌کند تا به اپلیکیشن‌ها این امکان را بدهد که برای هم داده بفرستند و یکدیگر را Subscribe کنند.

  1. Strategy (استراتژی)

وقتی چند الگوریتم برای انجام یک کار داریم و کلاینت در زمان اجرا تصمیم می‌گیرد از چه الگوریتمی استفاده کند، از این Design Pattern استفاده می‌کنیم.

نام دیگر دیزاین پترن Strategy، Policy است. چند الگوریتم تعریف می‌کنیم و کلاینت می‌تواند الگوریتم موردنظرش را به صورت یک پارامتر انتقال دهد. یکی از بهترین مثال‌های این دیزاین پترن، متد Collection.sort() است که پارامتر Comparator را می‌گیرد. اشیاء با توجه به پیاده‌سازی‌های مختلفِ واسط‌های Comprator، به انواع مختلفی Sort می‌شوند.

پیشنهاد فرانش به شما
بهترین زبان‌‌ های برنامه نویسی در ۲۰۱۹: ۱۰ زبان برتر برای یادگیری
  1. Command

از این Design Pattern برای داشتن Loose Coupling در یک مدل Request-Response استفاده می‌کنیم.
در دیزاین پترن Command، درخواست به Invoker فرستاده می‌شود و این Invoker  است که آن را به شئ Command کپسوله‌شده (Encapsulated) منتقل می‌کند.
شئ Command درخواست را به متد مناسب Receiver می‌فرستد تا این متد عملی خاص را اجرا کند.

فرض کنید می‌خواهیم یک سیستم فایل (File System) داشته باشیم که دارای متدهای Open، Write و Close است و باید از چند سیستم عامل مختلف مثل ویندوز و یونیکس پشتیبانی کند.

برای پیاده‌سازی این سیستم فایل، اول باید کلاس‌های Receiver را بسازیم که در واقع انجام تمام کارها برعهده‌ی آنهاست.
چون کدهایمان را بر اساس واسط‌های جاوا می‌نویسیم، می‌توانیم واسط FileSystemReceiver را طراحی کنیم.
سپس می‌توانیم کلاس‌هایی را، که آن را Implement می‌کنند، برای سیستم‌های عامل مختلف مثل ویندوز، یونیکس، سولاریس و … بنویسیم.

  1. State

زمانی از این Design Pattern استفاده می‌کنیم که یک شئ رفتارش را بر اساس State داخلی خود تغییر دهد.

اگر بخواهیم رفتار یک شئ را، براساس State آن عوض کنیم، می‌توانیم یک متغیر State داشته باشیم. برای انجام عمل‌های متفاوت نیز براساس این متغیر، می‌توانیم از بلوک شرطی if-else استفاده کنیم.
دیزاین پترن State از طریق پیاده‌سازی Context و State، روشی سیستماتیک و Loosely Coupled را عرضه می‌کند.

  1. Visitor

وقتی می‌خواهیم عملی را روی گروهی از اشیاء مشابه انجام دهیم، از این Design Pattern استفاده می‌کنیم. با کمک دیزاین پترن Visitor، می‌توانیم منطق عملیاتی اشیاء را به کلاسی دیگر ببریم.

برای مثال یک سبدخرید را درنظر بگیرید که می‌توانیم آیتم‌های (Element) مختلفی را به آن اضافه کنیم. وقتی روی گزینه‌ی پرداخت کلیک می‌کنیم، هزینه‌ی نهایی محاسبه می‌شود.
حالا می‌توانیم این منطق محاسبه‌ی هزینه را در کلاس‌های مربوط به هر آیتم قرار دهیم یا آن را با استفاده از دیزاین پترن Visitor به کلاس دیگری ببریم.

  1. Interpreter

از این Design Pattern برای نمایش گرامری یک زبان استفاده می‌شود. این پترن یک مفسر برای کار با این گرامر ارائه می‌کند.

بهترین مثال برای دیزاین پترن Interpreter کامپایلر جاواست که سورس کد جاوا را به بایت کد قابل فهم برای JVM تبدیل می‌کند.
گوگل ترنسلیتور نیز مثالی دیگر از یک الگوی Interpreter است که در آن ورودی می‌تواند به هر زبانی باشد و ما خروجی ترجمه‌شده به زبانی دیگر را تحویل می‌گیریم.

  1. Iterator

این پترن نیز یک Design Pattern رفتاری است و زمانی از آن استفاده می‌شود که به دنبال روشی استاندارد برای گشتن در میان گروهی از اشیاء هستیم.
از دیزاین پترن Iterator در Java Collection Framework بسیار استفاده شده است. واسط Iterator در این فریمورک، متدهایی را برای گشتن در میان یک Collection ارائه می‌کند.

از دیزاین پترن Iterator فقط به منظور گشتن در مجموعه‌ها استفاده نمی‌شود. می‌توانیم بر اساس نیازمان انواع مختلفی از Iteratorها را داشته باشیم.
این دیزاین پترن پیاده‌سازی اصلی را پنهان می‌کند و برنامه‌های کلاینت تنها از متدهای Iterator استفاده می‌کنند.

  1. Memento

وقتی به‌منظور استفاده‌های بعدی، قصد ذخیره‌ی State یک شئ را داریم، از این Design Pattern استفاده می‌کنیم.
دیزاین پترن Memento به گونه‌ای این کار را انجام می‌دهد که داده‌ی State شئ از بیرون قابل دسترسی نباشد. این کار صحت داده‌ی استیتِ ذخیره‌شده را حفظ می‌کند.

دیزاین پترن Memento با استفاده از دو شئ پیاده‌سازی می‌شود، Originator و Caretaker. می‌خواهیم استیت شئ Originator را ذخیره و بعداً از آن استفاده کنیم.
این شئ از یک کلاس داخلی (Inner) برای ذخیره‌ی استیت شئ استفاده می‌کند. این کلاس داخلی Private است و اشیاء دیگر نمی‌توانند به آن دسترسی داشته باشند.

پیشنهاد فرانش به شما
آموزش React Native: برنامه نویسی اندروید با جاوا اسکریپت

 

Design Patternهای متفرقه

Design Patternهای بسیاری هستند که جزو هیچ‌کدام از دیزاین‌ پترن‌های گروه GoF نیستند. در ادامه، برخی از این دیزاین پترن‌های معروف را با هم بررسی می‌کنیم.

  1. DAO

از این Design Pattern برای بردن منطق ماندگاری داده (Data Persistence Logic) به یک لایه‌ی دیگر استفاده می‌شود.
وقتی می‌خواهیم سیستمی طراحی کنیم که با پایگاه داده کار می‌کند، دیزاین پترن DAO بسیار مفید است. ایده‌ی اصلی این است که لایه‌ی Service را از لایه‌ی Data Access جدا کنیم.
در این صورت منطق‌های برنامه را از هم جدا کرده‌ایم.

  1. Dependency Injection

با این Design Pattern می‌توانیم Dependencyهای استاتیک (Hard-Coded) را حذف کنیم و اپلیکیشنی Loosely Coupled، قابل توسعه و با هزینه‌ی نگداری پایین داشته باشیم.
می‌توانیم با پیاده‌سازی Dependency Injection در جاوا Dependencyها را از زمان کامپایل، به زمان اجرا ببریم. فریمورک Spring بر مبنای اصل Dependency Injection بنا شده است.

  1. MVC

این پترن یکی از قدیمی‌ترین معماری‌های موجود برای ساخت اپلیکیشن‌های تحت وب است. کلمه‌ی MVC مخفف سه کلمه‌ی Model، View و Controller است.

معرفی Design Patternهای جاوا در اینجا به پایان می‌رسد. در این مقاله تنها هدفمان معرفی ساده‌ و ارائه‌ی لیستی کامل از آنها بود.