Primitif vs Kelas untuk mewakili objek domain sederhana?


14

Apa pedoman umum atau aturan praktis kapan harus menggunakan objek spesifik domain vs String atau angka biasa?

Contoh:

  • Kelas usia vs Integer?
  • FirstName class vs String?
  • UniqueID vs String
  • Kelas PhoneNumber vs String vs Long?
  • Kelas DomainName vs String?

Saya pikir sebagian besar praktisi OOP pasti akan mengatakan kelas khusus untuk PhoneNumber dan DomainName. Semakin banyak aturan tentang apa yang membuatnya valid dan bagaimana membandingkannya, membuat kelas sederhana menjadi lebih mudah dan lebih aman untuk dihadapi. Tetapi untuk tiga yang pertama ada lebih banyak perdebatan.

Saya tidak pernah menemukan kelas "Usia" tetapi orang dapat berargumen bahwa itu masuk akal mengingat itu harus non-negatif (oke saya tahu Anda bisa berdebat untuk usia negatif tapi itu contoh yang baik bahwa itu hampir setara dengan bilangan bulat primitif).

String biasa mewakili "Nama Depan" tetapi tidak sempurna karena String kosong adalah String yang valid tetapi bukan nama yang valid. Perbandingan biasanya dilakukan mengabaikan kasus. Tentu ada metode untuk memeriksa apakah ada yang kosong, bandingkan case-insensitive, dll tetapi itu mengharuskan konsumen untuk melakukan ini.

Apakah jawabannya tergantung pada lingkungan? Saya terutama peduli dengan perangkat lunak perusahaan / bernilai tinggi yang akan hidup dan dipelihara selama lebih dari satu dekade.

Mungkin saya terlalu memikirkan ini tetapi saya benar-benar ingin tahu apakah ada yang punya aturan kapan harus memilih kelas vs primitif.


2
Kelas usia bukanlah ide yang baik jika dapat diganti dengan bilangan bulat. Jika dibutuhkan tanggal lahir di konstruktor dan memiliki metode getCurrentAge () dan getAgeAt (Date date) maka kelas memiliki makna. Tapi itu tidak bisa diganti dengan integer.
kiwiron

5
"Suatu string kadang-kadang bukan string. Model itu sesuai." Ada posting bagus oleh Mark Seemann tentang 'obsesi primitif': blog.ploeh.dk/2015/01/19/…
Serhii Shushliapin

4
Sebenarnya kelas Age memang masuk akal dengan cara yang aneh. Definisi umum Barat tentang Usia adalah nol saat lahir dan tambahkan 1 pada ulang tahun Anda berikutnya. Metodologi Asia Timur adalah bahwa Anda berusia 1 tahun dan 1 ditambahkan pada hari Tahun Baru berikutnya. Jadi tergantung pada lokal Anda, usia Anda dapat dihitung secara berbeda. EG dari en.wikipedia.org/wiki/East_Asian_age_reckoning people may be one or two years older in Asian reckoning than in the western age system
Peter M

@ Peter, itu info bagus! Tanggal / Waktu dan sekarang bertambah. Itu seharusnya konsep sederhana, tetapi ini membuktikan ada lebih banyak kerumitan di dunia daripada yang biasa kita tangani.
Machado

6
Saya melihat banyak tulisan di bawah pertanyaan ini, tetapi jawabannya tidak terlalu rumit. Anda menggunakan Agekelas alih-alih bilangan bulat ketika Anda membutuhkan perilaku tambahan yang akan diberikan oleh kelas tersebut.
Robert Harvey

Jawaban:


20

Apa pedoman umum atau aturan praktis kapan harus menggunakan objek spesifik domain vs String atau angka biasa?

Pedoman umum adalah bahwa Anda ingin memodelkan domain Anda dalam bahasa spesifik domain.

Pertimbangkan: mengapa kita menggunakan integer? Kami dapat mewakili semua bilangan bulat dengan string dengan mudah. Atau dengan byte.

Jika kami pemrograman dalam bahasa agnostik domain yang menyertakan tipe primitif untuk integer dan usia, mana yang akan Anda pilih?

Apa yang sebenarnya terjadi adalah sifat "primitif" dari tipe-tipe tertentu adalah suatu kecelakaan dari pilihan bahasa untuk implementasi kita.

Angka, khususnya, biasanya membutuhkan konteks tambahan. Usia bukan hanya angka tetapi juga memiliki dimensi (waktu), satuan (tahun?), Aturan pembulatan! Menambahkan usia bersama-sama masuk akal dengan cara yang tidak menambahkan usia ke uang.

Membuat perbedaan jenis memungkinkan kita untuk memodelkan perbedaan antara alamat email yang tidak diverifikasi dan alamat email yang diverifikasi .

Kecelakaan bagaimana nilai-nilai ini direpresentasikan dalam memori adalah salah satu bagian yang paling tidak menarik. Model domain tidak peduli CustomerIddengan int, atau String, atau UUID / GUID, atau simpul JSON. Itu hanya menginginkan biaya.

Apakah kita benar-benar peduli apakah bilangan bulat adalah big endian atau sedikit endian? Apakah kita peduli jika Listkita telah lulus adalah abstraksi atas sebuah array, atau grafik? Ketika kami menemukan bahwa aritmatika presisi ganda tidak efisien, dan bahwa kami perlu mengubah ke representasi floating point, haruskah model domain peduli ?

Parnas, pada tahun 1972 , menulis

Kami mengusulkan sebaliknya bahwa seseorang mulai dengan daftar keputusan desain yang sulit atau keputusan desain yang cenderung berubah. Setiap modul kemudian dirancang untuk menyembunyikan keputusan seperti itu dari yang lain.

Dalam arti tertentu, tipe nilai spesifik domain yang kami perkenalkan adalah modul yang mengisolasi keputusan kami tentang representasi data yang mendasarinya yang harus digunakan.

Jadi sisi baiknya adalah modularitas - kami mendapatkan desain di mana lebih mudah untuk mengelola ruang lingkup perubahan. Kelemahannya adalah biaya - lebih banyak pekerjaan untuk membuat jenis yang dipesan lebih dahulu yang Anda butuhkan, memilih jenis yang tepat membutuhkan memperoleh pemahaman yang lebih dalam tentang domain. Jumlah pekerjaan yang diperlukan untuk membuat modul nilai akan tergantung pada dialek Blub lokal Anda .

Istilah lain dalam persamaan mungkin mencakup perkiraan masa pakai solusi (pemodelan yang cermat untuk script ware yang akan dijalankan setelah pengembalian investasi yang buruk), seberapa dekat domain dengan kompetensi inti bisnis.

Satu kasus khusus yang mungkin kita pertimbangkan adalah komunikasi lintas batas . Kami tidak ingin berada dalam situasi di mana perubahan pada satu unit yang dapat digunakan membutuhkan perubahan yang terkoordinasi dengan unit yang bisa digunakan lainnya. Jadi pesan cenderung lebih fokus pada representasi, tanpa pertimbangan invarian atau perilaku spesifik domain. Kami tidak akan mencoba untuk mengomunikasikan "nilai ini harus benar-benar positif" dalam format pesan, melainkan mengkomunikasikan perwakilannya pada kawat, dan menerapkan validasi ke representasi itu di batas domain.


Poin bagus tentang menyembunyikan (atau mengabstraksikan) hal-hal yang sulit dan cenderung berubah.
user949300

4

Anda berpikir tentang abstraksi, dan itu hebat. Namun, hal-hal yang tercantum dalam daftar Anda bagi saya sebagian besar merupakan atribut individu dari sesuatu yang lebih besar (tentu saja tidak semua hal yang sama).

Apa yang saya rasakan lebih penting adalah memberikan abstraksi yang mengelompokkan atribut-atribut bersama dan memberi mereka rumah yang layak, seperti kelas Person, sehingga programmer klien tidak harus berurusan dengan banyak atribut, tetapi lebih pada satu abstraksi level yang lebih tinggi.

Setelah Anda memiliki abstraksi ini, Anda tidak perlu abstraksi tambahan untuk atribut yang hanya berupa nilai. Kelas Person dapat mengadakan BirthDate sebagai Tanggal (primitif), bersama dengan PhoneNumber sebagai daftar string (primitif), dan Nama sebagai string (primitif).

Jika Anda memiliki nomor telepon dengan kode pemformatan, sekarang dua atribut, dan akan pantas kelas untuk nomor telepon (karena kemungkinan akan memiliki beberapa perilaku juga, seperti hanya mendapatkan nomor vs. mendapatkan nomor telepon yang diformat).

Jika Anda memiliki Nama sebagai banyak atribut (mis. Awalan / judul, pertama, terakhir, akhiran) yang pantas digunakan kelas (seperti sekarang Anda memiliki beberapa perilaku seperti mendapatkan nama lengkap sebagai string vs. mendapatkan potongan yang lebih kecil, judul dll. .).


1

Anda harus memodelkan sesuai kebutuhan, jika Anda hanya menginginkan beberapa data, Anda dapat menggunakan tipe primitif tetapi jika Anda ingin melakukan lebih banyak operasi pada data ini, ia harus dimodelkan dalam kelas-kelas tertentu, misalnya (saya setuju dengan @kiwiron dalam komentarnya) sebuah Kelas usia tidak masuk akal, tetapi akan lebih baik untuk menggantinya dengan kelas Tanggal Lahir dan Anda meletakkan metode ini di sana.

Manfaat memodelkan konsep-konsep ini di dalam kelas adalah Anda menerapkan kekayaan model Anda, misalnya, alih-alih Anda memiliki metode yang menerima Tanggal apa pun, pengguna dapat mengisinya dengan tanggal apa pun, tanggal mulai WW II atau tanggal akhir perang Dingin, tanggal ini masih merupakan tanggal lahir yang valid. Anda dapat menggantinya dengan Tanggal Lahir sehingga dalam hal ini, Anda menerapkan metode Anda untuk menerima Tanggal Lahir agar tidak menerima Tanggal apa pun.

Untuk Firstname saya tidak melihat banyak manfaatnya, UniqueID juga, sedikit PhoneNumber, Anda dapat meminta itu memberi Anda negara dan wilayah misalnya karena secara umum PhoneNumber merujuk ke negara dan wilayah, beberapa operator menggunakan Sufiks angka yang telah ditentukan sebelumnya untuk mengklasifikasikan Ponsel , Office ... angka, sehingga Anda dapat menambahkan metode ini ke kelas itu, sama untuk DomainName.

Untuk lebih jelasnya saya sarankan Anda untuk melihat Value Objects di DDD (Domain Driven Design).


1

Tergantung pada apakah Anda memiliki perilaku (yang perlu Anda pertahankan dan / atau ubah) terkait dengan data itu atau tidak.

Singkatnya, jika itu hanya sepotong data yang dilemparkan tanpa banyak perilaku yang terkait dengannya, gunakan tipe primitif atau, untuk bagian data yang agak lebih kompleks, struktur data (sebagian besar tidak berperilaku) (misalnya kelas dengan hanya pengambil dan setter).

Sebaliknya, jika Anda menemukan bahwa, untuk membuat keputusan, Anda memeriksa atau memanipulasi data ini di berbagai tempat dalam kode Anda, tempat-tempat yang sering tidak saling berdekatan - kemudian mengubahnya menjadi objek yang tepat dengan mendefinisikan kelas dan menempatkan perilaku yang relevan di dalamnya sebagai metode. Jika karena alasan tertentu Anda merasa tidak masuk akal untuk mendefinisikan kelas seperti itu, alternatifnya adalah mengkonsolidasikan perilaku ini menjadi semacam kelas "layanan" yang beroperasi pada data ini.


1

Contoh Anda lebih mirip properti atau atribut dari sesuatu yang lain. Apa itu artinya adalah masuk akal untuk memulai hanya dengan yang primitif. Pada titik tertentu Anda perlu menggunakan primitif, jadi saya biasanya menyimpan kompleksitas ketika saya benar-benar membutuhkannya.

Sebagai contoh, katakanlah saya membuat game dan saya tidak perlu khawatir tentang karakter yang menua. Menggunakan bilangan bulat untuk itu sangat masuk akal. Usia dapat digunakan dalam pemeriksaan sederhana untuk melihat apakah karakter diizinkan melakukan sesuatu atau tidak.

Seperti yang ditunjukkan orang lain, usia bisa menjadi subjek yang rumit tergantung pada lokasi Anda. Jika Anda perlu merekonsiliasi usia di berbagai tempat, dll. Maka sangat masuk akal untuk memperkenalkan Agekelas yang memiliki DateOfBirthdan Localesebagai properti. Kelas Umur itu juga dapat menghitung usia saat ini di stempel waktu tertentu juga.

Jadi ada alasan untuk mempromosikan primitif ke kelas, tetapi mereka sangat spesifik aplikasi. Berikut ini beberapa contohnya:

  • Anda memiliki logika validasi khusus yang harus diterapkan di mana pun jenis informasi yang digunakan (mis. Nomor telepon, alamat email).
  • Anda memiliki logika pemformatan khusus yang digunakan untuk membuat manusia membaca (mis. Alamat IP4 dan IP6)
  • Anda memiliki serangkaian fungsi yang semuanya berhubungan dengan tipe objek itu
  • Anda memiliki aturan khusus untuk membandingkan jenis nilai yang serupa

Pada dasarnya, jika Anda memiliki banyak aturan tentang bagaimana suatu nilai diperlakukan sehingga Anda ingin digunakan secara konsisten di seluruh proyek Anda, buat kelas. Jika Anda bisa hidup dengan nilai-nilai sederhana karena itu tidak terlalu penting untuk fungsionalitas, gunakan primitif.


1

Pada akhirnya, sebuah objek hanyalah saksi bahwa beberapa proses benar-benar dijalankan .

Sebuah primitif intberarti bahwa beberapa proses mem-nolkan 4 byte memori, dan kemudian membalik bit-bit yang diperlukan untuk mewakili beberapa bilangan bulat dalam kisaran -2,147,483,648 hingga 2,147,483,647; yang bukan pernyataan kuat tentang konsep usia. Demikian juga, a (non-null) System.Stringberarti bahwa beberapa byte dialokasikan dan bit dibalik untuk mewakili urutan karakter Unicode sehubungan dengan beberapa pengkodean.

Jadi, ketika memutuskan bagaimana mewakili objek domain Anda, Anda harus memikirkan proses yang berfungsi sebagai saksi. Jika FirstNamebenar - benar harus berupa string yang tidak kosong, maka itu harus berdiri sebagai saksi bahwa sistem menjalankan beberapa proses yang memastikan bahwa setidaknya satu karakter non-spasi dibuat dalam urutan karakter yang diserahkan kepada Anda.

Demikian pula, suatu Ageobjek mungkin harus menjadi saksi bahwa beberapa proses menghitung perbedaan antara dua tanggal. Karena intsumumnya bukan hasil perhitungan perbedaan antara dua tanggal, mereka sebenarnya tidak cukup untuk mewakili usia.

Jadi, cukup jelas bahwa Anda hampir selalu ingin membuat kelas (yang hanya sebuah program) untuk setiap objek domain. Jadi apa masalahnya? Masalahnya adalah bahwa C # (yang saya suka) dan Java menuntut terlalu banyak upacara dalam menciptakan kelas. Tapi, kita bisa membayangkan sintaks alternatif yang akan membuat mendefinisikan objek domain jauh lebih mudah:

//hypothetical syntax
class Age = |start:date - end:date|.Years

(* ML-like syntax *)
type Age(x, y) = datediff(year, x, y)

//C#-like syntax with primary constructors and expression-bodied classes
class Age(DateTime x, DateTime y) => implicit operator int (Age a) => Abs((y - x).Years);

F #, misalnya, sebenarnya memiliki sintaks yang bagus untuk ini dalam bentuk Pola Aktif :

//Impossible to produce an `Age` without first computing the difference of two dates
//But, any pair (tuple) of dates is implicitly converted to an `Age` when needed.

let (|Age|) (x, y) =  (date_diff y x).Days / 365

Intinya adalah hanya karena bahasa pemrograman Anda membuatnya sulit untuk mendefinisikan objek domain tidak berarti bahwa itu sebenarnya adalah ide yang buruk untuk melakukannya.


† Kami juga menyebut hal-hal ini memposting kondisi , tetapi saya lebih suka istilah "saksi" karena berfungsi sebagai bukti bahwa beberapa proses dapat dan memang berjalan.


0

Pikirkan tentang apa yang Anda dapatkan dari menggunakan kelas vs primitif. Jika menggunakan kelas dapat membantu Anda

  • validasi
  • pemformatan
  • metode lain yang bermanfaat
  • ketik keamanan (mis. dengan PhoneNumberkelas, seharusnya tidak mungkin bagi saya untuk menetapkannya ke string atau string acak (mis. "mentimun") di mana saya mengharapkan nomor telepon)
  • manfaat lainnya

dan manfaat itu membuat hidup Anda lebih mudah, meningkatkan kualitas kode, mudah dibaca dan lebih berat daripada rasa sakit menggunakan hal-hal seperti itu, lakukanlah. Jika tidak, tetap dengan primitif. Jika sudah dekat, buat panggilan penilaian.

Sadari juga bahwa kadang-kadang penamaan yang baik bisa menanganinya (mis. String bernama firstNamevs. membuat seluruh kelas yang hanya membungkus properti string). Kelas bukan satu-satunya cara untuk menyelesaikan masalah.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.