Apakah Anda biasanya mengirim objek atau variabel anggotanya ke dalam fungsi?


31

Praktek yang diterima secara umum di antara kedua kasus ini:

function insertIntoDatabase(Account account, Otherthing thing) {
    database.insertMethod(account.getId(), thing.getId(), thing.getSomeValue());
}

atau

function insertIntoDatabase(long accountId, long thingId, double someValue) {
    database.insertMethod(accountId, thingId, someValue);
}

Dengan kata lain apakah umumnya lebih baik untuk melewatkan seluruh objek di sekitar atau hanya bidang yang Anda butuhkan?


5
Itu sepenuhnya akan tergantung pada apa fungsi untuk dan bagaimana ia berhubungan (atau tidak berhubungan) dengan objek yang bersangkutan.
MetaFight

Itulah masalahnya. Saya tidak tahu kapan saya akan menggunakan yang satu atau yang lain. Saya merasa seperti saya selalu dapat mengubah kode untuk mengakomodasi kedua pendekatan tersebut.
AJJ

1
Dalam istilah API (dan sama sekali tidak melihat implementasinya), yang pertama bersifat abstrak dan berorientasi domain (yang baik), sedangkan yang kedua tidak (yang buruk).
Erik Eidt

1
Pendekatan pertama akan lebih OO 3-tier. Tetapi harus lebih dari itu dengan menghilangkan kata database dari metode. Itu harus "Store" atau "Persist" dan melakukan Akun atau Hal (tidak keduanya) Sebagai klien dari lapisan ini Anda tidak harus menyadari media penyimpanan. Saat mengambil Akun, Anda harus memberikan id atau kombinasi nilai properti (bukan nilai bidang) untuk mengidentifikasi objek yang diinginkan. Atau / dan metode penghitungan yang melewati semua akun.
Martin Maat

1
Biasanya, keduanya akan salah (atau, lebih tepatnya, kurang optimal). Bagaimana sebuah objek harus diserialisasi ke dalam database harus menjadi properti (fungsi anggota) dari objek, karena biasanya secara langsung tergantung pada variabel anggota objek. Jika Anda mengubah anggota objek, Anda juga perlu mengubah metode serialisasi. Itu bekerja lebih baik jika itu adalah bagian dari objek
tofro

Jawaban:


24

Tidak ada yang umumnya lebih baik dari yang lain. Ini panggilan penilaian yang harus Anda buat berdasarkan kasus per kasus.

Namun dalam praktiknya, ketika Anda berada dalam posisi yang benar-benar dapat membuat keputusan ini, itu karena Anda harus memutuskan lapisan mana dalam arsitektur program keseluruhan yang harus memecah objek menjadi primitif, jadi Anda harus memikirkan seluruh panggilan stack , bukan hanya metode yang Anda gunakan saat ini. Mungkin pemecahannya harus dilakukan di suatu tempat, dan itu tidak masuk akal (atau itu akan cenderung rawan kesalahan) untuk melakukannya lebih dari sekali. Pertanyaannya adalah di mana tempat itu seharusnya.

Cara termudah untuk membuat keputusan ini adalah dengan memikirkan kode apa yang harus atau tidak harus diubah jika objek diubah . Mari sedikit memperluas contoh Anda:

function addWidgetButtonClicked(clickEvent) {
    // get form data
    // get user's account
    insertIntoDatabase(account, data);
}
function insertIntoDatabase(Account account, Otherthing data) {
    // open database connection
    // check data doesn't already exist
    database.insertMethod(account.getId(), data.getId(), data.getSomeValue());
}

vs.

function addWidgetButtonClicked(clickEvent) {
    // get form data
    // get user's account
    insertIntoDatabase(account.getId(), data.getId(), data.getSomeValue());
}
function insertIntoDatabase(long accountId, long dataId, double someValue) {
    // open database connection
    // check data doesn't already exist
    database.insertMethod(accountId, dataId, someValue);
}

Dalam versi pertama, kode UI secara membabi buta melewati dataobjek dan terserah kode database untuk mengekstrak bidang yang berguna darinya. Pada versi kedua, kode UI memecah dataobjek menjadi bidang yang bermanfaat, dan kode database menerimanya secara langsung tanpa mengetahui dari mana asalnya. Implikasi utama adalah bahwa, jika struktur dataobjek berubah dalam beberapa cara, versi pertama hanya memerlukan kode database untuk berubah, sedangkan versi kedua hanya memerlukan kode UI untuk berubah . Yang mana dari keduanya yang benar sebagian besar tergantung pada data apa yang datadikandung oleh objek, tetapi biasanya sangat jelas. Misalnya, jikadataadalah string yang disediakan pengguna seperti "20/05/1999", harusnya tergantung pada kode UI untuk mengonversinya menjadi Datetipe yang tepat sebelum meneruskannya.


8

Ini bukan daftar lengkap, tetapi pertimbangkan beberapa faktor berikut ketika memutuskan apakah suatu objek harus diteruskan ke metode sebagai argumen:

Apakah objek tidak berubah? Apakah fungsinya 'murni'?

Efek samping adalah pertimbangan penting untuk pemeliharaan kode Anda. Ketika Anda melihat kode dengan banyak objek stateable yang dapat diubah yang diedarkan di semua tempat, kode itu sering kurang intuitif (dengan cara yang sama bahwa variabel keadaan global sering menjadi kurang intuitif), dan debugging sering menjadi lebih sulit dan waktu- mengkonsumsi.

Sebagai pedoman praktis, bertujuan untuk memastikan, sejauh memungkinkan secara wajar, bahwa benda apa pun yang Anda lewati ke metode jelas tidak dapat diubah.

Hindari (sekali lagi, sejauh memungkinkan) desain apa pun di mana keadaan argumen diharapkan akan diubah sebagai akibat dari pemanggilan fungsi - salah satu argumen terkuat untuk pendekatan ini adalah Prinsip Least Astonancy ; yaitu seseorang yang membaca kode Anda dan melihat argumen yang dialihkan ke suatu fungsi 'kecil kemungkinannya' untuk mengharapkan keadaannya berubah setelah fungsi tersebut kembali.

Berapa banyak argumen yang sudah dimiliki metode ini?

Metode dengan daftar argumen yang terlalu panjang (bahkan jika sebagian besar argumen memiliki nilai 'default') mulai terlihat seperti bau kode. Namun, kadang-kadang fungsi seperti itu diperlukan, dan Anda mungkin mempertimbangkan untuk membuat kelas yang tujuan utamanya adalah bertindak seperti Obyek Parameter .

Pendekatan ini dapat melibatkan sejumlah kecil pemetaan kode boilerplate tambahan dari objek 'sumber' Anda ke objek parameter Anda, namun itu biaya yang cukup rendah baik dalam hal kinerja dan kompleksitas, dan ada sejumlah manfaat dalam hal decoupling dan objek kekekalan.

Apakah objek yang diteruskan milik secara eksklusif ke "lapisan" dalam aplikasi Anda (misalnya, Model ViewM, atau Entitas ORM?)

Pikirkan Separation of Concerns (SoC) . Kadang-kadang bertanya pada diri sendiri apakah objek "milik" pada lapisan atau modul yang sama di mana metode Anda ada (misalnya perpustakaan pembungkus tangan digulung, atau Lapisan Logika Bisnis inti Anda, dll.) Dapat menginformasikan apakah objek itu benar-benar harus diteruskan ke sana metode.

SoC adalah fondasi yang baik untuk menulis kode modular yang bersih dan longgar. misalnya, objek entitas ORM (pemetaan antara kode Anda dan skema Database Anda) idealnya tidak boleh dilewatkan di lapisan bisnis Anda, atau lebih buruk di lapisan presentasi / UI Anda.

Dalam hal melewatkan data di antara 'lapisan', memiliki parameter data biasa yang dilewatkan ke dalam metode biasanya lebih baik daripada lewat di objek dari lapisan 'salah'. Meskipun mungkin ide yang baik untuk memiliki model terpisah yang ada di lapisan 'kanan' yang bisa Anda petakan.

Apakah fungsinya sendiri terlalu besar dan / atau rumit?

Ketika suatu fungsi membutuhkan banyak item data, mungkin ada baiknya mempertimbangkan apakah fungsi itu mengambil terlalu banyak tanggung jawab; mencari peluang potensial untuk refactor menggunakan objek yang lebih kecil dan fungsi yang lebih pendek, lebih sederhana.

Haruskah fungsi tersebut menjadi objek perintah / permintaan?

Dalam beberapa kasus hubungan antara data dan fungsi mungkin dekat; dalam kasus-kasus tersebut pertimbangkan apakah Obyek Perintah atau Obyek Kueri akan sesuai.

Apakah menambahkan parameter objek ke metode memaksa kelas yang mengandung untuk mengadopsi dependensi baru?

Kadang-kadang argumen terkuat untuk argumen "Data lama polos" hanyalah bahwa kelas penerima sudah mandiri, dan menambahkan parameter objek ke salah satu metodenya akan mencemari kelas (atau jika kelas sudah tercemar, maka itu akan memperburuk entropi yang ada)

Apakah Anda benar-benar harus melewati objek yang lengkap atau hanya perlu sebagian kecil dari antarmuka objek itu?

Pertimbangkan Prinsip Segregasi Antarmuka sehubungan dengan fungsi Anda - yaitu ketika meneruskan objek, itu hanya akan bergantung pada bagian antarmuka argumen yang dibutuhkan (fungsi) itu sebenarnya.


5

Jadi ketika Anda membuat suatu fungsi, Anda secara implisit mendeklarasikan beberapa kontrak dengan kode yang memanggilnya. "Fungsi ini mengambil info ini, dan mengubahnya menjadi hal lain ini (mungkin dengan efek samping)".

Jadi, haruskah kontrak Anda secara logis dengan objek (namun mereka dilaksanakan), atau dengan bidang yang kebetulan menjadi bagian dari objek lain ini. Anda menambahkan penggandengan, tetapi sebagai programmer, Anda yang menentukan di mana tempatnya.

Secara umum , jika tidak jelas, maka nikmatilah data terkecil yang diperlukan agar fungsi berfungsi. Itu sering berarti hanya melewati bidang, karena fungsi tidak memerlukan hal-hal lain yang ditemukan di objek. Tetapi kadang-kadang mengambil seluruh objek lebih benar karena menghasilkan dampak yang lebih kecil ketika hal-hal yang tak terhindarkan berubah di masa depan.


Mengapa Anda tidak mengubah nama metode untuk menyisipkanAccountIntoDatabase atau Anda akan meneruskan jenis lainnya ?. Pada sejumlah argumen untuk menggunakan obj tidak mudah untuk membaca kode. Dalam kasus Anda, saya lebih suka berpikir jika nama metode tidak jelas apa yang akan saya masukkan daripada bagaimana saya akan melakukannya.
Laiv

3

Tergantung.

Untuk menguraikan, parameter yang diterima metode Anda harus secara semantik cocok dengan apa yang Anda coba lakukan. Pertimbangkan satu EmailInviterdan tiga kemungkinan implementasi invitemetode ini:

void invite(String emailAddressString) {
  invite(EmailAddress.parse(emailAddressString));
}
void invite(EmailAddress emailAddress) {
  ...
}
void invite(User user) {
  invite(user.getEmailAddress());
}

Melewati bagian Stringyang harus Anda lewati EmailAddressadalah cacat karena tidak semua string adalah alamat email. The EmailAddresskelas yang lebih baik semantik cocok perilaku metode ini. Namun lewat dalam Userjuga cacat karena mengapa harus EmailInviterdibatasi untuk mengundang pengguna? Bagaimana dengan bisnis? Bagaimana jika Anda membaca alamat email dari file atau baris perintah dan itu tidak terkait dengan pengguna? Daftar surat? Daftarnya berlanjut.

Ada beberapa tanda peringatan yang dapat Anda gunakan untuk panduan di sini. Jika Anda menggunakan tipe nilai sederhana seperti Stringatau inttetapi tidak semua string atau int valid atau ada sesuatu yang "spesial" tentang mereka, Anda harus menggunakan tipe yang lebih bermakna. Jika Anda menggunakan objek dan satu-satunya yang Anda lakukan adalah memanggil pengambil, maka Anda harus meneruskan objek dalam pengambil secara langsung. Pedoman ini tidak sulit atau cepat, tetapi hanya sedikit pedoman.


0

Kode Bersih merekomendasikan memiliki argumen sesedikit mungkin, yang berarti Obyek biasanya akan menjadi pendekatan yang lebih baik dan saya pikir itu masuk akal. karena

insertIntoDatabase(new Account(id) , new Otherthing(id, "Value"));

adalah panggilan yang lebih mudah dibaca daripada

insertIntoDatabase(myAccount.getId(), myOtherthing.getId(), myOtherthing.getValue() );

tidak bisa setuju di sana. Keduanya tidak identik. Membuat 2 instance objek baru hanya untuk meneruskannya ke metode tidak baik. Saya akan menggunakan insertIntoDatabase (myAccount, myOtherthing) alih-alih salah satu opsi Anda.
jwenting

0

Lewati objek, bukan negara penyusunnya. Ini mendukung prinsip berorientasi objek enkapsulasi dan penyembunyian data. Mengekspos jeroan objek dalam berbagai antarmuka metode yang tidak perlu melanggar prinsip OOP inti.

Apa yang terjadi jika Anda mengubah bidang Otherthing? Mungkin Anda mengubah jenis, menambah bidang, atau menghapus bidang. Sekarang semua metode seperti yang Anda sebutkan dalam pertanyaan Anda perlu diperbarui. Jika Anda melewati objek, tidak ada perubahan antarmuka.

Satu-satunya waktu Anda harus menulis metode menerima bidang pada objek adalah ketika menulis metode untuk mengambil objek:

public User getUser(String primaryKey) {
  return ...;
}

Pada titik melakukan panggilan itu, kode panggilan belum memiliki referensi ke objek karena titik memanggil metode itu adalah untuk mendapatkan objek.


1
"Apa yang terjadi jika kamu mengganti bidang Otherthing?" (1) Itu akan melanggar prinsip terbuka / tertutup. (2) bahkan jika Anda memasukkan seluruh objek, kode di dalamnya lalu mengakses anggota objek itu (dan jika tidak, mengapa meneruskan objek?) Masih akan merusak ...
David Arno

@ DavidArno inti dari jawaban saya bukanlah bahwa tidak ada yang akan rusak, tetapi kurang akan rusak. Jangan lupa paragraf pertama, baik: terlepas dari apa yang rusak, keadaan internal objek harus diabstraksi menggunakan antarmuka objek. Melewati keadaan internal adalah pelanggaran prinsip OOP.

0

Dari perspektif rawatan, argumen harus jelas dapat dibedakan satu sama lain, lebih disukai di tingkat kompiler.

// this has exactly one way to call it
insertIntoDatabase(Account ..., Otherthing ...)

// the parameter order can be confused in practice
insertIntoDatabase(long ..., long ...)

Desain pertama mengarah ke deteksi dini bug. Desain kedua dapat menyebabkan masalah runtime halus yang tidak muncul dalam pengujian. Oleh karena itu desain pertama lebih disukai.


0

Dari keduanya, preferensi saya adalah metode pertama:

function insertIntoDatabase(Account account, Otherthing thing) { database.insertMethod(account.getId(), thing.getId(), thing.getSomeValue()); }

Alasannya adalah karena perubahan dilakukan pada objek mana pun di jalan, selama perubahan itu mempertahankan getter sehingga perubahan tersebut transparan di luar objek, maka Anda memiliki sedikit kode untuk diubah dan diuji serta lebih sedikit kemungkinan untuk mengganggu aplikasi.

Ini hanya proses pemikiran saya, sebagian besar didasarkan pada bagaimana saya suka bekerja dan menyusun hal-hal seperti ini dan yang terbukti sangat mudah dikelola dan dikelola dalam jangka panjang.

Saya tidak akan masuk ke konvensi penamaan tetapi akan menunjukkan bahwa meskipun metode ini memiliki kata "database" di dalamnya, mekanisme penyimpanan dapat berubah di jalan. Dari kode yang ditampilkan, tidak ada yang mengikat fungsi ke platform penyimpanan database yang digunakan - atau bahkan jika itu adalah database. Kami hanya berasumsi karena ada dalam nama. Sekali lagi, dengan anggapan para pengambil selalu diawetkan, mengubah cara / di mana benda-benda ini disimpan akan mudah.

Saya akan memikirkan kembali fungsi dan dua objek karena Anda memiliki fungsi yang memiliki ketergantungan pada dua struktur objek, dan khususnya getter yang dipekerjakan. Sepertinya fungsi ini mengikat kedua objek menjadi satu hal kumulatif yang bertahan. Perasaan saya memberi tahu saya bahwa objek ketiga mungkin masuk akal. Saya perlu tahu lebih banyak tentang objek-objek ini dan bagaimana mereka berhubungan dalam aktualitas dan peta jalan yang diantisipasi. Tapi nyali saya condong ke arah itu.

Saat kode berdiri sekarang, pertanyaannya adalah, "Di manakah fungsi ini atau seharusnya?" Apakah itu bagian dari Akun, atau OtherThing? Kemana perginya?

Saya kira sudah ada "database" objek ketiga, dan saya condong ke arah menempatkan fungsi ini ke objek itu, dan kemudian menjadi objek pekerjaan untuk dapat menangani Akun dan OtherThing, mengubah, dan kemudian juga bertahan hasilnya .

Jika Anda pergi sejauh membuat objek ke-3 sesuai dengan pola pemetaan objek-relasional (ORM), semua lebih baik. Itu akan membuatnya sangat jelas bagi siapa pun yang bekerja dengan kode untuk memahami "Ah, ini adalah tempat Akun dan OtherThing dihancurkan bersama dan bertahan".

Tetapi bisa juga masuk akal untuk memperkenalkan objek maju, yang menangani tugas menggabungkan dan mengubah Akun dan Lainnya, tetapi tidak menangani mekanisme bertahan. Saya akan melakukannya jika Anda mengantisipasi lebih banyak interaksi dengan atau di antara dua objek ini, karena pada saat itu tumbuh saya ingin bit kegigihan diperhitungkan menjadi objek yang mengelola kegigihan saja.

Saya akan memotret untuk menjaga desain sehingga salah satu dari Akun, OtherThing, atau objek ORM ketiga dapat diubah tanpa harus juga mengubah tiga lainnya. Kecuali ada alasan yang kuat untuk tidak melakukannya, saya ingin Akun dan Lainnya menjadi mandiri dan tidak harus mengetahui cara kerja dan struktur satu sama lain.

Tentu saja, jika saya tahu konteks lengkapnya, saya mungkin akan mengubah ide-ide saya sepenuhnya. Sekali lagi, ini hanya bagaimana saya berpikir ketika saya melihat hal-hal seperti ini, dan bagaimana lean.


0

Kedua pendekatan memiliki pro dan kontra mereka sendiri. Apa yang lebih baik dalam skenario tergantung banyak pada kasus penggunaan yang ada.


Pro Multi params, referensi Obyek Con:

  • Penelepon tidak terikat ke kelas tertentu , itu bisa melewati nilai dari sumber yang berbeda sekaligus
  • Status objek aman dari modifikasi yang tidak terduga di dalam eksekusi metode.

Referensi Obyek Pro:

  • Hapus antarmuka bahwa metode ini terikat ke tipe referensi Objek, sehingga sulit untuk secara tidak sengaja melewati nilai yang tidak terkait / tidak valid
  • Mengganti nama bidang / pengambil membutuhkan perubahan di semua doa metode dan tidak hanya dalam implementasinya
  • Jika properti baru ditambahkan dan perlu dilewati, tidak ada perubahan yang diperlukan dalam tanda tangan metode
  • Metode dapat mengubah status objek
  • Melewati terlalu banyak variabel dari tipe primitif serupa membuatnya membingungkan bagi penelepon mengenai pesanan (Masalah pola pembangun)

Jadi, apa yang perlu digunakan dan kapan banyak bergantung pada use-case

  1. Lewati parameter individual: Secara umum, jika metode ini tidak ada hubungannya dengan tipe objek, lebih baik untuk melewati daftar parameter individual sehingga dapat diterapkan ke audiens yang lebih luas.
  2. Memperkenalkan objek model baru: jika daftar parameter tumbuh menjadi besar (lebih dari 3), lebih baik untuk memperkenalkan objek model baru milik API yang disebut (pola pembangun lebih disukai)
  3. Lulus Referensi Obyek: Jika metode ini terkait dengan objek domain, maka lebih baik dari sudut pandang rawatan dan keterbacaan untuk meneruskan referensi objek.

0

Di satu sisi Anda memiliki Akun dan objek Otherthing. Di sisi lain, Anda memiliki kemampuan untuk memasukkan nilai ke dalam basis data, mengingat id akun dan id Otherthing. Itulah dua hal yang diberikan.

Anda dapat menulis metode menggunakan Akun dan Lainnya sebagai argumen. Di sisi pro, penelepon tidak perlu mengetahui detail tentang Akun dan Lainnya. Di sisi negatif, callee perlu tahu tentang metode Akun dan Lainnya. Dan juga, tidak ada cara untuk memasukkan hal lain ke dalam database selain nilai objek Otherthing dan tidak ada cara untuk menggunakan metode ini jika Anda memiliki id dari objek akun, tetapi bukan objek itu sendiri.

Atau Anda dapat menulis metode yang menggunakan dua id dan nilai sebagai argumen. Di sisi negatif, penelepon perlu mengetahui rincian Akun dan Lainnya. Dan mungkin ada situasi di mana Anda benar-benar membutuhkan lebih banyak detail dari Akun atau Lainnya dari sekadar id untuk dimasukkan ke dalam database, dalam hal ini solusi ini sama sekali tidak berguna. Di sisi lain, semoga tidak ada pengetahuan tentang Akun dan Hal Lain yang diperlukan di masa kecil, dan ada lebih banyak fleksibilitas.

Panggilan penilaian Anda: Apakah lebih banyak fleksibilitas diperlukan? Ini sering kali bukan masalah satu panggilan, tetapi akan konsisten melalui semua perangkat lunak Anda: Entah Anda menggunakan id sebagian besar waktu, atau Anda menggunakan objek. Mencampurnya membuat Anda menjadi yang terburuk dari kedua dunia.

Di C ++, Anda dapat memiliki metode yang mengambil dua id plus nilai, dan metode inline mengambil Akun dan Lainnya, sehingga Anda memiliki kedua cara dengan nol overhead.

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.