Apakah ada nama untuk pola (anti-) parameter passing yang hanya akan digunakan beberapa level di dalam rantai panggilan?

209

Saya mencoba mencari alternatif untuk penggunaan variabel global dalam beberapa kode lama. Tetapi pertanyaan ini bukan tentang alternatif teknis, saya terutama khawatir tentang terminologi .

Solusi yang jelas adalah melewatkan parameter ke dalam fungsi alih-alih menggunakan global. Dalam basis kode warisan ini, itu berarti bahwa saya harus mengubah semua fungsi dalam rantai panggilan lama antara titik di mana nilai akhirnya akan digunakan dan fungsi yang menerima parameter pertama.

higherlevel(newParam)->level1(newParam)->level2(newParam)->level3(newParam)

di mana newParamsebelumnya merupakan variabel global dalam contoh saya, tetapi itu bisa menjadi nilai hardcoded sebelumnya. Intinya adalah bahwa sekarang nilai newParam diperoleh pada higherlevel()dan harus "bepergian" ke sana level3().

Saya bertanya-tanya apakah ada nama untuk situasi / pola di mana Anda perlu menambahkan parameter ke banyak fungsi yang hanya "meneruskan" nilai yang tidak dimodifikasi.

Mudah-mudahan, menggunakan terminologi yang tepat akan memungkinkan saya untuk menemukan lebih banyak sumber daya tentang solusi untuk mendesain ulang dan menggambarkan situasi ini kepada rekan kerja.

ecerulm
sumber
94
Ini merupakan peningkatan dibandingkan menggunakan variabel global. Itu memperjelas persisnya keadaan masing-masing fungsi bergantung (dan merupakan satu langkah di jalan menuju fungsi murni). Saya pernah mendengarnya menyebut "threading" sebagai parameter, tapi saya tidak tahu seberapa umum terminologi ini.
gardenhead
8
Spektrum ini terlalu luas untuk memiliki jawaban spesifik. Pada level ini, saya sebut ini hanya "coding".
Machado
38
Saya pikir "masalah" hanyalah kesederhanaan. Ini pada dasarnya adalah injeksi ketergantungan. Saya kira mungkin ada mekanisme yang secara otomatis akan menyuntikkan ketergantungan melalui rantai, jika beberapa anggota yang lebih mendalam memilikinya, tanpa membengkak daftar parameter fungsi. Mungkin melihat strategi injeksi ketergantungan dengan berbagai tingkat kecanggihan mungkin mengarah pada terminologi yang Anda cari, jika ada.
null
7
Meskipun saya menghargai diskusi tentang apakah itu pola / antipattern / konsep / solusi yang baik, yang benar-benar ingin saya ketahui adalah apakah ada nama untuk itu.
ecerulm
3
Saya juga mendengar itu disebut threading paling umum, tetapi juga pipa ledeng , seperti dalam menurunkan garis timah di seluruh tumpukan panggilan.
wchargin

Jawaban:

202

Data itu sendiri disebut "tramp data" . Ini adalah "bau kode", yang menunjukkan bahwa satu potong kode berkomunikasi dengan sepotong kode lain dari kejauhan, melalui perantara.

  • Meningkatkan kekakuan kode, terutama dalam rantai panggilan. Anda jauh lebih terkendala dalam cara Anda memperbaiki metode apa pun dalam rantai panggilan.
  • Mendistribusikan pengetahuan tentang data / metode / arsitektur ke tempat-tempat yang tidak peduli tentang hal itu. Jika Anda perlu mendeklarasikan data yang baru saja lewat, dan deklarasi tersebut membutuhkan impor baru, Anda telah mencemari ruang nama.

Refactoring untuk menghapus variabel global sulit, dan menginjak-injak data adalah salah satu metode untuk melakukannya, dan seringkali cara termurah. Memang ada biayanya.

BobDalgleish
sumber
73
Dengan mencari "tramp data" saya dapat menemukan buku "Kode Lengkap" di langganan Safari saya. Ada bagian dalam buku yang disebut "Alasan untuk menggunakan data global" dan salah satu alasannya adalah "Penggunaan data global dapat menghilangkan data gelandangan". :) Saya merasa "tramp data" akan memungkinkan saya menemukan lebih banyak literatur tentang berurusan dengan global. Terima kasih!
ecerulm
9
@ JimmyJames, fungsi-fungsi itu tentu saja melakukan hal-hal. Hanya saja tidak dengan parameter baru tertentu yang sebelumnya hanya global.
ecerulm
174
Dalam 20 tahun pemrograman, saya benar-benar tidak pernah mendengar istilah ini sebelumnya juga tidak akan segera jelas apa artinya. Saya tidak mengeluh tentang jawabannya, hanya menyarankan bahwa istilah itu tidak banyak digunakan / diketahui. Mungkin hanya aku.
Derek Elkins
6
Beberapa data global baik-baik saja. Alih-alih menyebutnya "data global", Anda dapat menyebutnya "lingkungan" - karena memang seperti itu. Lingkungan mungkin termasuk, misalnya, jalur string untuk appdata (di windows), atau dalam proyek saya saat ini seluruh rangkaian kuas, pena, font GDI + dan sebagainya, yang digunakan oleh semua komponen.
Robinson
7
@ Robinson Tidak cukup. Misalnya, apakah Anda benar-benar ingin kode penulisan gambar Anda menyentuh% AppData%, atau Anda lebih suka mengambil argumen untuk tempat menulis? Itulah perbedaan antara negara global dan argumen. "Lingkungan" dapat dengan mudah menjadi ketergantungan yang disuntikkan, hanya ada bagi mereka yang bertanggung jawab untuk berinteraksi dengan lingkungan. GDI + brushes dll. Lebih masuk akal, tetapi itu sebenarnya lebih merupakan kasus pengelolaan sumber daya di lingkungan yang tidak dapat melakukannya untuk Anda - cukup banyak kekurangan API yang mendasarinya dan / atau bahasa / perpustakaan / runtime Anda.
Luaan
102

Saya tidak berpikir ini, dengan sendirinya, adalah anti-pola. Saya pikir masalahnya adalah bahwa Anda memikirkan fungsi sebagai rantai ketika Anda benar-benar harus menganggap masing-masing sebagai kotak hitam independen ( CATATAN : metode rekursif adalah pengecualian penting untuk saran ini.)

Sebagai contoh, katakanlah saya perlu menghitung jumlah hari antara dua tanggal kalender jadi saya membuat fungsi:

int daysBetween(Day a, Day b)

Untuk melakukan ini, saya kemudian membuat fungsi baru:

int daysSinceEpoch(Day day)

Maka fungsi pertama saya menjadi sederhana:

int daysBetween(Day a, Day b)
{
    return daysSinceEpoch(b) - daysSinceEpoch(a);
}

Tidak ada yang anti-pola tentang ini. Parameter metode daysBetween diteruskan ke metode lain dan tidak pernah direferensikan dalam metode tetapi mereka masih diperlukan untuk metode itu untuk melakukan apa yang perlu dilakukan.

Apa yang akan saya rekomendasikan adalah melihat setiap fungsi dan mulai dengan beberapa pertanyaan:

  • Apakah fungsi ini memiliki tujuan yang jelas dan terfokus atau apakah itu metode "lakukan beberapa hal"? Biasanya nama fungsi membantu di sini dan jika ada barang di dalamnya yang tidak dijelaskan oleh namanya, itu adalah bendera merah.
  • Apakah ada terlalu banyak parameter? Terkadang suatu metode dapat secara sah membutuhkan banyak input tetapi memiliki begitu banyak parameter membuatnya memberatkan untuk menggunakan atau memahami.

Jika Anda melihat tumpukan kode tanpa tujuan tunggal yang dibundel ke dalam metode, Anda harus mulai dengan menguraikannya. Ini bisa membosankan. Mulailah dengan hal-hal termudah untuk ditarik dan pindah ke metode terpisah dan ulangi sampai Anda memiliki sesuatu yang masuk akal.

Jika Anda hanya memiliki terlalu banyak parameter, pertimbangkan Method to Object refactoring .

JimmyJames
sumber
2
Yah, aku tidak bermaksud kontroversial dengan (anti-). Tapi saya masih bertanya-tanya apakah ada nama untuk "situasi" karena harus memperbarui banyak tanda tangan fungsi. Saya kira lebih dari "kode bau" daripada antipattern. Ini memberi tahu saya bahwa ada sesuatu yang harus diperbaiki dalam kode warisan ini, jika saya harus memperbarui 6 fungsi tanda tangan untuk mengakomodasi penghapusan global. Tapi saya pikir bahwa melewati parameter biasanya ok, dan saya menghargai advide tentang cara mengatasi masalah yang mendasarinya.
ecerulm
4
@ecerulm Saya tidak menyadarinya, tetapi saya akan mengatakan bahwa pengalaman saya memberi tahu saya bahwa mengubah global menjadi parameter benar-benar cara yang tepat untuk mulai menghilangkannya. Ini menghilangkan status bersama sehingga Anda dapat melanjutkan refactor. Saya menduga ada lebih banyak masalah dengan kode ini tetapi tidak ada cukup dalam deskripsi Anda untuk mengetahui apa itu.
JimmyJames
2
Saya biasanya mengikuti pendekatan itu juga, dan mungkin akan melakukan dalam kasus ini juga. Saya hanya ingin meningkatkan kosakata / terminologi saya di sekitar ini sehingga saya dapat meneliti lebih lanjut tentang ini dan melakukan pertanyaan yang lebih baik, lebih fokus di masa depan.
ecerulm
3
@ecerulm Saya tidak berpikir ada satu nama untuk ini. Ini seperti gejala yang umum pada banyak penyakit dan juga kondisi non penyakit misalnya 'mulut kering'. Jika Anda menyempurnakan deskripsi struktur kode, ini mungkin menunjuk ke sesuatu yang spesifik.
JimmyJames
@ecerulm Memberitahu Anda ada sesuatu yang harus diperbaiki - sekarang jauh lebih jelas bahwa ada sesuatu yang harus diperbaiki daripada ketika itu merupakan variabel global.
immibis
61

BobDalgleish telah mencatat bahwa pola (anti-) ini disebut " data gelandangan ".

Dalam pengalaman saya, penyebab paling umum dari data gelandangan berlebihan adalah memiliki banyak variabel status tertaut yang harus benar-benar dienkapsulasi dalam objek atau struktur data. Kadang-kadang, bahkan mungkin perlu untuk bersarang banyak objek untuk mengatur data dengan benar.

Untuk contoh sederhana, pertimbangkan gim yang memiliki karakter pemain yang dapat disesuaikan, dengan properti seperti playerName, playerEyeColordan sebagainya. Tentu saja, pemain juga memiliki posisi fisik di peta permainan, dan berbagai properti lainnya seperti, katakanlah, tingkat kesehatan saat ini dan maksimum, dan sebagainya.

Dalam iterasi pertama dari permainan seperti itu, mungkin ini merupakan pilihan yang masuk akal untuk membuat semua properti ini menjadi variabel global - lagipula, hanya ada satu pemain, dan hampir semua hal dalam permainan itu entah bagaimana melibatkan pemain. Jadi keadaan global Anda mungkin mengandung variabel seperti:

playerName = "Bob"
playerEyeColor = GREEN
playerXPosition = -8
playerYPosition = 136
playerHealth = 100
playerMaxHealth = 100

Tetapi pada titik tertentu, Anda mungkin perlu mengubah desain ini, mungkin karena Anda ingin menambahkan mode multipemain ke dalam gim. Sebagai upaya pertama, Anda bisa mencoba membuat semua variabel tersebut lokal, dan meneruskannya ke fungsi yang membutuhkannya. Namun, Anda kemudian mungkin menemukan bahwa tindakan tertentu dalam game Anda mungkin melibatkan rantai panggilan fungsi seperti, katakan:

mainGameLoop()
 -> processInputEvent()
     -> doPlayerAction()
         -> movePlayer()
             -> checkCollision()
                 -> interactWithNPC()
                     -> interactWithShopkeeper()

... dan interactWithShopkeeper()fungsinya meminta penjaga toko memanggil pemain dengan nama, jadi Anda sekarang tiba-tiba harus lulus playerNamesebagai data gelandangan melalui semua fungsi itu. Dan, tentu saja, jika penjaga toko berpikir bahwa pemain bermata biru naif, dan akan membebankan harga yang lebih tinggi untuk mereka, maka Anda harus melewati playerEyeColorseluruh rantai fungsi, dan sebagainya.

The tepat solusi, dalam hal ini, tentu saja untuk menentukan objek pemain yang merangkum nama, warna mata, posisi, kesehatan dan properti lainnya dari karakter pemain. Dengan begitu, Anda hanya perlu meneruskan objek tunggal itu ke semua fungsi yang melibatkan pemain.

Juga, beberapa fungsi di atas dapat secara alami dibuat menjadi metode objek pemain itu, yang secara otomatis akan memberi mereka akses ke properti pemain. Di satu sisi, ini hanyalah gula sintaksis, karena memanggil metode pada objek secara efektif melewatkan instance objek sebagai parameter tersembunyi ke metode tersebut, tetapi itu membuat kode terlihat lebih jelas dan lebih alami jika digunakan dengan benar.

Tentu saja, permainan tipikal akan memiliki lebih banyak kondisi "global" daripada hanya pemain; misalnya, Anda hampir pasti memiliki semacam peta tempat permainan berlangsung, dan daftar karakter non-pemain bergerak di peta, dan mungkin item diletakkan di situ, dan sebagainya. Anda bisa melewatkan semua itu di sekitar sebagai objek gelandangan juga, tapi itu lagi akan mengacaukan argumen metode Anda.

Alih-alih, solusinya adalah membuat objek menyimpan referensi ke objek lain yang memiliki hubungan permanen atau sementara dengannya. Jadi, misalnya, objek pemain (dan mungkin objek NPC juga) mungkin harus menyimpan referensi ke objek "dunia game", yang akan memiliki referensi ke level / peta saat ini, sehingga metode seperti player.moveTo(x, y)tidak perlu secara eksplisit diberi peta sebagai parameter.

Demikian pula, jika karakter pemain kami memiliki, misalnya, anjing peliharaan yang mengikuti mereka, kami secara alami akan mengelompokkan semua variabel keadaan yang menggambarkan anjing menjadi satu objek, dan memberikan objek pemain referensi ke anjing (sehingga pemain dapat , katakan, panggil anjing dengan nama) dan sebaliknya (agar anjing tahu di mana pemain berada). Dan, tentu saja, kami mungkin ingin membuat pemain dan objek anjing menjadi dua subclass dari objek "aktor" yang lebih umum, sehingga kami dapat menggunakan kembali kode yang sama untuk, katakanlah, memindahkan keduanya di sekitar peta.

Ps. Meskipun saya telah menggunakan game sebagai contoh, ada beberapa jenis program lain di mana masalah seperti itu muncul juga. Namun, dalam pengalaman saya, masalah mendasar cenderung selalu sama: Anda memiliki banyak variabel terpisah (baik lokal maupun global) yang benar-benar ingin dikelompokkan bersama menjadi satu atau lebih objek yang saling terkait. Apakah "data gelandangan" yang mengganggu fungsi Anda terdiri dari pengaturan opsi "global" atau kueri basis data yang di-cache atau vektor negara dalam simulasi numerik, solusinya selalu untuk mengidentifikasi konteks alami dari data tersebut, dan menjadikannya sebagai objek (atau apa pun yang setara terdekat dalam bahasa pilihan Anda).

Ilmari Karonen
sumber
1
Jawaban ini memberikan beberapa solusi untuk kelas masalah yang mungkin ada. Mungkin ada situasi di mana global digunakan yang akan menunjukkan solusi yang berbeda. Saya mengambil masalah dengan gagasan bahwa membuat metode bagian dari kelas pemain sama dengan melewatkan objek ke metode. Ini mengabaikan polimorfisme yang tidak mudah ditiru dengan cara ini. Sebagai contoh, jika saya ingin membuat berbagai jenis pemain yang memiliki aturan berbeda tentang gerakan dan berbagai jenis properti, cukup dengan memindahkan objek-objek ini ke satu metode implementasi akan memerlukan banyak logika kondisional.
JimmyJames
6
@ JimmyJames: Maksud Anda tentang polimorfisme adalah hal yang baik, dan saya berpikir untuk membuatnya sendiri, tetapi meninggalkannya untuk menjaga jawaban agar tidak semakin panjang. Maksud saya mencoba (mungkin buruk) adalah bahwa, sementara murni dalam hal aliran data ada sedikit perbedaan antara foo.method(bar, baz)dan method(foo, bar, baz), ada alasan lain (termasuk polimorfisme, enkapsulasi, lokalitas, dll) untuk memilih yang pertama.
Ilmari Karonen
@IlmariKaronen: juga manfaat yang sangat jelas karena di masa depan-bukti fungsi prototipe dari setiap perubahan / penambahan / penghapusan / refactorings di objek (misalnya playerAge). Ini saja sangat berharga.
smci
34

Saya tidak mengetahui nama spesifik untuk ini, tapi saya rasa perlu disebutkan bahwa masalah yang Anda uraikan hanyalah masalah menemukan kompromi terbaik untuk lingkup parameter seperti itu:

  • sebagai variabel global, cakupannya terlalu besar ketika program mencapai ukuran tertentu

  • sebagai parameter murni lokal, cakupannya mungkin terlalu kecil, ketika itu mengarah ke banyak daftar parameter berulang di rantai panggilan

  • jadi sebagai trade-off, Anda sering dapat membuat parameter seperti variabel anggota dalam satu atau beberapa kelas, dan itulah yang saya sebut desain kelas yang tepat .

Doc Brown
sumber
10
+1 untuk desain kelas yang tepat. Ini terdengar seperti masalah klasik menunggu solusi OO.
l0b0
21

Saya percaya pola yang Anda gambarkan adalah injeksi ketergantungan yang tepat . Beberapa komentator berpendapat bahwa ini adalah sebuah pola , bukan anti-pola , dan saya cenderung setuju.

Saya juga setuju dengan jawaban @JamesJames, di mana ia mengklaim bahwa itu adalah praktik pemrograman yang baik untuk memperlakukan setiap fungsi sebagai kotak hitam yang mengambil semua inputnya sebagai parameter eksplisit. Artinya, jika Anda menulis fungsi yang membuat sandwich selai kacang dan jelly, Anda bisa menuliskannya sebagai

Sandwich make_sandwich() {
    PeanutButter pb = get_peanut_butter();
    Jelly j = get_jelly();
    return pb + j;
}
extern PhysicalRefrigerator g_refrigerator;
PeanutButter get_peanut_butter() {
    return g_refrigerator.get("peanut butter");
}
Jelly get_jelly() {
    return g_refrigerator.get("jelly");
}

tetapi akan lebih baik untuk menerapkan injeksi ketergantungan dan menulisnya seperti ini sebagai gantinya:

Sandwich make_sandwich(Refrigerator& r) {
    PeanutButter pb = get_peanut_butter(r);
    Jelly j = get_jelly(r);
    return pb + j;
}
PeanutButter get_peanut_butter(Refrigerator& r) {
    return r.get("peanut butter");
}
Jelly get_jelly(Refrigerator& r) {
    return r.get("jelly");
}

Sekarang Anda memiliki fungsi yang dengan jelas mendokumentasikan semua dependensinya dalam tanda tangan fungsinya, yang sangat bagus untuk dibaca. Lagi pula, memang benar bahwa untuk make_sandwichAnda memerlukan akses ke Refrigerator; jadi tanda tangan fungsi lama pada dasarnya tidak jujur ​​dengan tidak mengambil kulkas sebagai bagian dari inputnya.

Sebagai bonus, jika Anda melakukan hierarki kelas dengan benar, hindari mengiris, dan sebagainya, Anda bahkan dapat menguji make_sandwichfungsi dengan mengirimkan sebuah MockRefrigerator! (Anda mungkin perlu mengujinya dengan cara ini karena lingkungan pengujian unit Anda mungkin tidak memiliki akses ke PhysicalRefrigerators.)

Saya mengerti bahwa tidak semua penggunaan injeksi ketergantungan memerlukan pipa yang bernama sama parameter berbagai tingkatan bawah panggilan stack, jadi aku tidak menjawab persis pertanyaan Anda bertanya ... tapi jika Anda sedang mencari untuk membaca lebih lanjut tentang hal ini, "injeksi ketergantungan" jelas merupakan kata kunci yang relevan untuk Anda.

Quuxplusone
sumber
10
Ini jelas merupakan pola anti . Sama sekali tidak ada panggilan untuk melewatkan kulkas. Sekarang, melewatkan Sumber Bahan generik mungkin bekerja, tetapi bagaimana jika Anda mendapatkan roti dari tempat roti, tuna dari lemari makan, keju dari lemari es ... dengan menyuntikkan ketergantungan pada sumber bahan ke dalam operasi pembentukan yang tidak terkait bahan menjadi sandwich, Anda telah melanggar pemisahan kekhawatiran dan membuat kode bau.
Dewi Morgan
8
@DewiMorgan: Jelas Anda bisa refactor lebih jauh untuk menggeneralisasi Refrigeratormenjadi IngredientSource, atau bahkan menggeneralisasi gagasan "sandwich" menjadi template<typename... Fillings> StackedElementConstruction<Fillings...> make_sandwich(ElementSource&); itu disebut "pemrograman generik" dan itu cukup kuat, tapi tentu saja ini jauh lebih misterius daripada yang ingin dicapai OP saat ini. Jangan ragu untuk membuka pertanyaan baru tentang tingkat abstraksi yang tepat untuk program sandwich. ;)
Quuxplusone
11
Jika tidak ada kesalahan, pengguna yang tidak berhak tidak boleh memiliki akses make_sandwich().
dotancohen
2
@Dewi - XKCD Link
Gavin Lock
19
Kesalahan paling serius dalam kode Anda adalah Anda menyimpan selai kacang di lemari es.
Malvolio
15

Ini cukup banyak definisi kopling buku teks , satu modul memiliki ketergantungan yang sangat mempengaruhi yang lain, dan yang menciptakan efek riak ketika diubah. Komentar dan jawaban lain benar bahwa ini adalah peningkatan dari global, karena kopling sekarang lebih eksplisit dan lebih mudah bagi programmer untuk melihat, daripada subversif. Itu tidak berarti itu tidak boleh diperbaiki. Anda harus bisa refactor untuk melepas atau mengurangi kopling, meskipun jika sudah ada di sana sementara itu bisa menyakitkan.

Karl Bielefeldt
sumber
3
Jika level3()perlu newParam, itu sudah pasti, tetapi entah bagaimana bagian kode yang berbeda harus berkomunikasi satu sama lain. Saya tidak perlu menyebut parameter fungsi kopling buruk jika fungsi itu menggunakan parameter. Saya pikir aspek masalah dari rantai adalah kopling tambahan yang diperkenalkan level1()dan level2()yang tidak ada gunanya newParamkecuali untuk meneruskannya. Jawaban bagus, +1 untuk pemasangan.
null
6
@null Jika mereka benar - benar tidak menggunakannya, mereka bisa membuat nilai alih-alih menerima dari pemanggil mereka.
Random832
3

Meskipun jawaban ini tidak secara langsung menjawab pertanyaan Anda, saya merasa saya akan lalai untuk membiarkannya berlalu tanpa menyebutkan bagaimana memperbaikinya (karena seperti yang Anda katakan, ini mungkin anti-pola). Saya harap Anda dan pembaca lain bisa mendapatkan nilai dari komentar tambahan ini tentang cara menghindari "data gelandangan" (seperti yang dinamai Bob Dalgleish dengan sangat membantu untuk kami).

Saya setuju dengan jawaban yang menyarankan melakukan sesuatu yang lebih OO untuk menghindari masalah ini. Namun, cara lain untuk juga membantu mengurangi berlalunya argumen ini secara mendalam tanpa hanya melompat ke " hanya lulus kelas di mana Anda dulu melewati banyak argumen! " Adalah dengan refactor sehingga beberapa langkah proses Anda terjadi di tingkat yang lebih tinggi daripada yang lebih rendah satu. Misalnya, inilah beberapa kode sebelum :

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   FilterAndReportStuff(stuffs, desiredName);
}

public void FilterAndReportStuff(IEnumerable<Stuff> stuffs, string desiredName) {
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   ReportStuff(stuffs.Filter(filter));
}

public void ReportStuff(IEnumerable<Stuff> stuffs) {
   stuffs.Report();
}

Perhatikan bahwa ini menjadi lebih buruk, semakin banyak hal yang harus dilakukan ReportStuff. Anda mungkin harus memberikan contoh Reporter yang ingin Anda gunakan. Dan segala macam dependensi yang harus diserahkan, berfungsi ke fungsi bersarang.

Saran saya adalah untuk menarik semua itu ke tingkat yang lebih tinggi, di mana pengetahuan tentang langkah-langkah membutuhkan hidup dalam metode tunggal alih-alih tersebar di rantai panggilan metode. Tentu saja akan lebih rumit dalam kode nyata, tetapi ini memberi Anda ide:

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   var filteredStuffs = stuffs.Filter(filter)
   filteredStuffs.Report();
}

Perhatikan bahwa perbedaan besar di sini adalah Anda tidak harus melewati ketergantungan melalui rantai panjang. Bahkan jika Anda meratakan tidak hanya satu tingkat, tetapi beberapa tingkat dalam, jika level tersebut juga mencapai beberapa "perataan" sehingga proses tersebut dilihat sebagai serangkaian langkah di tingkat itu, Anda akan membuat peningkatan.

Meskipun ini masih bersifat prosedural dan belum ada yang berubah menjadi objek, ini adalah langkah yang baik untuk memutuskan jenis enkapsulasi yang dapat Anda capai dengan mengubah sesuatu menjadi kelas. Metode yang dirantai dalam-dalam dalam skenario sebelum menyembunyikan detail dari apa yang sebenarnya terjadi dan dapat membuat kode sangat sulit untuk dipahami. Walaupun Anda bisa berlebihan dan akhirnya membuat kode tingkat tinggi tahu tentang hal-hal yang tidak seharusnya, atau membuat metode yang melakukan terlalu banyak hal sehingga melanggar prinsip tanggung jawab tunggal, secara umum saya telah menemukan bahwa meratakan hal-hal sedikit membantu kejelasan dan dalam membuat perubahan tambahan menuju kode yang lebih baik.

Perhatikan bahwa saat Anda melakukan semua ini, Anda harus mempertimbangkan kemampuan tes. Panggilan metode berantai benar-benar membuat pengujian unit lebih sulit karena Anda tidak memiliki titik masuk dan titik keluar yang baik dalam rakitan untuk irisan yang ingin Anda uji. Perhatikan bahwa dengan perataan ini, karena metode Anda tidak lagi menggunakan banyak dependensi, mereka lebih mudah untuk diuji, tidak memerlukan banyak ejekan!

Saya baru-baru ini mencoba menambahkan tes unit ke kelas (yang saya tidak tulis) yang mengambil sekitar 17 dependensi, yang semuanya harus diejek! Saya belum menyelesaikan semuanya, tapi saya membagi kelas menjadi tiga kelas, masing-masing berurusan dengan salah satu kata benda terpisah yang berkaitan, dan membuat daftar ketergantungan turun menjadi 12 untuk yang terburuk dan sekitar 8 untuk yang yang terbaik.

Testability akan memaksa Anda untuk menulis kode yang lebih baik. Anda harus menulis unit test karena Anda akan menemukan bahwa hal itu membuat Anda memikirkan kode Anda secara berbeda dan Anda akan menulis kode yang lebih baik sejak awal, terlepas dari seberapa sedikit bug yang mungkin Anda miliki sebelum menulis unit test.

ErikE
sumber
2

Anda tidak benar-benar melanggar Hukum Demeter, tetapi masalah Anda serupa dengan itu dalam beberapa hal. Karena inti pertanyaan Anda adalah untuk menemukan sumber daya, saya sarankan Anda membaca tentang Hukum Demeter dan melihat seberapa banyak saran itu berlaku untuk situasi Anda.

makanan kucing
sumber
1
Agak lemah pada detail, yang mungkin menjelaskan downvotes. Namun dalam semangat jawaban ini tepat: OP harus membaca tentang Hukum Demeter - ini adalah istilah yang relevan.
Konrad Rudolph
4
FWIW, saya tidak berpikir Hukum Demeter (alias "privilege terkecil") sama sekali relevan. Kasus OP adalah di mana fungsinya tidak akan dapat melakukan tugasnya jika tidak memiliki data gelandangan (karena lelaki berikutnya di tumpukan panggilan membutuhkannya, karena lelaki berikutnya membutuhkannya, dan seterusnya). Least privilege / Law of Demeter hanya relevan jika parameternya benar-benar tidak digunakan , dan dalam hal ini perbaikannya jelas: hapus parameter yang tidak digunakan!
Quuxplusone
2
Situasi pertanyaan ini sebenarnya tidak ada hubungannya dengan Hukum Demeter ... Ada kesamaan dangkal mengenai rantai panggilan metode, tetapi sebaliknya sangat berbeda.
Eric King
@Quuxplusone Kemungkinan, meskipun dalam kasus ini uraiannya cukup membingungkan karena panggilan berantai tidak benar-benar masuk akal dalam skenario itu: mereka harusnya bersarang .
Konrad Rudolph
1
Masalahnya adalah sangat mirip dengan pelanggaran LOD, karena refactoring biasa disarankan untuk menangani pelanggaran LOD adalah untuk memperkenalkan data yang gelandangan. IMHO, ini adalah titik awal yang baik untuk mengurangi kopling tetapi tidak cukup.
Jørgen Fogh
1

Ada beberapa contoh di mana yang terbaik (dalam hal efisiensi, pemeliharaan dan kemudahan implementasi) memiliki variabel tertentu sebagai global daripada overhead yang selalu melewati segala sesuatu di sekitar (misalkan Anda memiliki 15 atau lebih variabel yang harus bertahan). Jadi masuk akal untuk menemukan bahasa pemrograman yang mendukung pelingkupan yang lebih baik (sebagai variabel statis pribadi C ++) untuk mengurangi potensi kekacauan (dari namespace dan hal-hal yang dirusak). Tentu saja ini hanya pengetahuan umum.

Tapi, pendekatan yang dinyatakan oleh OP sangat berguna jika seseorang melakukan Pemrograman Fungsional.

kozner
sumber
0

Tidak ada anti-pola di sini, karena penelepon tidak tahu tentang semua level di bawah ini dan tidak peduli.

Seseorang memanggil HigherLevel (params) dan mengharapkan HigherLevel untuk melakukan tugasnya. Apa yang dilakukan level yang lebih tinggi pada param bukanlah urusan para penelepon. HigherLevel menangani masalah dengan cara terbaik yang bisa dilakukan, dalam hal ini dengan meneruskan params ke level1 (params). Benar-benar oke.

Anda melihat rantai panggilan - tetapi tidak ada rantai panggilan. Ada fungsi di bagian atas yang melakukan tugasnya sebaik mungkin. Dan ada fungsi lainnya. Setiap fungsi dapat diganti kapan saja.

gnasher729
sumber