一般來說使用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);