Alternativa a Switch Case in Java
-
07-07-2019 - |
Domanda
Esiste un modo alternativo per implementare un caso switch in Java diverso da if else che non sembra buono. Un insieme di valori sarà lì in combinazione, secondo la selezione deve essere eseguito il metodo corrispondente.
Soluzione
Presumibilmente stai lottando con la necessità che il caso sia costante. In genere si tratta di un odore di codice, ma ci sono cose che puoi fare. Potresti voler sollevare e collegare ad un'altra domanda che spiega in dettaglio perché stai cercando di cambiare.
Map<String,Object> map = new HasMap<String,Object>();
// ... insert stuff into map
// eg: map.add("something", new MyObject());
String key = "something";
if (map.contains(key)) {
Object o = map.get(key);
}
Nell'esempio sopra, potresti voler mappare a "gestori", qualcosa come
interface Handler {
public void doSomething();
}
che poi trasforma tutto questo in una ricerca.
if (map.contains(key)) { map.get(key).doSomething(); }
Ancora una volta, è un po 'di odore, quindi per favore pubblica una domanda che illustri il ragionamento.
Altri suggerimenti
Se hai molte dichiarazioni switch / case attorno al tuo codice e ti stanno facendo impazzire.
Puoi optare per il refactoring: Sostituisci condizionale con polimorfismo.
Supponiamo che tu abbia un software utilizzato per salvare informazioni su diversi dispositivi: sono definite 4 operazioni di persistenza: recupera, salva, cancella, aggiorna , che potrebbe essere implementato da N numero di meccanismi di persistenza (file flat, rete, RDBMS, XML, ecc.).
Il tuo codice deve supportarli tutti, quindi in 4 posti diversi hai questo:
prima
class YourProblematicClass {
....
public void fetchData( Object criteria ) {
switch ( this.persitanceType ) {
case FilePersistance:
// open file
// read it
// find the criteria
// build the data
// close it.
break;
case NetWorkPersistance:
// Connect to the server
// Authenticate
// retrieve the data
// build the data
// close connection
break();
case DataBasePersistace:
// Get a jdbc connection
// create the query
// execute the query
// fetch and build data
// close connection
break;
}
return data;
}
Lo stesso per salvare / cancellare / aggiornare
public void saveData( Object data) {
switch ( this.persitanceType ) {
case FilePersistance:
// open file, go to EOF, write etc.
break;
case NetWorkPersistance:
// Connect to the server
// Authenticate
// etc
break();
case DataBasePersistace:
// Get a jdbc connection, query, execute...
break;
}
}
E così via ....
public void deleteData( Object data) {
switch ( this.persitanceType ) {
case FilePersistance:
break;
case NetWorkPersistance:
break();
case DataBasePersistace:
break;
}
}
public void updateData( Object data) {
switch ( this.persitanceType ) {
case FilePersistance:
break;
case NetWorkPersistance:
break();
case DataBasePersistace:
break;
}
}
L'uso dell'istruzione switch / case diventa problematico:
-
Ogni volta che si desidera aggiungere un nuovo tipo, è necessario inserire un nuovo interruttore / caso in ogni sezione.
-
Molte volte, alcuni tipi sono simili e non hanno bisogno di un interruttore / caso diverso (potresti metterli in cascata)
- Alcuni altri lo sono e alcune volte differiscono leggermente
- Potrebbe anche essere necessario caricare tipi diversi in fase di esecuzione (come plug-in)
Quindi il refactoring qui sarebbe aggiungere un'interfaccia o un tipo astratto e avere i diversi tipi implementare quell'interfaccia e delegare la responsabilità a quell'oggetto.
Quindi avresti qualcosa del genere:
DOPO
public interface PersistenceManager {
public void fetchData( Object criteria );
public void saveData( Object toSave );
public void deleteData( Object toDelete );
public void updateData( Object toUpdate );
}
E diverse implementazioni
public class FilePersistence implements PersistanceManager {
public void fetchData( Object criteria ) {
// open file
// read it
// find the criteria
// build the data
// close it.
}
public void saveData( Object toSave ) {
// open file, go to EOF etc.
}
public void deleteData( Object toDelete ){
....
}
public void updateData( Object toUpdate ){
....
}
}
E gli altri tipi si implementerebbero secondo la loro logica. La rete avrebbe a che fare con socket e stream, DB avrebbe a che fare con JDBC, ResultSet ecc. XML con nodo etc.etc.
public class NetworkPersistence implements PersistanceManager {
public void fetchData( Object criteria ) {
// Socket stuff
}
public void saveData( Object toSave ) {
// Socket stuff
}
public void deleteData( Object toDelete ){
// Socket stuff
}
public void updateData( Object toUpdate ){
// Socket stuff
}
}
public class DataBasePersistence implements PersistanceManager {
public void fetchData( Object criteria ) {
// JDBC stuff
}
public void saveData( Object toSave ) {
// JDBC stuff
}
public void deleteData( Object toDelete ){
// JDBC stuff
}
public void updateData( Object toUpdate ){
// JDBC stuff
}
}
E infine devi solo delegare le invocazioni.
In seguito:
public YouProblematicClass { // not longer that problematic
PersistamceManager persistance = // initialize with the right one.
public void fetchData( Object criteria ) {
// remove the switch and replace it with:
this.persistance.fetchData( criteria );
}
public void saveData( Object toSave ) {
// switch removed
this.persistance.saveData( toSave );
}
public void deleteData( Object toDelete ){
this.persistance.deleteData( toDelete );
}
public void updateData( Object toUpdate ){
this.persistance.updateData( toUpdate );
}
}
Quindi, devi solo creare l'istanza corretta per il gestore di persistenza in base al tipo una sola volta. Quindi tutte le invocazioni sono risolte dal polimorfismo. Questa è una delle caratteristiche chiave della tecnologia orientata agli oggetti.
Se decidi di aver bisogno di un altro gestore di persistenza, devi solo creare la nuova implementazione e assegnarla alla classe.
public WavePersistance implements PersistanceManager {
public void fetchData( Object criteria ) {
// ....
}
public void saveData( Object toSave ) {
// ....
}
public void deleteData( Object toDelete ){
// ....
}
public void updateData( Object toUpdate ){
// ....
}
}
Il refactoring del codice per utilizzare il polimorfismo potrebbe eliminare la necessità di un'istruzione switch. Tuttavia, ci sono alcuni usi legittimi per passare quindi dipende dalla tua situazione.
una brutta serie di if, else if, else
?
oppure si potrebbe immaginare una specie di caso di commutazione dinamica:
public interface Task<T>
{
public void doSomething(T context);
}
public Class SwitchCase<T>
{
Map<Integer,Task<T>> tasks;
Task<T> defaultTask;
public void choose(int choice, T context)
{
Task<T> t= this.tasks.get(choice);
if(t!=null) { t.doSomething(context); return;}
if(defaultTask!=null) { defaultTask.doSomething(context);}
}
}
Immagino " Clean Code " ha un bel capitolo secondo switch / case vs. if / else.
Inoltre: penso che abbia senso decidere se è possibile ridurre il "rumore". e rendere il codice più pulito usando la custodia, il polimorfismo o anche un buon vecchio if / else. Il numero di casi svolge un ruolo importante qui, immagino.
Pubblico un caso tipico di come ho sostituito switch case con enum.
prima del refactor ho enum PatternTypes
:
public enum PatternTypes {
ALPHA_CHAR, ALPHANUMERIC_CHAR, ADDITIONAL_CHAR, UNICODE_BMP_CHARS
}
e funzione:
private static final String ALPHA_CHAR = "[a-zA-Z]+";
private static final String ALPHANUMERIC_CHAR = "[a-zA-Z0-9\\_]+";
private static final String ADDITIONAL_CHAR = "[a-zA-Z0-9\\_\\-\\,\\.\\s\\!\\#\\$\\&\\(\\)\\*\\+\\;\\:\\=\\?\\@\\|\\[\\]\\{\\}\\~]+";
private static final String UNICODE_BMP_CHARS = "[a-zA-Z0-9\\_\\-\\,\\.\\s\\!\\#\\$\\&\\(\\)\\*\\+\\;\\:\\=\\?\\@\\|\\[\\]\\{\\}\\~\u00A0-\uD7FF\uF900-\uFFFD]+";
/*
* Match given classAbbr with given RegEx pattern
*/
private void checkInvalidClassAbbr(String classAbbr,
PatternTypes classAbbrPattern) {
switch (classAbbrPattern) {
case ALPHA_CHAR:
checkUnmatched(classAbbr, ALPHA_CHAR, CLASS_ABBR_VAR_NAME);
break;
case ALPHANUMERIC_CHAR:
checkUnmatched(classAbbr, ALPHANUMERIC_CHAR, CLASS_ABBR_VAR_NAME);
break;
case ADDITIONAL_CHAR:
throw new MalFormedDNException("Not support Pattern Type:"
+ classAbbrPattern);
case UNICODE_BMP_CHARS:
throw new MalFormedDNException("Not support Pattern Type:"
+ classAbbrPattern);
}
}
Dopo refactor PatternTypes
modificato in:
public enum PatternTypes {
/**
* RegEx patterns divided by restriction level
*/
ALPHA_CHAR("[a-zA-Z]+"),
ALPHANUMERIC_CHAR("[a-zA-Z0-9\\_]+"),
ADDITIONAL_CHAR("[a-zA-Z0-9\\_\\-\\,\\.\\s\\!\\#\\$\\&\\(\\)\\*\\+\\;\\:\\=\\?\\@\\|\\[\\]\\{\\}\\~]+"),
UNICODE_BMP_CHARS("[a-zA-Z0-9\\_\\-\\,\\.\\s\\!\\#\\$\\&\\(\\)\\*\\+\\;\\:\\=\\?\\@\\|\\[\\]\\{\\}\\~\u00A0-\uD7FF\uF900-\uFFFD]+");
public String getPatternContent() {
return patternContent;
}
private String patternContent;
PatternTypes(String patternContent) {
this.patternContent = patternContent;
}
}
e la funzione si semplifica in:
/*
* Match given classAbbr with given RegEx pattern
*/
private void checkInvalidClassAbbr(String classAbbr, PatternTypes classAbbrPattern) {
if (PatternTypes.ADDITIONAL_CHAR.equals(classAbbrPattern) || PatternTypes.UNICODE_BMP_CHARS.equals(classAbbrPattern)){
throw new MalFormedDNException("RegEx pattern:" + classAbbrPattern.name() + "is not allowed for Class Abbr");
}
checkUnmatched(classAbbr, classAbbrPattern.getPatternContent(), CLASS_ABBR_VAR_NAME);
}
Hashmap è considerato non compatibile con la memoria, quindi è possibile utilizzare Enum per questo scopo.
Esempio:
class EnumExample4{
enum Season{
WINTER(5), SPRING(10), SUMMER(15), FALL(20);
private int value;
private Season(int value){
this.value=value;
}
}
public static void main(String args[]){
System.out.println(Season.WINTER.value); //This gives you 5
}}
Questo ti consentirà di scrivere Switch Case o if statement.
Per un'alternativa allo switch switch, penso che la soluzione migliore sarà utilizzare un enum . Ad esempio: considera il caso seguente: -
public enum EnumExample {
OPTION1{
public double execute() {
Log.info(CLASS_NAME, "execute", "The is the first option.");
return void;
}
},
OPTION2{
public double execute() {
Log.info(CLASS_NAME, "execute", "The is the second option.");
return void;
}
},
OPTION3{
public double execute() {
Log.info(CLASS_NAME, "execute", "The is the third option.");
return void;
};
public static final String CLASS_NAME = Indicator.class.getName();
public abstract void execute();
}
L'enumerazione di cui sopra può essere utilizzata nel modo seguente:
EnumExample.OPTION1.execute();
Speriamo che questo vi aiuti ragazzi.
Cosa vuoi fare? Perché Switch-Case non è abbastanza buono?
La risposta veloce è: usa if-else
if () {}
else if () {}
...
else if () {}
Ma non direi che è meglio ...
Che ne dici di un'istruzione if
(insieme a else if
e else
)? Mentre switch
ti consentirà di passare utilizzando l'uguaglianza rispetto ai tipi interi o Enum, se
ti consente di utilizzare qualsiasi logica booleana.
Potresti sempre sostituire un interruttore con if-else if-else if-else if ...
, anche se non vedo perché lo vorresti. A seconda del contesto, switch
può talvolta essere sostituito da array o hashmaps.
Se le stringhe sono statiche, puoi creare un ENUM. e fare un interruttore su di esso.