CodeGym /Java Blog /Acak /SOLID: Lima prinsip dasar desain kelas di Java
John Squirrels
Level 41
San Francisco

SOLID: Lima prinsip dasar desain kelas di Java

Dipublikasikan di grup Acak
Kelas adalah blok bangunan aplikasi. Sama seperti batu bata di sebuah bangunan. Kelas yang ditulis dengan buruk pada akhirnya dapat menyebabkan masalah. SOLID: Lima prinsip dasar desain kelas di Java - 1Untuk memahami apakah suatu kelas ditulis dengan benar, Anda dapat memeriksa bagaimana kelas tersebut memenuhi "standar kualitas". Di Jawa, inilah yang disebut prinsip SOLID, dan kita akan membicarakannya.

Prinsip SOLID di Jawa

SOLID adalah akronim yang dibentuk dari huruf kapital dari lima prinsip pertama OOP dan desain kelas. Prinsip-prinsip tersebut diungkapkan oleh Robert Martin pada awal tahun 2000-an, dan kemudian singkatan tersebut diperkenalkan kemudian oleh Michael Feathers. Berikut adalah prinsip-prinsip SOLID:
  1. Prinsip Tanggung Jawab Tunggal
  2. Prinsip Terbuka Tertutup
  3. Prinsip Pergantian Liskov
  4. Prinsip Segregasi Antarmuka
  5. Prinsip Pembalikan Ketergantungan

Prinsip Tanggung Jawab Tunggal (SRP)

Prinsip ini menyatakan bahwa tidak boleh ada lebih dari satu alasan untuk mengubah kelas. Setiap objek memiliki satu tanggung jawab, yang sepenuhnya dikemas dalam kelas. Semua layanan kelas ditujukan untuk mendukung tanggung jawab ini. Kelas seperti itu akan selalu mudah untuk dimodifikasi jika perlu, karena sudah jelas apa kelas itu dan bukan tanggung jawabnya. Dengan kata lain, kita akan mampu melakukan perubahan dan tidak takut akan akibatnya, yaitu dampaknya terhadap objek lain. Selain itu, kode seperti itu jauh lebih mudah untuk diuji, karena pengujian Anda mencakup satu fungsi secara terpisah dari yang lainnya. Bayangkan sebuah modul yang memproses pesanan. Jika pesanan dibuat dengan benar, modul ini menyimpannya dalam database dan mengirimkan email untuk mengonfirmasi pesanan:

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
    }
}
Modul ini dapat berubah karena tiga alasan. Pertama, logika untuk memproses pesanan dapat berubah. Kedua, cara pesanan disimpan (tipe database) dapat berubah. Ketiga, cara pengiriman konfirmasi dapat berubah (misalnya, misalkan kita perlu mengirim pesan teks daripada email). Prinsip tanggung jawab tunggal menyiratkan bahwa ketiga aspek masalah ini sebenarnya adalah tiga tanggung jawab yang berbeda. Itu berarti mereka harus berada di kelas atau modul yang berbeda. Menggabungkan beberapa entitas yang dapat berubah pada waktu yang berbeda dan untuk alasan yang berbeda dianggap sebagai keputusan desain yang buruk. Jauh lebih baik membagi modul menjadi tiga modul terpisah, yang masing-masing menjalankan satu fungsi:

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);
        }
    }

}

Prinsip Terbuka Tertutup (OCP)

Prinsip ini dijelaskan sebagai berikut: entitas perangkat lunak (kelas, modul, fungsi, dll.) harus terbuka untuk ekstensi, tetapi ditutup untuk modifikasi . Ini berarti bahwa perilaku eksternal kelas harus dapat diubah tanpa membuat perubahan pada kode kelas yang sudah ada. Menurut prinsip ini, kelas dirancang sedemikian rupa sehingga menyesuaikan kelas agar sesuai dengan kondisi tertentu hanya memerlukan perluasan dan mengesampingkan beberapa fungsi. Artinya sistem harus fleksibel, mampu bekerja dalam kondisi yang berubah-ubah tanpa mengubah source code. Melanjutkan contoh kita yang melibatkan pemrosesan pesanan, misalkan kita perlu melakukan beberapa tindakan sebelum pesanan diproses serta setelah email konfirmasi dikirimkan. Alih-alih mengubahOrderProcessorkelas itu sendiri, kami akan memperluasnya untuk mencapai tujuan kami tanpa melanggar Prinsip Terbuka Tertutup:

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
    }
}

Prinsip Pergantian Liskov (LSP)

Ini adalah variasi dari prinsip buka tutup yang telah kami sebutkan sebelumnya. Ini dapat didefinisikan sebagai berikut: objek dapat diganti dengan objek subkelas tanpa mengubah properti program. Ini berarti bahwa kelas yang dibuat dengan memperluas kelas dasar harus mengesampingkan metodenya sehingga fungsionalitasnya tidak terganggu dari sudut pandang klien. Artinya, jika pengembang memperluas kelas Anda dan menggunakannya dalam aplikasi, dia tidak boleh mengubah perilaku yang diharapkan dari metode yang diganti. Subkelas harus mengesampingkan metode kelas dasar sehingga fungsionalitasnya tidak rusak dari sudut pandang klien. Kita dapat menjelajahi ini secara rinci dalam contoh berikut. Misalkan kita memiliki kelas yang bertanggung jawab untuk memvalidasi pesanan dan memeriksa apakah semua barang yang dipesan tersedia.isValid()metode yang mengembalikan true atau false :

public class OrderStockValidator {

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

        return true;
    }
}
Misalkan juga beberapa pesanan perlu divalidasi berbeda dari yang lain, misalnya untuk beberapa pesanan kita perlu memeriksa apakah semua barang yang dipesan tersedia dan apakah semua barang sudah dikemas. Untuk melakukan ini, kami memperluas OrderStockValidatorkelas dengan membuat OrderStockAndPackValidatorkelas:

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;
    }
}
Tapi di sini kita telah melanggar prinsip substitusi Liskov, karena alih-alih mengembalikan false jika validasi pesanan gagal, metode kita melempar IllegalStateException. Klien yang menggunakan kode ini tidak mengharapkan ini: mereka mengharapkan nilai kembalian true atau false . Ini dapat menyebabkan kesalahan runtime.

Prinsip Segregasi Antarmuka (ISP)

Prinsip ini dicirikan oleh pernyataan berikut: klien tidak boleh dipaksa untuk mengimplementasikan metode yang tidak akan mereka gunakan . Prinsip pemisahan antarmuka berarti antarmuka yang terlalu "tebal" harus dibagi menjadi lebih kecil, lebih spesifik, sehingga klien yang menggunakan antarmuka kecil hanya mengetahui metode yang mereka butuhkan untuk pekerjaan mereka. Akibatnya, ketika metode antarmuka berubah, setiap klien yang tidak menggunakan metode tersebut tidak boleh berubah. Pertimbangkan contoh ini: Alex, seorang pengembang, telah membuat antarmuka "laporan" dan menambahkan dua metode: generateExcel()dangeneratedPdf(). Sekarang klien ingin menggunakan antarmuka ini, tetapi hanya bermaksud menggunakan laporan dalam format PDF, bukan di Excel. Akankah fungsionalitas ini memuaskan klien ini? Tidak. Klien harus mengimplementasikan dua metode, salah satunya sebagian besar tidak diperlukan dan hanya ada berkat Alex, orang yang merancang perangkat lunak. Klien akan menggunakan antarmuka yang berbeda atau tidak melakukan apa pun dengan metode untuk laporan Excel. Jadi apa solusinya? Itu untuk membagi antarmuka yang ada menjadi dua yang lebih kecil. Satu untuk laporan PDF, satu lagi untuk laporan Excel. Ini memungkinkan klien hanya menggunakan fungsionalitas yang mereka butuhkan.

Prinsip Inversi Ketergantungan (DIP)

Di Java, prinsip SOLID ini dijelaskan sebagai berikut: dependensi dalam sistem dibangun berdasarkan abstraksi. Modul tingkat yang lebih tinggi tidak bergantung pada modul tingkat yang lebih rendah. Abstraksi seharusnya tidak bergantung pada detail. Detail harus bergantung pada abstraksi. Perangkat lunak perlu dirancang sedemikian rupa sehingga berbagai modul dapat berdiri sendiri dan terhubung satu sama lain melalui abstraksi. Aplikasi klasik dari prinsip ini adalah Spring Framework. Di Spring Framework, semua modul diimplementasikan sebagai komponen terpisah yang dapat bekerja bersama. Mereka sangat otonom sehingga dapat digunakan dengan mudah dalam modul program selain Spring Framework. Ini dicapai berkat ketergantungan prinsip tertutup dan terbuka. Semua modul hanya menyediakan akses ke abstraksi, yang dapat digunakan di modul lain. Mari kita coba mengilustrasikan ini menggunakan sebuah contoh. Berbicara tentang prinsip tanggung jawab tunggal, kami mempertimbangkanOrderProcessorkelas. Mari kita lihat lagi kode kelas ini:

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);
        }
    }

}
Dalam contoh ini, OrderProcessorkelas kita bergantung pada dua kelas khusus: MySQLOrderRepositorydan ConfirmationEmailSender. Kami juga akan menyajikan kode kelas-kelas ini:

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
    }
}
Kelas-kelas ini jauh dari apa yang kita sebut abstraksi. Dan dari sudut pandang prinsip inversi ketergantungan, akan lebih baik untuk memulai dengan membuat beberapa abstraksi yang dapat kita kerjakan di masa mendatang, daripada implementasi khusus. Mari buat dua antarmuka: MailSenderdan OrderRepository). Ini akan menjadi abstraksi kami:

public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
Sekarang kami mengimplementasikan antarmuka ini di kelas yang telah disiapkan untuk ini:

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;
    }
}
Kami melakukan pekerjaan persiapan sehingga OrderProcessorkelas kami bergantung, bukan pada detail konkret, tetapi pada abstraksi. Kami akan mengubahnya dengan menambahkan dependensi kami ke konstruktor kelas:

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);
        }
    }
}
Sekarang kelas kita bergantung pada abstraksi, bukan implementasi spesifik. Kita dapat dengan mudah mengubah perilakunya dengan menambahkan dependensi yang diinginkan pada saat objek OrderProcessordibuat. Kami telah memeriksa prinsip-prinsip desain SOLID di Jawa. Anda akan belajar lebih banyak tentang OOP secara umum dan dasar-dasar pemrograman Java — tidak ada yang membosankan dan ratusan jam latihan — dalam kursus CodeGym. Saatnya menyelesaikan beberapa tugas :)
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION