Apakah kelas dengan metode tunggal (publik) merupakan masalah?

51

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 compressmetode yang mengambil video input tipe RawVideodan mengembalikan video output tipe CompressedVideo).
  • VideoSplitter (dengan splitmetode yang mengambil input video jenis RawVideodan mengembalikan vektor 2 video output, masing-masing jenis RawVideo).
  • VideoIndexer (dengan indexmetode yang mengambil input video jenis RawVideodan mengembalikan indeks jenis video VideoIndex).

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?

yjwong
sumber
14
Itu tergantung pada bahasanya. Dalam bahasa multi-paradigma seperti C ++ atau Python, kelas-kelas ini tidak memiliki bisnis: "Metode" mereka harus fungsi bebas.
3
@delnan: bahkan di C ++, Anda biasanya menggunakan kelas untuk membuat modul, bahkan jika Anda tidak memerlukan "kemampuan OO" penuh. Memang, ada alternatif menggunakan ruang nama sebagai ganti untuk mengelompokkan "fungsi bebas" bersama-sama ke sebuah modul, tapi saya tidak melihat kelebihannya.
Doc Brown
5
@ DocBrown: C ++ memiliki ruang nama untuk itu. Bahkan dalam C ++ modern Anda sering menggunakan fungsi bebas untuk metode, karena mereka dapat (secara statis) kelebihan beban untuk semua argumen sementara metode hanya dapat kelebihan beban untuk yang invocant.
Jan Hudec
2
@DocBrown: Ini adalah inti dari pertanyaan. Modul yang menggunakan ruang nama yang hanya memiliki satu metode "eksternal" baik-baik saja ketika fungsinya cukup kompleks. Karena ruang nama tidak berpura-pura mewakili sesuatu dan tidak berpura-pura berorientasi objek. Kelas memang demikian, tetapi kelas seperti ini benar-benar hanya ruang nama. Tentu saja di Jawa Anda tidak dapat memiliki fungsi, jadi inilah hasilnya. Malu di Jawa.
Jan Hudec
2
Terkait (hampir duplikat): programmers.stackexchange.com/q/175070/33843
Heinzi

Jawaban:

87

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!

Doc Brown
sumber
6
Ini adalah jawaban yang benar, dan saya telah memutarnya, tetapi Anda bisa membuatnya lebih baik dengan menjelaskan beberapa kelebihannya. Saya pikir apa yang dikatakan insting OP kepadanya adalah masalah adalah sesuatu yang lain ... yaitu, bahwa VideoCompressor benar-benar sebuah antarmuka. Mp4VideoCompressor adalah kelas dan harus dipertukarkan dengan VideoCompressor lain tanpa menyalin kode untuk VideoSplitter ke kelas baru.
pdr
1
Maaf tapi kamu salah. Kelas dengan metode tunggal seharusnya menjadi fungsi mandiri.
Miles Rout
3
@MilesRout: komentar Anda menunjukkan Anda salah paham tentang pertanyaan - OP menulis tentang kelas dengan satu metode publik, bukan dengan satu metode (dan ia memang berarti kelas dengan beberapa metode pribadi, seperti yang ia katakan di sini dalam satu komentar).
Doc Brown
2
"Kelas dengan metode tunggal seharusnya hanya fungsi mandiri." Asumsi kotor tentang apa metode itu. Saya memiliki kelas dengan satu metode publik. DI BALIK ITU ada banyak metode di kelas itu ditambah 5 kelas lain yang sama sekali tidak tahu klien. Penerapan SRP yang sangat baik akan menghasilkan antarmuka yang sederhana dan bersih dari struktur kelas yang berlapis, dengan kesederhanaan yang terwujud hingga ke puncak.
radarbob
2
@Andy: pertanyaannya adalah "apakah ini masalah" dan bukan "apakah ini sesuai dengan definisi OO tertentu". Selain itu, sama sekali tidak ada tanda dalam pertanyaan OP artinya kelas tanpa negara.
Doc Brown
26

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::functionatau 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? ).

Jan Hudec
sumber
12
"Kenapa berpura-pura kelas kalau tidak." Memiliki objek alih-alih fungsi bebas memungkinkan status dan subtipe. Itu bisa terbukti berharga di dunia di mana persyaratan berubah. Cepat atau lambat ada kebutuhan untuk bermain dengan pengaturan kompresi video atau untuk menyediakan algoritma kompresi alternatif. Sebelum itu kata "kelas" hanya memberitahu kita bahwa fungsi ini milik modul perangkat lunak yang mudah diperluas dan dipertukarkan dengan tanggung jawab yang jelas. Bukankah itu tujuan OO sebenarnya?
DATANG DARI
1
Nah, jika hanya memiliki satu metode publik (dan jenis menyiratkan tidak ada yang dilindungi), itu tidak dapat benar-benar diperpanjang. Tentu saja mengemas parameter kompresi mungkin masuk akal dalam hal ini menjadi objek fungsi (beberapa bahasa memiliki dukungan terpisah untuk itu, beberapa tidak).
Jan Hudec
3
Ini adalah koneksi mendasar antara objek dan fungsi: fungsi isomorfik untuk objek dengan metode tunggal dan tanpa bidang. Penutupan isomorfik untuk objek dengan metode tunggal dan beberapa bidang. Fungsi selektor mengembalikan salah satu dari beberapa fungsi yang semuanya menutup set variabel yang sama adalah isomorfik ke objek. (Sebenarnya, ini adalah bagaimana objek dikodekan dalam JavaScript, kecuali Anda menggunakan struktur data kamus alih-alih fungsi pemilih.)
Jörg W Mittag
4
"Ini bukan lagi berorientasi objek. Karena kelas-kelas itu tidak mewakili apa-apa, mereka hanya kapal untuk fungsi." - Ini salah. Saya berpendapat pendekatan ini LEBIH berorientasi objek. OO tidak berarti itu mewakili objek dunia nyata. Sejumlah besar penawaran pemrograman dengan konsep abstrak, tetapi apakah ini berarti Anda tidak dapat menerapkan pendekatan OO untuk menyelesaikannya? Tentu saja tidak. Ini lebih tentang modularitas dan abstraksi. Setiap objek memiliki tanggung jawab tunggal. Ini membuatnya mudah untuk membungkus kepala Anda di sekitar program dan membuat perubahan. Tidak cukup ruang untuk mendaftar banyak manfaat OOP.
Despertar
2
@ Phoshi: Ya, saya mengerti. Saya tidak pernah mengklaim bahwa pendekatan fungsional tidak akan berhasil juga. Namun, itu jelas bukan topiknya. Kompresor video atau transmogrifier video atau apa pun masih merupakan kandidat yang valid untuk suatu objek.
DATANG DARI
13

Mungkin ada alasan untuk mengekstrak metode yang diberikan ke kelas khusus. Salah satu alasannya adalah untuk memungkinkan Injeksi Ketergantungan.

Bayangkan Anda memiliki kelas bernama VideoExporteryang, pada akhirnya, harus dapat mengompres video. Cara yang bersih adalah dengan memiliki antarmuka:

interface IVideoCompressor
{
    Stream compress(Video video);
}

yang akan diimplementasikan seperti ini:

class MpegVideoCompressor : IVideoCompressor
{
    // ...
}

class FlashVideoCompressor : IVideoCompressor
{
    // ...
}

dan digunakan seperti ini:

class VideoExporter
{
    // ...
    void export(Destination destination, IVideoCompressor compressor)
    {
        // ...
        destination = compressor(this.source);
        // ...
    }
    // ...
}

Alternatif yang buruk adalah memiliki VideoExporteryang 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.

Arseni Mourzenko
sumber
2
Jawaban Anda tidak membedakan antara metode publik dan pribadi. Ini bisa dipahami sebagai rekomendasi untuk menempatkan semua kode kelas hanya satu metode, tapi saya kira bukan itu yang Anda maksud?
Doc Brown
2
@DocBrown: Metode pribadi tidak relevan dengan pertanyaan ini. Mereka dapat ditempatkan di kelas pembantu internal atau apa pun.
Jan Hudec
2
@JanHudec: saat ini teksnya adalah "Alternatif yang buruk adalah memiliki VideoExporter yang memiliki banyak metode" - tetapi seharusnya "Alternatif yang buruk adalah memiliki VideoExporter yang memiliki banyak metode publik ".
Doc Brown
@DocBrown: Setuju di sini.
Jan Hudec
2
@DocBrown: terima kasih atas komentar Anda. Saya mengedit jawaban saya. Awalnya, saya pikir itu diasumsikan bahwa pertanyaan (dan jawaban saya) adalah tentang metode publik saja. Tampaknya tidak sejelas itu.
Arseni Mourzenko
6

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.

Doval
sumber
1
Sejak Java 8, Anda memiliki lambdas, sehingga Anda dapat melewati beberapa fungsi.
Silviu Burcea
Poin yang adil, tetapi itu tidak akan dirilis secara resmi untuk beberapa saat lagi, dan beberapa tempat kerja lambat untuk pindah ke versi yang lebih baru.
Doval
Tanggal rilis pada bulan Maret. Juga, build EAP sangat populer untuk JDK 8 :)
Silviu Burcea
Meskipun, seperti Java 8 lambdas hanyalah cara singkat untuk mendefinisikan objek hanya dengan satu metode publik, selain menyimpan sedikit kode boilerplate, mereka tidak membuat perbedaan sama sekali di sini.
Jules
5

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 VideoCompressordan kemudian memiliki beberapa implementasi alternatif misalnya class H264Compressor implements VideoCompressordan class 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.

Emily L.
sumber
1
public interface IVideoProcessor
{
   void Split();

   void Compress();

   void Index();
}

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.

CodeART
sumber
Karena alasan masing-masing fungsi ini perlu diubah agak berbeda, saya berpendapat bahwa menyatukannya seperti ini melanggar SRP.
Jules
0

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

string mystring("Hello"); 
mystring.concat("World");

tetapi OP ingin itu berfungsi seperti ini:

string mystring();
string result = mystring.concat("Hello", "World");

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 dengan mystring.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.

gbjbaanb
sumber
9
-1, saya benar-benar tidak setuju. Desain OO pemula hanya berfungsi dengan objek data seperti a Videodan 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 seperti VideoCompressor(dan memungkinkan Videokelas hanya menjadi kelas data atau fasad untuk VideoCompressor).
Doc Brown
5
@DocBrown: Pada titik itu bagaimanapun bukan lagi desain berorientasi objek, karena VideoCompressortidak mewakili suatu objek . Tidak ada yang salah dengan itu, hanya menunjukkan batas desain berorientasi objek.
Jan Hudec
3
ah, tetapi para pemula OO di mana 1 fungsi berubah menjadi sebuah kelas tidak benar-benar OO sama sekali, dan hanya mendorong decoupling besar-besaran yang berakhir dalam ribuan file kelas yang tidak dapat dipertahankan oleh siapa pun. Saya pikir yang terbaik untuk menganggap kelas sebagai "pembungkus data" bukan "pembungkus fungsionalitas", yang akan memberikan pemahaman yang lebih baik kepada pemula tentang cara berpikir tentang program OO.
gbjbaanb
4
@DocBrown sebenarnya secara akurat menyoroti kekhawatiran saya; Saya memang memiliki Videokelas, tetapi metodologi kompresi sama sekali tidak sepele, jadi saya benar-benar akan memecah compressmetode menjadi beberapa metode pribadi lainnya. Ini adalah bagian dari alasan pertanyaan saya, setelah membaca Jangan membuat kelas kata kerja
yjwong
2
Saya pikir mungkin baik-baik saja untuk memiliki Videokelas, tetapi akan terdiri dari kelas a VideoCompressor, a VideoSplitter, dan yang terkait lainnya, yang seharusnya, dalam bentuk OO yang baik, menjadi kelas individu yang sangat kohesif.
Eric King
-1

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

Saya menemukan diri saya instantiating setiap kelas

, kelas-kelas ini tampaknya adalah layanan (dan dengan demikian tanpa kewarganegaraan). Pernahkah Anda berpikir untuk menjadikan mereka lajang?

diegomtassis
sumber
3
Untuk sisanya sementara prinsip pemisahan antarmuka adalah pola yang baik, pertanyaan ini adalah tentang implementasi, bukan antarmuka, jadi saya tidak yakin itu relevan.
Jan Hudec
3
Saya tidak akan masuk untuk membahas apakah singleton adalah pola atau antipemutaran. Saya menyarankan solusi yang mungkin (bahkan memiliki nama) untuk masalahnya, terserah dia untuk memutuskan apakah cocok atau tidak.
diegomtassis
Mengenai apakah ISP itu relevan atau tidak, pokok bahasan pertanyaannya adalah "apakah kelas dengan satu metode saja merupakan masalah?", Kebalikan dari itu adalah kelas dengan banyak metode, yang menjadi masalah saat Anda membuat klien bergantung langsung padanya ... persis seperti contoh xerox Robert Martin yang membawanya untuk merumuskan ISP.
diegomtassis
-1

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.

ddiukariev
sumber