Konstruktor umumnya tidak boleh memanggil metode

12

Saya menjelaskan kepada seorang kolega mengapa seorang konstruktor yang memanggil suatu metode bisa menjadi antipemernah.

contoh (dalam C berkarat saya ++)

class C {
public :
    C(int foo);
    void setFoo(int foo);
private:
    int foo;
}

C::C(int foo) {
    setFoo(foo);
}

void C::setFoo(int foo) {
    this->foo = foo
}

Saya ingin memotivasi fakta ini dengan lebih baik melalui kontribusi tambahan Anda. Jika Anda memiliki contoh, referensi buku, halaman blog, atau nama prinsip, mereka akan sangat disambut.

Sunting: Saya berbicara secara umum, tetapi kami sedang mengkode dengan python.

Stefano Borini
sumber
Apakah ini aturan umum atau khusus untuk bahasa tertentu?
ChrisF
Bahasa apa? Dalam C ++ lebih dari anti-pola: parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.5
LennyProgrammers
@ Lenny222, OP berbicara tentang "metode kelas", yang - bagi saya setidaknya - berarti metode non-instance . Karena itu tidak bisa virtual.
Péter Török
3
@ Alb Di Jawa tidak apa-apa. Apa yang tidak boleh Anda lakukan adalah secara eksplisit beralih thiske salah satu metode yang Anda panggil dari konstruktor.
biziclop
3
@Stefano Borini: Jika Anda melakukan pengkodean dengan Python, mengapa tidak menunjukkan contoh dalam Python sebagai ganti C ++ yang berkarat? Juga, tolong jelaskan mengapa ini adalah hal yang buruk. Kami selalu melakukannya.
S.Lott

Jawaban:

26

Anda belum menentukan bahasa.

Dalam C ++ seorang konstruktor harus waspada ketika memanggil fungsi virtual, karena fungsi sebenarnya yang dipanggil adalah implementasi kelas. Jika ini adalah metode virtual murni tanpa implementasi, ini akan menjadi pelanggaran akses.

Konstruktor dapat memanggil fungsi non-virtual.

Jika bahasa Anda adalah Java di mana fungsi umumnya virtual secara default masuk akal bahwa Anda harus ekstra hati-hati.

C # tampaknya menangani situasi seperti yang Anda harapkan: Anda dapat memanggil metode virtual dalam konstruktor dan itu memanggil versi paling final. Jadi di C # bukan anti-pola.

Alasan umum untuk memanggil metode dari konstruktor adalah Anda memiliki beberapa konstruktor yang ingin memanggil metode "init" yang umum.

Perhatikan bahwa destruktor akan memiliki masalah yang sama dengan metode virtual, sehingga Anda tidak dapat memiliki metode "pembersihan" virtual yang berada di luar destruktor Anda dan mengharapkannya dipanggil oleh destructor kelas dasar.

Java dan C # tidak memiliki destruktor, mereka memiliki finalizer. Saya tidak tahu perilaku dengan Java.

C # tampaknya menangani pembersihan dengan benar terkait hal ini.

(Perhatikan bahwa walaupun Java dan C # memiliki pengumpulan sampah, itu hanya mengelola alokasi memori. Ada pembersihan lain yang perlu dilakukan destruktor Anda yang tidak melepaskan memori).

Uang tunai
sumber
13
Ada beberapa kesalahan kecil di sini. Metode dalam C # tidak virtual secara default. C # memiliki semantik yang berbeda dari C ++ ketika memanggil metode virtual dalam konstruktor; metode virtual pada tipe yang paling diturunkan akan dipanggil, bukan metode virtual pada bagian dari tipe yang sedang dibangun. C # memang menyebut metode finalisasinya "destruktor" tetapi Anda benar bahwa mereka memiliki semantik finalizer. Metode virtual yang disebut dalam destruktor C # bekerja dengan cara yang sama seperti yang mereka lakukan dalam konstruktor; metode yang paling diturunkan disebut.
Eric Lippert
@ Péter: Saya bermaksud metode instan. maaf bila membingungkan.
Stefano Borini
1
@Eric Lippert. Terima kasih atas keahlian Anda di C #, saya telah mengedit jawaban saya sesuai dengan itu. Saya tidak tahu bahasa itu, saya tahu C ++ sangat baik dan Java kurang baik.
CashCow
5
Sama-sama. Perhatikan bahwa memanggil metode virtual di konstruktor kelas dasar di C # masih merupakan ide yang sangat buruk.
Eric Lippert
Jika Anda memanggil metode (virtual) di Jawa dari konstruktor, itu akan selalu memanggil override yang paling diturunkan. Namun, apa yang Anda sebut "dengan cara yang Anda harapkan" itu yang saya sebut membingungkan. Karena sementara Java memanggil override yang paling diturunkan, metode itu hanya akan melihat inisialisasi yang diajukan diproses tetapi bukan konstruktor dari kelasnya sendiri dijalankan. Meminta metode pada kelas yang belum memiliki invariannya bisa berbahaya. Jadi saya pikir C ++ membuat pilihan yang lebih baik di sini.
5gon12eder
18

OK, sekarang kebingungan tentang metode kelas vs metode contoh dihapus, saya bisa memberikan jawaban :-)

Masalahnya bukan dengan memanggil metode instance secara umum dari konstruktor; itu adalah dengan memanggil metode virtual (langsung atau tidak langsung). Dan alasan utamanya adalah ketika berada di dalam konstruktor, objek belum sepenuhnya dibangun . Dan terutama bagian-bagian subkelasnya sama sekali tidak dibangun ketika konstruktor kelas dasar mengeksekusi. Jadi keadaan internalnya tidak konsisten dengan cara yang tergantung pada bahasa, dan ini dapat menyebabkan bug halus yang berbeda dalam bahasa yang berbeda.

C ++ dan C # sudah dibahas oleh orang lain. Di Jawa, metode virtual dari tipe yang paling diturunkan akan dipanggil, namun tipe itu belum diinisialisasi. Jadi jika metode itu menggunakan bidang apa pun dari tipe turunan, bidang tersebut mungkin belum diinisialisasi dengan benar pada titik waktu tersebut. Masalah ini dibahas secara rinci dalam Effecive Java 2nd Edition , Butir 17: Desain dan dokumen untuk warisan atau melarangnya .

Perhatikan bahwa ini adalah kasus khusus masalah umum penerbitan referensi objek sebelum waktunya . Metode instan memiliki thisparameter implisit , tetapi meneruskan thissecara eksplisit ke metode dapat menyebabkan masalah yang sama. Terutama dalam program bersamaan di mana jika referensi objek diterbitkan sebelum waktunya ke utas lain, utas itu sudah bisa memanggil metode di atasnya sebelum konstruktor di utas pertama selesai.

Péter Török
sumber
3
(+1) "ketika berada di dalam konstruktor, objek belum sepenuhnya dibangun." Sama seperti "metode kelas vs contoh". Beberapa bahasa pemrograman menganggapnya dibangun ketika memasuki konstruktor, seolah-olah programmer di mana memberikan nilai kepada konstruktor.
umlcat
7

Saya tidak akan menganggap pemanggilan metode di sini sebagai antipattern dalam dirinya sendiri, lebih merupakan bau kode. Jika kelas menyediakan resetmetode, yang mengembalikan objek ke keadaan semula, maka memanggil reset()konstruktor adalah KERING. (Saya tidak membuat pernyataan apa pun tentang metode reset).

Berikut ini adalah artikel yang dapat membantu memenuhi permintaan Anda akan otoritas: http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/

Ini bukan tentang memanggil metode, tetapi tentang konstruktor yang melakukan terlalu banyak. IMHO, memanggil metode dalam konstruktor adalah bau yang mungkin menunjukkan bahwa konstruktor terlalu berat.

Ini terkait dengan betapa mudahnya menguji kode Anda. Alasannya termasuk:

  1. Pengujian unit melibatkan banyak penciptaan dan penghancuran - oleh karena itu konstruksi harus cepat.

  2. Bergantung pada apa yang dilakukan oleh metode-metode tersebut, mungkin menyulitkan untuk menguji unit kode yang terpisah tanpa mengandalkan beberapa prakondisi (yang berpotensi tidak dapat diuji coba) yang diatur dalam konstruktor (mis. Dapatkan info dari jaringan).

Paul Butcher
sumber
3

Secara filosofis, tujuan konstruktor adalah mengubah potongan memori mentah menjadi sebuah instance. Ketika konstruktor sedang dieksekusi, objek belum ada, oleh karena itu memanggil metodenya adalah ide yang buruk. Anda mungkin tidak tahu apa yang mereka lakukan secara internal, dan mereka mungkin menganggap objek itu setidaknya ada (ya!) Ketika mereka dipanggil.

Secara teknis, mungkin tidak ada yang salah dengan itu, di C ++ dan terutama di Python, terserah Anda untuk berhati-hati.

Secara praktis, Anda harus membatasi panggilan hanya pada metode yang menginisialisasi anggota kelas.


sumber
2

Ini bukan masalah tujuan umum. Ini masalah dalam C ++, khususnya ketika menggunakan metode pewarisan dan virtual, karena konstruksi objek terjadi mundur, dan penunjuk vtable disetel ulang dengan setiap lapisan konstruktor dalam hierarki warisan, jadi jika Anda memanggil metode virtual Anda mungkin tidak akhirnya mendapatkan yang benar-benar sesuai dengan kelas yang Anda coba buat, yang mengalahkan seluruh tujuan menggunakan metode virtual.

Dalam bahasa dengan dukungan OOP waras, yang mengatur pointer vtable dengan benar dari awal, masalah ini tidak ada.

Mason Wheeler
sumber
2

Ada dua masalah dengan memanggil metode:

  • memanggil metode virtual, yang dapat melakukan sesuatu yang tidak terduga (C ++) atau menggunakan bagian dari objek yang belum diinisialisasi
  • memanggil metode publik (yang seharusnya menegakkan invarian kelas), karena objek belum tentu lengkap (dan dengan demikian invariannya mungkin tidak berlaku)

Tidak ada yang salah dengan memanggil fungsi pembantu, selama itu tidak termasuk dalam dua kasus sebelumnya.

Matthieu M.
sumber
1

Saya tidak membeli ini. Dalam sistem berorientasi objek, memanggil metode adalah satu-satunya hal yang dapat Anda lakukan. Bahkan, itu kurang lebih definisi "berorientasi objek". Jadi, jika seorang konstruktor tidak dapat memanggil metode apa pun, lalu apa yang bisa dilakukannya?

Jörg W Mittag
sumber
Inisialisasi objek.
Stefano Borini
@Stefano Borini: Bagaimana? Dalam sistem berorientasi objek, satu-satunya hal yang dapat Anda lakukan adalah metode panggilan. Atau untuk melihatnya dari sudut yang berlawanan: apa pun dilakukan dengan memanggil metode. Dan "apa pun" jelas termasuk inisialisasi objek. Jadi, jika, untuk menginisialisasi objek, Anda perlu memanggil metode, tetapi konstruktor tidak dapat memanggil metode, lalu bagaimana konstruktor dapat menginisialisasi objek?
Jörg W Mittag
sama sekali tidak benar bahwa satu-satunya hal yang dapat Anda lakukan adalah memanggil metode. Anda dapat menginisialisasi keadaan tanpa panggilan apa pun, langsung ke internal objek Anda ... Inti konstruktor adalah membuat objek dalam keadaan konsisten. Jika Anda memanggil metode lain, ini mungkin memiliki masalah menangani objek dalam keadaan parsial, kecuali jika mereka metode yang dibuat khusus untuk dipanggil dari konstruktor (biasanya sebagai metode pembantu)
Stefano Borini
@Stefano Borini: "Anda dapat menginisialisasi keadaan tanpa panggilan apa pun, langsung ke internal objek Anda." Sayangnya, ketika itu melibatkan suatu metode, apa yang Anda lakukan? Salin dan lewati kode?
S.Lott
1
@ S.Lott: tidak, saya menyebutnya, tapi saya mencoba untuk membuatnya fungsi modul alih-alih metode objek, dan minta itu memberikan data pengembalian yang bisa saya masukkan ke status objek di konstruktor. Jika saya benar-benar harus memiliki metode objek, saya akan membuatnya pribadi dan mengklarifikasi bahwa itu untuk inisialisasi, seperti memberinya nama yang tepat. Namun, saya tidak akan pernah memanggil metode publik untuk mengatur status objek dari konstruktor.
Stefano Borini
0

Dalam teori OOP, seharusnya tidak masalah, tetapi dalam praktiknya, setiap bahasa pemrograman OOP menangani konstruktor berbeda . Saya tidak sering menggunakan metode statis.

Dalam C ++ & Delphi, Jika saya harus memberikan nilai awal ke beberapa properti ("anggota lapangan"), dan kode ini sangat diperluas, saya menambahkan beberapa metode sekunder sebagai ekstensi konstruktor.

Dan jangan memanggil metode lain yang melakukan hal-hal yang lebih kompleks.

Sedangkan untuk properti "getter" & "setter" metode, saya biasanya menggunakan variabel pribadi / dilindungi untuk menyimpan negara mereka, ditambah metode "getter" & "setter".

Dalam konstruktor saya menetapkan nilai "default" ke bidang status properti, TANPA memanggil "accessors".

umlcat
sumber