Inilah masalah yang sering saya temui: Biarkan ada proyek toko web yang memiliki kelas Produk. Saya ingin menambahkan fitur yang memungkinkan pengguna untuk mengirim ulasan ke suatu produk. Jadi saya memiliki kelas Ulasan yang mereferensikan suatu produk. Sekarang saya membutuhkan metode yang mencantumkan semua ulasan untuk suatu produk. Ada dua kemungkinan:
(SEBUAH)
public class Product {
...
public Collection<Review> getReviews() {...}
}
(B)
public class Review {
...
static public Collection<Review> forProduct( Product product ) {...}
}
Dari melihat kode, saya akan memilih (A): Ini tidak statis dan tidak perlu parameter. Namun, saya merasa bahwa (A) melanggar Prinsip Tanggung Jawab Tunggal (SRP) dan Prinsip Terbuka-Tertutup (OCP) sedangkan (B) tidak:
(SRP) Ketika saya ingin mengubah cara ulasan dikumpulkan untuk suatu produk, saya harus mengubah kelas Produk. Tetapi seharusnya hanya ada satu alasan mengapa mengubah kelas Produk. Dan itu tentu bukan ulasan. Jika saya mengemas setiap fitur yang ada hubungannya dengan produk dalam Produk, itu akan segera berantakan.
(OCP) Saya harus mengubah kelas Produk untuk memperluasnya dengan fitur ini. Saya pikir ini melanggar bagian prinsip 'Tertutup untuk perubahan'. Sebelum saya mendapat permintaan pelanggan untuk menerapkan ulasan, saya menganggap Produk sudah selesai, dan "menutup" itu.
Apa yang lebih penting: mengikuti prinsip SOLID, atau memiliki antarmuka yang lebih sederhana?
Atau apakah saya melakukan sesuatu yang salah di sini?
Hasil
Wow, terima kasih atas semua jawaban Anda yang luar biasa! Sulit untuk memilih satu sebagai jawaban resmi.
Biarkan saya meringkas argumen utama dari jawaban:
- pro (A): OCP juga bukan hukum dan keterbacaan masalah kode.
- pro (A): hubungan entitas harus dinavigasi. Kedua kelas mungkin tahu tentang hubungan ini.
- pro (A) + (B): lakukan keduanya dan delegasikan dalam (A) ke (B) sehingga Produk kecil kemungkinannya untuk diubah lagi.
- pro (C): memasukkan metode finder ke kelas tiga (layanan) yang tidak statis.
- contra (B): menghambat mengejek dalam tes.
Beberapa hal tambahan yang berkontribusi di perguruan tinggi saya:
- pro (B): kerangka kerja ORM kami dapat secara otomatis menghasilkan kode untuk (B).
- pro (A): untuk alasan teknis kerangka ORM kami, akan diperlukan untuk mengubah entitas "tertutup" dalam beberapa kasus, secara independen dari tempat penemu pergi. Jadi saya tidak akan selalu bisa tetap pada SOLID, sih.
- contra (C): terlalu ribut ;-)
Kesimpulan
Saya menggunakan keduanya (A) + (B) dengan delegasi untuk proyek saya saat ini. Namun, dalam lingkungan yang berorientasi layanan, saya akan menggunakan (C).
sumber
Assert(5 = Math.Abs(-5));
Abs()
bukanlah masalahnya, menguji sesuatu yang bergantung padanya. Anda tidak memiliki jahitan untuk mengisolasi Code-Under-Test (CUT) yang dependen untuk menggunakan tiruan. Ini berarti Anda tidak dapat mengujinya sebagai unit atom dan semua tes Anda menjadi tes integrasi yang menguji logika unit. Kegagalan dalam suatu tes bisa dalam CUT atau dalamAbs()
(atau kode ketergantungannya) dan menghilangkan manfaat diagnosis dari tes unit.Jawaban:
Antarmuka berhadapan SOLID
Ini tidak saling eksklusif. Antarmuka harus menyatakan sifat model bisnis Anda secara ideal dalam istilah model bisnis. Prinsip-prinsip SOLID adalah Koan untuk memaksimalkan pemeliharaan kode Berorientasi Objek (dan maksud saya "pemeliharaan" dalam arti luas). Yang pertama mendukung penggunaan dan manipulasi model bisnis Anda dan yang terakhir mengoptimalkan pemeliharaan kode.
Prinsip Terbuka / Tertutup
"jangan sentuh itu!" interpretasi yang terlalu sederhana. Dan dengan asumsi kita maksudkan "kelas" adalah arbitrer, dan belum tentu benar. Sebaliknya, OCP berarti bahwa Anda telah merancang kode Anda sehingga mengubah perilaku itu tidak (seharusnya tidak) mengharuskan Anda untuk secara langsung memodifikasi kode yang ada dan berfungsi. Lebih jauh, tidak menyentuh kode di tempat pertama adalah cara yang ideal untuk menjaga integritas antarmuka yang ada; ini adalah akibat wajar dari OCP dalam pandangan saya.
Akhirnya saya melihat OCP sebagai indikator kualitas desain yang ada. Jika saya mendapati diri saya terlalu sering membuka kelas terbuka (atau metode), dan / atau tanpa alasan (ha, ha) yang benar-benar solid untuk melakukannya maka ini mungkin memberitahu saya bahwa saya punya beberapa desain yang buruk (dan / atau saya tidak punya) tahu cara membuat kode OO).
Berikan yang terbaik, kami memiliki tim dokter yang siap siaga
Jika analisis persyaratan Anda memberi tahu Anda bahwa Anda perlu mengekspresikan hubungan Tinjauan Produk dari kedua perspektif, maka lakukanlah.
Karenanya, Wolfgang, Anda mungkin punya alasan bagus untuk memodifikasi kelas-kelas yang ada. Dengan adanya persyaratan baru, jika suatu Tinjauan sekarang menjadi bagian mendasar dari suatu Produk, jika setiap perluasan dari Produk membutuhkan Tinjauan, jika melakukannya membuat kode klien ekspresif secara tepat, kemudian mengintegrasikannya ke dalam Produk.
sumber
SOLID adalah pedoman, jadi pengaruhi pengambilan keputusan alih-alih mendiktekannya.
Satu hal yang harus diperhatikan ketika menggunakan metode statis adalah dampaknya pada testabilitas .
Pengujian
forProduct(Product product)
tidak akan menjadi masalah.Menguji sesuatu yang tergantung padanya.
Anda tidak akan memiliki jahitan untuk mengisolasi Code-Under-Test (CUT) yang dependen untuk menggunakan tiruan, karena ketika aplikasi sedang berjalan, metode statis harus ada.
Mari kita memiliki metode yang disebut
CUT()
panggilan ituforProduct()
Jika
forProduct()
inistatic
Anda tidak dapat mengujiCUT()
sebagai unit atom dan semua tes Anda menjadi tes integrasi unit tes logika.Kegagalan dalam tes untuk CUT, dapat disebabkan oleh masalah dalam
CUT()
atau dalamforProduct()
(atau salah satu kode dependennya) yang menghilangkan manfaat diagnosis dari tes unit.Lihat posting blog yang luar biasa ini untuk informasi lebih rinci: http://googletesting.blogspot.com/2008/12/static-methods-are-death-to-testability.html
Hal ini dapat menyebabkan frustrasi dengan kegagalan tes dan ditinggalkannya praktik dan manfaat yang baik di sekitarnya.
sumber
If forProduct() is static you can't test CUT() as an atomic unit and all of your tests become integration tests that test unit logic.
- Saya percaya Javascript dan Python keduanya memungkinkan metode statis ditimpa / dihina. Saya tidak 100% yakin.static
, Anda mensimulasikannya dengan penutupan dan pola singleton. Membaca di Python (terutama stackoverflow.com/questions/893015/… ), Anda harus mewarisi dan memperluas. Mengganti tidak mengejek; Sepertinya Anda masih belum memiliki jahitan untuk menguji kode sebagai unit atom.Jika Anda berpikir bahwa Produk adalah tempat yang tepat untuk menemukan Ulasan produk, Anda selalu dapat memberikan Produk kelas membantu untuk melakukan pekerjaan untuk itu. (Anda dapat mengetahui karena bisnis Anda tidak akan pernah membicarakan ulasan kecuali dalam hal suatu produk).
Sebagai contoh, saya akan tergoda untuk menyuntikkan sesuatu yang memainkan peran sebagai ulasan retriever. Saya mungkin akan memberikan antarmuka
IRetrieveReviews
. Anda dapat menempatkan ini di konstruktor produk (Injeksi Ketergantungan). Jika Anda ingin mengubah cara ulasan diambil, Anda dapat melakukannya dengan mudah dengan menyuntikkan kolaborator lain - aTwitterReviewRetriever
atauAmazonReviewRetriever
atauMultipleSourceReviewRetriever
atau apa pun yang Anda butuhkan.Keduanya sekarang memiliki satu tanggung jawab (menjadi pilihan untuk semua hal yang terkait dengan produk, dan masing-masing mengambil ulasan), dan di masa depan perilaku produk sehubungan dengan ulasan dapat dimodifikasi tanpa benar-benar mengubah produk (Anda dapat memperpanjangnya) sebagai
ProductWithReviews
jika Anda benar-benar ingin bertele-tele tentang prinsip-prinsip PADAT Anda, tetapi ini akan cukup baik bagi saya).sumber
IRetrieveReviews
menghentikannya berorientasi layanan - tidak menentukan apa yang mendapat ulasan, atau bagaimana, atau kapan. Mungkin ini layanan dengan banyak metode untuk hal-hal seperti ini. Mungkin kelas yang melakukan satu hal itu. Mungkin repositori, atau permintaan HTTP ke server. Kamu tidak tahu. Anda seharusnya tidak tahu. Itulah intinya.Saya akan memiliki kelas ProductsReview. Anda mengatakan Ulasan itu baru. Itu tidak berarti itu bisa saja apa saja. Masih harus memiliki satu alasan untuk berubah. Jika Anda mengubah cara Anda mendapatkan ulasan untuk alasan apa pun, Anda harus mengubah kelas Review.
Itu tidak benar.
Anda meletakkan metode statis di kelas Peninjauan karena ... mengapa? Bukankah itu yang sedang Anda perjuangkan? Bukankah itu keseluruhan masalahnya?
Kalau begitu jangan. Buat kelas yang satu-satunya tanggung jawab adalah mendapatkan ulasan produk. Anda kemudian dapat subklas ke ProductReviewsByStartRating apa pun. Atau subklas untuk mendapatkan ulasan untuk kelas produk.
sumber
Saya tidak akan menempatkan fungsionalitas 'Dapatkan Ulasan untuk Produk' baik di
Product
kelas maupun diReview
kelas ...Anda memiliki tempat untuk mengambil Produk Anda, bukan? Sesuatu dengan
GetProductById(int productId)
dan mungkinGetProductsByCategory(int categoryId)
dan seterusnya.Demikian juga, Anda harus memiliki tempat untuk mengambil Ulasan Anda, dengan
GetReviewbyId(int reviewId)
dan mungkin aGetReviewsForProduct(int productId)
.Jika Anda memisahkan akses data Anda dari kelas domain Anda, Anda tidak akan perlu mengubah baik kelas domain ketika Anda mengubah cara review dikumpulkan.
sumber
Pola dan prinsip adalah pedoman, bukan aturan yang tertulis di batu. Menurut pendapat saya pertanyaannya bukan apakah lebih baik untuk mengikuti prinsip-prinsip SOLID atau untuk menjaga antarmuka yang lebih sederhana. Apa yang harus Anda tanyakan pada diri sendiri adalah apa yang lebih mudah dibaca dan dipahami oleh sebagian besar orang. Seringkali ini berarti harus sedekat mungkin dengan domain.
Dalam hal ini saya lebih suka solusi (B) karena bagi saya titik awalnya adalah Produk, bukan Ulasan tetapi bayangkan Anda sedang menulis perangkat lunak untuk mengelola ulasan. Dalam hal ini pusat adalah Tinjauan sehingga solusi (A) mungkin lebih disukai.
Ketika saya memiliki banyak metode seperti ini ("koneksi" antar kelas), saya lepaskan semuanya di luar dan saya buat satu (atau lebih) kelas statis baru untuk mengaturnya. Biasanya Anda dapat melihatnya sebagai kueri atau jenis repositori.
sumber
Produk Anda hanya dapat mendelegasikan ke metode Ulasan statis Anda, dalam hal ini Anda menyediakan antarmuka yang nyaman di lokasi alami (Product.getReviews) tetapi detail implementasi Anda ada di Review.getForProduct.
SOLID adalah pedoman dan harus menghasilkan antarmuka yang sederhana dan masuk akal. Atau, Anda bisa mendapatkan SOLID dari antarmuka yang sederhana dan masuk akal. Ini semua tentang manajemen ketergantungan dalam kode. Tujuannya adalah untuk meminimalkan ketergantungan yang menciptakan gesekan dan menciptakan hambatan untuk perubahan yang tak terhindarkan.
sumber
Saya memiliki pandangan yang sedikit berbeda dari sebagian besar jawaban lainnya. Saya pikir itu
Product
danReview
pada dasarnya adalah objek transfer data (DTO). Dalam kode saya, saya mencoba membuat DTO / Entitas saya menghindari perilaku. Itu hanyalah API yang bagus untuk menyimpan keadaan model saya saat ini.Ketika Anda berbicara tentang OO dan SOLID Anda biasanya berbicara tentang "objek" yang tidak mewakili negara (tentu saja), tetapi sebaliknya mewakili beberapa jenis layanan yang menjawab pertanyaan untuk Anda, atau yang Anda dapat mendelegasikan beberapa pekerjaan Anda . Misalnya:
Maka aktual Anda
ProductRepository
akan mengembalikan nilaiExistingProduct
untukGetProductByProductId
metode, dll.Sekarang Anda mengikuti prinsip tanggung jawab tunggal (apa pun yang mewarisi dari
IProduct
hanya berpegang pada status, dan apa pun yang mewarisi dariIProductRepository
bertanggung jawab untuk mengetahui bagaimana bertahan dan rehidrasi model data Anda).Jika Anda mengubah skema database Anda, Anda dapat mengubah implementasi repositori Anda tanpa mengubah DTO Anda, dll.
Jadi, singkatnya, saya kira saya tidak akan memilih pilihan Anda. :)
sumber
Metode statis mungkin lebih sulit untuk diuji, tetapi itu tidak berarti Anda tidak dapat menggunakannya - Anda hanya perlu mengaturnya sehingga Anda tidak perlu mengujinya.
Buat keduanya product.GetReviews dan Review.ForProduct metode satu baris yang mirip
ReviewService berisi semua kode yang lebih kompleks dan memiliki antarmuka yang dirancang untuk diuji, tetapi tidak terpapar langsung ke pengguna.
Jika Anda harus memiliki cakupan 100%, mintalah tes integrasi Anda memanggil metode kelas produk / ulasan.
Mungkin membantu jika Anda berpikir tentang desain API publik daripada desain kelas - dalam konteks itu antarmuka sederhana dengan pengelompokan logis adalah yang terpenting - struktur kode aktual hanya penting bagi pengembang.
sumber