Saat ini saya sedang mengerjakan proyek perangkat lunak yang melakukan kompresi dan pengindeksan pada rekaman video pengawasan. Kompresi bekerja dengan memisahkan objek latar dan latar depan, lalu menyimpan latar belakang sebagai gambar statis, dan latar depan sebagai sprite.
Baru-baru ini, saya telah memulai meninjau beberapa kelas yang telah saya rancang untuk proyek ini.
Saya perhatikan bahwa ada banyak kelas yang hanya memiliki metode publik tunggal. Beberapa kelas ini adalah:
- VideoCompressor (dengan
compress
metode yang mengambil video input tipeRawVideo
dan mengembalikan video output tipeCompressedVideo
). - VideoSplitter (dengan
split
metode yang mengambil input video jenisRawVideo
dan mengembalikan vektor 2 video output, masing-masing jenisRawVideo
). - VideoIndexer (dengan
index
metode yang mengambil input video jenisRawVideo
dan mengembalikan indeks jenis videoVideoIndex
).
Saya menemukan diri saya instantiating setiap kelas hanya untuk membuat panggilan seperti VideoCompressor.compress(...)
, VideoSplitter.split(...)
, VideoIndexer.index(...)
.
Di permukaan, saya pikir nama kelas cukup deskriptif tentang fungsi yang dimaksudkan, dan mereka sebenarnya kata benda. Sejalan dengan itu, metode mereka juga kata kerja.
Apakah ini sebenarnya masalah?
sumber
Jawaban:
Tidak, ini bukan masalah, justru sebaliknya. Ini adalah tanda modularitas dan tanggung jawab yang jelas dari kelas. Antarmuka ramping mudah dipahami dari sudut pandang pengguna kelas itu, dan itu akan mendorong kopling longgar. Ini memiliki banyak keuntungan tetapi hampir tidak ada kekurangan. Saya berharap lebih banyak komponen akan dirancang seperti itu!
sumber
Tidak lagi berorientasi objek. Karena kelas-kelas itu tidak mewakili apa pun, mereka hanya kapal untuk fungsi.
Itu tidak berarti itu salah. Jika fungsionalitasnya cukup kompleks atau generik (yaitu argumennya adalah antarmuka, bukan tipe final yang konkret), masuk akal untuk menempatkan fungsionalitas itu dalam modul terpisah .
Dari sana tergantung pada bahasa Anda. Jika bahasa tersebut memiliki fungsi bebas, mereka haruslah modul fungsi ekspor. Mengapa berpura-pura kelas saat itu tidak. Jika bahasa tidak memiliki fungsi gratis seperti Java, maka Anda membuat kelas dengan metode publik tunggal. Nah, itu hanya menunjukkan batas-batas desain berorientasi objek. Terkadang fungsional lebih cocok.
Ada satu kasus ketika Anda mungkin memerlukan kelas dengan metode publik tunggal karena harus mengimplementasikan antarmuka dengan metode publik tunggal. Baik itu untuk pola pengamat atau injeksi ketergantungan atau apa pun. Sekali lagi ini tergantung pada bahasanya. Dalam bahasa yang memiliki functors kelas pertama (C ++ (
std::function
atau parameter template), C # (mendelegasikan), Python, Perl, Ruby (proc
), Lisp, Haskell, ...) pola ini menggunakan tipe fungsi dan tidak memerlukan kelas. Java tidak (belum, dalam versi 8) memiliki tipe fungsi, jadi Anda menggunakan antarmuka metode tunggal dan kelas metode tunggal yang sesuai.Tentu saja saya tidak menganjurkan menulis fungsi tunggal yang besar. Seharusnya memiliki subrutin pribadi, tetapi mereka dapat pribadi untuk file implementasi (tingkat file statis atau ruang nama anonim di C ++) atau dalam kelas pembantu pribadi yang hanya dipakai di dalam fungsi publik ( Untuk menyimpan data atau tidak? ).
sumber
Mungkin ada alasan untuk mengekstrak metode yang diberikan ke kelas khusus. Salah satu alasannya adalah untuk memungkinkan Injeksi Ketergantungan.
Bayangkan Anda memiliki kelas bernama
VideoExporter
yang, pada akhirnya, harus dapat mengompres video. Cara yang bersih adalah dengan memiliki antarmuka:yang akan diimplementasikan seperti ini:
dan digunakan seperti ini:
Alternatif yang buruk adalah memiliki
VideoExporter
yang memiliki banyak metode publik dan melakukan semua pekerjaan, termasuk mengompresi. Itu akan dengan cepat menjadi mimpi buruk pemeliharaan, membuatnya sulit untuk menambahkan dukungan untuk format video lainnya.sumber
Ini adalah tanda bahwa Anda ingin meneruskan fungsi sebagai argumen ke fungsi lain. Saya menduga bahasa Anda (Java?) Tidak mendukungnya; jika itu masalahnya, itu bukan kegagalan dalam desain Anda karena merupakan kekurangan dalam bahasa pilihan Anda. Ini adalah salah satu masalah terbesar dengan bahasa yang menegaskan bahwa semuanya harus kelas.
Jika Anda tidak benar-benar melewati fungsi-fungsi palsu ini di sekitar maka Anda hanya ingin fungsi bebas / statis.
sumber
Saya tahu saya terlambat ke pesta, tetapi karena setiap orang tampaknya telah ketinggalan untuk menunjukkan ini:
Ini adalah pola desain terkenal yang disebut: Pola Strategi .
Pola strategi digunakan ketika ada beberapa strategi yang mungkin untuk menyelesaikan sub-masalah. Biasanya Anda mendefinisikan antarmuka yang memberlakukan kontrak pada semua implementasi dan kemudian menggunakan beberapa bentuk Injeksi Ketergantungan untuk memberikan strategi konkret untuk Anda.
Misalnya dalam hal ini Anda dapat memiliki
interface VideoCompressor
dan kemudian memiliki beberapa implementasi alternatif misalnyaclass H264Compressor implements VideoCompressor
danclass XVidCompressor implements VideoCompressor
. Tidak jelas dari OP bahwa ada antarmuka yang terlibat, bahkan jika tidak ada, mungkin saja penulis asli membiarkan pintu terbuka untuk menerapkan pola strategi jika diperlukan. Yang dengan sendirinya adalah desain yang bagus juga.Masalah yang OP terus-menerus menemukan dirinya instantiating kelas untuk memanggil metode adalah masalah dengan dia tidak menggunakan injeksi ketergantungan dan pola strategi dengan benar. Alih-alih membuat instance di mana Anda membutuhkannya, kelas yang mengandung harus memiliki anggota dengan objek strategi. Dan anggota ini harus disuntikkan, misalnya di konstruktor.
Dalam banyak kasus, pola strategi menghasilkan kelas antarmuka (seperti yang Anda tunjukkan) hanya dengan satu
doStuff(...)
metode.sumber
Apa yang Anda miliki adalah modular dan itu bagus, tetapi jika Anda mengelompokkan tanggung jawab ini ke dalam IVideoProcessor, itu mungkin akan lebih masuk akal dari sudut pandang DDD.
Di sisi lain, jika pemisahan, pengompresan dan pengindeksan tidak berhubungan dengan cara apa pun, maka saya akan menyimpannya sebagai komponen yang terpisah.
sumber
Ini adalah masalah - Anda bekerja dari aspek fungsional desain, bukan data. Apa yang sebenarnya Anda miliki adalah 3 fungsi mandiri yang telah di-OO-ified.
Misalnya, Anda memiliki kelas VideoCompressor. Mengapa Anda bekerja dengan kelas yang dirancang untuk mengompresi video - mengapa Anda tidak memiliki kelas Video dengan metode untuk mengkompresi data (video) yang berisi setiap objek jenis ini?
Saat merancang sistem OO, yang terbaik untuk membuat kelas yang mewakili objek, bukan kelas yang mewakili aktivitas yang bisa Anda terapkan. Di masa lalu, kelas disebut tipe - OO adalah cara untuk memperluas bahasa dengan dukungan untuk tipe data baru. Jika Anda memikirkan OO seperti ini, Anda mendapatkan cara yang lebih baik untuk merancang kelas Anda.
SUNTING:
coba saya jelaskan diri saya sedikit lebih baik, bayangkan kelas string yang memiliki metode concat. Anda dapat mengimplementasikan hal seperti itu di mana setiap objek yang dibuat dari kelas berisi data string, sehingga Anda dapat mengatakan
tetapi OP ingin itu berfungsi seperti ini:
sekarang ada tempat-tempat di mana kelas dapat digunakan untuk menyimpan koleksi fungsi terkait, tapi itu bukan OO, ini cara yang mudah untuk menggunakan fitur OO bahasa untuk membantu mengelola basis kode Anda lebih baik, tetapi tidak mungkin sama sekali dari "Desain OO". Objek dalam kasus semacam itu benar-benar tiruan, hanya digunakan seperti ini karena bahasa tidak menawarkan sesuatu yang lebih baik untuk mengelola masalah semacam ini. misalnya. Dalam bahasa seperti C # Anda akan menggunakan kelas statis untuk menyediakan fungsionalitas ini - ia menggunakan kembali mekanisme kelas, tetapi Anda tidak perlu lagi instantiate objek hanya untuk memanggil metode di atasnya. Anda berakhir dengan metode seperti
string.IsNullOrEmpty(mystring)
yang saya pikir buruk dibandingkan denganmystring.isNullOrEmpty()
.Jadi, jika ada yang bertanya "bagaimana cara mendesain kelas saya", saya sarankan memikirkan data kelas yang akan berisi daripada fungsi yang dikandungnya. Jika Anda memilih "a class is a bunch of methods", maka Anda akhirnya menulis kode gaya "lebih baik C". (yang tidak selalu merupakan hal yang buruk jika Anda meningkatkan kode C) tetapi tidak akan memberi Anda program yang dirancang OO terbaik.
sumber
Video
dan cenderung melumpuhkan kelas-kelas tersebut dengan fungsionalitas, yang sering berakhir dengan kode berantakan dengan> 10K LOC per kelas. Desain OO canggih memecah fungsionalitas ke unit yang lebih kecil sepertiVideoCompressor
(dan memungkinkanVideo
kelas hanya menjadi kelas data atau fasad untukVideoCompressor
).VideoCompressor
tidak mewakili suatu objek . Tidak ada yang salah dengan itu, hanya menunjukkan batas desain berorientasi objek.Video
kelas, tetapi metodologi kompresi sama sekali tidak sepele, jadi saya benar-benar akan memecahcompress
metode menjadi beberapa metode pribadi lainnya. Ini adalah bagian dari alasan pertanyaan saya, setelah membaca Jangan membuat kelas kata kerjaVideo
kelas, tetapi akan terdiri dari kelas aVideoCompressor
, aVideoSplitter
, dan yang terkait lainnya, yang seharusnya, dalam bentuk OO yang baik, menjadi kelas individu yang sangat kohesif.The ISP (antarmuka prinsip pemisahan) mengatakan bahwa tidak ada klien harus dipaksa untuk bergantung pada metode itu tidak digunakan. Manfaatnya banyak dan jelas. Pendekatan Anda sangat menghormati ISP, dan itu bagus.
Pendekatan berbeda yang juga menghormati ISP adalah, misalnya, membuat antarmuka per setiap metode (atau serangkaian metode dengan kohesi tinggi) dan kemudian memiliki satu kelas yang mengimplementasikan semua antarmuka tersebut. Apakah ini atau tidak solusi yang lebih baik tergantung pada skenario. Manfaat dari ini adalah, ketika menggunakan injeksi dependensi, Anda bisa memiliki klien dengan kolaborator yang berbeda (satu per setiap antarmuka) tetapi pada akhirnya semua kolaborator akan menunjuk ke instance objek yang sama.
Ngomong-ngomong, katamu
, kelas-kelas ini tampaknya adalah layanan (dan dengan demikian tanpa kewarganegaraan). Pernahkah Anda berpikir untuk menjadikan mereka lajang?
sumber
Ya, ada masalah. Tapi tidak parah. Maksud saya, Anda dapat menyusun kode seperti ini dan tidak ada hal buruk yang akan terjadi, ini dapat dipertahankan. Tetapi ada beberapa kelemahan untuk penataan semacam itu. Misalnya pertimbangkan jika representasi video Anda (dalam kasus Anda itu dikelompokkan dalam kelas RawVideo) berubah, Anda perlu memperbarui semua kelas operasi Anda. Atau pertimbangkan bahwa Anda mungkin memiliki beberapa representasi video yang bervariasi dalam runtime. Maka Anda harus mencocokkan representasi ke kelas "operasi" tertentu. Juga secara subyektif menjengkelkan ketergantungan untuk setiap operasi yang ingin Anda lakukan saat bertiga dan untuk memperbarui daftar dependensi yang diedarkan setiap kali Anda memutuskan bahwa operasi tidak lagi diperlukan atau Anda membutuhkan operasi baru.
Juga itu sebenarnya merupakan pelanggaran SRP. Beberapa orang hanya memperlakukan SRP sebagai panduan untuk membagi tanggung jawab (dan membawanya jauh dengan memperlakukan setiap operasi sebagai tanggung jawab yang berbeda) tetapi mereka lupa bahwa SRP juga merupakan panduan untuk mengelompokkan tanggung jawab. Dan sesuai dengan tanggung jawab SRP yang berubah karena alasan yang sama harus dikelompokkan bersama sehingga jika perubahan terjadi dilokalisasi ke kelas / modul sesedikit mungkin. Sedangkan untuk kelas besar itu tidak masalah memiliki beberapa algoritma di kelas yang sama asalkan algoritma tersebut terkait (yaitu berbagi beberapa pengetahuan yang seharusnya tidak diketahui di luar kelas itu). Masalahnya adalah kelas-kelas besar yang memiliki algoritma yang tidak terkait dengan cara apa pun dan berubah / bervariasi karena berbagai alasan.
sumber