یک فنجان جاوا - دیزاین پترن ها - Builder

سلام. میتونید برای آشنایی با الگوهای طراحی (یا همون دیزاین پترن های) زبان جاوا و دیدن لیست مقالاتِ آموزشیشون، به مقاله ی یک فنجان جاوا - دیزاین پترن ها Design Patterns مراجعه کنید. https://virgool.io/@mohammad.ghodsian/java-design-patterns-ntnvkqzm3ns0 همونطور که گفته شده این الگو (بیلدر) زیرمجموعه ی الگوهای تکوینی یا ساختنی هست و لیست مقالات آموزشیِ من در مورد الگوهای Creational عبارت هستند از:Creational Design PatternsSingletonFactoryFactory of Factory (Abstract Factory)PrototypeBuilderObject PoolBuilder - Creational Design Patternsالگوی Builderاین پترن بیشتر زمانی مورد اهمیته که با کلاسای بزرگ و پیچیده سر و کار داریم. به عبارت ساده تر کمکمون میکنه که اشیاء با پیچیدگی زیاد، راحت تر و یا شاید با اشیاء ساده تر ساخته بشن.بریم یه مثال که از ویکیپدیای این الگو برداشتم رو نگاه کنیم. کد زیر کلاسی هست که از این پترن استفاده نمیکنه:import java.awt.*; public class StreetMap { private final Point origin; private final Point destination; private final Color waterColor; private final Color landColor; private final Color highTrafficColor; private final Color mediumTrafficColor; private final Color lowTrafficColor; public StreetMap(Point origin, Point destination, Color waterColor, Color landColor, Color highTrafficColor, Color mediumTrafficColor, Color lowTrafficColor) { // Required parameters this.origin = origin; this.destination = destination; // Optional parameters this.waterColor = waterColor; this.landColor = landColor; this.highTrafficColor = highTrafficColor; this.mediumTrafficColor = mediumTrafficColor; this.lowTrafficColor = lowTrafficColor; } }خب برای ساخت یه شئ از این کلاس باید کدی شبیه کد زیر زده بشه:StreetMap map = new StreetMap(new Point(50, 50), new Point(100, 100), Color.BLUE, Color.GRAY, Color.RED, Color.YELLOW, Color.GREEN);حالا اگه بخوایم الگوی سازنده رو پیاده سازی بکنیم، کلاسمون تبدیل به کلاس زیر میشه:public class StreetMap { private final Point origin; private final Point destination; private final Color waterColor; private final Color landColor; private final Color highTrafficColor; private final Color mediumTrafficColor; private final Color lowTrafficColor; public static class Builder { // Required parameters private final Point origin; private final Point destination; // Optional parameters - initialize with default values private Color waterColor = Color.BLUE; private Color landColor = new Color(30, 30, 30); private Color highTrafficColor = Color.RED; private Color mediumTrafficColor = Color.YELLOW; private Color lowTrafficColor = Color.GREEN; public Builder(Point origin, Point destination) { this.origin = origin; this.destination = destination; } public Builder waterColor(Color color) { waterColor = color; return this; } public Builder landColor(Color color) { landColor = color; return this; } public Builder highTrafficColor(Color color) { highTrafficColor = color; return this; } public Builder mediumTrafficColor(Color color) { mediumTrafficColor = color; return this; } public Builder lowTrafficColor(Color color) { lowTrafficColor = color; return this; } public StreetMap build() { return new StreetMap(this); } } private StreetMap(Builder builder) { // Required parameters origin = builder.origin; destination = builder.destination; // Optional parameters waterColor = builder.waterColor; landColor = builder.landColor; highTrafficColor = builder.highTrafficColor; mediumTrafficColor = builder.mediumTrafficColor; lowTrafficColor = builder.lowTrafficColor; } }اوه!! چقد کلاسمون بزرگتر شد، نه؟! ولی همیشه زیاد شدن کد بد نیست، چون جایی مثل اینجا باعث میشه پیچیدگی تعریف شئ از کلاس و در حقیقت حجم خط کد موقع ساخت اشیاء از این کلاس کاهش پیدا کنه. برای ساختن شئ از این کلاس به شکل زیر عمل میکنیم:StreetMap map = new StreetMap.Builder(new Point(50, 50), new Point(100, 100)).landColor(Color.GRAY).waterColor(Color.BLUE.brighter()) .build();همون طور که میبینین توابع landColor و waterColor کد رو بسیار خواناتر میکنن و حتی زمانی که خودمون میخوایم از کلاسی که خودمون نوشتیم شئ جدیدی بسازیم کارمون خیلی راحت تر شده. و در نهایت هم تابع build() شئ ساخته شده رو برمیگردونه.به عنوان مثال برای توضیح این کد، با صدا زدن تابع StreetMap و پاس دادن یه Builder بهش، شئ StreetMap تمام خواصش رو از Builder میگیره و تابع build() از کلاس Builder شئ جدید ساخته شده رو برمیگردونه. و به عنوان آخرین نکته از این کدها، با این ساختار کاربر پارامترهای origin و destination رو به عنوان پارامترهای اجباری باید تعیین کنه و سایر پارامترها رو بصورت اختیار میتونه وارد بکنه یا نکنه.به عنوان یه مثالِ دیگه کلاس زیر رو در نظر بگیریم:public class Person { private String firstName; private String middleName; private String lastName; private int age; public Person(String firstName, String middleName, String lastName, int age) { this.firstName = firstName; this.middleName = middleName; this.lastName = lastName; this.age = age; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getMiddleName() { return middleName; } public void setMiddleName(String middleName) { this.middleName = middleName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }این کلاس اگه بخواد با الگوری سازنده پیاده بشه به شکل زیر تغییر پیدا میکنه:public class Person { private String firstName; private String middleName; .... public Person(String firstName, String middleName, String lastName, int age, String fathersName, String mothersName, double height, double weight) { this.firstName = firstName; this.middleName = middleName; .... } public static class Builder { private String firstName; private String middleName; private String lastName; private int age; private String fathersName; private String mothersName; private double height; private double weight; public Builder setFirstName(String firstName) { this.firstName = firstName; return this; } public Builder setMiddleName(String middleName) { this.middleName = middleName; return this; } public Builder setLastName(String lastName) { this.lastName = lastName; return this; } ... public Person build() { return new Person(firstName, middleName, lastName, age, fathersName, mothersName, height, weight); } }چجوری میتونیم از builder واسه تعریف شئ جدید استفاده کنیم؟ به شکل زیر:Person person = new Person.Builder() .setAge(5) .setFirstName(&quotBob&quot) .setHeight(6) .setAge(19) .build();واضحه که اگه بخوایم تعداد زیادی شئ بسازیم، این پترن خیلی کمکمون میکنه. حتی واسه کلاسی مثل Person که زیاد پیچیده هم نیست دیدم که این الگو تأثیر زیادی داره، چه برسه به کلاسا یا ساختار های کلاسی که پیچیدگی بیشتری داره.حالا ببینیم چجوری میتونیم یذره حرفه ای تر این الگو رو استفاده کنیم. فرضِ ما شرکتایی هستن که CD به بازار ارائه میدن:با بسته بندی شروع میکنیم، که شامل قیمت و یه لیبل هست:public interface Packing { public String pack(); public int price(); }هر CD یه لیبل داره، پس کلاس CD رو اینجوری میتونیم تعریف کنیم:public abstract class CD implements Packing { public abstract String pack(); }و البته هر کارخونه ممکنه قیمت متفاوتی داشته باشه، پس کلاس کارخونه رو هم به شکل زیر تعریف میکنم:public abstract class Company extends CD { public abstract int price(); }خب الآن میتونیم شرکت هایی (برای مثال سونی و سامسونگ) رو اضافه کنیم:public class Sony extends Company { @Override public int price() { return 20; } @Override public String pack() { return &quotSony CD&quot } }public class Samsung extends Company { @Override public int price() { return 15; } @Override public String pack() { return &quotSamsung CD&quot } }به عنوان مرحله ی آخر قبل از استفاده از الگوی Builder کلاس CDType رو مینویسیم (لیستی از CD ها که میشه به اون لیست اضافه کرد، قیمت کل رو دید و یا موارد موجود توی لیست رو نشون داد):public class CDType { private List<Packing> items = new ArrayList<Packing>(); public void addItem(Packing packs) { items.add(packs); } public void getCost() { for (Packing packs : items) packs.price(); } public void showItems() { for (Packing packing : items) { System.out.print(&quotCD name : &quot + packing.pack()); System.out.println(&quot, Price : &quot + packing.price()); } } }تا اینجا که سختی خاصی نداشت و حالت عادی پیاده سازی بود. حالا میخوایم از الگوی Builder استفاده کنیم:public class CDBuilder { public CDType buildSonyCD() { CDType cds = new CDType(); cds.addItem(new Sony()); return cds; } public CDType buildSamsungCD() { CDType cds = new CDType(); cds.addItem(new Samsung()); return cds; } }خلاصه ی کدای بالا میشه اینکه وقتی ما میخوایم یه سی دی جدید تعریف کنیم به سادگی و بصورت زیر میشه این کار رو انجام داد:CDBuilder cdBuilder = new CDBuilder(); CDType cdType1 = cdBuilder.buildSonyCD(); cdType1.showItems();کد بالا بصورت خودکار CD از برند سونی تولید میکنه. واضحه که برای ساختن یه CD از برند سامسونگ هم میتونیم به شکل زیر عمل کنیم:CDType cdType2 = cdBuilder.buildSamsungCD(); cdType2.showItems();احتمالاً با خودتون میگین خب حالا که چی؟! این همه کد زدیم و کلاس ساختیم که چی؟؟جواب این سوال اینه که این کدها فقط نمونه ای هستن از این الگو. برای مثال با کمی تغییر توی توابع سازنده ی کلاس CD از برندای مختلف میتونم ویژگی های دلخواه خودمون رو اضافه کنیم (مثلاً هر CD قیمت و لیبل خودشو بگیره یا ...). یا هر زمانی خواستیم برند جدیدی بسازیم فقط اونو با ویژگی های مخصوص خودش پیاده میکنیم و از کلاس Company ارث میبره و یه تابع به CDBuilder اضافه میکنیم. یا مثلاً اگه بخوایم ویژگی ای به CD اضافه کنیم دیگه نیازی نیست به همه ی شرکتا اضافه بشه و فقط به کلاس CD اضافه میشه. حتی به این مورد فکر بکنین که کلاس Company بجای CD از کلاس دیگه ای (مثلاً Product) ارث ببره و اون کلاس خودش شامل چندین مورد (مثلاً CD، دستگاه هوشمند و سایر کالاها) باشه! میبینیم که توی این مورد، بجای پیچیدگی زیادی که ممکنه کدهای عادی برامون داشته باشن، صرفاً زمان پیاده سازی این الگو شاید کمی اذیت بشیم و بعدش دیگه موقع استفاده براحتی از برندای مختلف کالاهای مختلف تعریف و استفاده کنیم.اینا فقط مواردیه از این الگو. اگه احساس میکنین کمی گنگه براتون یا اینکه نمیدونین دقیقاً کجا و چجوری از این الگو استفاده کنین اصلاً نگران نباشین، فقط شروع کنین به کد زدن، مثال نوشتن، و به مرور زمان توی کد هاتون ازش استفاده کردن. بعد از یه مدت حس میکنین که سختتونه بدون این پترن ساختار های اینچنینی رو پیاده کنین. منتشر شده در ویرگول توسط محمد قدسیان https://virgool.io/@mohammad.ghodsian https://vrgl.ir/7S7fU