Cara mematuhi prinsip terbuka-tertutup dalam praktik

14

Saya mengerti maksud dari prinsip buka-tutup. Ini dimaksudkan untuk mengurangi risiko melanggar sesuatu yang sudah berfungsi saat memodifikasinya, dengan memberitahu Anda untuk mencoba memperluas tanpa memodifikasi.

Namun, saya kesulitan memahami bagaimana prinsip ini diterapkan dalam praktik. Menurut pemahaman saya, ada dua cara untuk menerapkannya. Sebelumnya dan setelah perubahan yang memungkinkan:

  1. Sebelum: program untuk abstraksi dan 'prediksi masa depan' sebanyak yang Anda bisa. Sebagai contoh, suatu metode drive(Car car)harus berubah jika Motorcycles ditambahkan ke sistem di masa depan, sehingga mungkin melanggar OCP. Tetapi metode drive(MotorVehicle vehicle)ini cenderung tidak harus berubah di masa depan, sehingga mematuhi OCP.

    Namun, cukup sulit untuk memprediksi masa depan dan mengetahui terlebih dahulu perubahan apa yang akan dilakukan pada sistem.

  2. Setelah: ketika perubahan diperlukan, perluas kelas alih-alih mengubah kode saat ini.

Latihan # 1 tidak sulit untuk dipahami. Namun itu praktik nomor 2 yang membuat saya kesulitan memahami cara mendaftar.

Misalnya (saya mengambilnya dari video di YouTube): katakanlah kita memiliki metode di kelas yang menerima CreditCardobjek:makePayment(CraditCard card) . Satu hari Voucherditambahkan ke sistem. Metode ini tidak mendukung mereka sehingga harus dimodifikasi.

Ketika menerapkan metode ini sejak awal, kami gagal memprediksi masa depan dan program dalam istilah yang lebih abstrak (mis makePayment(Payment pay) , jadi sekarang kita harus mengubah kode yang ada.

Praktik # 2 mengatakan kita harus menambahkan fungsionalitas dengan memperluas alih-alih memodifikasi. Apa artinya? Haruskah saya mensubkelas kelas yang ada daripada hanya mengubah kode yang sudah ada?Haruskah saya membuat semacam pembungkus untuk menghindari penulisan ulang kode?

Atau apakah prinsip itu bahkan tidak merujuk pada 'bagaimana cara memodifikasi / menambah fungsionalitas dengan benar', tetapi lebih mengacu pada 'bagaimana menghindari keharusan membuat perubahan di tempat pertama (yaitu program untuk abstraksi)?

Aviv Cohn
sumber
1
Prinsip Terbuka / Tertutup tidak menentukan mekanisme yang Anda gunakan. Warisan biasanya merupakan pilihan yang salah. Juga, tidak mungkin melindungi dari semua perubahan di masa depan. Sebaiknya jangan mencoba memprediksi masa depan, tetapi begitu perubahan diperlukan, modifikasi desain sehingga perubahan di masa depan dengan jenis yang sama dapat diakomodasi.
Doval

Jawaban:

14

Prinsip-prinsip desain harus selalu seimbang satu sama lain. Anda tidak dapat memprediksi masa depan, dan sebagian besar programmer melakukannya dengan mengerikan ketika mereka mencoba. Itu sebabnya kami memiliki aturan tiga , yang terutama tentang duplikasi, tetapi berlaku untuk refactoring untuk prinsip desain lainnya juga.

Ketika Anda hanya memiliki satu implementasi antarmuka, Anda tidak perlu terlalu peduli tentang OCP, kecuali itu jelas di mana ekstensi akan terjadi. Bahkan, Anda sering kehilangan kejelasan saat mencoba mendesain berlebihan dalam situasi ini. Ketika Anda memperpanjang sekali, Anda menolak untuk membuatnya ramah OCP jika itu cara termudah dan paling jelas untuk melakukannya. Ketika Anda memperluasnya ke implementasi ketiga, Anda pastikan untuk refactor dengan mempertimbangkan OCP, bahkan jika itu membutuhkan sedikit usaha lebih.

Dalam praktiknya, ketika Anda hanya memiliki dua implementasi, refactoring ketika Anda menambahkan ketiga biasanya tidak terlalu sulit. Justru ketika Anda membiarkannya tumbuh melewati titik itu menjadi sulit untuk dipertahankan.

Karl Bielefeldt
sumber
1
Terimakasih telah menjawab. Biarkan saya melihat apakah saya mengerti apa yang Anda katakan: apa yang Anda katakan adalah bahwa saya harus peduli tentang OCP terutama setelah saya dipaksa untuk membuat perubahan ke kelas. Artinya: ketika mengimplementasikan kelas untuk pertama kalinya, saya tidak perlu terlalu khawatir tentang OCP, karena bagaimanapun sulit untuk memprediksi masa depan. Ketika saya perlu memperluas / memodifikasinya untuk pertama kalinya, mungkin ide yang bagus untuk sedikit refactor agar lebih fleksibel di masa depan (lebih banyak OCP). Dan di ketiga kalinya saya perlu memperluas / memodifikasi kelas, saatnya untuk melakukan refactoring agar lebih mematuhi OCP. Apakah ini yang Anda maksud?
Aviv Cohn
1
Itulah idenya.
Karl Bielefeldt
2

Saya pikir Anda melihat terlalu jauh ke masa depan. Memecahkan masalah saat ini dengan cara yang fleksibel yang mematuhi buka / tutup.

Katakanlah Anda perlu menerapkan drive(Car car)metode. Tergantung pada bahasa Anda, Anda memiliki beberapa opsi.

  • Untuk bahasa yang mendukung overloading (C ++), maka cukup gunakan drive(const Car& car)

    Pada titik tertentu nanti Anda mungkin perlu drive(const Motorcycle& motorcycle), tetapi itu tidak akan mengganggu drive(const Car& car). Tidak masalah!

  • Untuk bahasa yang tidak mendukung overloading (Objective C), maka sertakan nama jenis dalam metode -driveCar:(Car *)car.

    Pada titik tertentu nanti Anda mungkin perlu -driveMotorcycle:(Motorcycle *)motorcycle, tetapi sekali lagi, itu tidak akan mengganggu.

Ini memungkinkan drive(Car car)untuk ditutup pada modifikasi, tetapi terbuka untuk memperluas ke jenis kendaraan lain. Perencanaan masa depan minimalis ini yang memungkinkan Anda menyelesaikan pekerjaan hari ini, tetapi membuat Anda tidak menghalangi diri Anda di masa depan.

Mencoba membayangkan tipe paling dasar yang Anda butuhkan dapat mengarah pada kemunduran tanpa batas. Apa yang terjadi ketika Anda ingin mengendarai Segue, sepeda atau jet Jumbo. Bagaimana Anda membangun satu tipe abstrak generik tunggal yang dapat menjelaskan semua perangkat yang digunakan orang dan digunakan untuk mobilitas?

Jeffery Thomas
sumber
Mengubah kelas untuk menambahkan metode baru Anda melanggar Prinsip Terbuka-Tertutup. Saran Anda juga menghilangkan kemampuan untuk menerapkan Prinsip Substitusi Liskov untuk semua kendaraan yang dapat mengemudi yang pada dasarnya menghilangkan salah satu bagian terkuat OO.
Dunk
@Dunk Saya mendasarkan jawaban saya pada prinsip buka / tutup polimorfik, bukan prinsip terbuka / tertutup Meyer yang ketat. Memungkinkan untuk memperbarui kelas untuk mendukung antarmuka baru. Dalam contoh ini, antarmuka mobil disimpan terpisah dari antarmuka sepeda motor. Ini dapat diformalkan sebagai kelas abstrak mengemudi terpisah untuk mobil dan sepeda motor yang dapat didukung oleh kelas pelaksana.
Jeffery Thomas
@Dunk Prinsip Substitusi Liskov berguna, tetapi tidak datang secara gratis. Jika spesifikasi aslinya hanya membutuhkan mobil, maka menciptakan kendaraan yang lebih umum mungkin tidak sepadan dengan biaya tambahan uang, waktu, dan kompleksitas. Selain itu, tidak mungkin bahwa kendaraan yang lebih umum akan sangat cocok untuk menangani subkelas yang tidak direncanakan. Entah antarmuka untuk sepeda motor perlu diseret ke antarmuka kendaraan (yang dirancang untuk hanya menangani mobil), atau Anda perlu memodifikasi kendaraan untuk menangani sepeda motor (pelanggaran nyata terbuka / tertutup).
Jeffery Thomas
Prinsip Substitusi Liskov tidak datang secara gratis tetapi juga tidak memerlukan banyak biaya. Dan biasanya ia membayar jauh lebih banyak daripada biaya berkali-kali lipat bahkan jika subclass lain tidak pernah diwarisi darinya dalam aplikasi utama. Menerapkan LSP membuat pengujian otomatis jauh lebih mudah, yang merupakan kemenangan. Juga, sementara Anda tentu tidak boleh menjadi liar dan menganggap semuanya akan membutuhkan LSP, jika Anda sedang membangun aplikasi dan tidak memiliki perasaan yang baik untuk apa yang mungkin membutuhkannya di rev masa depan maka Anda tidak cukup tahu tentang aplikasi Anda atau domainnya.
Dunk
1
Berkenaan dengan definisi OCP. Mungkin industri tempat saya bekerja, yang cenderung membutuhkan tingkat verifikasi yang lebih tinggi daripada hanya perusahaan komersial biasa, tetapi secara umum jika file / kelas berubah maka Anda tidak hanya perlu menguji ulang file / kelas tetapi semua yang memanfaatkan file / kelas dalam pengujian regresi Anda. Jadi tidak masalah jika seseorang mengatakan polymorphic open / closed baik-baik saja, mengubah antarmuka memiliki konsekuensi yang luas sehingga tidak semuanya baik-baik saja.
Dunk
2

Saya mengerti maksud dari prinsip buka-tutup. Ini dimaksudkan untuk mengurangi risiko melanggar sesuatu yang sudah berfungsi saat memodifikasinya, dengan memberitahu Anda untuk mencoba memperluas tanpa memodifikasi.

Ini juga tentang tidak memecah semua objek yang mengandalkan metode itu dengan tidak mengubah perilaku objek yang sudah ada. Setelah sebuah objek mengiklankan perubahan perilaku, hal itu berisiko karena Anda mengubah perilaku objek yang diketahui dan diharapkan tanpa tahu persis apa objek lain yang mengharapkan perilaku itu.

Apa artinya? Haruskah saya mensubkelas kelas yang ada daripada hanya mengubah kode yang sudah ada?

Ya.

"Hanya menerima kartu kredit" didefinisikan sebagai bagian dari perilaku kelas itu, melalui antarmuka publiknya. Pemrogram telah menyatakan kepada dunia bahwa metode objek ini hanya membutuhkan kartu kredit. Dia melakukannya dengan menggunakan nama metode yang tidak terlalu jelas, tetapi sudah selesai. Sisanya mengandalkan sistem ini.

Itu mungkin masuk akal pada saat itu, tetapi sekarang jika perlu berubah sekarang Anda harus membuat kelas baru yang menerima hal-hal selain kartu kredit.

Perilaku baru = kelas baru

Sebagai tambahan - Cara yang baik untuk memprediksi masa depan adalah dengan memikirkan nama yang Anda berikan metode. Sudahkah Anda memberikan nama metode terdengar sangat umum seperti makePayment ke metode dengan aturan khusus dalam metode untuk pembayaran apa yang dapat dilakukan? Itu bau kode. Jika Anda memiliki aturan khusus, ini harus dibuat jelas dari nama metode - makePayment harus makeCreditCardPayment. Lakukan ini ketika Anda menulis objek pertama kali dan programmer lain akan berterima kasih untuk itu.

Cormac Mulhall
sumber