CodeGym /وبلاگ جاوا /Random-FA /SOLID: پنج اصل اساسی طراحی کلاس در جاوا
John Squirrels
مرحله
San Francisco

SOLID: پنج اصل اساسی طراحی کلاس در جاوا

در گروه منتشر شد
کلاس ها بلوک های سازنده برنامه ها هستند. درست مثل آجرهای یک ساختمان. کلاس های بد نوشته شده در نهایت می تواند مشکلاتی ایجاد کند. SOLID: پنج اصل اساسی طراحی کلاس در جاوا - 1برای درک اینکه آیا یک کلاس به درستی نوشته شده است، می توانید بررسی کنید که چگونه با "استانداردهای کیفیت" مطابقت دارد. در جاوا، اینها به اصطلاح اصول SOLID هستند و ما در مورد آنها صحبت خواهیم کرد.

اصول جامد در جاوا

SOLID مخفف است که از حروف بزرگ پنج اصل اول طراحی OOP و کلاس تشکیل شده است. این اصول توسط رابرت مارتین در اوایل دهه 2000 بیان شد و سپس مخفف آن بعداً توسط مایکل فیرز معرفی شد. در اینجا اصول SOLID وجود دارد:
  1. اصل مسئولیت واحد
  2. اصل بسته را باز کنید
  3. اصل جایگزینی لیسکوف
  4. اصل جداسازی رابط
  5. اصل وارونگی وابستگی

اصل مسئولیت واحد (SRP)

این اصل بیان می کند که هرگز نباید بیش از یک دلیل برای تغییر یک کلاس وجود داشته باشد. هر شی یک مسئولیت دارد که به طور کامل در کلاس گنجانده شده است. تمام خدمات یک کلاس در راستای حمایت از این مسئولیت است. چنین کلاس هایی در صورت لزوم همیشه به راحتی قابل تغییر خواهند بود، زیرا مشخص است که کلاس چه مسئولیتی دارد و چه مسئولیتی ندارد. به عبارت دیگر، ما قادر خواهیم بود تغییراتی ایجاد کنیم و از عواقب آن یعنی تاثیر بر اشیاء دیگر نترسیدیم. به‌علاوه، آزمایش چنین کدی بسیار آسان‌تر است، زیرا آزمایش‌های شما یک بخش از عملکرد را جدا از بقیه پوشش می‌دهند. ماژولی را تصور کنید که سفارشات را پردازش می کند. اگر یک سفارش به درستی تشکیل شده باشد، این ماژول آن را در یک پایگاه داده ذخیره می کند و یک ایمیل برای تایید سفارش ارسال می کند:
public class OrderProcessor {

    public void process(Order order){
        if (order.isValid() && save(order)) {
            sendConfirmationEmail(order);
        }
    }

    private boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }

    private void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}
این ماژول به سه دلیل می تواند تغییر کند. اول، منطق پردازش سفارشات ممکن است تغییر کند. دوم، نحوه ذخیره سفارشات (نوع پایگاه داده) ممکن است تغییر کند. سوم، نحوه ارسال تاییدیه ممکن است تغییر کند (برای مثال، فرض کنید به جای ایمیل نیاز به ارسال یک پیام متنی داریم). اصل مسئولیت واحد دلالت بر این دارد که سه جنبه این مشکل در واقع سه مسئولیت متفاوت هستند. این بدان معناست که آنها باید در کلاس ها یا ماژول های مختلف باشند. ترکیب چندین موجودیت که می توانند در زمان های مختلف و به دلایل مختلف تغییر کنند، یک تصمیم طراحی ضعیف در نظر گرفته می شود. بهتر است یک ماژول را به سه ماژول جداگانه تقسیم کنید که هر کدام یک عملکرد واحد را انجام می دهند:
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}

public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}

اصل بسته باز (OCP)

این اصل به شرح زیر است: موجودیت های نرم افزار (کلاس ها، ماژول ها، توابع و غیره) باید برای گسترش باز باشند، اما برای اصلاح بسته باشند . این بدان معناست که باید بتوان رفتار خارجی کلاس را بدون ایجاد تغییرات در کد موجود کلاس تغییر داد. طبق این اصل، کلاس‌ها به گونه‌ای طراحی می‌شوند که تنظیم یک کلاس برای تناسب با شرایط خاص به سادگی مستلزم گسترش آن و لغو برخی از توابع است. این بدان معناست که سیستم باید انعطاف پذیر باشد و بتواند در شرایط متغیر بدون تغییر کد منبع کار کند. در ادامه مثال ما در مورد پردازش سفارش، فرض کنید باید برخی از اقدامات را قبل از پردازش سفارش و همچنین پس از ارسال ایمیل تأیید انجام دهیم. به جای تغییر OrderProcessorخود کلاس، آن را گسترش می دهیم تا بدون نقض اصل بسته باز، به هدف خود برسیم:
public class OrderProcessorWithPreAndPostProcessing extends OrderProcessor {

    @Override
    public void process(Order order) {
        beforeProcessing();
        super.process(order);
        afterProcessing();
    }

    private void beforeProcessing() {
        // Take some action before processing the order
    }

    private void afterProcessing() {
        // Take some action after processing the order
    }
}

اصل جایگزینی لیسکوف (LSP)

این یک تغییر از اصل بسته باز است که قبلاً به آن اشاره کردیم. می توان آن را به صورت زیر تعریف کرد: اشیاء را می توان با اشیاء زیر کلاس ها بدون تغییر ویژگی های برنامه جایگزین کرد. این بدان معنی است که کلاسی که با گسترش یک کلاس پایه ایجاد می شود باید روش های خود را لغو کند تا عملکرد از دیدگاه مشتری به خطر نیفتد. یعنی اگر توسعه‌دهنده‌ای کلاس شما را گسترش دهد و از آن در یک برنامه کاربردی استفاده کند، نباید رفتار مورد انتظار هیچ روشی را تغییر دهد. کلاس‌های فرعی باید متدهای کلاس پایه را لغو کنند تا عملکرد از دیدگاه مشتری شکسته نشود. در مثال زیر می توانیم این موضوع را به تفصیل بررسی کنیم. فرض کنید ما یک کلاس داریم که مسئول تایید یک سفارش و بررسی اینکه آیا همه کالاهای سفارش موجود هستند یا خیر. این کلاس isValid()متدی دارد که true یا false را برمی گرداند :
public class OrderStockValidator {

    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if (!item.isInStock()) {
                return false;
            }
        }

        return true;
    }
}
همچنین فرض کنید که برخی از سفارش‌ها نیاز به اعتبار متفاوتی نسبت به سایرین دارند، به عنوان مثال برای برخی از سفارش‌ها باید بررسی کنیم که آیا همه کالاهای سفارش در انبار هستند و آیا همه کالاها بسته‌بندی شده‌اند یا خیر. OrderStockValidatorبرای انجام این کار، کلاس را با ایجاد کلاس گسترش می دهیم OrderStockAndPackValidator:
public class OrderStockAndPackValidator extends OrderStockValidator {

    @Override
    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if ( !item.isInStock() || !item.isPacked() ){
                throw new IllegalStateException(
                     String.format("Order %d is not valid!", order.getId())
                );
            }
        }

        return true;
    }
}
اما در اینجا ما اصل جایگزینی Liskov را نقض کرده‌ایم، زیرا به جای اینکه اگر دستور اعتبارسنجی ناموفق بود، false راIllegalStateException برگردانیم، روش ما یک عدد را پرتاب می‌کند . مشتریانی که از این کد استفاده می کنند انتظار چنین چیزی را ندارند: آنها انتظار دارند مقدار بازگشتی درست یا نادرست داشته باشند . این می تواند منجر به خطاهای زمان اجرا شود.

اصل جداسازی رابط (ISP)

این یک اصل است که با عبارت زیر مشخص می شود: مشتری نباید مجبور به پیاده سازی روش هایی شود که از آنها استفاده نمی کند . اصل تفکیک رابط به این معنی است که رابط‌هایی که خیلی «ضخیم» هستند باید به واسط‌های کوچک‌تر و خاص‌تر تقسیم شوند تا مشتریانی که از رابط‌های کوچک استفاده می‌کنند فقط از روش‌هایی که برای کارشان نیاز دارند بدانند. در نتیجه، هنگامی که یک روش رابط تغییر می کند، هر کلاینتی که از آن روش استفاده نمی کند، نباید تغییر کند. این مثال را در نظر بگیرید: الکس، یک توسعه دهنده، یک رابط "گزارش" ایجاد کرده و دو روش اضافه کرده است: generateExcel()و generatedPdf(). اکنون یک کلاینت می خواهد از این رابط استفاده کند، اما فقط قصد دارد از گزارش ها در قالب PDF استفاده کند، نه در اکسل. آیا این عملکرد این مشتری را راضی می کند؟ خیر. مشتری باید دو روش را پیاده سازی کند، یکی از آنها تا حد زیادی مورد نیاز نیست و تنها به لطف الکس، کسی که نرم افزار را طراحی کرده، وجود دارد. کلاینت یا از یک رابط متفاوت استفاده می کند یا با روش گزارش های اکسل کاری انجام نمی دهد. پس راه حل چیست؟ این است که رابط موجود را به دو رابط کوچکتر تقسیم کنیم. یکی برای گزارش های PDF و دیگری برای گزارش های اکسل. این به مشتریان این امکان را می دهد که فقط از عملکرد مورد نیاز خود استفاده کنند.

اصل وارونگی وابستگی (DIP)

در جاوا، این اصل SOLID به شرح زیر است: وابستگی های درون سیستم بر اساس انتزاع ها ساخته می شوند . ماژول های سطح بالاتر به ماژول های سطح پایین وابسته نیستند. انتزاع ها نباید به جزئیات بستگی داشته باشند. جزئیات باید به انتزاعات بستگی داشته باشد. نرم افزار باید طوری طراحی شود که ماژول های مختلف از طریق انتزاع به یکدیگر متصل شوند. یک کاربرد کلاسیک این اصل چارچوب Spring است. در Spring Framework، همه ماژول ها به عنوان اجزای مجزا پیاده سازی می شوند که می توانند با هم کار کنند. آنها به قدری مستقل هستند که می توانند به راحتی در ماژول های برنامه غیر از Spring Framework استفاده شوند. این به لطف وابستگی به اصول بسته و باز به دست می آید. همه ماژول ها فقط به انتزاع دسترسی دارند که می تواند در ماژول دیگری استفاده شود. بیایید سعی کنیم با استفاده از یک مثال این موضوع را توضیح دهیم. صحبت از اصل مسئولیت واحد، OrderProcessorکلاس را در نظر گرفتیم. بیایید نگاهی دیگر به کد این کلاس بیندازیم:
public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}
در این مثال، OrderProcessorکلاس ما به دو کلاس خاص بستگی دارد: MySQLOrderRepositoryو ConfirmationEmailSender. همچنین کد این کلاس ها را ارائه خواهیم کرد:
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}
این طبقات با آنچه ما انتزاعات می نامیم فاصله دارند. و از نقطه نظر اصل وارونگی وابستگی، بهتر است به جای پیاده سازی های خاص، با ایجاد چند انتزاع که بتوانیم با آنها در آینده کار کنیم، شروع کنیم. بیایید دو رابط ایجاد کنیم: MailSenderو OrderRepository). اینها انتزاعات ما خواهند بود:
public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
اکنون این رابط ها را در کلاس هایی که قبلاً برای این کار آماده شده اند پیاده سازی می کنیم:
public class ConfirmationEmailSender implements MailSender {

    @Override
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }

}

public class MySQLOrderRepository implements OrderRepository {

    @Override
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}
ما کار مقدماتی را انجام دادیم تا OrderProcessorکلاس ما نه به جزئیات عینی، بلکه به انتزاعات وابسته باشد. ما آن را با افزودن وابستگی های خود به سازنده کلاس تغییر می دهیم:
public class OrderProcessor {

    private MailSender mailSender;
    private OrderRepository repository;

    public OrderProcessor(MailSender mailSender, OrderRepository repository) {
        this.mailSender = mailSender;
        this.repository = repository;
    }

    public void process(Order order){
        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }
}
اکنون کلاس ما به انتزاعات بستگی دارد، نه پیاده سازی های خاص. ما به راحتی می توانیم رفتار آن را با افزودن وابستگی مورد نظر در زمان OrderProcessorایجاد یک شی تغییر دهیم. ما اصول طراحی SOLID را در جاوا بررسی کرده ایم. در دوره CodeGym در مورد OOP به طور کلی و اصول اولیه برنامه نویسی جاوا - هیچ چیز خسته کننده و صدها ساعت تمرین - بیشتر خواهید آموخت. زمان حل چند کار است :)
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION