一般來說使用switch通常會產生一些缺點,e.g.
1. 同樣的 switch 散佈在不同的位置時, 當新的需求出現而需要修改 switch 時,必須找出所有的 switch 一併修改。
2. 因為 switch 代表做了複數的工作,導致 switch 很難符合開閉原則 (OCP)。
3. switch 中條件區塊擴大時會讓 switch 越來越難以理解。
以上的因素也可套用到 if-else。
因此可考慮使用 Strategy Pattern 取代 switch 或是 if-else
簡單的switch如下
private static void useSwitchExample(){ switch(condition){ case 0: System.out.println("audio error"); break; case 1: System.out.println("client error"); break; case 2: System.out.println("not match"); break; } }
根據 condition 執行不同的 case 條件,會印出不同的錯誤訊息,為了取代 switch ,先建立 Strategy 如下
public interface IErrorStrategy { public void showErrorMessage(); class AudioError implements IErrorStrategy{ @Override public void showErrorMessage() { System.out.println("audio error"); } } class ClientError implements IErrorStrategy{ @Override public void showErrorMessage() { System.out.println("client error"); } } class NoMatchError implements IErrorStrategy{ @Override public void showErrorMessage() { System.out.println("no match"); } } }
在 showErrorMessage 方法即是印出各個錯誤代碼,接著建立 Manager 來控制 Strategy e.g.,
public class ErrorStrategyManager { public static final int AUDIO_ERROR = 0; public static final int CLIENT_ERROR = 1; public static final int NOMATCH_ERROR = 2; private Map<Integer, IErrorStrategy> mErrors = new HashMap<Integer, IErrorStrategy>(); public ErrorStrategyManager() { initErrors(); } private void initErrors() { mErrors.put(CLIENT_ERROR, new IErrorStrategy.ClientError()); mErrors.put(AUDIO_ERROR, new IErrorStrategy.AudioError()); mErrors.put(NOMATCH_ERROR, new IErrorStrategy.NoMatchError()); } public void showErrorMessage(int errorMessage) { IErrorStrategy error = mErrors.get(errorMessage); error.showErrorMessage(); } }
其中使用 map 來對應不同的情況對應不同的 Strategy ,便可取代原本的 switch
private static void useStrategyInsteadSwitchExample(){ new ErrorStrategyManager().showErrorMessage(condition); }
以上的處理方式有個缺點,如果傳入的數值沒有在對應的項目之中就會出現 NullPointerException. e.g.,
new ErrorStrategyManager().showErrorMessage(100);
因此我們必須考慮 “例外” 的情況發生,另外判斷 null 的狀態, e.g.,
public void showErrorMessage(int errorMessage) { IErrorStrategy error = mErrors.get(errorMessage); if (error != null) { error.showErrorMessage(); } else { System.out.println("no this error message"); } }
或是確實使用 “例外” 來處理,端看是否把該情況視為例外。
public void showErrorMessage(int errorMessage) { try { IErrorStrategy error = mErrors.get(errorMessage); error.showErrorMessage(); } catch (NullPointerException e) { System.out.println("no this error message"); } }
以下是另外一個範例。
對字串做檢查,判斷字串是否符合某些規則。
一般來說最簡單直覺的方式就是以 if-else 來判斷 e.g.,
public boolean checkData(String data) { if(data == null){ return false; }else if(data.equals("")){ return false; } return true; }
當然也具有在開頭提到的種種缺點,因此我們一樣使用 Strategy 來取代 if-else
public interface DataFormatChecker { public static final String TAG = DataFormatChecker.class.getSimpleName(); public boolean checkData(String data); class CheckAllRules implements DataFormatChecker { private Collection<DataFormatChecker> mRules = new ArrayList<DataFormatChecker>(); public CheckAllRules() { mRules.add(new CheckNull()); mRules.add(new CheckEmpty()); } @Override public boolean checkData(String data) { for (DataFormatChecker checker : mRules) { if (checker.checkData(data)) { Log.d(TAG, "which checker:" + checker.getClass().getSimpleName()); return false; } } return true; } } class CheckNull implements DataFormatChecker { @Override public boolean checkData(String data) { return null == data; } } class CheckEmpty implements DataFormatChecker { @Override public boolean checkData(String data) { return data.equals(""); } } }
第7行的 CheckAllRules 會將所有的規則物件放到 list 中,在其 checkData 方法(第18行)將這些規則物件取出並比對字串與規則。
之後若有新的規則只要新增 CheckXXX 類別 implements DataFormatChecker,再加入到 CheckAllRules 的 list 中。
外部呼叫 e.g.,
String data = "1234"; new DataFormatChecker.CheckAllRules().checkData(data);
另外也可使用 enum 取代 CheckAllRules e.g,
public interface DataFormatChecker { ... public enum CheckAllRules { CHECK_NULL(new DataFormatChecker.CheckNull()), CHECK_EMPTY(new DataFormatChecker.CheckEmpty()); private DataFormatChecker mChecker; private CheckAllRules(DataFormatChecker checker) { mChecker = checker; } public static boolean checkData(String data) { for (CheckAllRules type : CheckAllRules.values()) { if (type.mChecker.checkData(data)) { Log.d(TAG, "check data error:" + type.mChecker.getClass().getSimpleName()); return false; } } Log.d(TAG, "check data valid"); return true; } } ... }
若有新的規則需求只要新增 CHECK_XXX(new DataFormatChecker.CheckXXX()) 在 enum 中,
並實作 DataFormatChecker.CheckXXX 內容 e.g.。
public enum CheckAllRules { CHECK_NULL(new DataFormatChecker.CheckNull()), CHECK_EMPTY(new DataFormatChecker.CheckEmpty()), CHECK_LENGTH_MAX(new DataFormatChecker.CheckLengthMax()); ... } ... class CheckLengthMax implements DataFormatChecker { @Override public boolean checkData(String data) { return data.length() > 100; } }
外部呼叫不需要修改(符合 OCP),但我們已經新增了另一項檢查規則(Check Length Max)了。
String data = "1234"; new DataFormatChecker.CheckAllRules().checkData(data);