Apakah tidak masalah bagi kelas untuk menggunakan metode publiknya sendiri?

23

Latar Belakang

Saat ini saya memiliki situasi di mana saya memiliki objek yang dikirim dan diterima oleh perangkat. Pesan ini memiliki beberapa konstruk, sebagai berikut:

public void ReverseData()
public void ScheduleTransmission()

The ScheduleTransmissionMetode perlu untuk memanggil ReverseDatametode setiap kali disebut. Namun, ada saat-saat di mana saya perlu menelepon secara ReverseDataeksternal (dan saya harus menambahkan di luar namespace sepenuhnya ) dari mana objek tersebut dipakai dalam aplikasi.

Adapun "menerima" Maksud saya ReverseDataakan dipanggil secara eksternal dalam object_receivedevent-handler untuk membatalkan data.

Pertanyaan

Apakah secara umum dapat diterima suatu objek untuk memanggil metode publiknya sendiri?

Mengintip
sumber
5
Tidak ada yang salah dengan memanggil metode publik dari caranya sendiri. Tetapi, berdasarkan nama metode Anda, ReverseData () bagi saya agak berbahaya menjadi metode publik jika itu membalikkan data internal. Bagaimana jika ReverseData () dipanggil di luar objek dan kemudian dipanggil lagi dengan ScheduleTransmission ().
Kaan
@ Kan Ini bukan nama sebenarnya dari metode saya, tetapi terkait erat. Faktanya "membalikkan data" hanya membalikkan 8 bit dari total kata dan dilakukan ketika kita menerima dan mengirimkan.
Mengintai
Pertanyaannya masih ada. Bagaimana jika 8 bit itu dibalik dua kali sebelum transmisi dijadwalkan? Ini terasa seperti lubang besar di antarmuka publik. Berpikir tentang antarmuka publik seperti yang saya sebutkan dalam jawaban saya mungkin membantu mengangkat masalah ini.
Daniel T.
2
@StevieV Saya percaya ini hanya memperkuat kekhawatiran Kaan. Kedengarannya seperti Anda mengekspos metode yang mengubah keadaan objek, dan negara terutama tergantung pada berapa kali metode ini dipanggil. Ini menjadi mimpi buruk dalam mencoba melacak keadaan objek di seluruh kode Anda. Kedengarannya bagi saya lebih seperti Anda akan mendapat manfaat dari tipe data terpisah dari negara-negara konseptual ini, sehingga Anda dapat mengetahui apa itu dalam potongan kode tertentu tanpa harus khawatir tentang hal itu.
jpmc26
2
@StevieV sesuatu seperti Data dan SerializedData. Data adalah apa yang dilihat oleh kode dan SerializedData adalah apa yang dikirim melalui jaringan. Kemudian buat data terbalik (atau lebih tepatnya data serial / deserialize) berubah dari satu jenis ke yang lain.
csiz

Jawaban:

33

Saya akan mengatakan itu tidak hanya dapat diterima tetapi dianjurkan terutama jika Anda berencana untuk mengizinkan ekstensi. Untuk mendukung ekstensi ke kelas dalam C #, Anda perlu menandai metode sebagai virtual per komentar di bawah ini. Anda mungkin ingin mendokumentasikan ini, sehingga seseorang tidak terkejut ketika mengganti ReverseData () mengubah cara kerja ScheduleTransmission ().

Itu benar-benar bermuara pada desain kelas. ReverseData () terdengar seperti perilaku mendasar kelas Anda. Jika Anda perlu menggunakan perilaku ini di tempat lain, Anda mungkin tidak ingin memiliki versi lain. Anda hanya perlu berhati-hati agar Anda tidak membiarkan detail khusus untuk ScheduleTransmission () bocor ke dalam ReverseData (). Itu akan menciptakan masalah. Tetapi karena Anda sudah menggunakan ini di luar kelas, Anda mungkin sudah memikirkannya.

JimmyJames
sumber
Wow, sepertinya sangat aneh bagi saya tetapi ini membantu. Ingin juga melihat apa yang dikatakan orang lain. Terima kasih.
Mengintai
2
"sehingga seseorang tidak terkejut ketika mengganti ReverseData () mengubah cara ScheduleTransmission () bekerja" : tidak, tidak juga. Setidaknya tidak dalam C # (perhatikan bahwa pertanyaan memiliki tag C #). Kecuali ReverseData()abstrak, tidak peduli apa yang Anda lakukan di kelas anak, itu selalu merupakan metode asli yang akan dipanggil.
Arseni Mourzenko
2
@ Utama Atau Atau virtual... Dan jika Anda mengganti metode, maka menurut definisi itu harus virtualatau abstract.
Pokechu22
1
@ Pokechu22: memang, virtualbekerja juga. Dalam semua kasus, tanda tangan, menurut OP, adalah public void ReverseData(), sehingga bagian dari jawaban tentang hal-hal utama sedikit menyesatkan.
Arseni Mourzenko
@ MainMa Anda mengatakan bahwa jika metode kelas dasar memanggil metode virtual itu tidak berperilaku seperti biasa kecuali itu abstract? Saya mencari jawaban pada abstractvs virtualdan tidak melihat yang disebutkan: agak abstrak hanya berarti tidak ada versi yang diberikan dasar ini.
JDługosz
20

Benar.

Visibilitas metode memiliki tujuan tunggal untuk mengizinkan atau menolak akses ke metode di luar kelas atau di dalam kelas anak; metode publik, terlindungi dan pribadi selalu dapat dipanggil di dalam kelas itu sendiri.

Tidak ada yang salah dalam memanggil metode publik. Ilustrasi dalam pertanyaan Anda adalah contoh sempurna dari situasi di mana memiliki dua metode publik persis apa yang harus Anda lakukan.

Namun, Anda dapat dengan cermat memperhatikan pola-pola berikut:

  1. Metode Hello()memanggil World(string, int, int)seperti ini:

    Hello()
    {
        this.World("Some magic value here", 0, 100);
    }

    Hindari pola ini. Sebagai gantinya, gunakan argumen opsional . Argumen opsional membuat mudah ditemukan: pemanggil yang mengetik Hello(tidak perlu tahu bahwa ada metode yang memungkinkan untuk memanggil metode dengan nilai default. Argumen opsional juga mendokumentasikan diri. World()tidak menunjukkan kepada pemanggil apa nilai default aktual.

  2. Metode Hello(ComplexEntity)memanggilWorld(string, int, int)seperti ini:

    Hello(ComplexEntity entity)
    {
        this.World(entity.Name, entity.Start, entity.Finish);
    }

    Sebaliknya, gunakan kelebihan beban . Alasan yang sama: kemampuan menemukan yang lebih baik. Penelepon dapat melihat sekaligus semua kelebihan beban melalui IntelliSense dan memilih yang benar.

  3. Metode hanya memanggil metode publik lainnya tanpa menambahkan nilai substansial.

    Berpikir dua kali. Apakah Anda benar-benar membutuhkan metode ini? Atau haruskah Anda menghapusnya dan membiarkan penelepon memanggil metode yang berbeda? Petunjuk: jika nama metode ini kelihatannya tidak benar atau sulit ditemukan, Anda mungkin harus menghapusnya.

  4. Metode memvalidasi input dan kemudian memanggil metode lain:

    Hello(string name, int start, int end)
    {
        if (name == null) throw new ArgumentNullException(...);
        if (start < 0) throw new OutOfRangeException(...);
        ...
        if (end < start) throw new ArgumentException(...);
    
        this.World(name, start, end);
    }

    Sebaliknya, Worldharus memvalidasi parameternya sendiri, atau bersifat pribadi.

Arseni Mourzenko
sumber
Apakah pemikiran yang sama berlaku jika ReverseData benar-benar internal, dan digunakan di seluruh namespace yang berisi contoh "objek"?
Mengintai
1
@StevieV: ya, tentu saja.
Arseni Mourzenko
@MainMa Saya tidak mengerti apa alasan di balik poin 1 dan 2
Kapol
@Kapol: terima kasih atas tanggapan Anda. Saya mengedit jawaban saya dengan menambahkan penjelasan tentang dua poin pertama.
Arseni Mourzenko
Bahkan dalam kasus 1, saya biasanya lebih suka kelebihan daripada argumen opsional. Jika opsinya sedemikian rupa sehingga menggunakan kelebihan bukan pilihan atau menghasilkan terlalu banyak, saya akan membuat jenis khusus dan menggunakannya sebagai argumen (seperti dalam saran 2), atau refactor kode.
Brian
8

Jika sesuatu bersifat publik, itu dapat dipanggil kapan saja oleh sistem apa pun. Tidak ada alasan Anda tidak bisa menjadi salah satu dari sistem itu juga!

Di perpustakaan yang sangat optimal, Anda ingin menyalin idiom yang diajukan java.util.ArrayList#ensureCapacity(int).

PastikanKapasitas

  • ensureCapacity bersifat publik
  • ensureCapacity memiliki semua batas yang diperlukan memeriksa dan nilai default, dll.
  • ensureCapacity panggilan ensureExplicitCapacity

memastikanKapasitasInternal

  • ensureCapacityInternal bersifat pribadi
  • ensureCapacityInternal memiliki pengecekan kesalahan minimal karena semua input berasal dari dalam kelas
  • ensureCapacityInternal Panggilan JUGA ensureExplicitCapacity

memastikanKapasitas Eksplisit

  • ensureExplicitCapacity adalah JUGA pribadi
  • ensureExplicitCapacitytidak memiliki pengecekan kesalahan
  • ensureExplicitCapacity melakukan pekerjaan yang sebenarnya
  • ensureExplicitCapacitytidak dipanggil dari mana pun kecuali oleh ensureCapacitydanensureCapacityInternal

Dengan cara ini, kode yang Anda percayai mendapat akses istimewa (dan lebih cepat!) Karena Anda tahu inputnya baik. Kode yang tidak Anda percayai melewati kekakuan tertentu untuk memverifikasi integritasnya dan membom atau memberikan default atau menangani input yang buruk. Keduanya menyalurkan ke tempat yang benar-benar berfungsi.

Namun, ini digunakan di ArrayList, salah satu kelas yang paling banyak digunakan di JDK. Sangat mungkin kasing Anda tidak memerlukan tingkat kerumitan dan kekakuan itu. Singkat cerita, jika semua ensureCapacityInternalpanggilan diganti dengan ensureCapacitypanggilan, kinerjanya masih akan sangat, sangat bagus. Ini adalah optimasi mikro mungkin hanya dilakukan setelah pertimbangan luas.

corsiKa
sumber
3

Beberapa jawaban baik telah diberikan, dan saya juga akan setuju dengan mereka bahwa ya, suatu objek dapat memanggil metode publiknya dari metode lainnya. Namun, ada sedikit peringatan desain yang harus Anda perhatikan.

Metode publik biasanya memiliki kontrak "mengambil objek dalam keadaan konsisten, melakukan sesuatu yang masuk akal, meninggalkan objek dalam keadaan konsisten (mungkin berbeda)". Di sini, "konsisten" dapat berarti, misalnya, bahwa Lengtha List<T>tidak lebih besar dari itu Capacitydan yang mereferensikan unsur-unsur pada indeks dari 0keLength-1 tidak akan melempar.

Tetapi di dalam metode objek, objek mungkin dalam keadaan tidak konsisten, jadi ketika Anda memanggil salah satu metode publik Anda, itu mungkin melakukan hal yang sangat salah, karena itu tidak ditulis dengan kemungkinan seperti itu dalam pikiran. Jadi, jika Anda berencana untuk memanggil metode publik Anda dari metode lain, pastikan bahwa kontrak mereka adalah "mengambil objek di beberapa negara, melakukan sesuatu yang masuk akal, meninggalkan objek dalam (mungkin berbeda) (mungkin tidak konsisten - tetapi hanya jika awal negara tidak konsisten) ".

Joker_vD
sumber
1

Contoh lain ketika memanggil metode publik di dalam metode publik lain sama sekali baik-baik saja adalah pendekatan CanExecute / Execute. Saya menggunakannya ketika saya membutuhkan validasi dan pelestarian yang tidak berubah-ubah .

Tetapi secara umum saya selalu berhati-hati tentang itu. Jika suatu metode a()disebut metode dalam b(), itu berarti metode tersebut a()adalah detail implementasi dari b(). Sangat sering itu menunjukkan bahwa mereka termasuk level abstraksi yang berbeda. Dan fakta bahwa mereka berdua bersifat publik membuat saya bertanya-tanya apakah itu merupakan pelanggaran prinsip tanggung jawab tunggal atau tidak.

Zapadlo
sumber
-5

Maaf, tetapi saya harus tidak setuju dengan sebagian besar jawaban 'ya Anda bisa' dan mengatakan bahwa:

Saya akan mencegah kelas memanggil satu metode publik dari yang lain

Ada beberapa masalah potensial dengan praktik ini.

1: Infinite loop di kelas yang diwarisi

Jadi kelas dasar Anda memanggil method1 dari method2 tetapi kemudian Anda, atau orang lain, mewarisi method1 dan menyembunyikan method1 dengan metode baru yang memanggil method2.

2: Acara, Pencatatan dll.

misal saya punya metode Add1 yang memancarkan suatu peristiwa '1 ditambahkan!' Saya mungkin tidak ingin metode Add10 meningkatkan kejadian itu, menulis ke log atau apa pun, sepuluh kali.

3: threading dan deadlock lainnya

Misalnya, InsertComplexData membuka koneksi db, memulai transaksi, mengunci tabel, Kemudian memanggil InsertSimpleData, dengan membuka koneksi, memulai transaksi, menunggu tabel dibuka ...

Saya yakin ada lebih banyak alasan, salah satu jawaban lain menyentuh 'Anda mengedit metode1 dan terkejut metode2 mulai berperilaku berbeda'

Secara umum, jika Anda memiliki dua metode publik yang berbagi kode, lebih baik membuat keduanya memanggil metode pribadi daripada satu panggilan yang lain.

Edit ----

Mari kita memperluas kasus khusus di OP.

kami tidak memiliki banyak detail tetapi kami tahu bahwa ReverseData dipanggil oleh event handler dari beberapa jenis serta metode ScheduleTransmission.

Saya berasumsi bahwa membalikkan data juga mengubah keadaan internal objek

Mengingat kasus ini, saya akan berpikir bahwa keselamatan utas akan menjadi penting dan karenanya keberatan ketiga saya terhadap praktik ini berlaku.

Untuk membuat thread ReverseData aman, Anda dapat menambahkan kunci. Tetapi jika ScheduleTransmission juga perlu diamankan, Anda ingin berbagi kunci yang sama.

Cara termudah untuk melakukan ini adalah dengan memindahkan kode ReverseData ke metode pribadi dan kedua metode publik menyebutnya. Anda kemudian dapat meletakkan pernyataan kunci di Metode Publik dan berbagi objek kunci.

Jelas Anda dapat berdebat "itu tidak akan pernah terjadi!" atau "Saya bisa memprogram kunci dengan cara lain" tetapi poin tentang praktik pengkodean yang baik adalah untuk menyusun kode Anda dengan baik sejak awal.

Dalam istilah akademis saya akan mengatakan ini melanggar huruf L dalam huruf padat. Metode publik lebih dari sekadar dapat diakses secara publik. Mereka juga dapat dimodifikasi oleh pewarisnya. Kode Anda harus ditutup untuk modifikasi yang berarti Anda harus memikirkan pekerjaan apa yang Anda lakukan baik dalam metode publik maupun yang dilindungi.

Inilah satu lagi: Anda juga berpotensi melanggar DDD. Jika objek Anda adalah objek domain, metode publiknya haruslah istilah Domain yang berarti sesuatu bagi bisnis. Dalam hal ini sangat tidak mungkin bahwa 'beli selusin telur' sama dengan 'beli 1 telur 12 kali' bahkan jika itu dimulai dengan cara itu.

Ewan
sumber
3
Masalah-masalah ini terdengar lebih seperti arsitektur yang dipikirkan dengan buruk daripada kecaman umum terhadap prinsip desain. (Mungkin memanggil "1 ditambahkan" baik-baik saja setiap kali. Jika tidak, kita harus memprogram berbeda. Mungkin jika dua metode mencoba untuk mengunci sumber daya yang sama secara eksklusif, mereka tidak boleh saling memanggil, atau kita menulisnya dengan buruk .) Daripada menerapkan yang buruk, Anda mungkin ingin fokus pada argumen yang lebih kuat yang menangani mengakses metode publik sebagai prinsip universal. Misalnya, diberi kode yang bagus, kenapa jelek?
doppelgreener
Tepat, jika tidak, Anda tidak punya tempat untuk meletakkan acara. Demikian pula dengan # 3 semuanya baik-baik saja sampai salah satu metode Anda di rantai membutuhkan transaksi, maka Anda kacau
Ewan
Semua masalah ini lebih mencerminkan kode itu sendiri yang berkualitas buruk daripada prinsip umum yang cacat. Ketiga kasus ini hanyalah kode yang buruk. Mereka dapat diselesaikan dengan beberapa versi "jangan lakukan itu, lakukan ini sebagai gantinya" (mungkin dengan bertanya "apa yang sebenarnya Anda inginkan?"), Dan jangan mempertanyakan prinsip umum dari suatu kelas yang menyebut publiknya sendiri. metode. Potensi samar untuk loop tak terbatas adalah satu-satunya di sini yang sampai pada masalah ini sebagai masalah prinsip, dan itupun tidak ditangani secara menyeluruh.
doppelgreener
satu-satunya cara 'kode' dalam contoh saham saya adalah satu metode publik memanggil yang lain. Jika Anda pikir itu 'kode buruk' bagus! Itulah pendapat saya
Ewan
Anda dapat menggunakan palu untuk mematahkan tangan Anda sendiri tidak berarti palu itu buruk. Ini berarti Anda tidak perlu melakukan hal-hal yang akan merusak tangan Anda.
doppelgreener