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:
Sebelum: program untuk abstraksi dan 'prediksi masa depan' sebanyak yang Anda bisa. Sebagai contoh, suatu metode
drive(Car car)
harus berubah jikaMotorcycle
s ditambahkan ke sistem di masa depan, sehingga mungkin melanggar OCP. Tetapi metodedrive(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.
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 CreditCard
objek:makePayment(CraditCard card)
. Satu hari Voucher
ditambahkan 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)?
sumber
Jawaban:
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.
sumber
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 mengganggudrive(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?
sumber
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.
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.
sumber