Apakah penggunaan operator casting eksplisit saya masuk akal atau hack yang buruk?

24

Saya memiliki objek besar:

class BigObject{
    public int Id {get;set;}
    public string FieldA {get;set;}
    // ...
    public string FieldZ {get;set;}
}

dan objek khusus, seperti DTO:

class SmallObject{
    public int Id {get;set;}
    public EnumType Type {get;set;}
    public string FieldC {get;set;}
    public string FieldN {get;set;}
}

Saya pribadi menemukan konsep yang secara eksplisit memasukkan BigObject ke dalam SmallObject - mengetahui bahwa itu adalah operasi satu arah, kehilangan data - sangat intuitif dan mudah dibaca:

var small = (SmallObject) bigOne;
passSmallObjectToSomeone(small);

Diimplementasikan menggunakan operator eksplisit:

public static explicit operator SmallObject(BigObject big){
    return new SmallObject{
        Id = big.Id,
        FieldC = big.FieldC,
        FieldN = big.FieldN,
        EnumType = MyEnum.BigObjectSpecific
    };
}

Sekarang, saya dapat membuat SmallObjectFactorykelas dengan FromBigObject(BigObject big)metode, yang akan melakukan hal yang sama, menambahkannya ke injeksi dependensi dan memanggilnya saat diperlukan ... tetapi bagi saya sepertinya lebih rumit dan tidak perlu.

PS Saya tidak yakin apakah ini relevan, tetapi akan ada OtherBigObjectyang juga akan dapat dikonversi menjadi SmallObject, pengaturan berbeda EnumType.

Gerino
sumber
4
Kenapa bukan konstruktor?
edc65
2
Atau metode pabrik statis?
Brian Gordon
Mengapa Anda membutuhkan kelas pabrik, atau injeksi ketergantungan? Anda telah membuat dikotomi palsu di sana.
user253751
1
@immibis - karena saya entah bagaimana tidak memikirkan apa yang @Telastyn usulkan: .ToSmallObject()metode (atau GetSmallObject()). Sebuah alasan sesaat - saya tahu ada sesuatu yang salah dengan pemikiran saya, jadi saya sudah bertanya kepada kalian :)
Gerino
3
Ini kedengarannya seperti kasus penggunaan yang sempurna untuk antarmuka ISmallObject yang hanya diimplementasikan oleh BigObject sebagai sarana untuk menyediakan akses ke kumpulan data terbatas / perilaku yang luas. Terutama ketika dikombinasikan dengan ide @ Telastyn tentang ToSmallObjectmetode.
Marjan Venema

Jawaban:

0

Tidak ada jawaban lain yang benar menurut pendapat saya. Dalam pertanyaan stackoverflow ini, jawaban dengan suara terbanyak berpendapat bahwa kode pemetaan harus dijauhkan dari domain. Untuk menjawab pertanyaan Anda, tidak - penggunaan operator pemeran Anda tidak bagus. Saya akan menyarankan untuk membuat layanan pemetaan yang terletak di antara DTO Anda dan objek domain Anda, atau Anda bisa menggunakan automapper untuk itu.

Esben Skov Pedersen
sumber
Ini ide yang hebat. Saya sudah memiliki Automapper, jadi sangat mudah. Satu-satunya masalah yang saya miliki: bukankah seharusnya ada jejak yang terkait dengan BigObject dan SmallObject?
Gerino
1
Tidak, saya tidak melihat keuntungan dengan menggabungkan BigObject dan SmallObject lebih jauh bersama-sama selain layanan pemetaan.
Esben Skov Pedersen
7
Sangat? Automapper adalah solusi Anda untuk masalah desain?
Telastyn
1
BigObject dapat dipetakan ke SmallObject, mereka tidak benar-benar terkait satu sama lain dalam arti OOP klasik, dan kode mencerminkan ini (kedua objek ada di domain, kemampuan pemetaan diatur dalam pemetaan konfigurasi bersama dengan banyak lainnya). Itu menghapus kode yang meragukan (menimpa operator malang saya), ia meninggalkan model bersih (tidak ada metode di dalamnya), jadi ya, tampaknya menjadi solusi.
Gerino
2
@EsbenSkovPedersen Solusi ini seperti menggunakan buldoser untuk menggali lubang untuk memasang kotak surat Anda. Untungnya OP ingin menggali halaman, jadi buldoser berfungsi dalam hal ini. Namun, saya tidak akan merekomendasikan solusi ini secara umum .
Neil
81

Itu ... Tidak hebat. Saya telah bekerja dengan kode yang melakukan trik pintar ini dan itu menyebabkan kebingungan. Setelah semua, Anda akan berharap untuk dapat hanya menetapkan variabel BigObjectke dalam suatu objek SmallObjectjika objek cukup terkait untuk melemparkannya. Itu tidak berfungsi - Anda mendapatkan kesalahan kompiler jika Anda mencoba karena sejauh menyangkut sistem tipe, mereka tidak berhubungan. Juga agak tidak disukai bagi operator casting untuk membuat objek baru.

Saya akan merekomendasikan .ToSmallObject()metode sebagai gantinya. Lebih jelas tentang apa yang sebenarnya terjadi dan tentang sebagai verbose.

Telastyn
sumber
18
Doh ... ToSmallObject () sepertinya pilihan yang paling jelas. Terkadang yang paling jelas adalah yang paling sulit dipahami;)
Gerino
6
mildly distastefuladalah pernyataan yang meremehkan. Sangat disayangkan bahwa bahasa ini memungkinkan hal semacam ini terlihat seperti tipografi. Tidak ada yang akan menduga itu adalah transformasi objek yang sebenarnya kecuali mereka menulisnya sendiri. Dalam tim satu orang, baik-baik saja. Jika Anda berkolaborasi dengan siapa pun, dalam kasus terbaik itu adalah buang-buang waktu karena Anda harus berhenti dan mencari tahu apakah itu benar-benar pemain, atau jika itu salah satu dari transformasi gila itu.
Kent A.
3
@ Telastyn Setuju bahwa itu bukan bau kode yang paling mengerikan. Tetapi penciptaan tersembunyi dari objek baru dari operasi yang kebanyakan programmer pahami sebagai instruksi kepada kompiler untuk memperlakukan objek yang sama sebagai tipe yang berbeda, tidak baik bagi siapa pun yang harus bekerja dengan kode Anda setelah Anda. :)
Kent A.
4
+1 untuk .ToSmallObject(). Hampir tidak pernah Anda menimpa operator.
ytoledano
6
@dorus - setidaknya dalam. NET, Getmenyiratkan mengembalikan sesuatu yang sudah ada. Kecuali jika Anda mengganti operasi pada objek kecil, dua Getpanggilan akan mengembalikan objek yang tidak setara, menyebabkan kebingungan / bug / wtfs.
Telastyn
11

Sementara saya dapat melihat mengapa Anda perlu memiliki SmallObject, saya akan mendekati masalahnya secara berbeda. Pendekatan saya untuk masalah jenis ini adalah menggunakan Facade . Tujuan utamanya adalah untuk merangkum BigObjectdan hanya menyediakan anggota khusus. Dengan cara ini, ini adalah antarmuka baru pada instance yang sama, dan bukan salinan. Tentu saja Anda juga bisa ingin melakukan salinan, tetapi saya akan merekomendasikan Anda melakukannya melalui metode yang dibuat untuk tujuan itu dalam kombinasi dengan Facade (misalnya return new SmallObject(instance.Clone())).

Facade memiliki sejumlah keunggulan lain, yaitu memastikan bahwa bagian-bagian tertentu dari program Anda hanya dapat memanfaatkan anggota yang disediakan melalui fasad Anda, secara efektif menjamin bahwa ia tidak dapat memanfaatkan apa yang seharusnya tidak diketahui. Selain itu, ini juga memiliki keuntungan luar biasa karena Anda memiliki lebih banyak fleksibilitas dalam mengubah BigObjectpemeliharaan di masa mendatang tanpa harus terlalu khawatir tentang bagaimana itu digunakan di seluruh program Anda. Selama Anda bisa meniru perilaku lama dalam beberapa bentuk atau lainnya, Anda dapat membuat SmallObjectpekerjaan sama seperti sebelumnya tanpa harus mengubah program Anda di mana pun BigObjectakan digunakan.

Catatan, ini berarti BigObjecttidak tergantung pada SmallObjectmelainkan sebaliknya (seperti yang seharusnya menurut pendapat saya yang sederhana).

Neil
sumber
Satu-satunya keuntungan yang Anda sebutkan bahwa fasad memiliki lebih dari menyalin bidang ke kelas baru adalah menghindari penyalinan (yang mungkin bukan masalah kecuali objek memiliki jumlah bidang yang tidak masuk akal). Di sisi lain itu memiliki kelemahan bahwa Anda harus memodifikasi kelas asli setiap kali Anda perlu mengkonversi ke kelas baru, tidak seperti metode konversi statis.
Doval
@Doval Saya kira itulah intinya. Anda tidak akan mengonversinya ke kelas baru. Anda akan membuat fasad lain jika itu yang Anda butuhkan. Perubahan yang dilakukan pada BigObject hanya perlu diterapkan ke kelas Facade dan tidak di mana-mana di mana ia digunakan.
Neil
Satu perbedaan menarik antara pendekatan ini dan jawaban Telastyn adalah apakah tanggung jawab untuk menghasilkan SmallObjectkebohongan dengan SmallObjectatau BigObject. Secara default, pendekatan ini memaksa SmallObjectuntuk menghindari ketergantungan pada anggota pribadi / terlindungi BigObject. Kita dapat melangkah lebih jauh dan menghindari ketergantungan pada anggota pribadi / yang dilindungi SmallObjectdengan menggunakan ToSmallObjectmetode ekstensi.
Brian
@Brian Anda berisiko mengacaukannya BigObject. Jika Anda ingin melakukan hal serupa, Anda akan dijamin untuk membuat ToAnotherObjectmetode ekstensi di dalam BigObject? Ini seharusnya tidak menjadi perhatian BigObjectkarena, mungkin sudah cukup besar. Ini juga memungkinkan Anda untuk terpisah BigObjectdari penciptaan dependensinya, artinya Anda dapat menggunakan pabrik dan sejenisnya. Pendekatan lain sangat pasangan BigObjectdan SmallObject. Itu mungkin baik-baik saja dalam kasus khusus ini, tetapi itu bukan praktik terbaik menurut pendapat saya yang sederhana.
Neil
1
@ Neil Sebenarnya, Brian menjelaskan salah, tapi dia adalah benar - metode penyuluhan yang menyingkirkan kopling. Ini tidak lagi BigObjectdigabungkan ke SmallObject, itu hanya metode statis di suatu tempat yang mengambil argumen BigObjectdan kembali SmallObject. Metode penyuluhan sebenarnya hanyalah gula sintaksis untuk memanggil metode statis dengan cara yang lebih baik. Metode ekstensi bukan bagian dari BigObject, itu adalah metode statis yang sepenuhnya terpisah. Ini sebenarnya penggunaan metode ekstensi yang cukup bagus, dan sangat berguna untuk konversi DTO secara khusus.
Luaan
6

Ada konvensi yang sangat kuat yang menggunakan tipe referensi yang bisa berubah adalah pelestarian identitas. Karena sistem umumnya tidak mengizinkan operator casting yang ditentukan pengguna dalam situasi di mana objek dari tipe sumber dapat ditugaskan ke referensi dari tipe tujuan, hanya ada beberapa kasus di mana operasi casting yang ditentukan pengguna akan masuk akal untuk referensi yang bisa berubah-ubah jenis.

Saya akan menyarankan sebagai persyaratan yang, diberikan x=(SomeType)foo;diikuti beberapa saat kemudian y=(SomeType)foo;, dengan kedua gips diterapkan pada objek yang sama, x.Equals(y)harus selalu dan selamanya benar, bahkan jika objek tersebut dimodifikasi di antara dua gips. Situasi seperti itu dapat berlaku jika misalnya satu memiliki sepasang objek dari jenis yang berbeda, masing-masing memiliki referensi yang tidak dapat diubah ke yang lain, dan melemparkan salah satu objek ke jenis lainnya akan mengembalikan instance yang dipasangkan. Itu juga bisa berlaku dengan tipe yang berfungsi sebagai pembungkus untuk objek yang bisa berubah, asalkan identitas objek yang dibungkus tidak dapat diubah, dan dua pembungkus dari tipe yang sama akan melaporkan diri mereka sama jika mereka membungkus koleksi yang sama.

Contoh khusus Anda menggunakan kelas yang bisa berubah, tetapi tidak mempertahankan bentuk identitas apa pun; dengan demikian, saya akan menyarankan bahwa ini bukan penggunaan yang tepat dari operator casting.

supercat
sumber
1

Mungkin baik-baik saja.

Masalah dengan contoh Anda adalah bahwa Anda menggunakan nama contoh-ish tersebut. Mempertimbangkan:

SomeMethod(long longNum)
{
  int num = (int)longNum;
  /* ... */

Sekarang, ketika Anda punya ide yang baik apa yang panjang dan int berarti, maka kedua pemain implisit intuntuk longdan cor eksplisit dari longke intcukup dimengerti. Ini juga bisa dimengerti bagaimana 3menjadi 3dan hanya cara lain untuk bekerja dengannya 3. Dapat dimengerti bagaimana ini akan gagal dengan int.MaxValue + 1dalam konteks yang diperiksa. Bahkan bagaimana itu akan bekerja dengan int.MaxValue + 1dalam konteks yang tidak dicentang untuk menghasilkanint.MinValue bukanlah hal yang paling sulit untuk dilakukan

Demikian juga, ketika Anda melemparkan secara implisit ke tipe basis atau secara eksplisit ke tipe turunan, hal ini dapat dimengerti oleh siapa pun yang tahu cara kerja pewarisan apa yang terjadi, dan apa hasilnya nanti (atau bagaimana itu mungkin gagal).

Sekarang, dengan BigObject dan SmallObject saya tidak memiliki perasaan tentang bagaimana hubungan ini bekerja. Jika tipe nyata Anda di mana sedemikian rupa sehingga hubungan casting di mana jelas, maka casting mungkin memang ide yang baik, meskipun banyak waktu, mungkin sebagian besar, jika ini yang terjadi maka harus tercermin dalam hierarki kelas dan casting berbasis warisan yang normal akan cukup.

Jon Hanna
sumber
Sebenarnya mereka tidak lebih dari apa yang disediakan - tetapi misalnya BigObjectmungkin menggambarkan Employee {Name, Vacation Days, Bank details, Access to different building floors etc.}, dan SmallObjectmungkin a MoneyTransferRecepient {Name, Bank details}. Ada terjemahan langsung dari Employeeke MoneyTransferRecepient, dan tidak ada alasan untuk mengirim ke aplikasi perbankan lebih dari data yang diperlukan.
Gerino