Saya melihat diri saya menggunakan lebih banyak tipe yang tidak dapat diubah ketika instance kelas tidak diharapkan untuk diubah . Ini membutuhkan lebih banyak pekerjaan (lihat contoh di bawah), tetapi membuatnya lebih mudah untuk menggunakan tipe-tipe dalam lingkungan multithreaded.
Pada saat yang sama, saya jarang melihat tipe yang tidak dapat diubah dalam aplikasi lain, bahkan ketika kemampuan berubah tidak menguntungkan siapa pun.
Pertanyaan: Mengapa tipe yang tidak dapat diubah sangat jarang digunakan dalam aplikasi lain?
- Apakah ini karena lebih lama menulis kode untuk tipe yang tidak dapat diubah,
- Atau apakah saya kehilangan sesuatu dan ada beberapa kelemahan penting saat menggunakan jenis yang tidak dapat diubah?
Contoh dari kehidupan nyata
Katakanlah Anda dapatkan Weather
dari RESTful API seperti itu:
public Weather FindWeather(string city)
{
// TODO: Load the JSON response from the RESTful API and translate it into an instance
// of the Weather class.
}
Apa yang biasanya kita lihat adalah (baris dan komentar baru dihapus untuk mempersingkat kode):
public sealed class Weather
{
public City CorrespondingCity { get; set; }
public SkyState Sky { get; set; } // Example: SkyState.Clouds, SkyState.HeavySnow, etc.
public int PrecipitationRisk { get; set; }
public int Temperature { get; set; }
}
Di sisi lain, saya akan menulis dengan cara ini, mengingat mendapatkan Weather
dari API, kemudian mengubahnya akan aneh: mengubah Temperature
atau Sky
tidak akan mengubah cuaca di dunia nyata, dan perubahan CorrespondingCity
juga tidak masuk akal.
public sealed class Weather
{
private readonly City correspondingCity;
private readonly SkyState sky;
private readonly int precipitationRisk;
private readonly int temperature;
public Weather(City correspondingCity, SkyState sky, int precipitationRisk,
int temperature)
{
this.correspondingCity = correspondingCity;
this.sky = sky;
this.precipitationRisk = precipitationRisk;
this.temperature = temperature;
}
public City CorrespondingCity { get { return this.correspondingCity; } }
public SkyState Sky { get { return this.sky; } }
public int PrecipitationRisk { get { return this.precipitationRisk; } }
public int Temperature { get { return this.temperature; } }
}
sumber
{get; private set;}
, dan bahkan yang bisa berubah harus memiliki konstruktor, karena semua bidang itu harus selalu disetel dan mengapa Anda tidak memaksakan itu? Membuat dua perubahan yang sangat masuk akal membawa mereka ke fitur dan paritas LoC.Jawaban:
Saya memprogram dalam C # dan Objective-C. Saya sangat suka mengetik, tetapi dalam kehidupan nyata saya selalu terpaksa membatasi penggunaannya, terutama untuk tipe data, karena alasan berikut:
StringBuilder
atauUriBuilder
dalam C #, atauWeatherBuilder
dalam kasus Anda. Ini adalah alasan utama bagi saya karena banyak kelas yang saya desain tidak sepadan dengan usaha seperti itu.Singkatnya, kekekalan baik untuk objek yang berperilaku seperti nilai, atau hanya memiliki beberapa properti. Sebelum membuat sesuatu yang abadi, Anda harus mempertimbangkan upaya yang diperlukan dan kegunaan kelas itu sendiri setelah membuatnya tidak berubah.
sumber
Umumnya jenis yang tidak dapat diubah yang dibuat dalam bahasa yang tidak berputar di sekitar ketidakmampuan akan cenderung menghabiskan lebih banyak waktu pengembang untuk membuat serta berpotensi digunakan jika mereka memerlukan beberapa jenis "pembangun" objek untuk mengekspresikan perubahan yang diinginkan (ini tidak berarti bahwa keseluruhan pekerjaan akan lebih banyak, tetapi ada biaya di muka dalam kasus ini). Terlepas dari apakah bahasa membuatnya sangat mudah untuk membuat tipe yang tidak dapat diubah atau tidak, itu akan cenderung selalu membutuhkan beberapa pemrosesan dan overhead memori untuk tipe data yang tidak sepele.
Membuat Fungsi Tanpa Efek Samping
Jika Anda bekerja dalam bahasa yang tidak berputar di sekitar ketidakberdayaan, maka saya pikir pendekatan pragmatis bukanlah berusaha membuat setiap jenis data tunggal tidak dapat diubah. Pola pikir yang berpotensi jauh lebih produktif yang memberi Anda banyak manfaat yang sama adalah fokus pada memaksimalkan jumlah fungsi dalam sistem Anda yang menyebabkan efek samping nol .
Sebagai contoh sederhana, jika Anda memiliki fungsi yang menyebabkan efek samping seperti ini:
Maka kita tidak memerlukan tipe data integer yang tidak dapat diubah yang melarang operator seperti penugasan pasca inisialisasi untuk membuat fungsi tersebut menghindari efek samping. Kita bisa melakukan ini:
Sekarang fungsi tidak berantakan dengan
x
atau apa pun di luar ruang lingkupnya, dan dalam kasus sepele ini kita bahkan mungkin mencukur beberapa siklus dengan menghindari overhead yang terkait dengan tipuan / aliasing. Paling tidak versi kedua seharusnya tidak lebih mahal secara komputasi daripada yang pertama.Hal-hal Yang Mahal untuk Menyalin Sepenuhnya
Tentu saja sebagian besar kasus tidak sepele ini jika kita ingin menghindari membuat fungsi menyebabkan efek samping. Kasus penggunaan dunia nyata yang kompleks mungkin lebih seperti ini:
Pada titik mana mesh mungkin memerlukan beberapa ratus megabyte memori dengan lebih dari seratus ribu poligon, bahkan lebih banyak simpul dan tepi, beberapa peta tekstur, target morf, dll. Akan sangat mahal untuk menyalin seluruh mesh hanya untuk membuat ini
transform
berfungsi bebas dari efek samping, seperti:Dan dalam kasus ini di mana menyalin sesuatu secara keseluruhan biasanya akan menjadi overhead epik di mana saya merasa berguna untuk berubah
Mesh
menjadi struktur data yang persisten dan tipe yang tidak dapat diubah dengan "pembangun" analogis untuk membuat versi modifikasi dari itu sehingga dapat dengan mudah menyalin dan menghitung jumlah bagian yang tidak unik. Semuanya dengan fokus untuk dapat menulis fungsi mesh yang bebas dari efek samping.Struktur Data Persisten
Dan dalam kasus-kasus ini di mana menyalin semuanya sangat mahal, saya menemukan upaya mendesain yang kekal
Mesh
untuk benar-benar melunasi walaupun biayanya sedikit curam di muka, karena itu tidak hanya menyederhanakan keamanan thread. Ini juga menyederhanakan pengeditan non-destruktif (memungkinkan pengguna untuk melapisi operasi mesh tanpa memodifikasi salinan aslinya), membatalkan sistem (sekarang sistem undo hanya dapat menyimpan salinan mesh yang tidak berubah sebelum perubahan yang dilakukan oleh operasi tanpa meledakkan memori gunakan), dan pengecualian-keselamatan (sekarang jika pengecualian terjadi pada fungsi di atas, fungsi tidak harus memutar kembali dan membatalkan semua efek sampingnya karena tidak menyebabkan apapun untuk memulai).Saya yakin dapat mengatakan dalam kasus-kasus ini bahwa waktu yang diperlukan untuk membuat struktur data yang besar dan kekal ini menghemat lebih banyak waktu daripada biayanya, karena saya telah membandingkan biaya perawatan dari desain baru ini dengan yang sebelumnya yang berkisar seputar kemampuan berubah-ubah dan fungsi yang menyebabkan efek samping, dan desain sebelumnya yang bisa berubah biaya jauh lebih banyak waktu dan jauh lebih rentan terhadap kesalahan manusia, terutama di daerah yang benar-benar menggoda bagi pengembang untuk mengabaikan selama waktu krisis, seperti pengecualian-keselamatan.
Jadi saya pikir tipe data yang tidak dapat diubah benar-benar terbayar dalam kasus-kasus ini, tetapi tidak semuanya harus dibuat tidak dapat diubah untuk membuat sebagian besar fungsi dalam sistem Anda bebas dari efek samping. Banyak hal yang cukup murah untuk disalin secara penuh. Juga banyak aplikasi dunia nyata yang perlu menyebabkan beberapa efek samping di sana-sini (paling tidak seperti menyimpan file), tetapi biasanya ada lebih banyak fungsi yang bisa tanpa efek samping.
Maksud dari memiliki beberapa tipe data yang tidak dapat diubah kepada saya adalah untuk memastikan bahwa kita dapat menulis jumlah fungsi maksimum agar bebas dari efek samping tanpa menimbulkan overhead epik dalam bentuk penyalinan mendalam struktur data besar-besaran kiri dan kanan penuh ketika hanya sebagian kecil dari mereka perlu dimodifikasi. Memiliki struktur data yang terus-menerus dalam kasus-kasus itu kemudian berakhir menjadi detail optimisasi untuk memungkinkan kita menulis fungsi kita agar bebas dari efek samping tanpa membayar biaya besar untuk melakukannya.
Overhead Abadi
Sekarang secara konseptual versi yang bisa berubah akan selalu memiliki keunggulan dalam efisiensi. Selalu ada overhead komputasi yang terkait dengan struktur data yang tidak dapat diubah. Tapi saya menganggapnya sebagai pertukaran yang layak dalam kasus-kasus yang saya jelaskan di atas, dan Anda dapat fokus untuk membuat biaya overhead yang minimal. Saya lebih suka jenis pendekatan di mana kebenaran menjadi mudah dan optimasi menjadi lebih sulit daripada optimasi menjadi lebih mudah tetapi kebenaran menjadi lebih sulit. Ini hampir tidak demoralisasi untuk memiliki kode yang berfungsi sempurna dengan benar membutuhkan beberapa tune up lebih dari kode yang tidak berfungsi dengan benar di tempat pertama, tidak peduli seberapa cepat itu mencapai hasil yang salah.
sumber
Satu-satunya kelemahan yang dapat saya pikirkan adalah bahwa dalam teori penggunaan data tidak berubah mungkin lebih lambat dari yang bisa berubah - lebih lambat untuk membuat contoh baru, dan mengumpulkan yang sebelumnya daripada memodifikasi yang sudah ada.
"Masalah" lainnya adalah Anda tidak bisa hanya menggunakan tipe yang tidak dapat diubah. Pada akhirnya Anda harus menggambarkan keadaan dan Anda harus menggunakan tipe yang bisa berubah untuk melakukannya - tanpa mengubah keadaan Anda tidak dapat melakukan pekerjaan apa pun.
Tetapi masih aturan umum adalah untuk menggunakan tipe yang tidak berubah di mana pun Anda bisa dan membuat tipe bisa berubah hanya ketika benar-benar ada alasan untuk melakukannya ...
Dan untuk menjawab pertanyaan " Mengapa tipe yang tidak dapat diubah sangat jarang digunakan dalam aplikasi lain? " - Saya benar-benar tidak berpikir mereka ... di mana pun Anda melihat semua orang merekomendasikan membuat kelas Anda sama abadi seperti yang bisa mereka ... misalnya: http://www.javapractices.com/topic/TopicAction.do?Id=29
sumber
Car
objek yang bisa berubah terus diperbarui dengan lokasi mobil fisik tertentu, maka jika saya memiliki referensi ke objek itu saya bisa mengetahui keberadaan mobil itu dengan cepat dan mudah. JikaCar
tidak berubah, menemukan keberadaan saat ini kemungkinan akan jauh lebih sulit.Untuk memodelkan sistem dunia nyata mana pun yang bisa berubah, keadaan yang bisa berubah perlu dikodekan di suatu tempat, entah bagaimana. Ada tiga cara utama suatu objek bisa tahan keadaan berubah-ubah:
Penggunaan pertama memudahkan objek untuk membuat snapshot abadi dari kondisi saat ini. Menggunakan yang kedua memudahkan objek untuk membuat tampilan langsung dari kondisi saat ini. Menggunakan yang ketiga kadang-kadang dapat membuat tindakan tertentu lebih efisien dalam kasus-kasus di mana ada sedikit kebutuhan yang diharapkan untuk snapshot abadi atau pandangan langsung.
Selain fakta bahwa memperbarui status yang disimpan menggunakan referensi yang bisa berubah ke objek yang tidak dapat diubah seringkali lebih lambat daripada memperbarui status yang disimpan dengan menggunakan objek yang bisa diubah, menggunakan referensi yang bisa berubah akan mengharuskan seseorang untuk melupakan kemungkinan membangun pandangan hidup yang murah dari negara tersebut. Jika seseorang tidak perlu membuat tampilan langsung, itu tidak masalah; Namun, jika seseorang perlu membuat tampilan langsung, ketidakmampuan untuk menggunakan referensi yang tidak dapat diubah akan membuat semua operasi dengan tampilan - keduanya membaca dan menulis- jauh lebih lambat dari yang seharusnya. Jika kebutuhan untuk snapshot yang tidak dapat diubah melebihi kebutuhan untuk tayangan langsung, peningkatan kinerja dari snapshot yang tidak berubah dapat membenarkan hit kinerja untuk tayangan langsung, tetapi jika seseorang membutuhkan tayangan langsung dan tidak memerlukan snapshot, menggunakan referensi yang tidak dapat diubah ke objek yang dapat diubah adalah cara untuk pergi.
sumber
Dalam kasus Anda jawabannya terutama karena C # memiliki dukungan yang buruk untuk Kekekalan ...
Akan lebih bagus jika:
semuanya akan berubah secara default kecuali disebutkan sebaliknya (yaitu dengan kata kunci 'bisa berubah'), mencampur tipe tidak dapat berubah dan dapat berubah membingungkan
metode mutasi (
With
) akan tersedia secara otomatis - meskipun ini sudah dapat dicapai lihat Denganakan ada cara untuk mengatakan hasil dari pemanggilan metode tertentu (yaitu
ImmutableList<T>.Add
) tidak dapat dibuang atau setidaknya akan menghasilkan peringatanDan terutama jika kompilator dapat memastikan imutabilitas sebanyak yang diminta (lihat https://github.com/dotnet/roslyn/issues/159 )
sumber
MustUseReturnValueAttribute
atribut khusus yang melakukan hal itu.PureAttribute
memiliki efek yang sama dan bahkan lebih baik untuk ini.Ketidakpedulian? Kurang pengalaman?
Objek yang tidak dapat berubah secara luas dianggap sebagai yang unggul saat ini, tetapi ini merupakan perkembangan yang relatif baru. Insinyur yang tidak mengikuti perkembangan terbaru, atau hanya terjebak dalam 'apa yang mereka ketahui' tidak akan menggunakannya. Dan memang butuh sedikit perubahan desain untuk menggunakannya secara efektif. Jika aplikasi sudah tua, atau insinyur lemah dalam keterampilan desain maka menggunakannya mungkin canggung atau menyusahkan.
sumber