Peta fungsi vs pernyataan sakelar

21

Saya sedang mengerjakan proyek yang memproses permintaan, dan ada dua komponen untuk permintaan: perintah dan parameter. Pawang untuk setiap perintah sangat sederhana (<10 baris, sering <5). Setidaknya ada 20 perintah, dan kemungkinan akan memiliki lebih dari 50 perintah.

Saya telah menemukan beberapa solusi:

  • satu saklar besar / jika-lain pada perintah
  • peta perintah ke fungsi
  • peta perintah ke kelas / lajang statis

Setiap perintah melakukan sedikit pengecekan kesalahan, dan satu-satunya bit yang dapat diabstraksi adalah memeriksa jumlah parameter, yang didefinisikan untuk setiap perintah.

Apa yang akan menjadi solusi terbaik untuk masalah ini, dan mengapa? Saya juga terbuka untuk pola desain yang mungkin saya lewatkan.

Saya telah membuat daftar pro / con berikut untuk masing-masing:

beralih

  • pro
    • menyimpan semua perintah dalam satu fungsi; karena sederhana, ini membuatnya menjadi tabel pencarian visual
    • tidak perlu mengacaukan sumber dengan banyak fungsi / kelas kecil yang hanya akan digunakan di satu tempat
  • kontra
    • sangat panjang
    • sulit untuk menambahkan perintah secara terprogram (perlu menggunakan rantai menggunakan case standar)

perintah peta -> fungsi

  • pro
    • potongan kecil, seukuran gigitan
    • dapat menambah / menghapus perintah secara terprogram
  • kontra
    • jika dilakukan in-line, sama secara visual dengan saklar
    • jika tidak dilakukan in-line, banyak fungsi hanya digunakan di satu tempat

perintah peta -> kelas statis / singleton

  • pro
    • dapat menggunakan polimorfisme untuk menangani pengecekan kesalahan sederhana (hanya seperti 3 baris, tapi tetap saja)
    • manfaat serupa untuk memetakan -> solusi fungsi
  • kontra
    • banyak kelas yang sangat kecil akan mengacaukan proyek
    • implementasi tidak semuanya di tempat yang sama, sehingga tidak mudah untuk memindai implementasi

Catatan tambahan:

Saya menulis ini di Go, tapi saya rasa solusinya tidak spesifik untuk bahasa. Saya mencari solusi yang lebih umum karena saya mungkin perlu melakukan sesuatu yang sangat mirip dalam bahasa lain.

Perintah adalah string, tetapi saya dapat dengan mudah memetakan ini ke nomor jika nyaman. Tanda tangan fungsinya adalah seperti:

Reply Command(List<String> params)

Go memiliki fungsi tingkat atas, dan platform lain yang saya pertimbangkan juga memiliki fungsi tingkat atas, maka perbedaan antara opsi kedua dan ketiga.

hajar
sumber
8
memetakan perintah ke fungsi dan memuatnya saat runtime dari konfigurasi. gunakan pola perintah.
Steven Evers
3
Jangan terlalu takut membuat banyak fungsi kecil. Biasanya, kumpulan beberapa fungsi kecil lebih mudah dikelola daripada satu fungsi besar.
Bart van Ingen Schenau
8
Ada apa dengan dunia kacau-balau ini, di mana orang-orang menjawab pertanyaan dalam komentar dan meminta lebih banyak informasi dalam jawabannya?
pdr
4
@SteveEvers: Jika tidak perlu dijabarkan, ini adalah jawaban, tidak peduli seberapa pendek. Jika ya, dan Anda tidak punya waktu atau apa pun, serahkan kepada orang lain untuk menjawab (selalu merasa seperti selingkuh untuk menulis jawaban yang mengonfirmasi komentar yang sudah memiliki setengah lusin upvotes). Secara pribadi, saya merasa ini perlu dijabarkan, OP benar-benar ingin tahu mengapa solusi terbaik adalah solusi terbaik.
pdr
1
@ pdr - Benar. Kecenderungan saya adalah peta perintah ke fungsi, tapi saya seorang programmer yang relatif junior dalam kursus desain CS. Profesor saya suka banyak kelas, jadi setidaknya ada 2 solusi yang sah. Saya ingin tahu favorit komunitas.
beatgammit

Jawaban:

14

Ini sangat cocok untuk peta (2 atau 3 solusi yang diusulkan). Saya sudah menggunakannya puluhan kali, dan itu sederhana dan efektif. Saya tidak benar-benar membedakan antara solusi ini; yang penting adalah bahwa ada peta dengan nama fungsi sebagai kunci.

Keuntungan utama dari pendekatan peta, menurut pendapat saya, adalah bahwa meja adalah data. Ini berarti bahwa hal itu dapat diedarkan, ditambah, atau dimodifikasi pada saat runtime; itu juga mudah untuk kode tambahan fungsi yang menafsirkan peta dalam cara baru yang menarik. Ini tidak akan mungkin dengan solusi case / switch.

Saya belum benar-benar mengalami kontra yang Anda sebutkan, tetapi saya ingin menyebutkan kelemahan tambahan: pengiriman mudah jika hanya nama string yang penting, tetapi jika Anda perlu mempertimbangkan informasi tambahan untuk memutuskan fungsi yang akan dieksekusi , itu jauh lebih bersih.

Mungkin saya tidak pernah mengalami masalah yang cukup sulit, tetapi saya melihat sedikit nilai di kedua pola perintah dan pengkodean pengiriman sebagai hierarki kelas, seperti yang telah disebutkan orang lain. Inti dari masalah adalah memetakan permintaan ke fungsi; peta sederhana, jelas, dan mudah diuji. Hirarki kelas membutuhkan lebih banyak kode dan desain, meningkatkan sambungan antara kode itu, dan mungkin memaksa Anda untuk membuat lebih banyak keputusan di muka yang nantinya akan perlu Anda ubah. Saya tidak berpikir pola perintah berlaku sama sekali.


sumber
4

Masalah Anda cocok dengan pola desain perintah . Jadi pada dasarnya Anda akan memiliki Commandantarmuka dasar dan kemudian akan ada beberapa CommandImplkelas yang akan mengimplementasikan antarmuka itu. Antarmuka pada dasarnya perlu hanya memiliki satu metode doCommand(Args args). Anda dapat meminta argumen diteruskan melalui instance Argskelas. Dengan cara ini Anda memanfaatkan kekuatan polimorfisme bukannya kikuk jika pernyataan lain. Desain ini juga mudah diperluas.

Kutu buku
sumber
3

Setiap kali saya bertanya apakah saya harus menggunakan pernyataan switch atau polimorfisme gaya-O, saya merujuk ke Masalah Ekspresi . Pada dasarnya, jika Anda memiliki "kasus" yang berbeda untuk data Anda dan tongkat untuk mendukung "tindakan" yang berbeda (di mana setiap tindakan melakukan sesuatu yang berbeda untuk setiap kasus) maka sangat sulit untuk membuat sistem yang secara alami memungkinkan Anda menambahkan kasus baru dan tindakan baru di masa depan.

Jika Anda menggunakan pernyataan peralihan (atau pola Pengunjung) maka mudah untuk menambahkan tindakan baru (karena Anda melihat semuanya dalam satu fungsi) tetapi sulit untuk menambahkan kasus baru (karena Anda harus kembali dan mengedit fungsi yang lama)

Sebaliknya, jika Anda menggunakan polimorfisme gaya OO, mudah untuk menambahkan case baru (hanya membuat kelas baru) tetapi sulit untuk menambahkan metode ke antarmuka (karena Anda perlu kembali dan mengedit banyak kelas)

Dalam kasus Anda, Anda hanya memiliki satu metode yang perlu Anda dukung (memproses permintaan) tetapi banyak kemungkinan kasus (setiap perintah berbeda). Karena memudahkan untuk menambahkan kasus baru lebih penting daripada menambahkan metode baru, buat saja kelas terpisah untuk setiap perintah yang berbeda.


Ngomong-ngomong, dari sudut pandang bagaimana hal-hal yang dapat dikembangkan, itu tidak membuat perbedaan besar apakah Anda menggunakan kelas atau fungsi. Jika kita membandingkan dengan pernyataan switch yang penting adalah bagaimana hal-hal "dikirim" dan kedua kelas dan fungsi dikirim dengan cara yang sama. Karena itu, cukup gunakan apa pun yang lebih nyaman dalam bahasa Anda (dan karena Go memiliki pelingkupan dan penutupan leksikal, perbedaan antara kelas dan fungsi sebenarnya sangat kecil).

Misalnya, Anda biasanya dapat menggunakan delegasi untuk melakukan bagian pemeriksaan kesalahan alih-alih mengandalkan pewarisan (contoh saya adalah dalam Javascript karena O tidak tahu sintaksis Go, saya harap Anda tidak keberatan)

function make_command(real_command){
    return function(x){
        if(check_arguments(x)){
            return real_command(x);
        }else{
            //handle error here
        }
    }
 }

 command1 = make_command(function(x){ 
     //do something
 })

 command2 = make_command(function(x){
     //do something else
 })

 command1(17);
 commnad2(42);

Tentu saja, contoh ini mengasumsikan bahwa ada cara yang masuk akal untuk memiliki fungsi pembungkus atau argumen pemeriksaan kelas induk untuk setiap kasus. Bergantung pada bagaimana keadaannya, mungkin lebih mudah untuk menempatkan panggilan ke check_arguments di dalam perintah itu sendiri (karena setiap perintah mungkin perlu memanggil fungsi pemeriksaan dengan argumen yang berbeda, karena jumlah argumen yang berbeda, jenis perintah yang berbeda, dll)

tl; dr: Tidak ada cara terbaik untuk menyelesaikan semua masalah. Dari perspektif "membuat sesuatu berfungsi", fokuslah untuk menciptakan abstraksi Anda dengan cara yang menegakkan invarian penting dan melindungi Anda dari kesalahan. Dari perspektif "proof-proofing", ingatlah bagian kode mana yang lebih mungkin diperluas.

hugomg
sumber
1

Saya tidak pernah menggunakan go, karena ac # programmer saya mungkin akan turun baris berikut, semoga arsitektur ini cocok dengan apa yang Anda lakukan.

Saya akan membuat kelas kecil / objek untuk masing-masing dengan fungsi utama untuk mengeksekusi, masing-masing harus tahu representasi string-nya. Ini memberi Anda kemudahan yang kedengarannya seperti yang Anda inginkan karena jumlah fungsi akan meningkat. Perhatikan bahwa saya tidak akan menggunakan statika kecuali Anda benar-benar perlu, mereka tidak memberikan banyak keuntungan.

Saya kemudian akan memiliki pabrik yang tahu bagaimana menemukan kelas-kelas ini pada waktu berjalan mengubah mereka untuk dimuat dari rakitan yang berbeda dll. Ini berarti Anda tidak memerlukan mereka semua dalam proyek yang sama.

Dengan demikian juga membuatnya lebih modular untuk pengujian dan harus membuat kode bagus dan kecil, yang lebih mudah untuk dipelihara nanti.

Ian
sumber
0

Bagaimana Anda memilih perintah / fungsi Anda?

Jika ada beberapa cara "pintar" untuk memilih fungsi yang benar maka itulah cara yang harus dilakukan - artinya Anda dapat menambahkan dukungan baru (mungkin di perpustakaan eksternal) tanpa mengubah logika inti.

Juga, menguji fungsi individu lebih mudah dalam isolasi daripada pernyataan switch yang sangat besar.

Akhirnya, hanya digunakan di satu tempat - Anda mungkin menemukan bahwa setelah Anda mencapai 50 bit yang berbeda dari fungsi yang berbeda dapat digunakan kembali?

Michael
sumber
Perintah adalah string unik. Saya bisa memetakan ini ke bilangan bulat jika diperlukan.
beatgammit
0

Saya tidak tahu bagaimana Go bekerja, tetapi arsitektur yang saya gunakan dalam ActionScript adalah memiliki Daftar Tertaut Ganda yang bertindak sebagai Rantai Tanggung Jawab. Setiap tautan memiliki fungsi determResponsibility (yang saya implementasikan sebagai panggilan balik, tetapi Anda dapat menulis setiap tautan secara terpisah jika itu berfungsi lebih baik untuk apa yang Anda coba lakukan). Jika sebuah tautan menentukan bahwa ia memiliki tanggung jawab, itu akan memanggil meetResponsibility (sekali lagi, sebuah callback), dan itu akan mengakhiri rantai. Jika tidak memiliki tanggung jawab, itu akan meneruskan permintaan ke tautan berikutnya dalam rantai.

Kasing penggunaan yang berbeda dapat dengan mudah ditambahkan dan dihapus hanya dengan menambahkan tautan baru di antara tautan (atau di akhir) rantai yang ada.

Ini mirip dengan, tetapi agak berbeda dari, ide Anda tentang peta fungsi. Yang menyenangkan adalah bahwa Anda baru saja menyampaikan permintaan dan mengerjakannya tanpa harus melakukan hal lain. Sisi buruknya adalah itu tidak berfungsi dengan baik jika Anda perlu mengembalikan nilai.

Amy Blankenship
sumber