it-swarm-id.com

Kapan tepat bagi konstruktor untuk melemparkan pengecualian?

Kapan tepat bagi konstruktor untuk melemparkan pengecualian? (Atau dalam kasus Objective C: kapan tepat bagi init'er untuk mengembalikan nol?)

Tampak bagi saya bahwa konstruktor harus gagal - dan karenanya menolak untuk membuat objek - jika objek tidak lengkap. Yaitu, konstruktor harus memiliki kontrak dengan peneleponnya untuk menyediakan objek fungsional dan kerja yang metode apa yang dapat dipanggil secara bermakna? Apakah itu masuk akal?

197
Mark R Lindsey

Pekerjaan konstruktor adalah untuk membawa objek ke keadaan yang dapat digunakan. Pada dasarnya ada dua aliran pemikiran tentang ini.

Satu kelompok lebih menyukai konstruksi dua tahap. Konstruktor hanya membawa objek ke keadaan tidur di mana ia menolak untuk melakukan pekerjaan apa pun. Ada fungsi tambahan yang melakukan inisialisasi sebenarnya.

Saya tidak pernah mengerti alasan di balik pendekatan ini. Saya tegas dalam kelompok yang mendukung konstruksi satu tahap, di mana objek sepenuhnya diinisialisasi dan dapat digunakan setelah konstruksi.

Konstruktor satu tahap harus dilempar jika gagal menginisialisasi objek secara penuh. Jika objek tidak dapat diinisialisasi, itu tidak boleh dibiarkan ada, jadi konstruktor harus membuang.

261
Sebastian Redl

kata Eric Lippert ada 4 jenis pengecualian.

  • Pengecualian fatal bukan kesalahan Anda, Anda tidak dapat mencegahnya, dan Anda tidak dapat membersihkannya dengan bijaksana.
  • Pengecualian berkepala adalah kesalahan Anda sendiri, Anda bisa mencegahnya dan karena itu bug dalam kode Anda.
  • Pengecualian yang menjengkelkan adalah hasil dari keputusan desain yang tidak menguntungkan. Pengecualian yang menjengkelkan dilemparkan dalam keadaan yang sama sekali tidak luar biasa, dan karenanya harus ditangkap dan ditangani sepanjang waktu.
  • Dan akhirnya, pengecualian eksogen tampak seperti pengecualian menjengkelkan, kecuali bahwa itu bukan hasil dari pilihan desain yang disayangkan. Sebaliknya, itu adalah hasil dari realitas eksternal yang tidak rapi yang menimpa logika program Anda yang indah dan tajam.

Konstruktor Anda seharusnya tidak pernah melemparkan pengecualian fatal sendiri, tetapi kode yang dijalankannya dapat menyebabkan pengecualian fatal. Sesuatu seperti "kehabisan memori" bukanlah sesuatu yang dapat Anda kontrol, tetapi jika itu terjadi di konstruktor, hei, itu terjadi.

Pengecualian bertulang tidak boleh terjadi di salah satu kode Anda, jadi itu benar.

Pengecualian yang menjengkelkan (contohnya adalah Int32.Parse()) tidak boleh dibuang oleh konstruktor, karena mereka tidak memiliki keadaan yang tidak biasa.

Akhirnya, pengecualian eksogen harus dihindari, tetapi jika Anda melakukan sesuatu di konstruktor Anda yang tergantung pada keadaan eksternal (seperti jaringan atau sistem file), akan lebih tepat untuk melemparkan pengecualian.

Tautan referensi: https://blogs.msdn.Microsoft.com/ericlippert/2008/09/10/vexing-exceptions/

56
Jacob Krall

Ada mumnya tidak akan diperoleh dengan menceraikan inisialisasi objek dari konstruksi. RAII benar, panggilan yang berhasil ke konstruktor harus menghasilkan objek langsung yang diinisialisasi penuh atau gagal, dan SEMUA kegagalan pada setiap titik dalam kode apa pun jalan harus selalu melempar pengecualian. Anda tidak mendapatkan apa-apa dengan menggunakan metode init () terpisah kecuali kompleksitas tambahan pada tingkat tertentu. Kontrak ctor harus berupa mengembalikan objek fungsional yang valid atau membersihkan setelah dirinya sendiri dan melempar.

Pertimbangkan, jika Anda menerapkan metode init yang terpisah, Anda masih harus memanggilnya. Itu masih akan memiliki potensi untuk melemparkan pengecualian, mereka masih harus ditangani dan mereka hampir selalu harus dipanggil segera setelah konstruktor, kecuali sekarang Anda memiliki 4 status objek yang mungkin alih-alih 2 (IE, dibangun, diinisialisasi, tidak diinisialisasi, dan gagal vs hanya valid dan tidak ada).

Dalam kasus apa pun saya telah menemukan dalam 25 tahun OO kasus pengembangan di mana sepertinya metode init terpisah akan 'memecahkan beberapa masalah' adalah kekurangan desain. Jika Anda tidak memerlukan objek SEKARANG maka Anda seharusnya tidak membangunnya sekarang, dan jika Anda memang membutuhkannya sekarang maka Anda membutuhkannya diinisialisasi. KISS harus selalu diikuti prinsip, bersama dengan konsep sederhana bahwa perilakunya, negara, dan API dari antarmuka apa pun harus mencerminkan APA yang dilakukan objek, bukan BAGAIMANA melakukannya, kode klien bahkan tidak perlu menyadari bahwa objek memiliki jenis keadaan internal apa pun yang memerlukan inisialisasi, sehingga pola init setelahnya melanggar prinsip ini.

30
Alhazred

Karena semua masalah yang disebabkan oleh sebagian kelas yang diciptakan, saya tidak akan pernah mengatakannya.

Jika Anda perlu memvalidasi sesuatu selama konstruksi, buat konstruktor sebagai pribadi dan tentukan metode pabrik statis publik. Metode ini dapat membuang jika ada sesuatu yang tidak valid. Tetapi jika semuanya memeriksa, itu memanggil konstruktor, yang dijamin tidak akan membuang.

6
Michael L Perry

Konstruktor harus mengeluarkan pengecualian ketika tidak dapat menyelesaikan konstruksi objek tersebut.

Misalnya, jika konstruktor seharusnya mengalokasikan ram 1024 KB, dan gagal melakukannya, ia harus melemparkan pengecualian, dengan cara ini penelepon konstruktor tahu bahwa objek tidak siap untuk digunakan dan ada kesalahan suatu tempat yang perlu diperbaiki.

Objek yang setengah-diinisialisasi dan setengah mati hanya menyebabkan masalah dan masalah, karena benar-benar tidak ada cara bagi penelepon untuk mengetahui. Saya lebih suka konstruktor saya melemparkan kesalahan ketika ada masalah, daripada harus bergantung pada pemrograman untuk menjalankan panggilan ke fungsi isOK () yang mengembalikan benar atau salah.

5
Denice

Itu selalu sangat cerdik, terutama jika Anda mengalokasikan sumber daya di dalam konstruktor; tergantung pada bahasa Anda, destruktor tidak akan dipanggil, jadi Anda perlu membersihkan secara manual. Itu tergantung pada bagaimana ketika masa hidup objek dimulai dalam bahasa Anda.

Satu-satunya saat saya benar-benar melakukannya adalah ketika ada masalah keamanan di suatu tempat yang berarti objek tidak boleh, bukannya tidak bisa, dibuat.

4
blowdart

Masuk akal jika konstruktor melempar pengecualian selama itu membersihkan dirinya sendiri dengan benar. Jika Anda mengikuti RAII paradigma (Akuisisi Sumber Daya Adalah Inisialisasi) maka itu adalah cukup umum bagi seorang konstruktor untuk melakukan pekerjaan yang bermakna; konstruktor yang ditulis dengan baik pada gilirannya akan membersihkan sendiri jika tidak dapat sepenuhnya diinisialisasi.

4
Matt Dillard

Sejauh yang saya tahu, tidak ada yang menyajikan solusi yang cukup jelas yang mewujudkan yang terbaik dari konstruksi satu tahap dan dua tahap.

note: Jawaban ini mengasumsikan C #, tetapi prinsipnya dapat diterapkan dalam sebagian besar bahasa.

Pertama, manfaat keduanya:

Satu panggung

Konstruksi satu tahap menguntungkan kita dengan mencegah benda-benda berada dalam keadaan tidak valid, sehingga mencegah segala macam manajemen negara yang salah dan semua bug yang menyertainya. Namun, itu membuat sebagian dari kita merasa aneh karena kita tidak ingin konstruktor kita membuat pengecualian, dan kadang-kadang itulah yang perlu kita lakukan ketika argumen inisialisasi tidak valid.

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    public Person(string name, DateTime dateOfBirth)
    {
        if (string.IsNullOrWhitespace(name))
        {
            throw new ArgumentException(nameof(name));
        }

        if (dateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(dateOfBirth));
        }

        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }
}

Dua Tahap melalui metode validasi

Konstruksi dua tahap menguntungkan kami dengan memungkinkan validasi kami dieksekusi di luar konstruktor, dan karena itu mencegah perlunya melemparkan pengecualian dalam konstruktor. Namun, ia meninggalkan kita dengan instance "tidak valid", yang berarti ada keadaan kita harus melacak dan mengelola instance, atau kita membuangnya segera setelah alokasi tumpukan. Itu menimbulkan pertanyaan: Mengapa kita melakukan alokasi tumpukan, dan dengan demikian pengumpulan memori, pada objek yang bahkan tidak kita gunakan?

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    public Person(string name, DateTime dateOfBirth)
    {
        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }

    public void Validate()
    {
        if (string.IsNullOrWhitespace(Name))
        {
            throw new ArgumentException(nameof(Name));
        }

        if (DateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(DateOfBirth));
        }
    }
}

Satu-Tahap melalui konstruktor pribadi

Jadi bagaimana kita bisa menjaga pengecualian dari konstruktor kita, dan mencegah diri kita melakukan alokasi tumpukan pada objek yang akan segera dibuang? Ini cukup mendasar: kami menjadikan konstruktor sebagai pribadi dan membuat instance melalui metode statis yang ditunjuk untuk melakukan instantiasi, dan oleh karena itu tumpukan-alokasi, hanya after validasi.

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    private Person(string name, DateTime dateOfBirth)
    {
        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }

    public static Person Create(
        string name,
        DateTime dateOfBirth)
    {
        if (string.IsNullOrWhitespace(Name))
        {
            throw new ArgumentException(nameof(name));
        }

        if (dateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(DateOfBirth));
        }

        return new Person(name, dateOfBirth);
    }
}

Async Single-Stage melalui konstruktor pribadi

Selain manfaat validasi dan alokasi heap-alokasi yang disebutkan di atas, metodologi sebelumnya memberi kita keuntungan lain: dukungan async. Ini sangat berguna ketika berhadapan dengan autentikasi multi-tahap, seperti ketika Anda perlu mengambil token pembawa sebelum menggunakan API Anda. Dengan cara ini, Anda tidak berakhir dengan klien API "keluar" yang tidak valid, dan sebagai gantinya Anda dapat dengan mudah membuat kembali klien API jika Anda menerima kesalahan otorisasi saat mencoba melakukan permintaan.

public class RestApiClient
{
    public RestApiClient(HttpClient httpClient)
    {
        this.httpClient = new httpClient;
    }

    public async Task<RestApiClient> Create(string username, string password)
    {
        if (username == null)
        {
            throw new ArgumentNullException(nameof(username));
        }

        if (password == null)
        {
            throw new ArgumentNullException(nameof(password));
        }

        var basicAuthBytes = Encoding.ASCII.GetBytes($"{username}:{password}");
        var basicAuthValue = Convert.ToBase64String(basicAuthBytes);

        var authenticationHttpClient = new HttpClient
        {
            BaseUri = new Uri("https://auth.example.io"),
            DefaultRequestHeaders = {
                Authentication = new AuthenticationHeaderValue("Basic", basicAuthValue)
            }
        };

        using (authenticationHttpClient)
        {
            var response = await httpClient.GetAsync("login");
            var content = response.Content.ReadAsStringAsync();
            var authToken = content;
            var restApiHttpClient = new HttpClient
            {
                BaseUri = new Uri("https://api.example.io"), // notice this differs from the auth uri
                DefaultRequestHeaders = {
                    Authentication = new AuthenticationHeaderValue("Bearer", authToken)
                }
            };

            return new RestApiClient(restApiHttpClient);
        }
    }
}

Kelemahan dari metode ini sedikit, menurut pengalaman saya.

Secara umum, menggunakan metodologi ini berarti bahwa Anda tidak dapat lagi menggunakan kelas sebagai DTO karena deserializing ke objek tanpa konstruktor default publik sulit, paling-paling. Namun, jika Anda menggunakan objek sebagai DTO, Anda seharusnya tidak benar-benar memvalidasi objek itu sendiri, tetapi sebaliknya membatalkan nilai pada objek saat Anda mencoba menggunakannya, karena secara teknis nilai tidak "tidak valid" terkait ke DTO.

Ini juga berarti bahwa Anda akan akhirnya membuat metode atau kelas pabrik ketika Anda harus mengizinkan wadah IOC untuk membuat objek, karena jika tidak, wadah tersebut tidak akan tahu cara membuat instance objek. Namun, dalam banyak kasus metode pabrik akhirnya menjadi salah satu metode Create sendiri.

4
cwharris

Perhatikan bahwa jika Anda melempar pengecualian pada penginisialisasi, Anda akan berakhir bocor jika ada kode yang menggunakan [[[MyObj alloc] init] autorelease] pola, karena pengecualian akan melewati autorelease.

Lihat pertanyaan ini:

Bagaimana Anda mencegah kebocoran saat menaikkan pengecualian di init?

3
stevex

Jika Anda menulis Kontrol-UI (ASPX, WinForms, WPF, ...) Anda harus menghindari melemparkan pengecualian pada konstruktor karena desainer (Visual Studio) tidak bisa menanganinya ketika itu menciptakan kontrol Anda. Ketahui siklus kontrol-hidup Anda (acara kontrol) dan gunakan inisialisasi malas jika memungkinkan.

3
Nick

Lihat C++ FAQ bagian 17.2 dan 17.4 .

Secara umum, saya telah menemukan bahwa kode yang lebih mudah untuk port dan memelihara hasil jika konstruktor ditulis sehingga mereka tidak gagal, dan kode yang dapat gagal ditempatkan dalam metode terpisah yang mengembalikan kode kesalahan dan meninggalkan objek dalam keadaan lembam .

3
moonshadow

Melempar pengecualian jika Anda tidak dapat menginisialisasi objek di konstruktor, salah satu contohnya adalah argumen ilegal.

Sebagai aturan umum, sebuah pengecualian harus selalu dilemparkan sesegera mungkin, karena hal itu membuat proses debug lebih mudah ketika sumber masalahnya lebih dekat ke metode pensinyalan sesuatu yang salah.

2
user14070

Anda benar-benar harus membuang pengecualian dari konstruktor jika Anda tidak dapat membuat objek yang valid. Ini memungkinkan Anda untuk memberikan invarian yang tepat di kelas Anda.

Dalam latihan, Anda mungkin harus sangat berhati-hati. Ingatlah bahwa di C++, destructor tidak akan dipanggil, jadi jika Anda melempar setelah mengalokasikan sumber daya Anda, Anda harus sangat berhati-hati untuk menanganinya dengan benar!

Halaman ini memiliki diskusi menyeluruh tentang situasi di C++.

2
Luke Halliwell

Saya tidak bisa membahas praktik terbaik di Objective-C, tetapi dalam C++ tidak masalah bagi konstruktor untuk melempar pengecualian. Terutama karena tidak ada cara lain untuk memastikan bahwa kondisi luar biasa yang dihadapi pada konstruksi dilaporkan tanpa menggunakan metode isOK ().

Fitur blok fungsi coba dirancang khusus untuk mendukung kegagalan inisialisasi anggota konstruktor (meskipun dapat juga digunakan untuk fungsi biasa). Ini satu-satunya cara untuk memodifikasi atau memperkaya informasi pengecualian yang akan dibuang. Tetapi karena tujuan desain aslinya (digunakan dalam konstruktor) itu tidak mengizinkan pengecualian untuk ditelan oleh tangkapan kosong () klausa.

1
mlbrock

Saya tidak yakin bahwa jawaban apa pun dapat sepenuhnya agnostik bahasa. Beberapa bahasa menangani pengecualian dan manajemen memori secara berbeda.

Saya telah bekerja sebelumnya di bawah standar pengkodean yang membutuhkan pengecualian yang tidak pernah digunakan dan hanya kode kesalahan pada inisialisasi, karena pengembang telah dibakar oleh bahasa yang menangani pengecualian dengan buruk. Bahasa tanpa pengumpulan sampah akan menangani tumpukan dan tumpukan yang sangat berbeda, yang mungkin penting untuk objek non RAII. Penting bahwa tim memutuskan untuk konsisten sehingga mereka tahu secara default jika mereka perlu memanggil inisialisasi setelah konstruktor. Semua metode (termasuk konstruktor) juga harus didokumentasikan dengan baik tentang pengecualian apa yang dapat mereka berikan, sehingga penelepon tahu cara menanganinya.

Saya umumnya mendukung konstruksi satu tahap, karena mudah untuk lupa menginisialisasi objek, tetapi ada banyak pengecualian untuk itu.

  • Dukungan bahasa Anda untuk pengecualian tidak terlalu baik.
  • Anda memiliki alasan desain yang mendesak untuk tetap menggunakan new dan delete
  • Inisialisasi Anda intensif prosesor dan harus berjalan async ke utas yang membuat objek.
  • Anda sedang membuat DLL yang mungkin melempar pengecualian di luar antarmuka ke aplikasi menggunakan bahasa yang berbeda. Dalam hal ini mungkin bukan masalah tidak membuang pengecualian, tetapi pastikan mereka ditangkap sebelum antarmuka publik. (Anda dapat menangkap pengecualian C++ di C #, tetapi ada rintangan untuk dilewati.)
  • Konstruktor statis (C #)
1
Denise Skidmore

Ya, jika konstruktor gagal membangun salah satu bagian internalnya, ia dapat - dengan pilihan - tanggung jawabnya untuk melempar (dan dalam bahasa tertentu menyatakan) --- pengecualian eksplisit , sebagaimana tercantum dalam dokumentasi konstruktor .

Ini bukan satu-satunya pilihan: Ini bisa menyelesaikan konstruktor dan membangun objek, tetapi dengan metode 'isCoherent ()' mengembalikan false, agar dapat memberi sinyal keadaan tidak koheren (yang mungkin lebih disukai dalam kasus tertentu, agar untuk menghindari gangguan brutal terhadap alur kerja eksekusi karena pengecualian)
Peringatan: seperti yang dikatakan oleh EricSchaefer dalam komentarnya, yang dapat membawa beberapa kompleksitas pada pengujian unit (lemparan dapat meningkatkan kompleksitas siklomatik dari fungsi karena kondisi yang memicu itu)

Jika gagal karena penelepon (seperti argumen nol yang disediakan oleh penelepon, di mana konstruktor yang dipanggil mengharapkan argumen non-nol), konstruktor akan tetap melemparkan pengecualian runtime yang tidak dicentang.

1
VonC

Melempar pengecualian selama konstruksi adalah cara yang bagus untuk membuat kode Anda menjadi lebih kompleks. Hal-hal yang kelihatannya sederhana tiba-tiba menjadi sulit. Sebagai contoh, katakanlah Anda memiliki setumpuk. Bagaimana Anda memunculkan tumpukan dan mengembalikan nilai teratas? Nah, jika objek dalam stack dapat melempar konstruktor mereka (membangun sementara untuk kembali ke pemanggil), Anda tidak dapat menjamin bahwa Anda tidak akan kehilangan data (mengurangi penumpukan pointer, membangun kembali nilai menggunakan copy constructor nilai dalam tumpukan, yang melempar, dan sekarang memiliki tumpukan yang baru saja kehilangan item)! Inilah sebabnya mengapa std :: stack :: pop tidak mengembalikan nilai, dan Anda harus memanggil std :: stack :: top.

Masalah ini dijelaskan dengan baik di sini , periksa Item 10, menulis kode pengecualian-aman.

1
Don Neufeld

Kontrak yang biasa dalam OO adalah metode objek benar-benar berfungsi.

Jadi sebagai petugas patroli, untuk tidak pernah mengembalikan objek zombie membentuk konstruktor/init.

Zombie tidak berfungsi dan mungkin kehilangan komponen internal. Hanya pengecualian null-pointer yang menunggu untuk terjadi.

Saya pertama kali membuat zombie di Objective C, bertahun-tahun yang lalu.

Seperti semua aturan praktis, ada "pengecualian".

Sangat mungkin bahwa antarmuka spesifik dapat memiliki kontrak yang mengatakan bahwa ada metode "inisialisasi" yang diizinkan untuk memasukkan pengecualian. Bahwa objek yang memasang antarmuka ini mungkin tidak merespons dengan benar untuk panggilan apa pun kecuali setel properti hingga inisialisasi telah dipanggil. Saya menggunakan ini untuk driver perangkat dalam sistem operasi OO selama proses boot, dan itu bisa diterapkan.

Secara umum, Anda tidak ingin benda zombie. Dalam bahasa-bahasa seperti Smalltalk dengan menjadi hal-hal menjadi sedikit bersuara-buzzy, tetapi terlalu sering menggunakan menjadi juga merupakan gaya yang buruk. Jadi memungkinkan suatu objek berubah menjadi objek lain di tempat, jadi tidak perlu lagi envelope-wrapper (Advanced C++) atau pola strategi (GOF).

1
Tim Williscroft

Pertanyaan OP memiliki tag "bahasa-agnostik" ... pertanyaan ini tidak dapat dijawab dengan aman dengan cara yang sama untuk semua bahasa/situasi.

Hirarki kelas C # example berikut dilemparkan ke konstruktor kelas B, melewatkan panggilan langsung ke kelas A IDisposeable.Dispose saat keluar dari using utama, melompati pembuangan sumber daya kelas A secara eksplisit.

Jika, misalnya, kelas A telah membuat Socket pada konstruksi, terhubung ke sumber daya jaringan, kemungkinan akan tetap seperti itu setelah blok using (anomali yang relatif tersembunyi).

class A : IDisposable
{
    public A()
    {
        Console.WriteLine("Initialize A's resources.");
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose A's resources.");
    }
}

class B : A, IDisposable
{
    public B()
    {
        Console.WriteLine("Initialize B's resources.");
        throw new Exception("B construction failure: B can cleanup anything before throwing so this is not a worry.");
    }

    public new void Dispose()
    {
        Console.WriteLine("Dispose B's resources.");
        base.Dispose();
    }
}
class C : B, IDisposable
{
    public C()
    {
        Console.WriteLine("Initialize C's resources. Not called because B throws during construction. C's resources not a worry.");
    }

    public new void Dispose()
    {
        Console.WriteLine("Dispose C's resources.");
        base.Dispose();
    }
}


class Program
{
    static void Main(string[] args)
    {
        try
        {
            using (C c = new C())
            {
            }
        }
        catch
        {           
        }

        // Resource's allocated by c's "A" not explicitly disposed.
    }
}
1
Ashley

Saya baru belajar Objective C, jadi saya tidak bisa benar-benar berbicara dari pengalaman, tetapi saya membaca tentang ini di dokumen Apple.

http://developer.Apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/chapter_3_section_6.html

Tidak hanya akan memberi tahu Anda cara menangani pertanyaan yang Anda ajukan, tetapi juga menjelaskan hal itu dengan baik.

0
Scott Swezey

Saran terbaik yang pernah saya lihat tentang pengecualian adalah untuk melemparkan pengecualian jika, dan hanya jika, alternatifnya adalah kegagalan untuk memenuhi kondisi pos atau untuk mempertahankan invarian.

Saran itu menggantikan keputusan subjektif yang tidak jelas (apakah itu ide bagus) dengan pertanyaan teknis yang tepat berdasarkan keputusan desain (kondisi invarian dan postingan) yang seharusnya sudah Anda buat.

Konstruktor hanyalah kasus khusus, tetapi tidak khusus, untuk saran itu. Jadi pertanyaannya menjadi, invarian apa yang harus dimiliki kelas? Pendukung metode inisialisasi terpisah, yang dipanggil setelah konstruksi, menyarankan bahwa kelas memiliki dua atau lebih mode operasi, dengan mode belum siap setelah konstruksi dan setidaknya satu - siap mode, dimasukkan setelah inisialisasi. Itu adalah komplikasi tambahan, tetapi dapat diterima jika kelas memiliki beberapa mode operasi. Sulit untuk melihat bagaimana komplikasi itu bermanfaat jika kelas tidak akan memiliki mode operasi.

Perhatikan bahwa mendorong pengaturan ke metode inisialisasi terpisah tidak memungkinkan Anda untuk menghindari pengecualian. Pengecualian yang mungkin dilemparkan oleh konstruktor Anda akan dilempar dengan metode inisialisasi. Semua metode yang berguna dari kelas Anda harus membuang pengecualian jika mereka dipanggil untuk objek yang tidak diinisialisasi.

Perhatikan juga bahwa menghindari kemungkinan pengecualian yang dilemparkan oleh konstruktor Anda merepotkan, dan dalam banyak kasus tidak mungkin di banyak perpustakaan standar. Ini karena para perancang perpustakaan percaya bahwa melemparkan pengecualian dari konstruktor adalah ide yang bagus. Secara khusus, setiap operasi yang mencoba untuk memperoleh sumber daya yang tidak dapat dibagikan atau terbatas (seperti mengalokasikan memori) dapat gagal, dan bahwa kegagalan biasanya ditunjukkan dalam bahasa OO bahasa dan perpustakaan dengan melemparkan pengecualian.

0
Raedwald

Berbicara secara ketat dari sudut pandang Java, setiap kali Anda menginisialisasi konstruktor dengan nilai ilegal, ia harus mengeluarkan pengecualian. Dengan begitu, itu tidak dapat dibangun dalam keadaan buruk.

0
scubabbl

Bagi saya itu adalah keputusan desain yang agak filosofis.

Sangat menyenangkan memiliki instance yang valid selama masih ada, mulai dari waktu ctor hingga seterusnya. Untuk banyak kasus nontrivial, ini mungkin memerlukan pengecualian lemparan dari ctor jika alokasi memori/sumber daya tidak dapat dibuat.

Beberapa pendekatan lain adalah metode init () yang dilengkapi dengan beberapa masalah sendiri. Salah satunya adalah memastikan init () benar-benar dipanggil.

Varian menggunakan pendekatan lazy untuk secara otomatis memanggil init () saat pertama kali accessor/mutator dipanggil, tetapi itu membutuhkan penelepon potensial harus khawatir tentang objek yang valid. (Berbeda dengan "itu ada, maka itu filsafat yang sah").

Saya telah melihat berbagai pola desain yang diusulkan untuk menangani masalah ini juga. Seperti bisa membuat objek awal melalui ctor, tetapi harus memanggil init () untuk mendapatkan objek terkandung yang diinisialisasi dengan aksesor/mutator.

Setiap pendekatan memiliki pasang surut; Saya telah menggunakan semua ini dengan sukses. Jika Anda tidak membuat objek yang siap digunakan dari instan mereka dibuat, maka saya sarankan dosis atau pengecualian yang berat untuk memastikan pengguna tidak berinteraksi sebelum init ().

Addendum

Saya menulis dari perspektif programmer C++. Saya juga berasumsi bahwa Anda benar menggunakan idiom RAII untuk menangani sumber daya yang dirilis ketika pengecualian dilemparkan.

0
nsanders

Menggunakan pabrik atau metode pabrik untuk semua pembuatan objek, Anda dapat menghindari objek yang tidak valid tanpa membuang pengecualian dari konstruktor. Metode pembuatan harus mengembalikan objek yang diminta jika dapat membuatnya, atau nol jika tidak. Anda kehilangan sedikit fleksibilitas dalam menangani kesalahan konstruksi di pengguna kelas, karena mengembalikan nol tidak memberi tahu Anda apa yang salah dalam pembuatan objek. Tetapi juga menghindari menambahkan kompleksitas dari beberapa penangan pengecualian setiap kali Anda meminta suatu objek, dan risiko menangkap pengecualian yang tidak seharusnya Anda tangani.

0
Tegan Mulholland