جاوا و تکنولوژی های آن

java programming language

در این وبلاگ به بررسی نکات موجود در جاوا و تکنولوژی های آن می پردازیم

طبقه بندی موضوعی

در این بخش به نحوه ایجاد روابط شی گرایی در دنیای رابطه ای میپردازیم و برخی کانفیگ های ضروری هایبرنیت و استراتژی های تولید Id میپردازیم 


در این بخش به بررسی دنیای شی گرایی و رابطه ای میپردازیم و همینطور کمی روی کلاس های Entity تمرکز میکنیم :


Creational Design Patterns


singleton : از ابجکت تنها یکبار instance ساخته شود و هر باری که یک instance میگیریم به همان ابجکت اولیه اشاره کند

کاربرد : Logger , DB Connection , Caching , Thread Pool , ...


Factory : یک Interface یا Abstract class داشته باشیم و در runtime تصمیم بگیریم که کدام یک از فرزندان آنرا بسته به شرایط انتخاب و ایجاد کنیم

کاربرد : انعطاف پذیری Instance ایجاد شده و وابستگی کمتر به یک کلاس در شرایطی که یک کلاس چندین کاربرد را میتواند برای ما داشته باشد مثلا فرض کنید مالیات فروش در یک سال با سال دیگر فرق کند کافی است دو کلاس فرزند با ضریب مالیات مختلف ایجاد کنیم و بعد در کد استفاده کنیم


Abstract Factory : گاهی به آن Factory of Factory هم گفته میشود ، موقعی که شرایط Factory ما پیچیده تر باشد و نیاز داشته باشیم بیش از یک حالت Factory داشته باشیم در این صورت نیاز است که یک Factory بزرگتر برای ایجاد Sub Factory ها داشته باشیم و بعدا از طریق Sub Factory ها Instance مورد نیاز را انتخاب و ایجاد میکنیم

کاربرد : مواقعی که نیاز باشد چندین نوع Instance که مربوط به یک موضوع باشند را از طریق Factory انتخاب و ایجاد کنیم


Prototype : همانطور که از اسمش پیداست (نمونه اولیه) از Instance ساخته شده یک clone می دهد و دو نوع clone داریم یکی Shallow و دیگری Deep Copy که سطحی و عمیق هستند با اینترفیس Clonable و متد clone یک Shallow کپی داریم و با اینترفیس Serializable یک Deep Copy از ابجکت داریم

کاربرد : زمانی که میخواهیم یک ابجکت دیگر عین ابجکت جاری داشته باشیم ولی Reference آن یکی نباشد و کاملا مستقل باشد


Registery : یک دفتر ثبت ابجکت و یک کلید دارد که با دادن کلید یک clone از آن ابجکت میگیرد و برمیگرداند و با Prototype قابل ادغام است

کاربرد : زمانیکه نیاز داریم از یک ابجکت و محتوای درون آن تعداد زیادی استفاده کنیم


Builder : یکی از پر کاربردترین الگو های طراحی است و زمانی که بخواهیم پیچیدگی های ایجاد یک ابجکت بزرگ را ساختارمند کنیم میتوانیم استفاده کنیم

کاربرد : زمانی که یک ابجکت برای ایجاد شدن نیاز به کانفیگ ها و پارامتر های زیادی باشد ساخت آن ابجکت را ساده تر میکند نمونه این الگو را در اپلیکیشن سرور ها میتوان دید


Object Pool : زمانی که استفاده از یک نوع ابجکت بسیار زیاد ، ایجاد و یا دسترسی به آن ابجکت زمان بر و یا حجم زیادی را روی حافظه اشغال کند ما نیاز داریم که این نوع از ابجکت ها را بعد از ایجاد و استفاده از بین نبریم و باز دوباره آنها را مورد باز استفاده قرار دهیم برای این منظور نیاز به یک Container ای داریم که آن ابجکت ها را برای ما مدیریت کنند و مواقعی که نیاز داریم یکی به ما بدهد و مواقعی که در حال استفاده است آنرا به درخواست کننده دیگر ندهد و در پایان آن ابجکت را دوباره نگهداری کند

کاربرد : پرفرمانس کد را بسیار بالا میبرد و مصرف منابع سیستم را بسیار کمتر خواهد کرد و به ابجکت ها قابلیت استفاده مجدد را میدهد مثل DB Connection 




Structural Design Patterns


Adapter : موقعی که ابجکتی اولیه نیاز به ابجکتی ثانویه دیگری دارد و ما برای ایجاد آن ابجکت ثانویه باید اطلاعات یک یا چند ابجکت دیگر را که با ابجکت اولیه ناسازگار هستند را در قالب ابجکت ثانویه تبدیل کنیم تا مورد استفاده ابجکت اولیه قرار گیرد اینجا باید از الگوی adapter برای تبدیل استفاده کنیم و اطلاعات مورد نیاز را استخراج و به یک ابجکت جدید تبدیل کنیم

کاربرد : زمانی که ابجکت نا سازگار با آن چیزی که مورد استفاده است داشته باشیم و برای ایجاد باید اطلاعات مورد نظر را جهت هماهنگی تبدیل به ابجکتی دیگر کنیم


Bridge : فرض کنید یک توالی ارث بری شده از کلاس های Abstract یا interface ها داریم که در هر لایه دارای متد هایی برای انجام وظایفی وجود دارد و تعداد زیادی پیاده سازی از آنها برای شرایط مختلف مورد نیاز است (n در m حالت) که این خود حجم زیادی کدنویسی میبرد و همین تعداد زیاد کلاس های پیاده سازی شده شرایط استفاده را پیچیده و نگهداری و بازنویسی کد را هم ضعیف میکند ، کوچکترین تغییری در یکی از سلسله مراتب ارث بری منجر به بازنویسی در تعداد زیادی کلاس پیاده ساز خواهد شد و برنامه انعطاف پذیری پایینی خواهد داشت

تکنیک ساخت Bridge : در کد هایی که این وضعیت را دارند (تعداد زیادی کلاس Concrete برای حالت های مختلف کاری) می آییم نگاه میکنیم کدام قسمت ها را میتوان جدا کرد (interface یا abstract کلاس های موجود در سلسله ارث بری موجود هستند) ، این تکنیک جدا سازی باید متمرکز و محدود به انجام یک task واحد باشد و بعد حالت های مختلف فقط آن task را پیاده سازی میکنیم ، برای بقیه لایه های جدا شده هم پیاده سازی های مختلف را در نظر میگیریم

و بعد در کلاس نهایی که قرار است مورد استفاده قرار گیرد اینترفیس مربوطه را بصورت Composition وارد میکنیم و از طریق Constractor پیاده ساز مورد نظر را به آن ارسال میکنیم و بعد با متد های مورد نظرش کار کنیم به این تکنیک پل ساختن یا Bridge میگویند

کاربرد : منعطف کردن برنامه و کم کردن پیچیدگی های حاصل از ایجاد تعداد زیادی پیاده سازی برای حالت های مختلف کاری و اگر نیاز بود ویژگی جدید یا task جدیدی اضافه شود با کمترین زمان و پیچیدگی قابل اضافه شدن است


Composite :  به ترکیب یک نوع ابجکت درون ابجکت خودش هست (مثلا کارکنان یک سازمان) و با Composition فرق دارد و هر جا که ساختار درختی داشتیم میتوانیم از Composite استفاده کنیم و در این الگوی طراحی معمولا سه نوع ابجکت را خواهیم داشت : Component , Leaf , Composite 

component همان کلاس پدر اول است که همه از آن ارث بری کرده اند (در مورد لیست کارکنان یک سازمان می شود اینترفیس Employee)

Leaf کلاس هایی که فرزندان را تشکیل میدهند و ممکن است با هم یک فرق هایی هم داشته باشند

Composite همان ابجکت بزرگ است که در بر گیرنده root و تمامی leaf ها است و دارای رابطه یک به چند دارد چون هر node میتواند خود شامل تعداد زیادی sub-node باشد و همینطور ساختار درختی خواهیم داشت تا به آخرین node برسیم و معمولا داخل ابجکت composite متد های add , remove , getChild , operation داریم

کاربرد : ساختار ارث بری را آنجا که نیاز داریم ابجکت هایی از یک نوع زیر مجموعه خودشان قرار گیرند یکسان میکند


Decorator : نام دیگرش Wrapper است و هدف این الگوی طراحی اضافه کردن ویژگی های جدید به یک سلسله مراتب ارث بری که از قبل وجود دارد به این صورت که مثلا به متد (ویژگی) کنونی رفتاری جدیدتر و یا بیشتری را اعمال کنیم و رابطه کلاس های فرزند با پدرشان رابطه Composition است پس در طراحی نیاز به ارث بری و Composition داریم و نیاز به یک Constractor داریم که ابجکتی از جنس Hierarchy به عنوان آرگومان بگیرد و بعد و یا قبل و یا بجای اجرای متد خاص آن Composition Object ویژگی های خاص مورد نظر را اضافه کنیم مثل DataOutputStream

کاربرد : اضافه کردن یک رفتار جدید بدون تاثیر گذاری روی اصل پیاده سازی 


Facade : موقعی که ما یک API پیچیده داریم که شامل تعداد زیادی کلاس باشد که هر یک کاری را انجام میدهند برای ساده سازی کار با API نیاز به یک Facade داریم که ما یا آن کار کنیم و هر نوع سرویسی که میخواهیم را آن برای ما فراهم کند و به این صورت است که داخلش کلاس های سرویس Composition شده است و متد های آنها را مشابه سازی کرده و یا دارای یک متد است که وقتی صدا زده میشود از آن سرویس ها داخلیش استفاده میکند در واقع درخواست را به کلاس ها داخلی delegate میکند 

در Facade همیشه باید به معماری پیاده سازی هم فکر کنیم و مانند بقیه الگوهای طراحی از طراحی یکسانی برخوردار نیست و بسته به کاربرد و نحوه اجرا طراحی ما فرق هایی خواهد داشت 

کاربرد : ساده سازی استفاده از سرویس های سیستم خصوصا سرویس هایی که مرتبط با یک حوزه کاری باشند


ّFlyweight : مواقعی که نیاز است ابجکت هایی داشته باشیم که خیلی پر کاربرد هستند و مرتب باید instance از آنها ساخته شود ، Immutable هستند مثل String 

معمولا یک Map با id و interface پدر اصلی ایجاد میکنیم و با دادن id ابجکت مورد نظر را دریافت میکنیم و اگر آن ابجکت نبود یکی ایجاد میشود 

اینطوری ابجکت ها بعد از استفاده از بین نمی روند و در cache ما وجود خواهند داشت

اگر بخواهیم ابجکت ها هر یک scope مختص به خود را داشته باشند هنگام گرفتن ابجکت یک clone از آن میگیریم و بعد بر می گردانیم

کاربرد : بهینه سازی مصرف حافظه با مدیریت بهتر ابجکت ها تا مقدار کمتری حافظه را اشغال کند


Strategy : در این الگوی طراحی میخواهیم بتوانیم بصورت داینامیک رفتار یک کلاس را نسبت به شرایط تغییر دهیم ، اینکار باعث میشه از عبارات شرطی برای تعیین رفتار در شرایط مختلف استفاده نکنیم ، اینطوری یک کلاس abstract یا interface میگیریم و بعد رفتار های مختلف را پیاده و در شرایط مختلف از پیاده سازی های مختلف نسبت به استراتژی تعیین شده استفاده میکنیم 

در جاوا 8 اساس لامبدا بر اساس همین الگوی طراحی میباشد

نحوه استفاده : استراتژی پیاده شده را در runtime به کلاس اصلی composition میکنیم و بعد از ان استفاده میکنیم 

کاربرد : استفاده از حالت های رفتاری مختلف از پیش تعیین شده طبق استراتژی مورد نظر


Proxy : خیلی شبیه Decorator و گاهی شبیه Adapter است و تنها هدفش با آن فرق دارد . همانند Decorator امکان Wrapping را میدهد . به این صورت است که یک interface داریم که پیاده سازی هایی دارد و از آن ابجکتی که نیاز داریم یک ابجکت Composition میکند و به ابجکت اصلیمون functionality اضافه میکند

در طراحی نیاز داریم یک interface اصلی داشته باشیم که آن interface پیاده سازی هایی دارد (Real Object) و ابجکت Proxy مون هم از این Interface پیاده سازی کرده و داخل proxy یک ابجکت از نوع real object ها بصورت composition خواهیم داشت و وقتی client بخواهد با متد های proxy کار کند (دقت کنید که proxy خودش از نوع interface پدر است) ابجکت proxy متد های real object را call خواهد کرد با این تفاوت که متواند قبل از اجرا و بعد از اجرا یکسری عملیات اضافه ای داشته باشیم

تفاوت Proxy و Decorator : 

در Proxy همانند decorator میتواند روی real object یکسری قابلیت اجرا اضافه کند ولی هدف Proxy از اینکار کنترل real object است و نه عوض کردن نحوه اجرا عملیات مثل پیاد سازی امنیت اجرای عملیات و یا انجام caching و یا lazy objects

در proxy در compile time مشخص است که real object از چه نوعی است ولی در decorator اینطور نیست

کاربرد : کنترل ابجکت هدف مثل پیاده سازی سیستم Logging قبل و بعد از اجرای عملیات که در گیر پیاده سازی نشویم پیاده سازی لایه امنیت ، ساده سازی پیاده سازی عملیات ، RMI ، DI IOC استفاده میشود که از لحاظ ساختاری به الگوهای Decorator و Adapter شباهت هایی دارد


AOP در واقع با proxy کار میکند 

در جاوا رسما این الگوی طراحی پیاده سازی شده است و در java.lang.reflect.Proxy  موجود است و به آن JDK Proxy میگویند که مکانیزمی دارد که اجازه میدهد ما proxy ها را راحت تر پیاده سازی بکنیم و برای استفاده متوانیم به این صورت اقدام کنیم :


public interface Proxy_IService {

    public void serviceCalling();
}


public class Proxy_ServiceRealObjectImpl implements Proxy_IService{

    @Override
    public void serviceCalling() {
        System.out.println(getClass().getSimpleName()+ " service called!");
    }
    
}


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Proxy_ServiceProxyObjectImpl implements InvocationHandler {

    Proxy_IService proxyRealObject;

    public Proxy_ServiceProxyObjectImpl(Proxy_IService proxyRealObject) {
        this.proxyRealObject = proxyRealObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(getClass().getSimpleName() + " service invoked!");
        Object invocation = method.invoke(proxyRealObject, args);
        System.out.println(getClass().getSimpleName() + " END service invoked!");
        return invocation;
    }

    //static proxy service creator
    public static Proxy_IService createProxyService(Proxy_IService proxy_IService) {
        Proxy_IService proxyRealObject = (Proxy_IService) Proxy.newProxyInstance(proxy_IService.getClass().getClassLoader(), proxy_IService.getClass().getInterfaces(),
                new Proxy_ServiceProxyObjectImpl(proxy_IService));
        return proxyRealObject;
    }

}


public class TestProxy {

    private static Proxy_IService proxy_IService;

    public static void main(String[] args) {
        proxy_IService = new Proxy_ServiceRealObjectImpl();
        Proxy_IService proxy_IService2 = Proxy_ServiceProxyObjectImpl.createProxyService(proxy_IService);
        proxy_IService2.serviceCalling();
    }

}


output :
Proxy_ServiceProxyObjectImpl service invoked!
Proxy_ServiceRealObjectImpl service called!
Proxy_ServiceProxyObjectImpl END service invoked!



Behavioral Design Pattern


Chain of Responsibility : به معنی زنجیره مسولیت ها ، موقعی پیش میاد که برای اجرای یکسری از عملیات یکسری Handler داریم که میخواهیم سلسله عملیات ما را انجام دهند به هر یک از این handler ها یک حلقه زنجیره گفته میشود و اینطوری کار میکند که هر حلقه زنجیره که کارش تمام شد ادامه کار به زنجیره بعدی ارسال میشود 

در صورتی که تعداد زنجیره ها بزرگ شود احتمال کاهش پرفرمانس وجود دارد 

کاربرد : میتوانیم بصورت داینامیک زنجیره ها را کم و زیاد کنیم و یا پیاده سازی های مختلفی را برای یک نوع از کار تعریف کنیم loosely couple می شود 


Command : این الگو یکسری دستورالعمل را به ابجکتی که آن دستورات را دریافت میکند و اجرا میکند ایجاد میکند و برای طراحی به یک Command Interface و ابجکت هایی به ازای هر دستور و پیاده سازی متد های دستورات و Invoker و Reciever 

کاربرد : تعیین دستورات اجرایی طبق ساختار از پیش تعیین شده به ابجکت هایی که آن رفتار را بپذیرند و درخواست کننده دستورات را از اجرا کننده دستورات جدا میکند و اینطوری انعطاف پذیری بیشتری خواهیم داشت


Interpreter : برای ایجاد ساختاری گرامری در برنامه میتوان از این الگوی طراحی استفاده کرد مانند Regular Expression ها که یک رشته را به عنوان ورودی از کاربر میگیرد و انرا تبدیل به یک کد قابل اجرا میکند در واقع آن رشته یک ساختار معنا داری دارد که میتوان آنرا به کد اجرایی تفسیر کرد و در طراحی این الگو چند نوع ابجکت درگیر هستند :

َ- AbstractExpression : ابجکتی است که کلیه sub-abstration ها داخلش پیاده سازی شده که در موقع اجرا مورد استفاده قرار میگیرند

- TerminalExpression : کلاسی است که عملیات تفسیر کردن از آن آغاز میشود و sub-class ای از AbstractExpression است 

- NonterminalExpression : یکسری ترمینال هستند که داخل هم compose میشوند و sub-class ای از AbstractExpression است 

- Context : چیزی است که مورد تفسیر قرار میگیرد همان رشته ورودی که درخواست کننده ارسال میکند

- Client : درخواست کننده تفسیر

کاربرد : میتوانیم یک ساختار تفسیری دلخواه و گرامری برای انجام عملیاتی که در آینده قرار است ابتدا تفسیر شود و بعد عملیاتی را انجام دهد ، ایجاد کنیم


Iterator : از این الگوی طراحی برای انجام پیمایش استفاده میشود و پیچیدگی های نحوه پیمایش از استفاده کننده مخفی است و اینطوری میتوانیم الگوریتم پیمایش را از منطق برنامه جدا کنیم و برای پیاده سازی :

 یک اینترفیس میخواهیم که متد های hasNext و next داخلش باشد بنام Iterator مثلا

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

در جاوا نیازی نیست ما تمام مراحل را پیاده سازی کنیم برای اینکه ابجکتی از نوع Iterator داشته باشیم کافی است از اینترفیس Iterable پیاده سازی کنیم 

کاربرد : ایجاد پیمایش روی ساختار sequential 


Mediator : یک کلاس واسط است که بین چندین کلاس قرار میگیرد و هدف این است که ابجکت های دیگر براحتی بتوانند با این ابجکت واسط ارتباط برقرار بکنند ، امکان استفاده دوباره از ابجکت ها وجود دارد و Mediator یک جور نقش Router و Hub را در برنامه بازی میکند

کاربرد : زمانی که در برنامه ارتباط بین ابجکت ها زیاد باشد برای هماهنگی و نقش واسط میتوان به این الگو فکر کرد


Memento : با این الگو میتوان state ابجکت ها را نگه داری کرد که بعدا به آن state برگردیم و برای طراحی سه نوع کلاس داریم :

- Originator : همان ابجکت اصلی که میخواهیم state فعلی آن را نگه داریم

- Memento : ابجکت کپی است که از روی Originator برمیداریم و ممکن است در لحظه استفاده با Originator یکسان باشد 

- Caretaker : مسول نگه داری و پیاده ساز Memento ها است 

بهتر است برای نگه داری ابجکت ها برای طولانی مدت از فایل استفاده کنیم و از ObjectInputStream و ObjectOutputStream استفاده کنیم و برای کوتاه مدت از کلاس Stack یا LinkedList استفاده کنیم که در حافظه رم ذخیره خواهد شد ، استفاده از Serializable و clone توصیه نمیشود چون وجود Constractor را از دست میدهیم و برنامه buggy میشود چون یکسری از rule ها و logic ها را از دست خواهیم داد 

کاربرد : گذاشتن یک save point در برنامه که در حالت های مختلف بخواهیم به آن حالت برگردیم


Observer : در این طراحی یک Subject داریم و یکسری دنبال کننده Subject که تغییرات Subject را رصد میکنند و وقتی اتفاقی در Subject افتاد همه دنبال کننده ها باخبر میشوند 

در جاوا یک کلاس برای طراحی راحت تر این الگو داریم که در java.util.Observer قرار دارد و برای پیاده سازی کافی است ابجکت subject از java.util.Observable ارث بری کند این کلاس همان کلاسی است که بقیه کلاس ها تغییرات آنرا قرار است دنبال کنند

و درون کلاس subject یک متدی مینویسیم و آرگومانی که قرار است به دنبال کننده ها ارسال شود را هم مینویسیم که در ابتدای بدنه این متد ، از کلاس Observable متد setCahnged را فراخوانی میکنیم که به همه دنبال کننده ها خبر میدهد که تغییری رخ داده است و با متد notifyObservers آن ابجکت ورودی را به همه دنبال کننده ها ارسال میکنیم 

برای دنبال کننده ها کافی است از اینترفیس java.util.Observer ارث بری و متد update را پیاده سازی میکنیم

و بعد برای اجرا وقتی از کلاس subject یک instance میسازیم که خود این کلاس Observable هم هست و با متد addObserver دنبال کننده ها را به آن اضافه میکنیم

کاربرد : ارسال پیامی یکسان به یکسری از ابجکت های مختلف که نیاز به آن اطلاعات دارند و به محض فراهم شدن اطلاعات میخواهند از وجود آن باخبر شوند تا عملیاتی را انجام دهند 


State : به مقادیری که به ازای هر ابجکتی درون آن وجود دارد (فیلد ها) و میخواهیم وضعیت های خاصی از ابجکت ها را نگه داری کنیم 

برای طراحی نیاز به یک کلاس Context داریم که در آن State ها نگه داری میشوند 

و یک اینترفیس State داریم که پیاده سازی های مختلفی خواهد داشت

کاربرد : نگه داری state های مختلف کاری و تغییر دادن state در حالت های مختلفی که میخواهیم داشته باشیم مانند ریموت کنترل تلویزیون 


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

طراحی : یک کلاس abstract میگیریم و متد های مشترک را پیاده سازی میکنیم و متد یا متد هایی که مشترکا وجود دارند و استفاده میشوند ولی قرار است نسبت به شرایط فرق داشته باشد را از نوع abstract میگیریم و کلاس های دیگر که از این کلاس ارث بری کنند آن متد ها را پیاده سازی میکنند به ایجاد کلاس پایه که رفتار های مشترک را پوشش داده و رفتار های مختلف را اماده پیاده سازی کرده است یک Template pattern میگویند 

کاربرد : موقعی که میخواهیم از نوشتن کد های تکراری که بین چندین ابجکت مشترک است پرهیز کنیم و رفتار های مشترکی که نوعش فرق میکند را پوشش دهیم اینطوری نگهداری کد را هم افزایش داده ایم و قابلیت گسترش کد را بهتر کرده ایم 


Visitor : موقعی که نیاز است functionality کد را به یک ابجکت دیگر بسپاریم که ان یکسری تغییرات را در کد ما انجام میدهند و به ما برمیگرداند 

یک عنصر element داریم که قرار است Visitor روی آن تاثیر بگذارد و یک Visitor داریم که کارش اعمال تغییرات است 

طراحی : یک کلاس client داریم 

در جاوا کلاس های کمکی برای پیاده سازی این الگوی طراحی موجود است :

 java.lang.model.element.Element  و  java.lang.model.element.ElementVisitor 

کاربرد : میتوانیم یک کلاس پایه داشته باشیم و تغییرات آنرا بیرون از کلاس پیاده سازی کنیم و یا یک adapting ایجاد بکنیم ، در این صورت پیچیدگی های کد کمتر خواهد شد




دیزاین پترن ها الگو ها و تفکر کدنویسی استاندارد و تکرار پذیری هستند که طی سالها چالش هایی که متخصصان در حین تولید نرم افزار به آن بر خورده اند به تکامل رسیده اند


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


پیروی از الگو های دیزاین پترن ها کمک میکند که قابلیت نگهداری ،بازنگری ، کیفیت و ساختار کدنویسی بهتری داشته باشیم


گاهی در حین تولید نرم افزار نیاز به الگو هایی برای نحوه تولید ابجکت ها نیاز داریم که به آن Creational Design Pattern میگوند حال ممکن است فکر کنید که همیشه میتوان یک ابجکت را با new کردن یک instance از آن ساخت و چه نیازی به یک الگو دارد ؟

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


گاهی در حین تولید نرم افزار نیاز به الگو هایی برای ساختار درونی و ترکیب ابجکت ها نیاز داریم که به آن Structural Design Pattern میگوند

این دسته از دیزاین پترن ها به چگونگی ارتباط بین ابجکت ها برای ترکیب شدن باهم میپردازد 


گاهی در حین تولید نرم افزار نیاز به الگو هایی برای نحوه رفتار ابجکت ها و جدا کردن عملیات درونی نیاز داریم که به آن Behavioral Design Pattern میگوند

این دسته از دیزاین پترن به استاندارد سازی رفتار و عملیات درونی ابجکت میپردازد طوری که ابجکت ها برای اجرای عملیات بدون اینکه بین آنها وابستگی ایجاد شود براحتی باهم ارتباط برقرارکنند 


نکته مهمی که وجود دارد این است که بسته به زبان و تکنولوژی مورد استفاده بعضی از این دیزاین پترن ها کاربردی ندارد و مسایلی که الگوهای طراحی حل میکنند در زبان و تکنولوژی حل شده اند مانند وقتی که از AOP استفاده میکنید و یا زبان هایی که در درونشان مشکلات را حل کرده اند 

پس درک عمیق از آنچه که نیاز دارید و انچه که مشکل شما را حل میکند بسیار مهم است و گاها دولوپر هایی با سالها سابقه اشتباهات تکنیکی این چنینی میکنند و محتمل است



جاوا در نسخه 8 خود تغییراتی داشته که از ابتدا تا کنون جز پر اهمیت ترین تغییرات میباشد و یادگیری آن بسیار حائز اهمیت است

این ویژگی ها در جاوا 8 تمرکزشان روی what to do است و نه مثل قبل how to do 

یعنی تمرکز را بگذاریم روی نتیجه بجای اینکه تمرکز را روی نحوه پردازش گذاشته باشیم


default method in interface : یک اینترفیس مانند یک کلاس است که همه متد های آن Abstract هستند ولی از جاوا 8 به بعد یک اینترفیس میتواند یک متد دارای بدنه هم داشته باشد که به این متد ها default method گفته میشود و با کلمه کلیدی default مشخص میشوند

interface BankAccount{

    long getBalance();

    default long getBankBranchName(){
       return "khojaste";
    }
}

متد های default میتواند در کلاس فرزند override شوند 


نکته : همانطور که میبینم در جاوا 8 میتوانیم اینترفیس هایی با متد هایی از نوع  default داشته باشیم که پیاده سازی شده اند و در کلاس ها میتوانیم بیش از یک اینترفیس را ارث بری کنیم و طبیعتا multiple inheritance خواهیم داشت ! اما ارث بری چندگانه آیا خطرناک است ؟ 

ابتدا باید بگوییم که ما چند نوع وراثت چندگانه داریم : وراثت از نوع ، حالت و رفتار 

خطرناکترین نوع وراثت چندگانه حالت است که متغییر ها و ویژگی های چند کلاس کاملا به ارث برده میشود با وجود اینترفیس بودن کلاس ها وراثت چند گانه از نوع حالت همچنان نداریم


Functional Interface : اینترفیسی که فقط یک متد abstract داشته باشد functional interface گفته میشود و در بالای یک functional interface میتوانیم FunctionalInterface@ را برای خوانایی بیشتر بگذاریم


Lambda : یک تکه کدی است که بدنه یک تابع را تعریف میکند همانند ریاضی که داشتیم :

r -> r * 2 * 3.14
(x,y) -> x + y

هر عبارت لامبدا میتواند به عنوان یک Functional Interface استفاده شود چون هر functional interface فقط یک متد abstract دارد به عنوان مثال :

public interface Comprator<T>{
     int compare(T o1, T o2);
}

اینترفیس Comprator که در جاوا برای مرتب سازی و مقایسه دو ابجکت مورد استفاده قرار میگیرد تنها یک متد abstract دارد و میتواند توسط lambda بدنه آنرا تعریف کرد

Comprator <Person> comp =
     (a,b) -> a.age().compareTo(b.age());

و یا برای مرتب سازی قبلا از روش زیر استفاده میکردیم : 

Collections.sort( list , new Comprator<Person>(){

    @Override
    public int compare (Person a , Person b ){
       return a.age().compareTo(b.age());
     }
});

را میتوانیم به این صورت با lambda جایگزین کنیم :

Collections.sort( list ,
    (a,b) -> a.age().compareTo(b.age());


Method Reference : از :: برای ارجاع به یک متد میتوان استفاده کرد به عنوان مثال کلاسی داریم بنام Str که یک متدی بنام startsWith دارد که کاراکتر اول رشته ورودی را برمیگرداند

class Str{
   Character startsWith(String s) {
      return s.charAt(0);
   }
}

و یک Functional Interface داریم بنام Converter که یک متد abstract بنام convert دارد و کارش گرفتن یک ورودی و دادن یک خروجی است :

@FunctionalInterface
interface Converter <F,T> {
   T convert (F f);
}

حال میخواهیم بدنه پیاده سازی شده متد startsWith در کلاس Str را به متد abstract ارجاع دهیم :

Str str = new Str();
Converter <String , Character> conv = str::startsWith;
Character char = conv.convert("Human");


ارجاع به Constractor : 

@FunctionalInterface
interface Factory<T> {
    T create();
}

Factory<Car> factory = Car::new;
Car car = factory.create();

هر جا که یک functional interface داشته باشیم میتوانیم از Method Reference و  Lambda استفاده کنیم و بدنه آن متد را تعیین کنیم


بسیاری از interface های معروفی که قبلا استفاده میکردیم در جاوا 8 تبدیل به functional interface شده اند مانند Comprator و Runnable و ...


اینترفیس های جدیدی هم در جاوا 8 اضافه شده اند مانند Predicate , Function ,Supplier , Consumer که در پکیج java.until.function قرار دارند 


اینترفیس تابعی predicate : مثل یک تابع است که یک ورودی توسط متد test میگیرد و یک مقدار boolean بر میگرداند و برای ترکیب predicate ها از and , or , negate میتوان استفاده کرد

String str = "input";
Predicate <String> isNotEmpty = (a) -> a.length() > 0 ;
boolean bol = false;
bol= isNotEmpty.test(str);           //true
bol= isNotEmpty.negate().test(str);  //false

Predicate<String> notNull = a -> a!=null;
bol = notNull.and(isNotEmpty).test(str);    //true

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty2 = isEmpty.negate();

اینترفیس تابعی Function : تابعی است که یک ورودی میگیرد و یک خروجی تولید میکند و با متد apply کار میکند و با متد andThen میتوان Function های مختلفی را باهم ترکیب کرد در حقیقت میتوانیم ببینیم که Predicate یک نوع خاص تری از Function است که یک ورودی میگرفت و تنها boolean بر میگرداند ولی در Function محدودیتی در نوع خروجی نداریم 
Function<String,Integer> toInteger = Integer::valueOf;

Function<String,String> backToString= toInteger.andThen(String::valueOf);

backToString.apply("12345") ;  // "12345"


اینترفیس تابعی Supplier : یک تابعی است که یک ابجکت با کمک متد get تولید میکند و هیچ پارامتر ورودی هم ندارد 

Supplier<Person> personSupplier = Person::new;
Person person = personSupplier.get(); // new person


اینترفیس تابعی Consumer : تابعی که یک پارامتر ورودی میگیرد و خروجی ندارد و با متد accept اجرا میشود 

Consumer<Person> greeter = p -> System.out.println(p.getFirstName());
greeter.accept(person);

اینترفیس تابعی BiFunction دو پارامتر از جنس T و U میگیرد و یک خروجی از نوع V تولید میکند

BiFunction<Integer , Integer , String> biFunction=
      (num1, num2) -> "Result: " + (num1 + num2);
System.out.println(biFunction.apply(20,25));

اینترفیس تابعی UnaryOperator یک پارامتر از جنس T میگیرد و یک خروجی از جنس T تولید میکند

اینترفیس تابعی BinaryOperator دو پارامتر از جنس T میگیرد و یک خروجی از جنس T تولید میکند 

اینترفیس تابعی BiConsumer که دو پارامتر از جنس T و U میگیرد و خروجی ای تولید نمیکند

Map<Integer,String> map ;
Biconsumer<Integer,String> biConsumer = (key,value) ->
System.out.println("key: " + key + " value: "+value);
map.forEach(biConsumer);

اینترفیس تابعی BiPredicate  که دو پارامتر از جنس T و U میگیرد و خروجی از جنس boolean بر میگرداند نمیکند

BiPredicate<Integer,String> condition = 
    (i , s) -> i.toString().equals(s);
System.out.println(condition.test(10 , "10")); //true
System.out.println(condition.test(10 , "20")); //false


کلاس Optional : از این کلاس برای کاهش خطای NullPointerException استفاده میشود . Optional یک کلاس Generic است که هر نوع ابجکتی را میتواند نگهداری کند

بعد از بوجود امدن این کلاس خروجی خیلی از کتابخانه های جدید هم بصورت Optional تعریف شده اند 

چنانچه ابجکت درون آن null باشد میتوان قبل از اینکه به خطا NullPointerException بر بخوریم null بودن آنرا براحتی چک کنیم 

متد isPresent اگر مقدار داخلش null نباشد true بر میگرداند

متد get ابجکت داخلی را بر میگرداند

متد orElse اگر ابجکت داخلی وجود داشت آنرا بر میگرداند وگرنه ابجکتی که داخل آرگومان ارسال کردیم را بر میگرداند

متد ifPresent یک آرگومان از جنس Consumer میگیرد و اگر ابجکت داخلی موجود بود آنرا به Consumer ارسال و اجرا میکند 



Stream : یک دنباله ای از ابجکت ها است که ما میتوانیم روی این دنباله یک یا چند عملیات را انجام دهیم که معمولا از یک Collection ایجاد شده است ولی میتواند از روی منابعی دیگری مانند آرایه ، کانال I/O ، یا یک تابع مولد ایجاد شود 


Stream میتواند روی اعضای دنباله ایجاد شده پیمایش کند و عملیاتی را روی آنها انجام دهد که این عملیات شامل دو نوع هستند :

- عملیات پایانی یا terminal که یک داده ای را برمیگرداند  مثل (Integer , String) و یا بصورت void میتواند باشد 

- عملیات میانی یا intermediate که همان stream را بر میگرداند و میتوان زنجیر وار عملیاتی دیگر را انجام داد 

نکته : تا موقعی که یک عمل terminal یا پایانی فراخوانی نشود هیچکدام از عملیات میانی یا intermediate اجرا نمیشوند 

بر روی هر Collection میتوانیم یک Stream ایجاد بکنیم 

List<String> list ;
Stream<String> stream = list.stream();
stream.forEach(System.out::println);

مثال : یک لیست ماشین داریم که میخواهیم دو ماشین با کمترین قیمت و رنگ مشکی را در خروجی چاپ کنیم :

List<Car> list = ...;

list.stream()
.filter( a -> "Black".equals(a.color))
.sorted((a,b) -> a.price - b.price)
.limit(2)
.forEach(System.out:println);
عبارت داخل filter در حقیقت یک Predicate است 
اگر اعضای دنباله از نوع  Comparable باشند نیازی نیست که درون sort را پر کنیم 
متد limit تنها بر روی تعداد خاصی از اعضای ابتدایی تا تعداد مشخص شده را در نظر میگیرد و بقیه را نادیده میگیرد 
متد skip تعداد خاصی از اعضای مشخص شده از ابتدای دنباله را در نادیده میگیرد و بقیه مورد استفاده قرار میگیرد

متد parallel : میتوانیم اجرای عملیات را بصورت MultiThread نیز انجام دهیم کافی است متد parallel قبل از اجرای عملیات بیاوریم

list.stream().parallel()
.filter( a -> "Black".equals(a.color))
.sorted((a,b) -> a.price - b.price)
.limit(2)
.forEach(System.out:println);

اما باید حواسمان در استفاده کردن از این خاصیت باشد چون ممکن است باعث کاهش پرفرمانس و همچنین خروجی همراه با خطا باشد

مثال برای دیدن کاهش پرفرمانس :

Stream.iterate(1 , i -> i+1)
.limit(n)
.parallel()
.reduce( (a,b) -> a + b);

در کد بالا اگر parallel را حذف کنیم چندین برابر سرعت کد بالاتر میرود چرا ؟ چون iterate کردن نتیجه یک عملیات متوالی است و باید عضو بعدی از روی عضو قبلی ساخته شود و تقسیم کردن چنین عملیاتی در چند ترد این عملیات را کندتر میکند پس متد iterate برای parallel مناسب نیست و تجزیه پذیری ضعیفی دارد

تمامی collection ها تجزیه پذیری خوبی ندارند و در پایین انها را مقایسه میکنیم :

تجزیه پذیری عالی : ArrayList , IntStream.range

تجزیه پذیری خوب : HashSet , TreeSet

تجزیه پذیری ضعیف : LinkedList , Stream.iterate


نکته ای که وجود دارد این است که حتی در مواردی که از ArrayList یک stream گرفته باشیم استفاده از parallel آیا کار ما را سریعتر میکند ؟ همیشه نه و باید دقت کنیم که اگر عناصر موجود تعدادشان کم باشد تاثیر معکوسی در پرفرمانس خواهد داشت 

و یا موردی که از یک collection با تجزیه پذیری ضعیف استفاده کرده باشیم مانند LinkedList آیا استفاده از parallel پرفرمانس را کاهش میدهد همیشه  ؟ باز بستگی دارد اگر عملیات ما بسیار وقتگیر بوده باشد شکستن LinkedList به چند stream و انجام عملیات همروند میتواند پرفرمانس ما را افزایش دهد 

و بهترین کار استفاده از بنچمارک های دستی است 


مثالی برای دیدن نتایج اشتباه با استفاده از parallel در stream :

class Accumulator {
     long total=0;
     public void add(long value) {
          total+=value;
     }
}

long sideEffectParallelSum(long n) {
    Accumulator acc = new Accumulator();
    LongStream.rangeClosed(1 , n)
       .parallel().forEach(acc::add);
     
     return acc.total;
}
در کد بالا ترد های مختلفی متد add را فراخوانی میکنند و قبل از اینکه کار متد add پایان بپذیرد متد دیگری مقدار ورودی را به total اضافه کرده و اینطوری جوابی که در خروجی خواهیم داشت صحیح نخواهد بود و باید متد add بصورت synchronized تعریف میشد که در آن صورت باز استفاده از parallel تاثیری نخواهد داشت 

متد sequential : برعکس متد parallel عملیات را از حالت چند تردی خارج میکند 

متد map : تک تک اعضای دنباله را به نوعی دیگر تبدیل میکند پارامتر ورودی map از نوع Function است و درون Function ما میتوانیم نوع ورودی و نوع خروجی را مشخص کنیم 

List<Car> list = ...;

list.stream().map (car -> car.color)

list.stream()
.map(car -> car.color)
.filter(color -> color.startsWith("B"))
.forEach(System.out::println);

در کد بالا ابتدا ما Stream از نوع Car داشتیم که تبدیل شده با Stream از نوع String که رنگ ماشین ها را نگهداری میکند 


متد reduce : یک عملیات terminal است که یک مقدار تجمیع شده را بر میگرداند که پارامتر ورودی آن یک BinaryOperator است که دو آرگومان میگیرد و آن دو را باهم ترکیب و یک مقدار واحد از نوع Optional بر میگرداند 

چرا خروجی از نوع Optional است ؟ چون لیست ما میتواند بدون عضو باشد 

Optional<Integer> sum = list.stream()
.map(car -> car.price)
.reduce((price1 , price2) -> price1 + price2);

sum.ifPresent(System.out::prinln);

متد count : یک عملیات terminal است و تعداد اعضای دنباله را بصورت یک long برمیگرداند 

متد collect : یک عملیات terminal است که یک stream را به یکی از انواه Collection ها تبدیل میکند و یک ورودی از نوع Collectors میگیرد 

List<Car> newList = list.stream()
    .filter((a) -> a.price < 50)
    .collect(Collectors.toList());

Set<Car> newSet = list.stream()
    .filter((a) -> a.price < 50)
    .collect(Collectors.toSet());

Map<String,Car> newMap = list.stream()
    .filter((a) -> a.price < 50)
    .collect(Collectors.toMap(car -> car.color , car -> car));

در کد بالا در نوع تبدیل به Map  مقدار ورودی Collectors دو پارامتر از نوع Function را گرفته است 


متد anyMatch : چک میکند که آیا حداقل یک شرط  Predicate در بین اعضای دنباله وجود دارد یا خیر و مقدار boolean بر میگرداند 

متد allMatch : چک میکند که آیا در کل اعضای دنباله شرط  Predicate وجود داشته باشد و مقدار boolean بر میگرداند 

متد noneMatch : چک میکند که آیا در کل اعضای دنباله شرط  Predicate وجود نداشته باشد و مقدار boolean بر میگرداند 

boolean anyBlack =
list.stream()
.anyMatch(car -> car.color.equals("Black"));

boolean allBlack =
list.stream()
.allMatch(car -> car.color.equals("Black"));

boolean noneBlack =
list.stream()
.noneMatch(car -> car.color.equals("Black"));
متد distinct : اعضای یونیک موجود در دنباله را در قالب یک stream بر میگرداند
متد findAny : یکی از اعضای دنباله را بصورت تصادفی در قالب Optional برمیگرداند
متد findFirst : اولین عضو دنباله را در قالب Optional برمیگرداند
متد max : عضوی با بزرگترین مقدار از دنباله را بر میگرداند
متد min : عضوی با کمترین مقدار از دنباله را بر میگرداند
متد toArray : دنباله های موجود را در قالب یک ارایه برمیگرداند 



راه های دیگری برای ایجاد stream های اعداد وجود دارند که بعضا پرفرمانس بالاتری هم دارند 

IntStream oneTo19 = IntStream.range(1 , 20);
IntStream oneTo20 = IntStream.rangeClosed(1 , 20);


ایجاد stream ای از اعداد با کمک iterator :

Stream.iterator(0, x -> x + 2)
.limit(10)
.forEach(System.out::println);

پارامتر اول در iterator مبدا شروع و پارامتر دوم یک UnaryOperator است که نجوه ایجاد عضو بعدی از روی عضو قبلی را مشخص کرده است و میتواند بینهایت عضو بسازد و باید بلافاصله با متد limit آنرا محدود به تعداد خاصی کنیم 


ساخت stream با استفاده از of :

Stream<String> stream = Stream.of("a","b","c");

ساخت stream با استفاده از Arrays : 

String [] arr = {"a","b","c"};
Stream<String> stream = Arrays.stream(arr);

ساخت stream روی فایل : 

Stream<String> stream = Files.lines(Paths.get("file.txt"));