Pada titik apakah kelas yang tidak dapat berubah menjadi beban?

16

Ketika merancang kelas untuk menampung model data Anda, saya sudah membacanya dapat berguna untuk membuat objek yang tidak dapat diubah tetapi pada titik apa beban daftar parameter konstruktor dan salinan yang dalam menjadi terlalu banyak dan Anda harus meninggalkan batasan yang tidak dapat diubah?

Sebagai contoh, ini adalah kelas yang tidak dapat diubah untuk merepresentasikan sesuatu yang bernama (Saya menggunakan sintaksis C # tetapi prinsipnya berlaku untuk semua bahasa OO)

class NamedThing
{
    private string _name;    
    public NamedThing(string name)
    {
        _name = name;
    }    
    public NamedThing(NamedThing other)
    {
         this._name = other._name;
    }
    public string Name
    {
        get { return _name; }
    }
}

Hal-hal yang dinamai dapat dikonstruksikan, ditanyakan dan disalin ke hal-hal yang dinamai baru tetapi namanya tidak dapat diubah.

Ini semua baik tetapi apa yang terjadi ketika saya ingin menambahkan atribut lain? Saya harus menambahkan parameter ke konstruktor dan memperbarui konstruktor salin; yang tidak terlalu banyak bekerja tetapi masalahnya mulai, sejauh yang saya bisa lihat, ketika saya ingin membuat objek yang kompleks tidak dapat diubah.

Jika kelas berisi atribut may dan koleksi, berisi kelas kompleks lainnya, menurut saya daftar parameter konstruktor akan menjadi mimpi buruk.

Jadi pada titik apa kelas menjadi terlalu rumit untuk tidak dapat diubah?

Tony
sumber
Saya selalu berusaha membuat kelas dalam model saya tidak berubah. Jika Anda memiliki daftar parameter konstruktor yang besar dan panjang, maka mungkin kelas Anda terlalu besar dan dapat dibagi? Jika objek level bawah Anda juga tidak berubah dan mengikuti pola yang sama maka objek level tinggi Anda tidak akan menderita (terlalu banyak). Saya merasa jauh lebih sulit untuk mengubah kelas yang ada menjadi tidak berubah daripada membuat model data tidak berubah ketika saya mulai dari awal.
Tidak seorang pun
1
Anda dapat melihat pola Builder yang disarankan dalam pertanyaan ini: stackoverflow.com/questions/1304154/…
Ant
Sudahkah Anda melihat MemberwiseClone? Anda tidak perlu memperbarui pembuat salinan untuk setiap anggota baru.
kevin cline
3
@Tony Jika koleksi Anda dan semua yang ada di dalamnya juga tidak dapat diubah, Anda tidak perlu salinan yang dalam, salinan yang dangkal sudah cukup.
mjcopple
2
Selain itu, saya biasanya menggunakan bidang "set-sekali" di kelas-kelas di mana kelas harus "cukup" tidak berubah, tetapi tidak sepenuhnya. Saya menemukan ini memecahkan masalah konstruktor besar, tetapi memberikan sebagian besar manfaat dari kelas yang tidak dapat diubah. (yaitu, kode kelas internal Anda tidak perlu khawatir tentang perubahan nilai)
Earlz

Jawaban:

23

Kapan mereka menjadi beban? Sangat cepat (khususnya jika bahasa pilihan Anda tidak memberikan dukungan sintaksis yang cukup untuk kekekalan.)

Kekekalan dijual sebagai peluru perak untuk dilema multi-inti dan semua itu. Tetapi kekekalan dalam sebagian besar bahasa OO memaksa Anda untuk menambahkan artefak dan praktik buatan dalam model dan proses Anda. Untuk setiap kelas yang tidak dapat diubah kompleks Anda harus memiliki pembangun yang sama-sama kompleks (setidaknya secara internal). Tidak peduli bagaimana Anda mendesainnya, itu tetap memperkenalkan kopling yang kuat (jadi kami lebih baik memiliki alasan yang baik untuk memperkenalkannya.)

Hal ini belum tentu memungkinkan untuk memodelkan semuanya dalam kelas kecil non-kompleks. Jadi untuk kelas dan struktur besar, kami membuat partisi secara artifisial - bukan karena hal itu masuk akal dalam model domain kami, tetapi karena kami harus berurusan dengan instantiasi dan pembangunnya yang rumit dalam kode.

Lebih buruk lagi ketika orang mengambil gagasan tentang ketidakbergantungan terlalu jauh dalam bahasa tujuan umum seperti Java atau C #, membuat semuanya tidak berubah. Kemudian, sebagai hasilnya, Anda melihat orang-orang memaksa konstruksi s-ekspresi dalam bahasa yang tidak mendukung hal-hal seperti itu dengan mudah.

Rekayasa adalah tindakan pemodelan melalui kompromi dan pertukaran. Membuat semuanya tidak dapat diubah oleh dekrit karena seseorang membaca bahwa semuanya tidak dapat diubah dalam bahasa fungsional X atau Y (model pemrograman yang sama sekali berbeda), yang tidak dapat diterima. Itu bukan rekayasa yang bagus.

Hal-hal kecil, mungkin yang bersifat kesatuan, dapat dibuat tidak berubah. Hal-hal yang lebih kompleks dapat dibuat tidak berubah ketika itu masuk akal . Tapi ketetapan bukan peluru perak. Kemampuan untuk mengurangi bug, untuk meningkatkan skalabilitas dan kinerja, itu bukan satu-satunya fungsi kekekalan. Ini adalah fungsi dari praktik rekayasa yang tepat . Lagi pula, orang-orang telah menulis perangkat lunak yang bagus dan dapat diukur tanpa cacat.

Kekekalan menjadi beban yang sangat cepat (menambah kompleksitas yang tidak disengaja) jika dilakukan tanpa alasan, ketika dilakukan di luar apa yang masuk akal dalam konteks model domain.

Saya, untuk satu, mencoba menghindarinya (kecuali saya bekerja dalam bahasa pemrograman dengan dukungan sintaksis yang baik untuk itu.)

luis.espinal
sumber
6
Namun, pernahkah Anda memperhatikan bagaimana jawaban yang ditulis dengan baik, benar secara pragmatis yang ditulis dengan penjelasan tentang prinsip-prinsip rekayasa yang sederhana namun baik cenderung tidak mendapatkan suara sebanyak yang menggunakan mode pengkodean canggih? Ini adalah jawaban yang sangat bagus.
Huperniketes
3
Terima kasih :) Saya sudah memperhatikan tren rep, tapi tidak apa-apa. Fad fanboys churn code yang nanti bisa kita perbaiki dengan tarif per jam yang lebih baik, hahah :) jk ...
luis.espinal
4
Peluru perak? tidak. Layak sedikit kecanggungan di C # / Java (tidak terlalu buruk)? Benar. Juga, peran multicore dalam imutabilitas cukup kecil ... manfaat sebenarnya adalah kemudahan bernalar.
Mauricio Scheffer
@Mauricio - jika Anda bilang begitu (ketidakmampuan di Jawa tidak seburuk itu). Setelah bekerja di Jawa dari tahun 1998 hingga 2011, saya mohon berbeda, tidak sepele dikecualikan dalam basis kode sederhana. Namun, orang memiliki pengalaman berbeda, dan saya mengakui bahwa POV saya tidak bebas dari subjektivitas. Maaf, tidak bisa setuju di sana. Saya setuju, bagaimanapun, dengan kemudahan penalaran menjadi hal yang paling penting dalam hal ketidakberdayaan.
luis.espinal
13

Saya melalui fase bersikeras bahwa kelas tidak dapat diubah jika memungkinkan. Apakah pembangun untuk hampir semuanya, array tidak berubah, dll, dll. Saya menemukan jawaban untuk pertanyaan Anda sederhana: Pada titik apa kelas tidak berubah menjadi beban? Sangat cepat. Begitu Anda ingin membuat serial sesuatu, Anda harus dapat deserialize, yang berarti harus bisa diubah; segera setelah Anda ingin menggunakan ORM, sebagian besar dari mereka bersikeras bahwa properti bisa berubah. Dan seterusnya.

Saya akhirnya mengganti kebijakan itu dengan antarmuka yang tidak dapat diubah ke objek yang bisa berubah.

class NamedThing : INamedThing
{
    private string _name;    
    public NamedThing(string name)
    {
        _name = name;
    }    

    public NamedThing(NamedThing other)
    {
        this._name = other._name;
    }

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}

interface INamedThing
{
    string Name { get; }
}

Sekarang objek memiliki fleksibilitas tetapi Anda masih bisa memberi tahu kode panggilan bahwa itu tidak boleh mengedit properti ini.

pdr
sumber
8
Disamping Minor berdalih, saya setuju bahwa objek yang tidak dapat diubah dapat menjadi masalah di pantat dengan sangat cepat ketika pemrograman dalam bahasa imperatif, tapi saya tidak begitu yakin apakah antarmuka yang tidak dapat diubah benar-benar menyelesaikan masalah yang sama, atau masalah apa pun. Alasan utama untuk menggunakan objek yang tidak dapat diubah adalah agar Anda dapat melemparkannya ke mana saja kapan saja dan tidak perlu khawatir tentang orang lain yang merusak kondisi Anda. Jika objek yang mendasarinya bisa berubah maka Anda tidak memiliki jaminan itu, terutama jika alasan Anda menyimpannya bisa berubah adalah karena berbagai hal perlu untuk mengubahnya.
Aaronaught
1
@Aonaonaught - Poin menarik. Saya kira itu masalah psikologis lebih dari perlindungan yang sebenarnya. Namun, baris terakhir Anda memiliki premis yang salah. Alasan untuk menjaganya agar tetap bisa berubah adalah lebih karena berbagai hal perlu dipakai dan diisi melalui refleksi, bukan untuk bermutasi setelah dipakai.
pdr
1
@Aonaonaught: Cara yang sama IComparable<T>menjamin bahwa jika X.CompareTo(Y)>0dan Y.CompareTo(Z)>0, kemudian X.CompareTo(Z)>0. Antarmuka memiliki kontrak . Jika kontrak untuk IImmutableList<T>menentukan bahwa nilai semua item dan properti harus "ditetapkan" sebelum contoh apa pun diekspos ke dunia luar, maka semua implementasi yang sah akan melakukannya. Tidak ada yang mencegah IComparableimplementasi melanggar transitivitas, tetapi implementasi yang melakukannya tidak sah. Jika SortedDictionarykerusakan saat diberi tidak sah IComparable, ...
supercat
1
@Aaronaught: Mengapa saya harus percaya bahwa implementasi dari IReadOnlyList<T>akan berubah, mengingat bahwa (1) ada ketentuan tersebut dinyatakan dalam dokumentasi antarmuka, dan (2) pelaksanaan yang paling umum, List<T>, bahkan tidak hanya-baca ? Saya tidak begitu jelas apa yang mendua tentang istilah saya: koleksi dapat dibaca jika data di dalamnya dapat dibaca. Ini hanya-baca jika dapat menjanjikan bahwa data yang terkandung tidak dapat diubah kecuali beberapa referensi eksternal dipegang oleh kode yang akan mengubahnya. Itu abadi jika dapat menjamin bahwa itu tidak dapat diubah, titik.
supercat
1
@supercat: Kebetulan, Microsoft setuju dengan saya. Mereka merilis paket koleksi yang tidak berubah dan perhatikan bahwa mereka semua adalah tipe yang konkret, karena Anda tidak pernah dapat menjamin bahwa kelas atau antarmuka abstrak benar-benar tidak dapat diubah.
Aaronaught
8

Saya kira tidak ada jawaban umum untuk ini. Semakin kompleks suatu kelas, semakin sulit untuk berpikir tentang perubahan negaranya, dan semakin mahal untuk membuat salinan baru darinya. Jadi di atas beberapa tingkat kompleksitas (pribadi) itu akan menjadi terlalu menyakitkan untuk membuat / menjaga kelas tetap.

Perhatikan bahwa kelas yang terlalu kompleks, atau daftar parameter metode panjang adalah desain bau per se, terlepas dari keabadian.

Jadi biasanya solusi yang disukai adalah memecah kelas seperti itu menjadi beberapa kelas yang berbeda, yang masing-masing dapat dibuat bisa berubah atau berubah dengan sendirinya. Jika ini tidak layak, itu bisa berubah bisa berubah.

Péter Török
sumber
5

Anda dapat menghindari masalah penyalinan jika Anda menyimpan semua bidang yang tidak dapat diubah di bagian dalam struct. Ini pada dasarnya adalah variasi dari pola kenang-kenangan. Kemudian ketika Anda ingin membuat salinan, cukup salin kenang-kenangannya:

class MyClass
{
    struct Memento
    {
        public int field1;
        public string field2;
    }

    private readonly Memento memento;

    public MyClass(int field1, string field2)
    {
        this.memento = new Memento()
            {
                field1 = field1,
                field2 = field2
            };
    }

    private MyClass(Memento memento) // for copying
    {
        this.memento = memento;
    }

    public int Field1 { get { return this.memento.field1; } }
    public string Field2 { get { return this.memento.field2; } }

    public MyClass WithNewField1(int newField1)
    {
        Memento newMemento = this.memento;
        newMemento.field1 = newField1;
        return new MyClass(newMemento);
    }
}
Scott Whitlock
sumber
Saya pikir struktur batin tidak perlu. Ini hanyalah cara lain untuk melakukan MemberwiseClone.
Codism
@Codism - ya dan tidak. Ada kalanya Anda mungkin membutuhkan anggota lain yang tidak ingin Anda tiru. Bagaimana jika Anda menggunakan evaluasi malas di salah satu pengambil Anda dan menyimpan hasilnya di anggota? Jika Anda melakukan MemberwiseClone, Anda akan mengkloning nilai yang di-cache, maka Anda akan mengubah salah satu anggota Anda yang bergantung pada nilai yang di-cache. Lebih bersih untuk memisahkan status dari cache.
Scott Whitlock
Mungkin layak menyebutkan keuntungan lain dari struktur dalam: itu membuatnya mudah bagi suatu objek untuk menyalin keadaannya ke objek yang ada referensi lain . Sumber ambiguitas yang umum dalam OOP adalah apakah metode yang mengembalikan referensi objek mengembalikan tampilan objek yang mungkin berubah di luar kendali penerima. Jika alih-alih mengembalikan referensi objek, metode menerima referensi objek dari pemanggil dan menyalinnya, kepemilikan objek akan jauh lebih jelas. Pendekatan semacam itu tidak bekerja dengan baik dengan tipe yang dapat diwarisi secara bebas, tapi ...
supercat
... itu bisa sangat berguna bagi pemegang data yang bisa berubah. Pendekatan ini juga membuatnya sangat mudah untuk memiliki kelas "paralel" yang dapat berubah dan tidak dapat diubah (berasal dari basis "dapat dibaca" abstrak) dan membuat konstruktor mereka dapat menyalin data dari satu sama lain.
supercat
3

Anda memiliki beberapa hal yang sedang bekerja di sini. Kumpulan data yang tidak berubah sangat bagus untuk skalabilitas multithreaded. Pada dasarnya, Anda dapat sedikit mengoptimalkan memori Anda sehingga satu set parameter adalah satu instance dari kelas - di mana saja. Karena objek tidak pernah berubah, Anda tidak perlu khawatir tentang sinkronisasi saat mengakses anggotanya. Itu hal yang baik. Namun, seperti yang Anda tunjukkan, semakin kompleks objek semakin Anda membutuhkan beberapa sifat berubah-ubah. Saya akan mulai dengan alasan seperti ini:

  • Apakah ada alasan bisnis mengapa suatu objek dapat mengubah kondisinya? Misalnya, objek pengguna yang disimpan dalam database unik berdasarkan ID-nya, tetapi harus dapat mengubah status seiring waktu. Di sisi lain ketika Anda mengubah koordinat pada kisi, berhenti menjadi koordinat asli sehingga masuk akal untuk membuat koordinat tetap. Sama dengan string.
  • Bisakah beberapa atribut dihitung? Singkatnya, jika nilai-nilai lain dalam salinan baru suatu objek adalah fungsi dari beberapa nilai inti yang Anda berikan, Anda bisa menghitungnya di konstruktor atau sesuai permintaan. Ini mengurangi jumlah pemeliharaan karena Anda dapat menginisialisasi nilai-nilai tersebut dengan cara yang sama pada penyalinan atau pembuatan.
  • Berapa banyak nilai yang membentuk objek abadi yang baru? Pada titik tertentu kompleksitas membuat objek menjadi non-sepele dan pada saat itu memiliki lebih banyak contoh objek dapat menjadi masalah. Contohnya termasuk struktur pohon yang tidak dapat diubah, objek dengan lebih dari tiga parameter dilewatkan, dll. Semakin banyak parameter semakin besar kemungkinan mengacaukan urutan parameter atau membatalkan yang salah.

Dalam bahasa yang hanya mendukung objek yang tidak dapat diubah (seperti Erlang), jika ada operasi yang tampaknya mengubah keadaan objek yang tidak dapat diubah, hasil akhirnya adalah salinan baru dari objek dengan nilai yang diperbarui. Misalnya, ketika Anda menambahkan item ke vektor / daftar:

myList = lists:append([[1,2,3], [4,5,6]])
% myList is now [1,2,3,4,5,6]

Itu bisa menjadi cara yang waras untuk bekerja dengan objek yang lebih rumit. Ketika Anda menambahkan simpul pohon misalnya, hasilnya adalah pohon baru dengan simpul yang ditambahkan. Metode dalam contoh di atas mengembalikan daftar baru. Dalam contoh dalam paragraf ini tree.add(newNode)akan mengembalikan pohon baru dengan simpul yang ditambahkan. Bagi pengguna, ini menjadi mudah untuk dikerjakan. Bagi para penulis perpustakaan menjadi membosankan ketika bahasa tidak mendukung penyalinan implisit. Ambang itu terserah kesabaran Anda sendiri. Untuk pengguna perpustakaan Anda, batas paling waras yang saya temukan adalah sekitar tiga hingga empat parameter teratas.

Berin Loritsch
sumber
Jika seseorang cenderung menggunakan referensi objek yang bisa berubah sebagai nilai [artinya tidak ada referensi yang terkandung di dalam pemiliknya dan tidak pernah terpapar], membangun objek abadi yang menyimpan konten "yang diubah" yang diinginkan setara dengan memodifikasi objek secara langsung, meskipun kemungkinan lebih lambat. Objek yang bisa berubah, juga dapat digunakan sebagai entitas . Bagaimana kita membuat benda yang berperilaku seperti entitas tanpa benda yang bisa berubah?
supercat
0

Jika Anda memiliki beberapa anggota kelas akhir dan tidak ingin mereka diekspos ke semua objek yang perlu membuatnya, Anda dapat menggunakan pola builder:

class NamedThing
{
    private string _name;    
    private string _value;
    private NamedThing(string name, string value)
    {
        _name = name;
        _value = value;
    }    
    public NamedThing(NamedThing other)
    {
        this._name = other._name;
        this._value = other._value;
    }
    public string Name
    {
        get { return _name; }
    }

    public static class Builder {
        string _name;
        string _value;

        public void setValue(string value) {
            _value = value;
        }
        public void setName(string name) {
            _name = name;
        }
        public NamedThing newObject() {
            return new NamedThing(_name, _value);
        }
    }
}

keuntungannya adalah Anda dapat dengan mudah membuat objek baru dengan nilai yang berbeda dari nama yang berbeda.

Salandur
sumber
Saya pikir pembangun Anda menjadi statis tidak benar. Utas lain dapat mengubah nama statis atau nilai statis setelah Anda mengaturnya, tetapi sebelum Anda menelepon newObject.
ErikE
Hanya kelas pembangun yang statis, tetapi anggotanya tidak. Ini berarti bahwa untuk setiap pembuat yang Anda buat, mereka memiliki anggota sendiri dengan nilai yang sesuai. Kelas harus statis sehingga dapat digunakan dan dipakai di luar kelas yang mengandung ( NamedThingdalam hal ini)
Salandur
Saya mengerti apa yang Anda katakan, saya hanya membayangkan masalah karena ini tidak membuat pengembang "jatuh ke dalam jurang kesuksesan." Fakta bahwa ia menggunakan variabel statis berarti bahwa jika Builderdigunakan kembali, ada risiko nyata dari hal yang terjadi yang saya sebutkan. Seseorang mungkin sedang membangun banyak objek dan memutuskan bahwa karena sebagian besar propertinya sama, untuk digunakan kembali Builder, dan pada kenyataannya, mari kita menjadikannya singleton global yang disuntikkan ketergantungan! Aduh. Bug utama diperkenalkan. Jadi saya pikir pola campuran instantiated vs statis ini buruk.
ErikE
1
@Salandur kelas dalam di C # selalu "statis" dalam arti kelas batin Jawa.
Sebastian Redl
0

Jadi pada titik apa kelas menjadi terlalu rumit untuk tidak dapat diubah?

Menurut pendapat saya, tidak ada gunanya membuat kelas kecil tidak dapat diubah dalam bahasa seperti yang Anda tampilkan. Saya menggunakan kecil di sini dan tidak kompleks , karena bahkan jika Anda menambahkan sepuluh bidang ke kelas itu dan itu benar-benar suka operasi pada mereka, saya ragu itu akan mengambil kilobyte apalagi megabyte apalagi gigabyte, jadi fungsi apa pun menggunakan instance dari Anda kelas dapat dengan mudah membuat salinan murah dari seluruh objek untuk menghindari memodifikasi yang asli jika ingin menghindari menyebabkan efek samping eksternal.

Struktur Data Persisten

Di mana saya menemukan penggunaan pribadi untuk kekekalan adalah untuk struktur data pusat yang besar yang mengagregasi sekelompok kecil data seperti contoh dari kelas yang Anda tampilkan, seperti yang menyimpan sejuta NamedThings. Dengan menjadi bagian dari struktur data persisten yang tidak dapat diubah dan berada di belakang antarmuka yang hanya memungkinkan akses baca-saja, elemen-elemen yang menjadi milik kontainer menjadi tidak dapat diubah tanpa kelas elemen ( NamedThing) yang harus menghadapinya.

Salinan Murah

Struktur data yang persisten memungkinkan bagian-bagiannya ditransformasi dan dibuat unik, menghindari modifikasi ke aslinya tanpa harus menyalin struktur data secara keseluruhan. Itulah keindahan yang sebenarnya. Jika Anda ingin secara naif menulis fungsi yang menghindari efek samping yang menginput struktur data yang membutuhkan gigabytes memori dan hanya memodifikasi memori senilai satu megabyte, maka Anda harus menyalin seluruh hal aneh untuk menghindari menyentuh input dan mengembalikan yang baru keluaran. Baik menyalin gigabyte untuk menghindari efek samping atau menyebabkan efek samping dalam skenario itu, membuat Anda harus memilih di antara dua pilihan yang tidak menyenangkan.

Dengan struktur data yang persisten, ini memungkinkan Anda untuk menulis fungsi seperti itu dan menghindari membuat salinan dari seluruh struktur data, hanya membutuhkan sekitar satu megabita memori ekstra untuk output jika fungsi Anda hanya mengubah nilai memori satu megabita.

Beban

Adapun beban, ada yang langsung setidaknya dalam kasus saya. Saya perlu pembangun yang dibicarakan orang atau "transien" saat saya memanggil mereka untuk dapat secara efektif mengekspresikan transformasi pada struktur data besar itu tanpa menyentuhnya. Kode seperti ini:

void transform_stuff(MutList<Stuff>& stuff, int first, int last)
{
     // Transform stuff in the range, [first, last).
     for (; first != last; ++first)
          transform(stuff[first]);
}

... maka harus ditulis seperti ini:

ImmList<Stuff> transform_stuff(ImmList<Stuff> stuff, int first, int last)
{
     // Grab a "transient" (builder) list we can modify:
     TransientList<Stuff> transient(stuff);

     // Transform stuff in the range, [first, last)
     // for the transient list.
     for (; first != last; ++first)
          transform(transient[first]);

     // Commit the modifications to get and return a new
     // immutable list.
     return stuff.commit(transient);
}

Tetapi sebagai ganti dari dua baris kode tambahan, fungsi sekarang aman untuk memanggil utas dengan daftar asli yang sama, itu menyebabkan tidak ada efek samping, dll. Ini juga membuatnya sangat mudah untuk membuat operasi ini menjadi tindakan pengguna yang tidak dapat diurungkan karena batalkan hanya dapat menyimpan salinan murah dari daftar lama.

Pengecualian-Keamanan atau Pemulihan Kesalahan

Tidak semua orang dapat mengambil manfaat sebanyak yang saya lakukan dari struktur data yang persisten dalam konteks seperti ini (saya menemukan banyak kegunaannya dalam sistem undo dan pengeditan non-destruktif yang merupakan konsep sentral dalam domain VFX saya), tetapi satu hal yang dapat diterapkan pada hampir semua semua orang untuk dipertimbangkan adalah pengecualian-keselamatan atau pemulihan kesalahan .

Jika Anda ingin membuat fungsi mutasi yang asli pengecualian-aman, maka itu memerlukan logika rollback, yang untuk itu implementasi paling sederhana memerlukan menyalin seluruh daftar:

void transform_stuff(MutList<Stuff>& stuff, int first, int last)
{
    // Make a copy of the whole massive gigabyte-sized list 
    // in case we encounter an exception and need to rollback
    // changes.
    MutList<Stuff> old_stuff = stuff;

    try
    {
         // Transform stuff in the range, [first, last).
         for (; first != last; ++first)
             transform(stuff[first]);
    }
    catch (...)
    {
         // If the operation failed and ran into an exception,
         // swap the original list with the one we modified
         // to "undo" our changes.
         stuff.swap(old_stuff);
         throw;
    }
}

Pada titik ini versi pengecualian-aman yang dapat diubah bahkan lebih mahal secara komputasi dan bahkan lebih sulit untuk menulis dengan benar daripada versi yang tidak dapat diubah menggunakan "builder". Dan banyak pengembang C ++ mengabaikan keamanan-pengecualian dan mungkin itu baik-baik saja untuk domain mereka, tetapi dalam kasus saya saya ingin memastikan kode saya berfungsi dengan benar bahkan jika ada pengecualian (bahkan menulis tes yang dengan sengaja melemparkan pengecualian untuk menguji pengecualian) keamanan), dan itu membuatnya jadi saya harus bisa mengembalikan semua efek samping yang menyebabkan fungsi setengah jalan jika ada sesuatu yang dilemparkan.

Ketika Anda ingin menjadi pengecualian-aman dan pulih dari kesalahan dengan anggun tanpa aplikasi Anda mogok dan terbakar, maka Anda harus mengembalikan / membatalkan segala efek samping yang dapat disebabkan oleh suatu fungsi jika terjadi kesalahan / pengecualian. Dan di sana pembangun sebenarnya dapat menghemat lebih banyak waktu programmer daripada biaya seiring dengan waktu komputasi karena: ...

Anda tidak perlu khawatir tentang mengembalikan efek samping dalam suatu fungsi yang tidak menyebabkan apa pun!

Jadi kembali ke pertanyaan mendasar:

Pada titik apakah kelas yang tidak dapat berubah menjadi beban?

Mereka selalu menjadi beban dalam bahasa yang lebih banyak berkisar seputar mutabilitas daripada ketidakmampuan, itulah sebabnya saya pikir Anda harus menggunakannya di mana manfaatnya jauh lebih besar daripada biayanya. Tetapi pada tingkat yang cukup luas untuk struktur data yang cukup besar, saya percaya ada banyak kasus di mana itu merupakan trade-off yang layak.

Juga di tambang, saya hanya memiliki beberapa tipe data yang tidak dapat diubah, dan semuanya merupakan struktur data besar yang dimaksudkan untuk menyimpan sejumlah besar elemen (piksel gambar / tekstur, entitas dan komponen ECS, dan simpul / tepi / poligon dari sebuah jaring).


sumber