Saya mengalami masalah desain tentang properti .NET.
interface IX
{
Guid Id { get; }
bool IsInvalidated { get; }
void Invalidate();
}
Masalah:
Antarmuka ini memiliki dua properti hanya-baca, Id
dan IsInvalidated
. Namun, fakta bahwa itu hanya baca-saja, tidak dengan sendirinya menjamin bahwa nilai-nilai mereka akan tetap konstan.
Katakanlah itu maksud saya untuk membuatnya sangat jelas bahwa ...
Id
mewakili nilai konstan (yang karenanya dapat di-cache dengan aman), sementaraIsInvalidated
mungkin mengubah nilainya selama masa pakaiIX
objek (dan karenanya tidak perlu di-cache).
Bagaimana saya bisa memodifikasi interface IX
agar kontrak itu cukup eksplisit?
Tiga upaya saya sendiri pada solusi:
Antarmuka sudah dirancang dengan baik. Kehadiran metode yang disebut
Invalidate()
memungkinkan seorang programmer untuk menyimpulkan bahwa nilai properti dengan nama yang samaIsInvalidated
mungkin dipengaruhi olehnya.Argumen ini hanya berlaku dalam kasus di mana metode dan properti diberi nama yang sama.
Tambahkan antarmuka ini dengan sebuah acara
IsInvalidatedChanged
:bool IsInvalidated { get; } event EventHandler IsInvalidatedChanged;
Kehadiran
…Changed
acara untukIsInvalidated
menyatakan bahwa properti ini dapat mengubah nilainya, dan tidak adanya acara serupa untukId
merupakan janji bahwa properti itu tidak akan mengubah nilainya.Saya suka solusi ini, tetapi banyak hal tambahan yang mungkin tidak digunakan sama sekali.
Ganti properti
IsInvalidated
dengan metodeIsInvalidated()
:bool IsInvalidated();
Ini mungkin perubahan yang terlalu halus. Seharusnya petunjuk bahwa nilai dihitung baru setiap kali - yang tidak perlu jika itu adalah konstanta. Topik MSDN "Memilih Antara Properti dan Metode" memiliki ini untuk mengatakan tentang hal itu:
Gunakan metode, daripada properti, dalam situasi berikut. […] Operasi mengembalikan hasil yang berbeda setiap kali dipanggil, bahkan jika parameternya tidak berubah.
Jawaban apa yang saya harapkan?
Saya paling tertarik dengan solusi yang sama sekali berbeda untuk masalah ini, bersama dengan penjelasan bagaimana mereka mengalahkan upaya saya di atas.
Jika upaya saya secara logika cacat atau memiliki kerugian yang signifikan belum disebutkan, sehingga hanya satu solusi (atau tidak ada) yang tersisa, saya ingin mendengar tentang kesalahan saya.
Jika kekurangannya kecil, dan lebih dari satu solusi tetap setelah dipertimbangkan, silakan komentar.
Paling tidak, saya ingin umpan balik yang merupakan solusi pilihan Anda, dan untuk alasan apa.
sumber
IsInvalidated
perluprivate set
?class Foo : IFoo { private bool isInvalidated; public bool IsInvalidated { get { return isInvalidated; } } public void Invalidate() { isInvalidated = true; } }
InvalidStateException
s, misalnya, tetapi saya tidak yakin apakah ini secara teori mungkin. Akan menyenangkan.Jawaban:
Saya lebih suka solusi 3 daripada 1 dan 2.
Masalah saya dengan solusi 1 adalah : apa yang terjadi jika tidak ada
Invalidate
metode? Asumsikan sebuah antarmuka denganIsValid
properti yang mengembalikanDateTime.Now > MyExpirationDate
; Anda mungkin tidak memerlukanSetInvalid
metode eksplisit di sini. Bagaimana jika suatu metode memengaruhi banyak properti? Asumsikan tipe koneksi denganIsOpen
danIsConnected
properti - keduanya dipengaruhi olehClose
metode.Solusi 2 : Jika satu-satunya tujuan acara ini adalah untuk memberi tahu pengembang bahwa properti dengan nama yang sama dapat mengembalikan nilai yang berbeda pada setiap panggilan, maka saya akan sangat menyarankan untuk tidak melakukannya. Anda harus menjaga antarmuka Anda singkat dan jelas; Selain itu, tidak semua implementasi mungkin dapat memecat acara itu untuk Anda. Saya akan menggunakan kembali
IsValid
contoh saya dari atas: Anda harus menerapkan Timer dan menjalankan suatu peristiwa ketika Anda mencapaiMyExpirationDate
. Lagipula, jika acara itu adalah bagian dari antarmuka publik Anda, maka pengguna antarmuka akan mengharapkan acara itu berfungsi.Yang dikatakan tentang solusi ini: mereka tidak buruk. Kehadiran metode atau peristiwa AKAN menunjukkan bahwa properti dengan nama yang sama dapat mengembalikan nilai yang berbeda pada setiap panggilan. Yang saya katakan adalah bahwa mereka sendiri tidak cukup untuk selalu menyampaikan makna itu.
Solusi 3 adalah tujuan saya. Seperti yang disebutkan aviv, ini hanya dapat bekerja untuk pengembang C #. Bagi saya sebagai C # -dev, fakta bahwa
IsInvalidated
itu bukan properti segera menyampaikan makna "itu bukan hanya pengakses sederhana, ada sesuatu yang terjadi di sini". Ini tidak berlaku untuk semua orang, dan seperti yang ditunjukkan MainMa, kerangka .NET itu sendiri tidak konsisten di sini.Jika Anda ingin menggunakan solusi 3, saya akan merekomendasikan untuk menyatakannya sebagai konvensi dan minta seluruh tim mengikutinya. Dokumentasi juga penting, saya percaya. Menurut pendapat saya itu sebenarnya cukup sederhana untuk petunjuk pada mengubah nilai-nilai: " kembali benar jika nilai masih berlaku ", " menunjukkan apakah sambungan sudah dibuka " vs " kembali benar jika objek ini adalah valid ". Tidak ada ruginya sama sekali untuk menjadi lebih eksplisit dalam dokumentasi.
Jadi saran saya adalah:
Nyatakan solusi 3 sebuah konvensi dan ikuti secara konsisten. Jelaskan dalam dokumentasi properti apakah properti memiliki nilai yang berubah atau tidak. Pengembang yang bekerja dengan properti sederhana akan dengan benar menganggap mereka tidak berubah (yaitu hanya berubah ketika keadaan objek diubah). Pengembang menghadapi sebuah metode yang suara seperti itu bisa menjadi sebuah properti (
Count()
,Length()
,IsOpen()
) tahu bahwa someting yang terjadi dan (mudah-mudahan) membaca metode dokumentasi untuk memahami apa sebenarnya metode yang dilakukan dan bagaimana berperilaku.sumber
Ada solusi keempat: mengandalkan dokumentasi .
Bagaimana Anda tahu
string
kelas itu tidak berubah? Anda hanya tahu itu, karena Anda sudah membaca dokumentasi MSDN.StringBuilder
, di sisi lain, bisa berubah, karena, sekali lagi, dokumentasi memberi tahu Anda hal itu.Solusi ketiga Anda tidak buruk, tetapi .NET Framework tidak mengikutinya . Sebagai contoh:
adalah properti, tetapi jangan berharap mereka tetap konstan setiap saat.
Apa yang secara konsisten digunakan di .NET Framework itu sendiri adalah ini:
adalah metode, sementara:
adalah properti. Dalam kasus pertama, melakukan hal-hal mungkin memerlukan pekerjaan tambahan, termasuk, misalnya, menanyakan database. Pekerjaan tambahan ini mungkin memakan beberapa waktu. Dalam kasus properti, diperkirakan akan memakan waktu singkat : memang, panjangnya dapat berubah selama masa daftar, tetapi praktis tidak diperlukan apa pun untuk mengembalikannya.
Dengan kelas, hanya dengan melihat kode jelas menunjukkan bahwa nilai properti tidak akan berubah selama masa objek. Contohnya,
jelas: harga akan tetap sama.
Sayangnya, Anda tidak dapat menerapkan pola yang sama ke antarmuka, dan mengingat bahwa C # tidak cukup ekspresif dalam kasus tersebut, dokumentasi (termasuk diagram Dokumentasi XML dan UML) harus digunakan untuk menutup celah.
sumber
Lazy<T>.Value
mungkin perlu waktu yang sangat lama untuk menghitung (saat diakses untuk pertama kali).2 sen saya:
Opsi 3: Sebagai non-C # -er, itu tidak akan menjadi petunjuk; Namun, menilai dari kutipan yang Anda bawa, harus jelas bagi para programmer C # yang mahir bahwa ini berarti sesuatu. Anda tetap harus menambahkan dokumen tentang ini.
Opsi 2: Kecuali Anda berencana untuk menambahkan acara ke setiap hal yang dapat berubah, jangan pergi ke sana.
Pilihan lain:
id
, yang biasanya dianggap tidak berubah bahkan dalam objek yang bisa berubah.sumber
Pilihan lain adalah dengan membagi antarmuka: dengan asumsi bahwa setelah memanggil
Invalidate()
setiap doaIsInvalidated
harus mengembalikan nilai yang samatrue
, tampaknya tidak ada alasan untuk memanggilIsInvalidated
untuk bagian yang sama yang dipanggilInvalidate()
.Jadi, saya sarankan, apakah Anda memeriksa tidak valid atau Anda menyebabkannya , tetapi tampaknya tidak masuk akal untuk melakukan keduanya. Oleh karena itu masuk akal untuk menawarkan satu antarmuka yang berisi
Invalidate()
operasi ke bagian pertama dan antarmuka lain untuk memeriksa (IsInvalidated
) ke yang lain. Karena cukup jelas dari tanda tangan antarmuka pertama yang akan menyebabkan instance menjadi tidak valid, pertanyaan yang tersisa (untuk antarmuka kedua) memang cukup umum: bagaimana menentukan apakah jenis yang diberikan tidak dapat diubah .Saya tahu, ini tidak menjawab pertanyaan secara langsung, tetapi setidaknya mengurangi pertanyaan tentang bagaimana menandai beberapa tipe yang tidak dapat diubah , yang merupakan masalah umum yang dapat dipahami. Misalnya, dengan mengasumsikan bahwa semua jenis bisa berubah kecuali ditentukan lain, Anda dapat menyimpulkan bahwa antarmuka kedua (
IsInvalidated
) dapat berubah dan karena itu dapat mengubah nilaiIsInvalidated
dari waktu ke waktu. Di sisi lain, anggaplah antarmuka pertama tampak seperti ini (pseudo-sintaks):Karena ditandai tidak berubah, Anda tahu bahwa ID tidak akan berubah. Tentu saja, panggilan yang tidak valid akan menyebabkan keadaan instance berubah, tetapi perubahan ini tidak akan terlihat melalui antarmuka ini.
sumber
[Immutable]
selama itu mencakup metode yang tujuan utamanya adalah menyebabkan mutasi. Saya akan memindahkanInvalidate()
metode keInvalidatable
antarmuka.Id
, Anda akan mendapatkan nilai yang sama setiap kali. Hal yang sama berlaku untukInvalidate()
(Anda tidak mendapatkan apa-apa, karena jenis pengembaliannya adalahvoid
). Oleh karena itu, jenisnya tidak berubah. Tentu saja, Anda akan memiliki efek samping , tetapi Anda tidak akan dapat mengamatinya melalui antarmukaIX
, sehingga mereka tidak akan berdampak pada klienIX
.IX
tidak berubah. Dan itu adalah efek samping; mereka hanya didistribusikan di antara dua antarmuka terpisah sehingga klien tidak perlu peduli (dalam halIX
) atau jelas apa efek sampingnya (dalam kasusInvalidatable
). Tapi mengapa tidak pindahInvalidate
keInvalidatable
? Itu akan membuatIX
kekal dan murni, danInvalidatable
tidak akan menjadi lebih bisa berubah, atau lebih tidak murni (karena keduanya sudah ada).Invalidate()
danIsInvalidated
oleh konsumen antarmuka yang sama . Jika Anda meneleponInvalidate()
, Anda harus tahu itu menjadi tidak berlaku, jadi mengapa memeriksanya? Dengan demikian Anda dapat menyediakan dua antarmuka yang berbeda untuk tujuan yang berbeda.