مقالات باگتو

آموزش دیزاین پترن Builder در کاتلین
آموزش دیزاین پترن Builder در کاتلین

دیزاین پترنbuilderثابت کرده است که پارادایمی مفید در زبان برنامه نویسی جاوا است  در حالی که پارامترهای زیادی برای ساخت یک شی مورد نیاز است. همانطور که جاوا اشاره می کند ، سازنده ها یا متد های کارخانه با پارامترهای بسیار زیاد (وقتی پارامترها به طور رندوم در کلاینت ها عوض می شوند) در معرض اشکال قرار می گیرند. پارامترهای نامگذاری شدهKotlinدر بسیاری از موارد این مشکل را برطرف می کنند زیرا در کاتلین زمانی که متد را فراخوانی و مقدار دهی میکنیم می توانیم نام هر پارامتر را مشخص کنیم و احتمال جایگذاری نامناسب پارامتر را کاهش بدهیم. با این حال از آنجا کهclientهای جاوا نمی توانند از پارامترهای نام برده استفاده کنند  دیزاین پترنbuilderهمچنان مفید است. علاوه بر این تنظیم داینامیک پارامترهای اختیاری می تواند مفید باشد ، همچنین فقط با دیزاین پترنbuilderامکان پذیر است.

بیایید یک دیزاین پترنbuilderساده را در زبان برنامه نویسی جاوا در نظر بگیریم. ما ابتدا یک کلاسPOJOبه اسمCompany(شرکت) داریم که دارای چندینattributeاست ، شاید برای ساختش استفاده ازbuilderکافی باشد:

public final class Company {
    public final String name;
    public final double marketCap;
    public final double annualCosts;
    public final double annualRevenue;
    public final List<Employee> employees;
    public final List<Office> offices;

    private Company(Builder builder) {
        List<Employee> builtEmployees = new ArrayList<>();
        for (Employee.Builder employee : builder.employees) {
            builtEmployees.add(employee.build());
        }
        List<Office> builtOffices = new ArrayList<>();
        for (Office.Builder office : builder.offices) {
            builtOffices.add(office.build());
        }
        employees = Collections.unmodifiableList(builtEmployees);
        offices = Collections.unmodifiableList(builtOffices);
        name = builder.name;
        marketCap = builder.marketCap;
        annualCosts = builder.annualCosts;
        annualRevenue = builder.annualRevenue;
    }

    public static class Builder {
        private String name;
        private double marketCap;
        private double annualCosts;
        private double annualRevenue;
        private List<Employee.Builder> employees = new ArrayList<>();
        private List<Office.Builder> offices = new ArrayList<>();

        public Company build() {
            return new Company(this);
        }

        public Builder addEmployee(Employee.Builder employee) {
            employees.add(employee);
            return this;
        }

        public Builder addOffice(Office.Builder office) {
            offices.add(office);
            return this;
        }

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        public Builder setMarketCap(double marketCap) {
            this.marketCap = marketCap;
            return this;
        }

        public Builder setAnnualCosts(double annualCosts) {
            this.annualCosts = annualCosts;
            return this;
        }

        public Builder setAnnualRevenue(double annualRevenue) {
            this.annualRevenue = annualRevenue;
            return this;
        }
    }
}

 

یک شرکت لیستی ازEmployeeها و لیستی ازofficeدارد. در این کلاس ها از دیزاین پترنbuilderنیز استفاده می شود:

 

public final class Employee {
    public final String firstName;
    public final String lastName;
    public final String id;
    public final boolean isManager;
    public final String managerId;

    private Employee(Builder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.id = builder.id;
        this.isManager = builder.isManager;
        this.managerId = builder.managerId;
    }

    public static class Builder {
        private String firstName;
        private String lastName;
        private String id;
        private boolean isManager;
        private String managerId;

        Employee build() {
            return new Employee(this);
        }

        public Builder setFirstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder setLastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public Builder setId(String id) {
            this.id = id;
            return this;
        }

        public Builder setIsManager(boolean manager) {
            isManager = manager;
            return this;
        }

        public Builder setManagerId(String managerId) {
            this.managerId = managerId;
            return this;
        }
    }
}

 

و یک کلاس به نامofficeداریم که به صورت زیر میباشد:

 

public final class Office {
    public final String address;
    public final int capacity;
    public final int occupancy;
    public final int sqft;

    private Office(Builder builder) {
        address = builder.address;
        capacity = builder.capacity;
        occupancy = builder.occupancy;
        sqft = builder.sqft;
    }

    public static class Builder {
        private String address;
        private int capacity;
        private int occupancy;
        private int sqft;

        Office build() {
            return new Office(this);
        }

        public Builder setAddress(String address) {
            this.address = address;
            return this;
        }

        public Builder setCapacity(int capacity) {
            this.capacity = capacity;
            return this;
        }

        public Builder setOccupancy(int occupancy) {
            this.occupancy = occupancy;
            return this;
        }

        public Builder setSqft(int sqft) {
            this.sqft = sqft;
            return this;
        }
    }
}

 

حال اگر بخواهیم شرکتی ایجاد کنیم که شامل یک کارمند(Employee) و یک تماس گیرندهofficeباشد ، می توانیم کارهای زیر را در جاوا انجام دهیم:

 

public class JavaClient {
    public Company buildCompany() {
        Company.Builder company = new Company.Builder();
        Employee.Builder employee = new Employee.Builder()
                .setFirstName("Doug")
                .setLastName("Sigelbaum")
                .setIsManager(false)
                .setManagerId("XXX");
        Office.Builder office = new Office.Builder()
                .setAddress("San Francisco")
                .setCapacity(2500)
                .setOccupancy(2400);
        company.setAnnualCosts(0)
                .setAnnualRevenue(0)
                .addEmployee(employee)
                .addOffice(office);
        return company.build();
    }
}

 

حال اگر این کلاس را با زبانkotlin  بنویسیم به صورت زیر خواهد بود:

 

class KotlinClient {
    fun buildCompany(): Company {
        val company = Company.Builder()
        val employee = Employee.Builder()
            .setFirstName("Doug")
            .setLastName("Sigelbaum")
            .setIsManager(false)
            .setManagerId("XXX")
        val office = Office.Builder()
            .setAddress("San Francisco")
            .setCapacity(2500)
            .setOccupancy(2400)
        company.setAnnualCosts(0.0)
            .setAnnualRevenue(0.0)
            .addEmployee(employee)
            .addOffice(office)
        return company.build()
    }
}

 

متد هایwrapperکاتلین با پارامترهایLambda

 wrapperسطح بالا

در اینجا یک تابعKotlinوجود دارد که تنها تابع سطح بالا در کلاسCompanyما خواهد بود:

inline fun company(buildCompany: Company.Builder.() -> Unit): Company {
    val builder = Company.Builder()
    // Since `buildCompany` is an extension function for Company.Builder,
    // buildCompany() is called on the Company.Builder object.
    builder.buildCompany()
    return builder.build()
}

توجه: ما عملکرد را به صورت درون خطی علامت گذاری می کنیم تاoverheadرا از لامبدا حذف کنیم.

از آنجا که پارامترlambdaاز نوعCompany.Builder. () -> Unitبرچسب گذاری شده است ، تمام عبارات موجود درlambdaازScope(دامنه)Company.Builderاستفاده میکنند.

در حال حاضر ،clientکاتلین می تواند از تابعcompanyبرای جلوگیری از ایجاد مستقیم نمونه استفاده کند.Company.Builderو فراخوانیbuild() :

class KtxClient1 {
    fun buildCompany(): Company {
        return company {
            // `this` scope is the Company.Builder being built.
            addEmployee(
                Employee.Builder()
                    .setFirstName("Doug")
                    .setLastName("Sigelbaum")
                    .setIsManager(false)
                    .setManagerId("XXX")
            )
            addOffice(
                Office.Builder()
                    .setAddress("San Francisco")
                    .setCapacity(2500)
                    .setOccupancy(2400)
            )
        }
    }
}

wrapper builderتو در تو

اکنون می توانیم چند متدextensionبرایcompanyاضافه کنیم. Builderبرای جلوگیری از اضافه کردن مستقیمEmployeeیا اضافه کردن آنها.یک راه حل بالقوه وجود دارد:

inline fun Company.Builder.employee(
    buildEmployee: Employee.Builder.() -> Unit
) {
    val builder = Employee.Builder()
    builder.buildEmployee()
    addEmployee(builder)
}

inline fun Company.Builder.office(buildOffice: Office.Builder.() -> Unit) {
    val builder = Office.Builder()
    builder.buildOffice()
    addOffice(builder)
}

 

با استفاده از این توابعextension، کلاسclientکاتلین مانند زیر است:

 

class KtxClient2 {    
    fun buildCompany(): Company {
        return company {
            employee {
                setFirstName("Doug")
                setLastName("Sigelbaum")
                setIsManager(false)
                setManagerId("XXX")
            }
            office {
                setAddress("San Francisco")
                setCapacity(2500)
                setOccupancy(2400)
            }
        }
    }
}

 

تقریباً تمام شد!  ما در مرتب سازیAPI Builderبه هدف خود رسیده ایم ، اما موضوع جدیدی را معرفی کرده ایم. کلاسclientزیر را بررسی کنید:

 

class KtxBadClient {  
    fun buildBadCompany(): Company {
        return company {
            employee {
                setFirstName("Doug")
                setLastName("Sigelbaum")
                setIsManager(false)
                setManagerId("XXX")
                employee {
                    setFirstName("Sean")
                    setLastName("Mcq")
                    setIsManager(false)
                    setManagerId("XXX")
                }
            }
            office {
                setAddress("San Francisco")
                setCapacity(2500)
                setOccupancy(2400)
            }
        }
    }
}

متأسفانه این کار با وجود کلاسEmployeeتودرتو کامپایل و اجرا می شود! در کاتلین  Scopeشما می تواند همزمان چندین شی را دربر بگیرد. این بدان معنی است که هر چیزی در داخلcompany{…}بهCompany.Builder. دسترسی دارد. بنابراین ، هرچیزی درون { … }employeeتو در تو  بهEmployeeدسترسی دارد. BuilderوCompany.Builder. بنابراین این کد به طور خطی 2 کارمند "Doug" و "Sean" را بهCompanyاضافه می کند که هیچ رابطه مستقیمی بینDougوSeanندارند.

چگونه می توانیم توابعextensionخود را تغییر دهیم تا هنگام دسترسیclientبهScopeاشتباه باعث ایجاد خطای کامپایلر شود؟ به عبارت دیگر ، چگونه می توانیم نوعDSLخود را ایمن کنیم؟ خوشبختانهKotlin 1.1انوتیشنDslMarkerرا برای حل این مشکل معرفی کرد.

DSLخود را با استفاده از انوتیشنDslMarkerایمن کنید.

ابتدا یک کلاسannotationبا انوتیشنDslMarkerایجاد کنیم:

@DslMarker
annotation class CompanyDsl

حال ، اگر مجموعه ای از کلاس ها با انوتیشنCompanyDslرا بسازیم تماس گیرندگان این کلاس ها  دسترسی ضمنی به چندینreceiverکه کلاس آنها در مجموعه کلاس هایannotationاست ، نخواهند داشت. در عوض تماس گیرنده ها فقط با نزدیکترین محدوده بهreceiverدسترسی ضمنی دارند. DslMarkerدرKotlin stdlibاست ، بنابراین این احتمال وجود دارد که کلاسهایBuilderاصلی شما این وابستگی را نداشته باشند و بنابراین نمی توانید مستقیماً برای کلاسهایBuilderخود انوتیشن بنویسد. در این صورت ، می توانیدBuilderها را زیر کلاس کنید و از این زیر کلاس ها در متد های  wrapperکاتلین خود استفاده کنید:

@CompanyDsl
class CompanyBuilderDsl : Company.Builder()

@CompanyDsl
class EmployeeBuilderDsl : Employee.Builder()

@CompanyDsl
class OfficeBuilderDsl : Office.Builder()

inline fun company(buildCompany: CompanyBuilderDsl.() -> Unit): Company {
    val builder = CompanyBuilderDsl()
    // Since `buildCompany` is an extension function for Company.Builder,
    // buildCompany() is called on the Company.Builder object.
    builder.buildCompany()
    return builder.build()
}

inline fun CompanyBuilderDsl.employee(
    buildEmployee: EmployeeBuilderDsl.() -> Unit
) {
    val builder = EmployeeBuilderDsl()
    builder.buildEmployee()
    addEmployee(builder)
}

inline fun CompanyBuilderDsl.office(
    buildOffice: OfficeBuilderDsl.() -> Unit
) {
    val builder = OfficeBuilderDsl()
    builder.buildOffice()
    addOffice(builder)
}

 

اکنون ، مثالclientقبلی خطای کامپایلر مورد نظر ما را دریافت می کند:

can’t be called in this context by implicit receiver. Use the explicit one if necessary.

 

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

تگ‌ها
اشتراک

0 نظرات


;