Apakah ini anti-pola jika properti kelas membuat dan mengembalikan contoh baru kelas?

24

Saya memiliki kelas yang dipanggil Headingyang melakukan beberapa hal, tetapi seharusnya juga mampu mengembalikan kebalikan dari nilai heading saat ini, yang akhirnya harus digunakan melalui pembuatan instance baru dari Headingkelas itu sendiri.

Saya dapat memiliki properti sederhana yang dipanggil reciprocaluntuk mengembalikan judul yang berlawanan dari nilai saat ini dan kemudian secara manual membuat contoh baru dari kelas Heading, atau saya dapat membuat metode seperti createReciprocalHeading()untuk secara otomatis membuat contoh baru dari kelas Heading dan mengembalikannya ke pengguna.

Namun, salah satu kolega saya merekomendasikan saya untuk hanya membuat properti kelas yang disebut reciprocalyang mengembalikan instance baru dari kelas itu sendiri melalui metode pengambil.

Pertanyaan saya adalah: Bukankah ini merupakan suatu anti-pola bagi sebuah properti kelas untuk berperilaku seperti itu?

Saya khususnya menemukan ini kurang intuitif karena:

  1. Dalam pikiran saya, properti kelas tidak boleh mengembalikan instance baru kelas, dan
  2. Nama properti, yaitu reciprocal, tidak membantu pengembang untuk sepenuhnya memahami perilakunya, tanpa mendapatkan bantuan dari IDE atau memeriksa tanda tangan pengambil.

Apakah saya terlalu ketat tentang apa yang harus dilakukan properti kelas atau apakah itu masalah yang sah? Saya selalu mencoba untuk mengelola keadaan kelas melalui bidang dan propertinya serta perilakunya melalui metodenya, dan saya gagal melihat bagaimana ini cocok dengan definisi properti kelas.

53777A
sumber
6
Seperti @Ewan katakan dalam paragraf terakhir dari jawabannya, jauh dari anti-pola, memiliki Headingsebagai tipe yang tidak berubah dan reciprocalmengembalikan yang baru Headingadalah "lubang keberhasilan" praktik yang baik. (dengan peringatan di kedua panggilan untuk reciprocalharus mengembalikan "hal yang sama", yaitu mereka harus lulus ujian kesetaraan.)
David Arno
20
"kelas saya tidak berubah". Ada anti-pola Anda yang sebenarnya, di sana. ;)
David Arno
3
@ Davidvidno Yah, kadang-kadang ada penalti kinerja yang sangat besar untuk membuat hal-hal tertentu tidak berubah, terutama jika bahasa tidak mendukungnya secara asli, tetapi sebaliknya saya setuju bahwa ketidakmampuan adalah praktik yang baik dalam banyak kasus.
53777A
2
@dcorking kelas diimplementasikan baik dalam C # dan TypeScript tapi saya lebih suka bahasa ini agnostik.
53777A
2
@Kaiserludi terdengar seperti jawaban (jawaban yang bagus) - komentar untuk meminta kejelasan
dcorking

Jawaban:

22

Ini tidak diketahui memiliki hal-hal seperti Salin () atau Klon () tapi ya saya pikir Anda benar untuk khawatir tentang yang satu ini.

ambil contoh:

h2 = h1.reciprocal
h2.Name = "hello world"
h1.reciprocal.Name = ?

Akan menyenangkan jika ada peringatan bahwa properti itu adalah objek baru setiap kali.

Anda mungkin juga berasumsi bahwa:

h2.reciprocal == h1

Namun. Jika kelas heading Anda adalah tipe nilai yang tidak dapat diubah maka Anda akan dapat menerapkan hubungan ini dan timbal balik mungkin nama yang baik untuk operasi

Ewan
sumber
1
Catat itu Copy()dan Clone()merupakan metode, bukan properti. Saya percaya OP mengacu pada pengambil properti yang mengembalikan instance baru.
Lynn
6
aku tahu. tetapi properti (semacam) juga metode dalam c #
Ewan
4
Dalam hal ini, C # memiliki lebih banyak untuk menawarkan: String.Substring(), Array.Reverse(), Int32.GetHashCode(), dll
Lynn
If your heading class was an immutable value type- Saya pikir Anda harus menambahkan bahwa setter properti pada objek tidak berubah harus selalu mengembalikan instance baru (atau setidaknya jika nilai baru tidak sama dengan nilai lama), karena itulah definisi objek tidak berubah. :)
Ivan Kolmychek
17

Antarmuka kelas mengarahkan pengguna kelas untuk membuat asumsi tentang cara kerjanya.

Jika banyak dari asumsi ini benar dan sedikit yang salah, antarmuka bagus.

Jika banyak dari asumsi ini salah dan sedikit yang benar, antarmuka adalah sampah.

Asumsi umum tentang Properties adalah memanggil fungsi get itu murah. Asumsi umum lainnya tentang properti adalah memanggil fungsi get dua kali berturut-turut akan mengembalikan hal yang sama.


Anda dapat mengatasi ini dengan menggunakan konsistensi, untuk mengubah harapan. Sebagai contoh, untuk sebuah perpustakaan 3D kecil di mana Anda perlu Vector, Ray Matrix, dll Anda dapat membuatnya seperti yang getter seperti Vector.normaldan Matrix.inversecukup banyak selalu mahal. Sayangnya bahkan jika antarmuka Anda secara konsisten menggunakan properti yang mahal, antarmuka yang terbaik akan seintuitif yang menggunakan Vector.create_normal()dan Matrix.create_inverse()- namun saya tahu tidak ada argumen kuat yang dapat dibuat bahwa menggunakan properti menciptakan antarmuka yang lebih intuitif, bahkan setelah mengubah harapan.

Peter - Unban Robert Harvey
sumber
10

Properti harus kembali dengan sangat cepat, mengembalikan nilai yang sama dengan panggilan berulang, dan mendapatkan nilainya tidak memiliki efek samping. Apa yang Anda jelaskan di sini tidak boleh diterapkan sebagai properti.

Intuisi Anda secara luas benar. Metode pabrik tidak mengembalikan nilai yang sama dengan panggilan berulang. Ini mengembalikan contoh baru setiap kali. Ini cukup banyak mendiskualifikasi sebagai properti. Ini juga tidak cepat jika pengembangan kemudian menambah bobot pada pembuatan instance, misalnya ketergantungan jaringan.

Properti umumnya harus operasi yang sangat sederhana yang mendapatkan / mengatur bagian dari keadaan kelas saat ini. Operasi seperti pabrik tidak memenuhi kriteria ini.

Brad Thomas
sumber
1
The DateTime.Nowproperti umum digunakan dan mengacu pada objek baru. Saya pikir nama properti dapat membantu menghilangkan ambiguitas tentang apakah objek yang sama dikembalikan setiap kali atau tidak. Itu semua atas nama dan cara penggunaannya.
Greg Burghardt
3
DateTime. Sekarang dianggap sebagai kesalahan. Lihat stackoverflow.com/questions/5437972/…
Brad Thomas
@GregBurghardt Efek samping dari objek baru mungkin tidak menjadi masalah dalam dirinya sendiri, karena DateTimemerupakan tipe nilai, dan tidak memiliki konsep identitas objek. Jika saya mengerti dengan benar, masalahnya adalah tidak menentukan dan mengembalikan nilai baru setiap kali.
Zev Spitz
1
@GregBurghardt DateTime.Newadalah statis, dan karena itu Anda tidak dapat mengharapkannya untuk mewakili keadaan suatu objek.
Tsahi Asher
5

Saya tidak berpikir ada jawaban bahasa-agnostik untuk ini karena apa yang merupakan "properti" adalah pertanyaan khusus-bahasa, dan apa yang diharapkan penelepon dari "properti" adalah pertanyaan khusus bahasa. Saya pikir cara yang paling bermanfaat untuk memikirkan hal ini adalah dengan memikirkan seperti apa penampilannya dari sudut pandang si penelepon.

Dalam C #, properti berbeda karena sifatnya (secara konvensional) dikapitalisasi (seperti metode) tetapi tidak memiliki tanda kurung (seperti variabel instance publik). Jika Anda melihat kode berikut, tidak ada dokumentasi, apa yang Anda harapkan?

var reciprocalHeading = myHeading.Reciprocal;

Sebagai pemula C #, tetapi orang yang membaca Panduan Penggunaan Properti Microsoft , saya berharap Reciprocal, antara lain:

  1. menjadi anggota data logis dari Headingkelas
  2. murah untuk menelepon, sehingga tidak perlu bagi saya untuk men-cache nilai
  3. tidak memiliki efek samping yang dapat diamati
  4. menghasilkan hasil yang sama jika dipanggil dua kali berturut-turut
  5. (mungkin) menawarkan ReciprocalChangedacara

Dari asumsi-asumsi ini, (3) dan (4) mungkin benar (dengan asumsi Headingadalah tipe nilai yang tidak berubah, seperti dalam jawaban Ewan ), (1) dapat diperdebatkan, (2) tidak diketahui tetapi juga dapat diperdebatkan, dan (5) tidak mungkin untuk masuk akal semantik (meskipun apa pun yang memiliki tajuk mungkin harus memiliki HeadingChangedacara). Ini menunjukkan kepada saya bahwa dalam C # API, "dapatkan atau hitung timbal balik" tidak boleh diimplementasikan sebagai properti, tetapi terutama jika perhitungannya murah dan Headingtidak dapat diubah, ini merupakan kasus batas.

(Namun, perlu diketahui bahwa tidak ada satu pun dari kekhawatiran ini yang ada hubungannya dengan apakah memanggil properti menciptakan contoh baru , bahkan tidak (2). Membuat objek di CLR, dengan sendirinya, cukup murah.)

Di Jawa, properti adalah konvensi penamaan metode. Jika saya melihat

Heading reciprocalHeading = myHeading.getReciprocal();

harapan saya mirip dengan yang di atas (jika tidak secara eksplisit ditetapkan): Saya berharap panggilan menjadi murah, idempoten, dan kurang dalam efek samping. Namun, di luar kerangka JavaBeans konsep "properti" tidak terlalu berarti di Jawa, dan terutama ketika mempertimbangkan properti yang tidak dapat diubah tanpa korespondensi setReciprocal(), getXXX()konvensi ini sekarang agak kuno. Dari Effective Java , edisi kedua (sudah lebih dari delapan tahun sekarang):

Metode yang mengembalikan non- booleanfungsi atau atribut objek di mana mereka dipanggil biasanya dinamai dengan kata benda, frase kata benda, atau frase kata kerja yang dimulai dengan kata kerja get…. Ada kontingen vokal yang mengklaim bahwa hanya bentuk ketiga (dimulai dengan get) yang dapat diterima, tetapi ada sedikit dasar untuk klaim ini. Dua bentuk pertama biasanya mengarah pada kode yang lebih mudah dibaca ... (hlm. 239)

Dalam API kontemporer dan lebih lancar, saya berharap bisa melihatnya

Heading reciprocalHeading = myHeading.reciprocal();

- yang sekali lagi akan menyarankan bahwa panggilan itu murah, idempoten, dan tidak memiliki efek samping, tetapi tidak akan mengatakan apa-apa tentang apakah perhitungan baru dilakukan atau objek baru dibuat. Ini baik; dalam API yang baik, saya seharusnya tidak peduli.

Di Ruby, tidak ada yang namanya properti. Ada "atribut", tetapi jika saya melihat

reciprocalHeading = my_heading.reciprocal

Saya tidak memiliki cara langsung untuk mengetahui apakah saya mengakses variabel instan @reciprocalmelalui attr_readermetode accessor sederhana, atau apakah saya memanggil metode yang melakukan perhitungan mahal. Fakta bahwa nama metode adalah kata benda sederhana, meskipun, daripada mengatakan calcReciprocal, menyarankan, sekali lagi, bahwa panggilan itu setidaknya murah dan mungkin tidak memiliki efek samping.

Dalam Scala, konvensi penamaan adalah bahwa metode dengan efek samping mengambil kurung dan metode tanpa mereka tidak, tetapi

val reciprocal = heading.reciprocal

dapat berupa:

// immutable public value initialized at creation time
val reciprocal: Heading = … 

// immutable public value initialized on first use
lazy val reciprocal: Heading = … 

// public method, probably recalculating on each invocation
def reciprocal: Heading = …

// as above, with parentheses that, by convention, the caller
// should only omit if they know the method has no side effects
def reciprocal(): Heading = …

(Perhatikan bahwa Scala memungkinkan berbagai hal yang tetap tidak disarankan oleh panduan gaya . Ini adalah salah satu gangguan utama saya pada Scala.)

Kurangnya tanda kurung memberi tahu saya bahwa panggilan itu tidak memiliki efek samping; namanya, sekali lagi, menunjukkan bahwa panggilan itu harus relatif murah. Di luar itu, saya tidak peduli bagaimana hal itu memberi saya nilai.

Singkatnya: Ketahui bahasa yang Anda gunakan, dan tahu apa harapan yang akan dibawa oleh programmer lain ke API Anda. Yang lainnya adalah detail implementasi.

David Moles
sumber
1
+1 salah satu jawaban favorit saya, terima kasih telah meluangkan waktu Anda untuk menulis ini.
53777A
2

Seperti yang orang lain katakan, itu adalah pola yang agak umum untuk mengembalikan instance dari kelas yang sama.

Penamaan harus berjalan seiring dengan konvensi penamaan bahasa.

Sebagai contoh, di Jawa saya mungkin berharap akan dipanggil getReciprocal();

Yang sedang berkata, saya akan mempertimbangkan benda yang bisa berubah vs benda yang tidak berubah .

Dengan yang tidak berubah , banyak hal yang mudah, dan tidak ada salahnya jika Anda mengembalikan objek yang sama atau tidak.

Dengan yang bisa berubah , ini bisa menjadi sangat menakutkan.

b = a.reciprocal
a += 1
b = what?

Sekarang apa yang dimaksud dengan b? Kebalikan dari nilai asli a? Atau kebalikan dari yang berubah? Ini mungkin contoh yang terlalu pendek, tetapi Anda mengerti maksudnya.

Dalam kasus tersebut cari penamaan yang lebih baik yang mengkomunikasikan apa yang terjadi, misalnya createReciprocal()mungkin merupakan pilihan yang lebih baik.

Tapi itu benar-benar tergantung pada konteksnya juga.

Eiko
sumber
Apakah maksud Anda bisa berubah ?
Carsten S
@ Karsten Ya, tentu saja, terima kasih. Tidak tahu di mana kepalaku berada. :)
Eiko
Setuju sedikit getReciprocal(), karena itu "terdengar" seperti gaya pengambil JavaBeans "normal". Lebih suka "createXXX ()" atau mungkin "calculXXX ()" yang mengindikasikan atau memberi petunjuk kepada programmer lain bahwa sesuatu yang berbeda sedang terjadi
user949300
@ user949300 Saya agak setuju dengan keprihatinan Anda - dan menyebutkan nama buat ... lebih jauh ke bawah. Ada juga kerugiannya. create ... menyiratkan contoh baru yang mungkin tidak selalu menjadi kasus (pikirkan objek khusus tunggal untuk beberapa kasus khusus), hitung ... semacam detail implementasi kebocoran - yang mungkin mendorong asumsi yang salah pada label harga.
Eiko
1

Untuk kepentingan single-responsiblity dan kejelasan saya akan memiliki ReverseHeadingFactory yang mengambil objek dan mengembalikannya. Ini akan membuatnya lebih jelas bahwa objek baru sedang dikembalikan dan akan berarti kode untuk menghasilkan kebalikannya adalah enkapsulasi dari kode lain.

matt freake
sumber
1
+1 untuk kejelasan nama, tetapi apa yang salah dengan SRP dalam solusi lain?
53777A
3
Mengapa Anda merekomendasikan kelas pabrik daripada metode pabrik? (Menurut pendapat saya, tanggung jawab yang berguna dari suatu objek sering memancarkan objek yang seperti itu sendiri, seperti iterator.next. Apakah pelanggaran ringan SRP menyebabkan masalah rekayasa perangkat lunak lainnya?)
dcorking
2
@dcorking Kelas pabrik versus metode (menurut saya) biasanya jenis pertanyaan yang sama dengan "Apakah objeknya sebanding, atau haruskah saya membuat pembanding?" Itu selalu baik-baik saja untuk membuat komparator, tapi hanya apa-apa untuk membuat mereka sebanding jika itu adalah cara yang cukup universal membandingkan mereka. (Misalnya, angka yang lebih besar lebih besar daripada yang lebih kecil, ini bagus untuk dibandingkan, tetapi Anda bisa mengatakan semua peristiwa lebih besar dari semua peluang, saya akan menjadikannya sebagai pembanding) - Jadi untuk ini, saya akan berpikir hanya ada satu cara untuk membalikkan header, jadi saya akan cenderung membuat metode untuk menghindari kelas tambahan.
Kapten Man
0

Tergantung. Saya biasanya mengharapkan properti mengembalikan hal-hal yang merupakan bagian dari instance, jadi mengembalikan instance berbeda dari kelas yang sama akan sedikit tidak biasa.

Tetapi misalnya kelas string dapat memiliki properti "firstWord", "lastWord", atau jika ia menangani Unicode "firstLetter", "lastLetter" yang akan menjadi objek string penuh - biasanya hanya yang lebih kecil.

gnasher729
sumber
0

Karena jawaban lain mencakup bagian Properti, saya hanya akan menjawab # 2 tentang reciprocal:

Jangan gunakan apa pun selain reciprocal. Ini adalah satu-satunya istilah yang tepat untuk apa yang Anda gambarkan (dan itu adalah istilah formal). Jangan menulis perangkat lunak yang salah untuk menyelamatkan pengembang Anda dari mempelajari domain tempat mereka bekerja. Dalam navigasi, istilah memiliki makna yang sangat spesifik dan menggunakan sesuatu yang tampaknya tidak berbahaya reverseatau oppositedapat menyebabkan kebingungan dalam konteks tertentu.

Shaz
sumber
0

Secara logis, tidak, ini bukan properti dari heading. Jika ya, Anda juga bisa mengatakan bahwa negatif dari angka adalah properti dari angka itu, dan terus terang yang akan membuat "properti" kehilangan semua makna, hampir semuanya akan menjadi properti.

Timbal balik adalah fungsi murni dari sebuah pos, sama seperti negatif dari suatu angka adalah fungsi murni dari nomor tersebut.

Sebagai aturan praktis, jika pengaturannya tidak masuk akal, itu mungkin bukan properti. Pengaturan itu masih dapat dianulir tentu saja, tetapi menambahkan setter harus secara teori memungkinkan dan bermakna.

Sekarang, dalam beberapa bahasa, mungkin masuk akal untuk memilikinya sebagai properti sebagaimana ditentukan dalam konteks bahasa itu, jika itu membawa beberapa keuntungan karena mekanisme bahasa itu. Misalnya, jika Anda ingin menyimpannya ke database, mungkin masuk akal untuk menjadikannya sebagai properti jika memungkinkan penanganan otomatis oleh kerangka kerja ORM. Atau mungkin itu adalah bahasa di mana tidak ada perbedaan antara properti dan fungsi anggota tanpa parameter (saya tidak tahu bahasa seperti itu, tapi saya yakin ada beberapa). Kemudian tergantung pada perancang API dan dokumenter untuk membedakan antara fungsi dan properti.


Sunting: Khusus untuk C #, saya akan melihat kelas yang ada, terutama yang ada di Perpustakaan Standar.

  • Ada BigIntegerdan Complexstruktur. Tetapi mereka adalah struktur, dan hanya memiliki fungsi statis, dan semua properti memiliki tipe yang berbeda. Jadi tidak banyak desain yang membantu untuk Headingkelas Anda .
  • Math.NET Numerics memiliki Vectorkelas , yang memiliki sedikit properti, dan tampaknya cukup dekat dengan Anda Heading. Jika Anda melakukannya, maka Anda akan memiliki fungsi untuk timbal balik, bukan properti.

Anda mungkin ingin melihat perpustakaan yang benar-benar digunakan oleh kode Anda, atau kelas serupa yang ada. Cobalah untuk tetap konsisten, itu biasanya yang terbaik, jika satu cara tidak jelas benar dan cara lain salah.

Hyde
sumber
-1

Pertanyaan yang sangat menarik. Saya melihat tidak ada pelanggaran SOLID + KERING + CIUMAN dalam apa yang Anda usulkan, tapi tetap saja baunya tidak enak.

Metode yang mengembalikan instance kelas disebut konstruktor , kan? jadi Anda membuat contoh baru dari metode non-konstruktor. Ini bukan akhir dunia, tetapi sebagai klien, saya tidak akan mengharapkan itu. Ini biasanya dilakukan dalam keadaan tertentu: pabrik atau, kadang-kadang, metode statis dari kelas yang sama (berguna untuk lajang dan untuk ... programmer yang tidak terlalu berorientasi objek?)

Plus: apa yang terjadi jika Anda memohon getReciprocal()pada objek yang dikembalikan? Anda mendapatkan instance kelas yang lain, itu bisa jadi salinan yang tepat dari yang pertama! Dan jika Anda memohon getReciprocal()pada satu itu? Obyek siapa saja yang tergeletak?

Sekali lagi: bagaimana jika Invoker yang membutuhkan timbal balik nilai (sebagai skalar, maksudku)? getReciprocal()->getValue()? kami melanggar Hukum Demeter untuk keuntungan kecil.

Gianluigi Zane Zanettini
sumber
Saya dengan senang hati menerima argumen balasan dari downvoter, terima kasih!
Dr. Gianluigi Zane Zanettini
1
Saya tidak mengundurkan diri tetapi saya curiga alasannya mungkin karena fakta itu tidak banyak menambah sisa jawaban, tetapi saya mungkin salah.
53777A
2
SOLID dan KISS sering saling bertentangan.
whatsisname