Cara menulis konstruktor yang mungkin gagal untuk instantiate objek dengan benar

11

Terkadang Anda perlu menulis konstruktor yang bisa gagal. Sebagai contoh, katakan saya ingin instantiate objek dengan path file, sesuatu seperti

obj = new Object("/home/user/foo_file")

Selama jalur menunjuk ke file yang sesuai, semuanya baik-baik saja. Tetapi jika string bukan jalur yang valid, hal-hal yang harus dipatahkan. Tapi bagaimana caranya?

Anda bisa:

  1. lempar pengecualian
  2. return null object (jika bahasa pemrograman Anda memungkinkan konstruktor untuk mengembalikan nilai)
  3. mengembalikan objek yang valid tetapi dengan bendera yang menunjukkan bahwa jalurnya tidak diset dengan benar (ugh)
  4. orang lain?

Saya berasumsi bahwa "praktik terbaik" dari berbagai bahasa pemrograman akan menerapkan ini secara berbeda. Misalnya saya pikir ObjC lebih suka (2). Tetapi (2) tidak mungkin diimplementasikan dalam C ++ di mana konstruktor harus membatalkan sebagai tipe kembali. Dalam hal ini saya menganggap bahwa (1) digunakan.

Dalam bahasa pemrograman pilihan Anda, dapatkah Anda menunjukkan bagaimana Anda akan menangani masalah ini dan menjelaskan mengapa?

garasiàtrois
sumber
1
Pengecualian di Jawa adalah salah satu cara menangani hal ini.
Mahmoud Hossam
Konstruktor C ++ tidak kembali void- mereka mengembalikan objek.
gablin
6
@ Gablin: Sebenarnya, itu juga tidak sepenuhnya benar. newpanggilan operator newuntuk mengalokasikan memori, maka konstruktor untuk mengisinya. Konstruktor tidak mengembalikan apa pun, dan newmengembalikan pointer yang didapatnya operator new. Apakah "tidak mengembalikan apa-apa" menyiratkan "pengembalian void" adalah untuk diperebutkan.
Jon Purdy
@ Jon Purdy: Hm, terdengar masuk akal. Panggilan yang bagus.
gablin

Jawaban:

8

Tidak pernah baik untuk mengandalkan konstruktor untuk melakukan pekerjaan kotor. Selain itu, juga tidak jelas bagi programmer lain apakah pekerjaan akan dilakukan dalam konstruktor kecuali ada dokumentasi eksplisit yang menyatakan demikian (dan bahwa pengguna kelas telah membacanya atau diberi tahu demikian).

Misalnya (dalam C #):

public Sprite
{
    public Sprite(string filename)
    {
    }
}

Apa yang terjadi jika pengguna tidak ingin langsung memuat file? Bagaimana jika mereka ingin melakukan caching file berdasarkan permintaan? Mereka tidak bisa. Anda mungkin berpikir untuk meletakkan bool loadFileargumen di konstruktor, tetapi ini membuat ini berantakan karena Anda sekarang masih memerlukan Load()metode untuk memuat file.

Dengan skenario saat ini, akan lebih fleksibel dan lebih jelas bagi pengguna kelas untuk melakukan ini:

Sprite sprite = new Sprite();
sprite.Load("mario.png");

Atau sebagai alternatif (untuk sesuatu seperti sumber daya):

Sprite sprite = new Sprite();
sprite.Source = "mario.png";
sprite.Cache();

// On demand caching of file contents.
public void Cache()
{
     if (image == null)
     {
         try
         {
             image = new Image.FromFile(Source);
         }
         catch(...)
         {
         }
     }
}
Nick Bedford
sumber
dalam kasus Anda jika memuat (..) gagal, kami memiliki objek yang sama sekali tidak berguna. Saya tidak yakin bahwa saya ingin sprite tanpa sprite apa pun ... Tetapi pertanyaannya lebih mirip subjek holywar daripada pertanyaan sebenarnya :)
avtomaton
7

Di Jawa, Anda bisa menggunakan pengecualian atau menggunakan pola pabrik, yang memungkinkan Anda mengembalikan nol.

Di Scala, Anda bisa mengembalikan Opsi [Foo] dari metode pabrik. Itu akan bekerja di Jawa juga, tetapi akan lebih rumit.

Kim
sumber
Menggunakan Pengecualian atau pabrik dalam bahasa .NET juga.
Beth Whitezel
3

Lempar pengecualian.

Null perlu diperiksa jika Anda dapat mengembalikannya (dan tidak akan diperiksa)

Inilah yang mengecualikan pengecualian. Anda tahu itu bisa gagal. Penelepon harus menangani ini.

Tim Williscroft
sumber
3

Dalam C ++ , konstruktor digunakan untuk membuat / menginisialisasi anggota kelas.

Tidak ada jawaban yang tepat untuk pertanyaan ini. Tapi yang saya amati sejauh ini, adalah bahwa sebagian besar waktu adalah klien (atau siapa pun yang akan menggunakan API Anda) yang memilih bagaimana Anda harus menangani hal ini.

Kadang-kadang mereka mungkin meminta Anda untuk mengalokasikan semua sumber daya yang mungkin dibutuhkan objek pada konstruktor dan melemparkan pengecualian jika ada yang gagal (membatalkan pembuatan objek), atau tidak melakukan itu pada konstruktor, dan memastikan bahwa kreasi akan selalu berhasil , meninggalkan tugas-tugas ini untuk beberapa fungsi anggota untuk dilakukan.

Jika terserah Anda untuk memilih perilaku, pengecualian adalah cara C ++ default untuk menangani kesalahan dan Anda harus menggunakannya saat Anda bisa.

karlphillip
sumber
1

Anda juga bisa instantiate objek tanpa parameter, atau hanya dengan parameter yang pasti tidak pernah gagal, dan kemudian Anda menggunakan fungsi inisialisasi atau metode dari mana Anda dapat dengan aman melemparkan pengecualian atau melakukan apa pun yang Anda inginkan.

Gablin
sumber
6
Saya pikir mengembalikan objek yang tidak dapat digunakan yang memerlukan inisialisasi lebih lanjut adalah pilihan terburuk. Sekarang Anda memiliki fragmen multi-baris yang harus disalin setiap kali kelas digunakan, atau diperhitungkan dalam metode baru. Anda mungkin juga meminta konstruktor melakukan semua pekerjaan dan melemparkan pengecualian jika objek tidak dapat dibangun.
kevin cline
2
@ kevin: Tergantung pada objek. Kegagalan mungkin disebabkan oleh sumber daya eksternal sehingga entitas mungkin ingin mendaftarkan niat (misalnya nama file) tetapi memungkinkan upaya berulang untuk sepenuhnya diinisialisasi. Untuk objek nilai saya mungkin cenderung melaporkan kesalahan yang dapat menangkap kesalahan mendasar, jadi mungkin melempar pengecualian (terbungkus?).
Dave
-4

Saya lebih suka untuk tidak menginisialisasi kelas atau daftar apa pun di konstruktor. Inisialisasi kelas atau daftar kapan pun Anda butuhkan. Misalnya.

public class Test
{
    List<Employee> test = null;
}

Jangan lakukan ini

public class Test
{
    List<Employee> test = null;

    public Test()
    {
        test = new List<Employee>();
    }
}

Alih-alih menginisialisasi ketika diminta misalnya.

public class Test
{
    List<Employee> emp = null;

    public Test()
    { 
    }

    public void SomeFunction()
    {
         emp = new List<Employee>();
    }
}
uji
sumber
1
Ini saran yang buruk. Anda seharusnya tidak membiarkan objek berada dalam keadaan tidak valid. Untuk itulah konstruktor itu. Jika Anda membutuhkan logika yang kompleks, maka gunakan pola pabrik.
AlexFoxGill
1
Konstruktor ada di sana untuk membuat invarian kelas, kode sampel tidak membantu untuk menyatakan itu sama sekali.
Niall