Prinsip Terbuka Tertutup dalam pola desain

8

Saya agak bingung tentang bagaimana prinsip Open Closed dapat diterapkan dalam kehidupan nyata. Persyaratan dalam setiap bisnis berubah dari waktu ke waktu. Menurut prinsip Open-Closed Anda harus memperluas kelas alih-alih memodifikasi kelas yang ada. Bagi saya setiap kali memperpanjang kelas sepertinya tidak praktis untuk memenuhi persyaratan. Biarkan saya memberi contoh dengan sistem pemesanan kereta.

Dalam sistem pemesanan kereta akan ada objek Tiket. Mungkin ada berbagai jenis tiket Tiket Reguler, Tiket Konsesi dll. Tiket adalah kelas abstrak dan RegularTicket dan ConcessionTickets adalah kelas konkret. Karena semua tiket akan memiliki metode PrintTicket yang umum, maka itu ditulis dalam Tiket yang merupakan kelas abstrak dasar. Katakanlah ini bekerja dengan baik selama beberapa bulan. Sekarang persyaratan baru datang, yang menyatakan untuk mengubah format tiket. Mungkin beberapa bidang lagi ditambahkan pada tiket yang dicetak atau mungkin formatnya diubah. Untuk memenuhi persyaratan ini, saya memiliki opsi berikut

  1. Ubah metode PrintTicket () di kelas abstrak Tiket. Tetapi ini akan melanggar prinsip Open-Closed.
  2. Mengganti metode PrintTicket () di kelas anak tetapi ini akan menduplikasi logika pencetakan yang melanggar prinsip KERING (Jangan ulangi sendiri).

Jadi pertanyaannya adalah

  1. Bagaimana saya dapat memenuhi persyaratan bisnis di atas tanpa melanggar prinsip Terbuka / Tertutup.
  2. Kapan kelas seharusnya ditutup untuk modifikasi? Apa kriteria untuk mempertimbangkan kelas ditutup untuk modifikasi? Apakah setelah implementasi awal kelas atau mungkin setelah penyebaran pertama dalam produksi atau mungkin sesuatu yang lain.
paragraf
sumber
1
Saya kira masalahnya di sini adalah kelas tiket Anda seharusnya tidak tahu cara mencetaknya sendiri. Kelas TicketPrinter harus menerima tiket dan tahu cara mencetaknya. Dalam hal ini Anda dapat menggunakan pola decotator untuk menambah kelas printer Anda atau menimpanya untuk mengubahnya secara lengkap.
1
Penghias juga akan menghadapi masalah Terbuka / Tertutup yang sama. Persyaratan dapat berubah berkali-kali di masa mendatang. Dalam hal ini Anda akan mengganti dekorator atau memperpanjang dekorator. Memperluas dekorator setiap saat tampaknya tidak praktis dan memodifikasi dekorator akan melanggar prinsip terbuka / tertutup.
paragraf
1
Sangat sulit untuk merancang dengan OCP untuk perubahan desain yang berubah- ubah . Ini pertaruhan untuk memilih dimensi yang Anda harapkan akan bervariasi sehubungan dengan bagian yang stabil.
Fuhrmanator

Jawaban:

5

Saya memiliki opsi berikut

  • Ubah PrintTicket()metode dalam kelas abstrak Tiket. Tetapi ini akan melanggar prinsip Open-Closed.
  • Mengganti PrintTicket()metode dalam kelas anak tetapi ini akan menduplikasi logika pencetakan yang melanggar prinsip KERING (Jangan ulangi sendiri).

Ini tergantung pada cara PrintTicketpenerapannya. Jika metode perlu mempertimbangkan informasi dari subclass, ia perlu menyediakan cara bagi mereka untuk memberikan informasi tambahan.

Selain itu, Anda dapat mengganti tanpa mengulangi kode Anda juga. Misalnya, jika Anda memanggil metode kelas dasar Anda dari implementasi, Anda menghindari pengulangan:

class ConcessionTicket : Ticket {
    public override string PrintTicket() {
        return $"{base.PrintTicket()} (concession)"
    }
}

Bagaimana saya bisa memenuhi persyaratan bisnis di atas tanpa melanggar prinsip Terbuka / Tertutup?

Pola Metode Templat menyediakan opsi ketiga: terapkanPrintTicketdi kelas dasar, dan bergantung pada kelas turunan untuk menyediakan perincian tambahan sesuai kebutuhan.

Berikut adalah contoh menggunakan hierarki kelas Anda:

abstract class Ticket {
    public string Name {get;}
    public string Train {get;}
    protected virtual string AdditionalDetails() {
        return "";
    }
    public string PrintTicket() {
        return $"{Name} : {Train}{AdditionalDetails()}";
    }
}

class RegularTicket : Ticket {
    ... // Uses the default implementation of AdditionalDetails()
}


class ConcessionTicket : Ticket {
    protected override string AdditionalDetails() {
        return " (concession)";
    }
}

Apa kriteria untuk mempertimbangkan kelas ditutup untuk modifikasi?

Bukan kelas yang harus ditutup untuk modifikasi, tetapi antarmuka kelas itu (maksud saya "antarmuka" dalam arti luas, yaitu kumpulan metode dan properti dan perilaku mereka, bukan konstruksi bahasa). Kelas menyembunyikan implementasinya, sehingga pemilik kelas dapat memodifikasinya kapan saja, selama perilakunya yang terlihat secara eksternal tetap tidak berubah.

Apakah setelah implementasi awal kelas atau mungkin setelah penyebaran pertama dalam produksi atau mungkin sesuatu yang lain?

Antarmuka kelas harus tetap ditutup setelah pertama kali Anda menerbitkannya untuk penggunaan eksternal. Kelas penggunaan internal tetap terbuka untuk refactoring selamanya, karena Anda dapat menemukan dan memperbaiki semua penggunaannya.

Terlepas dari kasus-kasus paling sederhana, tidaklah praktis untuk mengharapkan bahwa hierarki kelas Anda akan mencakup semua skenario penggunaan yang mungkin setelah sejumlah iterasi refactoring. Persyaratan tambahan yang menyerukan metode yang sama sekali baru di kelas dasar datang secara teratur, sehingga kelas Anda tetap terbuka untuk modifikasi oleh Anda selamanya.

dasblinkenlight
sumber
Ya, dalam hal ini templating akan berfungsi. Tapi ini hanya contoh untuk menempatkan masalah. Mungkin ada banyak skenario / masalah bisnis di mana metode templating / plugin mungkin tidak berlaku.
paragraf
1
Poin Anda tentang antarmuka kelas menurut saya resolusi terbaik untuk skenario khusus ini. Untuk memfokuskannya pada pertanyaan spesifik: seseorang dapat memiliki metode PrintTicket dengan output yang ditentukan didefinisikan dalam antarmuka untuk kelas Tiket (bersama dengan input yang ditentukan) maka selama output tiket selalu sama, tidak masalah bagaimana kelas yang mendasarinya berubah. - Juga jika properti tiket selalu diharapkan berubah, mengapa tidak mendesain kelas Anda dengan itu sebagai persyaratan bisnis yang dikenal. Tiket harus berisi koleksi objek ticketProperty semacam
user3908435
Bisa juga membuat TicketPrinterkelas yang diteruskan ke konstruktor.
Zymus
2

Mari kita mulai dengan garis sederhana prinsip Terbuka-tertutup - "Open for extension Closed for modification". Pertimbangkan masalah Anda, saya akan merancang kelas-kelas sehingga Tiket dasar akan bertanggung jawab untuk mencetak informasi Tiket umum dan jenis Tiket lainnya akan bertanggung jawab untuk mencetak format mereka sendiri di atas pencetakan dasar.

abstract class Ticket
{
    public string Print()
    {
        // Print base properties here. and return 
        return PrintBasicInfo() + AddionalPrint();
    }

    protected abstract string AddionalPrint();

    private string PrintBasicInfo()
    {
        // print base ticket info
    }
}

class ConcessionTicket : Ticket
{
    protected override string AddionalPrint()
    {
        // Specific to Conecession ticket printing
    }
}

class RegularTicket : Ticket
{
    protected override string AddionalPrint()
    {
        // Specific to Regular ticket printing
    }
}

Dengan cara ini Anda menegakkan jenis tiket baru yang akan diperkenalkan dalam sistem akan menerapkan fitur pencetakan sendiri di atas informasi pencetakan tiket dasar. Tiket Pangkalan sekarang ditutup untuk modifikasi tetapi terbuka untuk perpanjangan dengan menyediakan metode tambahan untuk jenis turunannya.

vendettamit
sumber
2

Masalahnya bukan bahwa Anda melanggar prinsip Terbuka / Tertutup, masalahnya adalah bahwa Anda melanggar Prinsip Tanggung Jawab Tunggal.

Ini secara harfiah adalah contoh buku sekolah dari masalah SRP, atau seperti yang dinyatakan wikipedia :

Sebagai contoh, pertimbangkan modul yang mengkompilasi dan mencetak laporan. Bayangkan modul seperti itu dapat diubah karena dua alasan. Pertama, isi laporan bisa berubah. Kedua, format laporan bisa berubah. Dua hal ini berubah untuk tujuan yang sangat berbeda; satu substantif, dan satu kosmetik. Prinsip tanggung jawab tunggal mengatakan bahwa kedua aspek masalah ini adalah dua tanggung jawab yang terpisah, dan karenanya harus berada dalam kelas atau modul yang terpisah. Ini akan menjadi desain yang buruk untuk memasangkan dua hal yang berubah karena alasan yang berbeda pada waktu yang berbeda.

Kelas-kelas Tiket yang berbeda dapat berubah karena Anda menambahkan informasi baru kepada mereka. Pencetakan tiket dapat berubah karena Anda memerlukan tata letak baru, karenanya Anda harus memiliki kelas TicketPrinter yang menerima tiket sebagai input.

Kelas Tiket Anda kemudian dapat mengimplementasikan antarmuka yang berbeda untuk menyediakan tipe data yang berbeda atau menyediakan semacam templat data.

Bjorn
sumber
1

Ubah metode PrintTicket () di kelas abstrak Tiket. Tetapi ini akan melanggar prinsip Open-Closed.

Ini tidak melanggar prinsip buka-tutup. Kode berubah sepanjang waktu, itu sebabnya SOLID penting, sehingga kode tetap dapat dipelihara, fleksibel, dapat diubah. Tidak ada yang salah dengan mengubah kode.


OCP lebih banyak tentang kelas eksternal yang tidak dapat merusak fungsionalitas kelas yang dimaksudkan.

Sebagai contoh, saya memiliki kelas Ayang mengambil String di konstruktor, dan memverifikasi String ini memiliki format tertentu dengan memanggil metode untuk memverifikasi string. Metode verifikasi ini juga harus dapat digunakan oleh klien A, jadi dalam implementasi naif, itu dibuat public.

Sekarang saya dapat 'mematahkan' kelas ini dengan mewarisi darinya dan mengganti metode verifikasi untuk selalu kembali true. Karena polimorfisme, saya masih dapat menggunakan subkelas saya di mana saja di mana saya dapat menggunakan A, mungkin membuat perilaku yang tidak diinginkan dalam program saya.

Sepertinya ini adalah tindakan jahat yang harus dilakukan, tetapi dalam basis kode yang lebih kompleks, ini mungkin dilakukan sebagai 'kesalahan jujur'. Untuk menyesuaikan dengan OCP, kelas Aharus dirancang sedemikian rupa sehingga membuat kesalahan ini tidak mungkin.

Saya bisa melakukan ini dengan, misalnya, membuat metode verifikasi final.

Jorn Vernee
sumber
Saya belum pernah melihat deskripsi OCP yang akan menyatakan bahwa ini merupakan pelanggaran terhadapnya. LSP, pasti, tetapi tidak OCP.
Jules
@ Jules saya menemukan kemudian bahwa definisi wikipedia memang sangat berbeda dari ini. Ini adalah salah satu contoh yang digunakan untuk mengajari saya OCP (meskipun mungkin sedikit berbeda). Intinya, ini bukan tentang melarang diri Anda mengubah kode Anda. Bagi saya tidak masuk akal juga. Jika Anda menulis kode, dan lingkungan berubah sehingga kode Anda tidak lagi cocok, ubah, atau lebih baik, buang dan mulai lagi. Mengapa menyimpan sesuatu yang rusak ...
Jorn Vernee
0

Warisan bukan satu-satunya mekanisme yang dapat digunakan untuk memenuhi persyaratan OCP, dan dalam banyak kasus saya berpendapat bahwa itu jarang yang terbaik - biasanya ada cara yang lebih baik, selama Anda berencana sedikit ke depan untuk mempertimbangkan jenis perubahan yang mungkin Anda butuhkan.

Dalam contoh ini, saya berpendapat bahwa jika Anda berharap untuk sering mendapatkan perubahan format pada sistem pencetakan tiket Anda (dan itu sepertinya taruhan yang cukup aman bagi saya), menggunakan sistem templating akan sangat masuk akal. Sekarang, kita tidak perlu menyentuh kode sama sekali: yang harus kita lakukan untuk memenuhi permintaan ini adalah mengubah templat (selama sistem templat Anda dapat mengakses semua data yang diperlukan, toh).

Pada kenyataannya, suatu sistem tidak pernah dapat sepenuhnya ditutup terhadap modifikasi, dan bahkan untuk menutupnya akan membutuhkan sejumlah besar upaya yang sia-sia, jadi Anda harus membuat keputusan untuk menilai perubahan seperti apa yang mungkin terjadi, dan menyusun kode sesuai dengan itu.

Jules
sumber