Apa cara untuk menghilangkan penggunaan kode saklar?
Apa cara untuk menghilangkan penggunaan kode saklar?
Jawaban:
Switch-statement bukan merupakan antipattern per se, tetapi jika Anda mengkode objek berorientasi Anda harus mempertimbangkan jika penggunaan switch lebih baik diselesaikan dengan polimorfisme daripada menggunakan pernyataan switch.
Dengan polimorfisme, ini:
foreach (var animal in zoo) {
switch (typeof(animal)) {
case "dog":
echo animal.bark();
break;
case "cat":
echo animal.meow();
break;
}
}
menjadi ini:
foreach (var animal in zoo) {
echo animal.speak();
}
typeof
, dan jawaban ini tidak menyarankan cara atau alasan untuk bekerja di sekitar pernyataan switch dalam situasi lain.
Lihat Beralih Pernyataan Bau :
Biasanya, pernyataan switch yang serupa tersebar di seluruh program. Jika Anda menambah atau menghapus klausa dalam satu saklar, Anda sering harus menemukan dan memperbaiki yang lain juga.
Baik Refactoring dan Refactoring to Patterns memiliki pendekatan untuk menyelesaikannya.
Jika kode (semu) Anda terlihat seperti:
class RequestHandler {
public void handleRequest(int action) {
switch(action) {
case LOGIN:
doLogin();
break;
case LOGOUT:
doLogout();
break;
case QUERY:
doQuery();
break;
}
}
}
Kode ini melanggar Prinsip Terbuka Terbuka dan rentan terhadap setiap jenis kode tindakan baru yang muncul. Untuk memperbaiki ini, Anda bisa memperkenalkan objek 'Perintah':
interface Command {
public void execute();
}
class LoginCommand implements Command {
public void execute() {
// do what doLogin() used to do
}
}
class RequestHandler {
private Map<Integer, Command> commandMap; // injected in, or obtained from a factory
public void handleRequest(int action) {
Command command = commandMap.get(action);
command.execute();
}
}
Jika kode (semu) Anda terlihat seperti:
class House {
private int state;
public void enter() {
switch (state) {
case INSIDE:
throw new Exception("Cannot enter. Already inside");
case OUTSIDE:
state = INSIDE;
...
break;
}
}
public void exit() {
switch (state) {
case INSIDE:
state = OUTSIDE;
...
break;
case OUTSIDE:
throw new Exception("Cannot leave. Already outside");
}
}
Maka Anda bisa memperkenalkan objek 'Negara'.
// Throw exceptions unless the behavior is overriden by subclasses
abstract class HouseState {
public HouseState enter() {
throw new Exception("Cannot enter");
}
public HouseState leave() {
throw new Exception("Cannot leave");
}
}
class Inside extends HouseState {
public HouseState leave() {
return new Outside();
}
}
class Outside extends HouseState {
public HouseState enter() {
return new Inside();
}
}
class House {
private HouseState state;
public void enter() {
this.state = this.state.enter();
}
public void leave() {
this.state = this.state.leave();
}
}
Semoga ini membantu.
Map<Integer, Command>
tidak perlu switch?
Switch adalah pola, apakah diimplementasikan dengan pernyataan switch, jika rantai lain, tabel pencarian, oop polimorfisme, pencocokan pola atau sesuatu yang lain.
Apakah Anda ingin menghilangkan penggunaan " beralih pernyataan " atau " beralih pola "? Yang pertama dapat dihilangkan, yang kedua, hanya jika pola / algoritma lain dapat digunakan, dan sebagian besar waktu itu tidak mungkin atau itu bukan pendekatan yang lebih baik untuk melakukannya.
Jika Anda ingin menghilangkan pernyataan switch dari kode, pertanyaan pertama yang harus ditanyakan adalah di mana masuk akal untuk menghilangkan pernyataan switch dan menggunakan beberapa teknik lainnya. Sayangnya jawaban untuk pertanyaan ini adalah spesifik domain.
Dan ingat bahwa kompiler dapat melakukan berbagai optimasi untuk mengganti pernyataan. Jadi misalnya jika Anda ingin melakukan pemrosesan pesan secara efisien, pernyataan beralih cukup banyak cara untuk pergi. Tetapi di sisi lain menjalankan aturan bisnis berdasarkan pernyataan switch mungkin bukan cara terbaik untuk pergi dan aplikasi harus di-ulang.
Berikut adalah beberapa alternatif untuk beralih pernyataan:
Beralih itu sendiri tidak terlalu buruk, tetapi jika Anda memiliki banyak "switch" atau "if / else" pada objek dalam metode Anda, itu mungkin merupakan tanda bahwa desain Anda sedikit "prosedural" dan bahwa objek Anda hanya bernilai ember. Pindahkan logika ke objek Anda, aktifkan metode pada objek Anda dan biarkan mereka memutuskan bagaimana meresponsnya.
Saya pikir cara terbaik adalah menggunakan Peta yang bagus. Menggunakan kamus Anda dapat memetakan hampir semua input ke beberapa nilai / objek / fungsi lainnya.
kode Anda akan terlihat seperti (psuedo) seperti ini:
void InitMap(){
Map[key1] = Object/Action;
Map[key2] = Object/Action;
}
Object/Action DoStuff(Object key){
return Map[key];
}
Semua orang suka if else
blok BESAR . Sangat mudah dibaca! Saya ingin tahu mengapa Anda ingin menghapus pernyataan switch. Jika Anda memerlukan pernyataan switch, Anda mungkin perlu pernyataan switch. Serius, saya akan mengatakan itu tergantung pada apa kode lakukan. Jika semua switch lakukan adalah memanggil fungsi (katakanlah) Anda bisa melewati pointer fungsi. Apakah itu solusi yang lebih baik masih bisa diperdebatkan.
Bahasa juga merupakan faktor penting di sini.
Saya pikir apa yang Anda cari adalah Pola Strategi.
Ini dapat diimplementasikan dalam beberapa cara, yang telah disebutkan dalam jawaban lain untuk pertanyaan ini, seperti:
switch
pernyataan akan baik untuk diganti jika Anda menemukan diri Anda menambahkan negara baru atau perilaku baru ke pernyataan:
keadaan int; String getString () { beralih (status) { kasus 0: // perilaku untuk status 0 kembalikan "nol"; kasus 1: // perilaku untuk negara 1 kembalikan "satu"; } melempar IllegalStateException baru (); } gandakan getDouble () { switch (this.state) { kasus 0: // perilaku untuk status 0 return 0d; kasus 1: // perilaku untuk negara 1 kembalikan 1d; } melempar IllegalStateException baru (); }
Menambahkan perilaku baru memerlukan menyalin switch
, dan menambahkan status baru berarti menambahkan yang lain case
untuk setiap switch
pernyataan.
Di Jawa, Anda hanya bisa mengganti sejumlah tipe primitif yang sangat terbatas yang nilainya Anda ketahui saat runtime. Ini menghadirkan masalah dalam dan dari dirinya sendiri: negara diwakili sebagai angka ajaib atau karakter.
Pencocokan pola , dan banyak if - else
blok dapat digunakan, meskipun benar-benar memiliki masalah yang sama ketika menambahkan perilaku baru dan status baru.
Solusi yang orang lain sarankan sebagai "polimorfisme" adalah contoh dari pola Negara :
Ganti masing-masing negara bagian dengan kelasnya sendiri. Setiap perilaku memiliki metode sendiri di kelas:
Kondisi miring; String getString () { return state.getString (); } gandakan getDouble () { return state.getDouble (); }
Setiap kali Anda menambahkan status baru, Anda harus menambahkan implementasi IState
antarmuka baru. Di switch
dunia, Anda akan menambahkan case
masing-masing switch
.
Setiap kali Anda menambahkan perilaku baru, Anda perlu menambahkan metode baru ke IState
antarmuka, dan masing-masing implementasinya. Ini adalah beban yang sama seperti sebelumnya, meskipun sekarang kompiler akan memeriksa apakah Anda memiliki implementasi perilaku baru pada setiap negara yang sudah ada sebelumnya.
Yang lain sudah mengatakan, bahwa ini mungkin terlalu berat, jadi tentu saja ada titik Anda mencapai di mana Anda berpindah dari satu ke yang lain. Secara pribadi, kedua kalinya saya menulis saklar adalah titik di mana saya refactor.
jika ada
Saya menyangkal premis bahwa saklar pada dasarnya buruk.
Mengapa kamu mau? Di tangan kompiler yang baik, pernyataan switch bisa jauh lebih efisien daripada jika blok lain (dan juga lebih mudah dibaca), dan hanya switch terbesar yang mungkin dipercepat jika diganti oleh apa pun struktur data pencarian-tidak langsung.
'switch' hanyalah konstruksi bahasa dan semua konstruksi bahasa dapat dianggap sebagai alat untuk menyelesaikan pekerjaan. Seperti alat nyata, beberapa alat lebih cocok untuk satu tugas daripada yang lain (Anda tidak akan menggunakan palu godam untuk memasang kait gambar). Bagian penting adalah bagaimana 'menyelesaikan pekerjaan' didefinisikan. Apakah itu perlu dipertahankan, apakah perlu cepat, apakah perlu skala, apakah perlu diperpanjang dan seterusnya.
Pada setiap titik dalam proses pemrograman biasanya ada berbagai konstruksi dan pola yang dapat digunakan: switch, urutan if-else-if, fungsi virtual, tabel lompatan, peta dengan pointer fungsi dan sebagainya. Dengan pengalaman, seorang programmer akan secara naluriah mengetahui alat yang tepat untuk digunakan untuk situasi tertentu.
Harus diasumsikan bahwa siapa pun yang memelihara atau meninjau kode setidaknya sama terampilnya dengan penulis asli sehingga setiap konstruk dapat digunakan dengan aman.
Jika saklar ada untuk membedakan berbagai jenis objek, Anda mungkin kehilangan beberapa kelas untuk menggambarkan objek-objek itu dengan tepat, atau beberapa metode virtual ...
Untuk C ++
Jika Anda merujuk ke yaitu AbstractFactory, saya pikir metode registerCreatorFunc (..) biasanya lebih baik daripada harus menambahkan case untuk setiap pernyataan "baru" yang diperlukan. Kemudian membiarkan semua kelas membuat dan mendaftarkan creatorFunction (..) yang dapat dengan mudah diimplementasikan dengan makro (jika saya berani menyebutkan). Saya percaya ini adalah pendekatan umum yang dilakukan banyak kerangka kerja. Saya pertama kali melihatnya di ET ++ dan saya pikir banyak kerangka kerja yang membutuhkan makro DECL dan IMPL menggunakannya.
Dalam bahasa prosedural, seperti C, maka beralih akan lebih baik daripada alternatif lainnya.
Dalam bahasa berorientasi objek, maka hampir selalu ada alternatif lain yang tersedia yang lebih baik memanfaatkan struktur objek, terutama polimorfisme.
Masalah dengan pernyataan switch muncul ketika beberapa blok switch sangat mirip terjadi di beberapa tempat dalam aplikasi, dan dukungan untuk nilai baru perlu ditambahkan. Sangat umum bagi pengembang untuk lupa menambahkan dukungan untuk nilai baru ke salah satu blok sakelar yang tersebar di sekitar aplikasi.
Dengan polimorfisme, maka kelas baru menggantikan nilai baru, dan perilaku baru ditambahkan sebagai bagian dari penambahan kelas baru. Perilaku pada titik-titik switch ini kemudian diwarisi dari superclass, diganti untuk memberikan perilaku baru, atau diimplementasikan untuk menghindari kesalahan kompilator ketika metode super abstrak.
Di mana tidak ada polimorfisme yang jelas terjadi, mungkin ada baiknya menerapkan pola Strategi .
Tetapi jika alternatif Anda adalah JIKA besar ... LALU ... LAGI blok, lupakan saja.
Function pointer adalah salah satu cara untuk mengganti pernyataan switch chunky besar, mereka sangat baik dalam bahasa di mana Anda dapat menangkap fungsi dengan nama mereka dan membuat barang dengan mereka.
Tentu saja, Anda seharusnya tidak memaksakan pernyataan peralihan dari kode Anda, dan selalu ada kemungkinan Anda melakukan semuanya salah, yang menghasilkan potongan kode berlebihan yang bodoh. (Terkadang hal ini tidak dapat dihindari, tetapi bahasa yang baik harus memungkinkan Anda untuk menghapus redundansi saat tetap bersih.)
Ini adalah contoh pembagian & taklukkan yang hebat:
Katakanlah Anda memiliki semacam juru bahasa.
switch(*IP) {
case OPCODE_ADD:
...
break;
case OPCODE_NOT_ZERO:
...
break;
case OPCODE_JUMP:
...
break;
default:
fixme(*IP);
}
Sebagai gantinya, Anda dapat menggunakan ini:
opcode_table[*IP](*IP, vm);
... // in somewhere else:
void opcode_add(byte_opcode op, Vm* vm) { ... };
void opcode_not_zero(byte_opcode op, Vm* vm) { ... };
void opcode_jump(byte_opcode op, Vm* vm) { ... };
void opcode_default(byte_opcode op, Vm* vm) { /* fixme */ };
OpcodeFuncPtr opcode_table[256] = {
...
opcode_add,
opcode_not_zero,
opcode_jump,
opcode_default,
opcode_default,
... // etc.
};
Perhatikan bahwa saya tidak tahu cara menghapus redundansi opcode_table di C. Mungkin saya harus mengajukan pertanyaan tentang itu. :)
Jawaban yang paling jelas, tidak tergantung pada bahasa, adalah menggunakan serangkaian 'jika'.
Jika bahasa yang Anda gunakan memiliki pointer fungsi (C) atau memiliki fungsi yang merupakan nilai kelas 1 (Lua), Anda dapat mencapai hasil yang mirip dengan "switch" menggunakan array (atau daftar) fungsi (pointer ke) fungsi.
Anda harus lebih spesifik pada bahasa tersebut jika Anda menginginkan jawaban yang lebih baik.
Pernyataan pergantian sering kali dapat diganti dengan desain OO yang bagus.
Misalnya, Anda memiliki kelas Akun, dan menggunakan pernyataan switch untuk melakukan perhitungan berbeda berdasarkan jenis akun.
Saya menyarankan bahwa ini harus diganti oleh sejumlah kelas akun, mewakili berbagai jenis akun, dan semua yang mengimplementasikan antarmuka Akun.
Peralihan kemudian menjadi tidak perlu, karena Anda dapat memperlakukan semua jenis akun sama dan berkat polimorfisme, perhitungan yang sesuai akan dijalankan untuk jenis akun.
Tergantung mengapa Anda ingin menggantinya!
Banyak penerjemah menggunakan 'goto yang dikomputasi' alih-alih beralih pernyataan untuk eksekusi opcode.
Apa yang saya lewatkan tentang C / C ++ switch adalah Pascal 'in' dan rentang. Saya juga berharap saya bisa mengaktifkan string. Tapi ini, meskipun sepele bagi penyusun untuk makan, adalah kerja keras ketika dilakukan menggunakan struktur dan iterator dan hal-hal lainnya. Jadi, sebaliknya, ada banyak hal yang saya harap bisa saya ganti dengan saklar, jika saja saklar C () lebih fleksibel!
Switch bukanlah cara yang baik untuk pergi karena melanggar Open Close Principal. Beginilah cara saya melakukannya.
public class Animal
{
public abstract void Speak();
}
public class Dog : Animal
{
public virtual void Speak()
{
Console.WriteLine("Hao Hao");
}
}
public class Cat : Animal
{
public virtual void Speak()
{
Console.WriteLine("Meauuuu");
}
}
Dan inilah cara menggunakannya (mengambil kode Anda):
foreach (var animal in zoo)
{
echo animal.speak();
}
Pada dasarnya yang kami lakukan adalah mendelegasikan tanggung jawab ke kelas anak alih-alih meminta orang tua memutuskan apa yang harus dilakukan dengan anak-anak.
Anda mungkin juga ingin membaca tentang "Prinsip Pergantian Liskov".
Dalam JavaScript menggunakan array asosiatif:
ini:
function getItemPricing(customer, item) {
switch (customer.type) {
// VIPs are awesome. Give them 50% off.
case 'VIP':
return item.price * item.quantity * 0.50;
// Preferred customers are no VIPs, but they still get 25% off.
case 'Preferred':
return item.price * item.quantity * 0.75;
// No discount for other customers.
case 'Regular':
case
default:
return item.price * item.quantity;
}
}
menjadi ini:
function getItemPricing(customer, item) {
var pricing = {
'VIP': function(item) {
return item.price * item.quantity * 0.50;
},
'Preferred': function(item) {
if (item.price <= 100.0)
return item.price * item.quantity * 0.75;
// Else
return item.price * item.quantity;
},
'Regular': function(item) {
return item.price * item.quantity;
}
};
if (pricing[customer.type])
return pricing[customer.type](item);
else
return pricing.Regular(item);
}
Voting lain untuk / lainnya. Saya bukan penggemar kasus atau beralih pernyataan karena ada beberapa orang yang tidak menggunakannya. Kode kurang dapat dibaca jika Anda menggunakan kasing atau sakelar. Mungkin tidak kurang mudah dibaca oleh Anda, tetapi bagi mereka yang tidak pernah perlu menggunakan perintah.
Hal yang sama berlaku untuk pabrik objek.
Jika / selain itu blok adalah konstruksi sederhana yang didapat semua orang. Ada beberapa hal yang dapat Anda lakukan untuk memastikan bahwa Anda tidak menyebabkan masalah.
Pertama - Jangan mencoba dan membuat indentasi jika pernyataan lebih dari beberapa kali. Jika Anda menemukan indentasi, maka Anda melakukan kesalahan.
if a = 1 then
do something else
if a = 2 then
do something else
else
if a = 3 then
do the last thing
endif
endif
endif
Benar-benar buruk - lakukan ini sebagai gantinya.
if a = 1 then
do something
endif
if a = 2 then
do something else
endif
if a = 3 then
do something more
endif
Optimasi terkutuk. Itu tidak membuat banyak perbedaan dengan kecepatan kode Anda.
Kedua, saya tidak segan untuk keluar dari If Block selama ada cukup pernyataan break yang tersebar melalui blok kode tertentu untuk membuatnya jelas
procedure processA(a:int)
if a = 1 then
do something
procedure_return
endif
if a = 2 then
do something else
procedure_return
endif
if a = 3 then
do something more
procedure_return
endif
end_procedure
EDIT : On Switch dan mengapa saya pikir sulit untuk grok:
Berikut adalah contoh pernyataan beralih ...
private void doLog(LogLevel logLevel, String msg) {
String prefix;
switch (logLevel) {
case INFO:
prefix = "INFO";
break;
case WARN:
prefix = "WARN";
break;
case ERROR:
prefix = "ERROR";
break;
default:
throw new RuntimeException("Oops, forgot to add stuff on new enum constant");
}
System.out.println(String.format("%s: %s", prefix, msg));
}
Bagi saya masalahnya di sini adalah bahwa struktur kontrol normal yang berlaku dalam bahasa seperti C telah benar-benar rusak. Ada aturan umum bahwa jika Anda ingin menempatkan lebih dari satu baris kode di dalam struktur kontrol, Anda menggunakan tanda kurung kurawal atau awal / akhir.
misalnya
for i from 1 to 1000 {statement1; statement2}
if something=false then {statement1; statement2}
while isOKtoLoop {statement1; statement2}
Bagi saya (dan Anda dapat mengoreksi saya jika saya salah), pernyataan Case membuang aturan ini dari jendela. Blok kode yang dieksekusi secara kondisional tidak ditempatkan di dalam struktur awal / akhir. Karena itu, saya percaya bahwa Case secara konseptual cukup berbeda untuk tidak digunakan.
Semoga itu menjawab pertanyaan Anda.