1. 程式人生 > 其它 >如何少寫if-else

如何少寫if-else

1.衛語句提前return

假設有如下程式碼

if (condition) {
    // do something
} else {
    return xxx;
}

通過對判斷條件取反,程式碼在邏輯表達上會更加清晰

if (!condition) {
    return xxx;
}
// do something

2.使用Optional簡化if判空

2.1簡化1級判空

假設有如下程式碼

if (input != null) {
    // return value1
} else {
    // return value2
}

使用Optional後

Optional.ofNullable(input).map(value1).orElse(value2);

2.2簡化多級判空

假設有如下程式碼

if (input != null && input.getUser() != null && input.getUser().getName() != null) {
    // return value1
} else {
    // return value2
}

使用Optional後

return Optional.ofNullable(input)
    .map(Input::getUser)
    .map(User::getName)
    .map(value1)
    .orElse(value2);

對於沒有else的場景,使用ifPresent即可

if (input != null && input.getUser() != null && input.getUser.getName() != null) {
    // do action
}
Optional.ofNullable(input)
    .map(Input::getUser)
    .map(User::getName)
    .ifPresent(action);

3.策略模式

假設有如下程式碼:

if ("dog".equals(petType)) {
    // 處理dog
} else if ("cat".equals(petType)) {
    // 處理cat
} else if ("pig".equals(petType)) {
    // 處理pig
} else if ("rabbit".equals(petType)) {
    // 處理rabbit
} else {
    throw new UnsupportedOperationException();
}

這就是不要根據不同的引數型別走不同的程式碼邏輯,這種場景很常見,他還會以switch-case的方式出現:

switch (petType) {
    case "dog":
        // 處理dog
        break;
    case "cat":
        // 處理cat
        break;
    case "pig":
        // 處理pig
        break;
    case "rabbit":
        // 處理rabbit
        break;
    default:
        throw new UnsupportedOperationException();
}

不同的程式碼邏輯就代表了不同的策略,我們可以通過如下幾個方式改寫。

3.1多型

public interface Strategy {
    void invoke(); // 處理各個邏輯
}
public class DogStrategy implements Strategy {
    @Override
    public void invoke() {
        // 處理dog
    }
}
public class CatStrategy implements Strategy {
    @Override
    public void invoke() {
        // 處理cat
    }
}
public class PigStrategy implements Strategy {
    @Override
    public void invoke() {
        // 處理pig
    }
}
public class RabbitStrategy implements Strategy {
    @Override
    public void invoke() {
        // 處理rabbit
    }
}

具體的策略物件可以放在一個Map中,優化後的實現類似如下

Strategy strategy = map.get(petType);
stratefy.invoke();

關於如何存放到Map中也多個可以參考的方式。

3.1.1靜態表

Map<String, Strategy> map = ImmutableMap.<String, Strategy>builder()
    .put("dog", new DogStrategy())
    .put("cat", new CatStrategy())
    .put("pig", new PigStrategy())
    .put("rabbit", new RabbitStrategy())
    .build();

3.1.2Spring託管下的動態註冊

(1) 定義一個註冊中心用於接受註冊資訊

public enum StrategyMapping {
    INSTANCE;

    private final Map<String, Class<? extends Strategy>> map = new ConcurrentHashMap<>();

    public void register(String type,  Class<? extends Strategy> clazz) {
        map.put(type, clazz);
    }

    public Strategy getStrategy(String type) {
        Class<? extends Strategy> clazz = map.get(type);
        if (clazz == null) {
            throw new UnregisteredException();
        }
        return SpringContextHolder.getBean(clazz);
    }
}

(2) 將每個Strategy交由Spring管理,並在構造後註冊

@Component
public class DogStrategy implements Strategy {
    @PostConstruct
    public void init() {
        StrategyMapping.INSTANCE.register("dog", this.getClass());
    }

    @Override
    public void invoke() {
        // 處理dog
    }
}

(3) 使用方式就變成了

Strategy strategy = StrategyMapping.INSTANCE.getStrategy(petType);
stratefy.invoke();

3.1.3Spring託管下的註解繫結

如果你不想在每個bean裡面使用@PostConstruct去註冊,你還可以使用註解來完成繫結並註冊

(1) 先宣告一個註解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface StrategyMapping {
    String type();
}

(2) 在bean上繫結註解

@StrategyMapping(type = "dog")
@Component
public class DogStrategy implements Strategy {   
    @Override
    public void invoke() {
        // 處理dog
    }
}

(3) 增加一個監聽器用於註冊

@Component
public class StrategyAnnotationRegistry implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        registerStrategy(event);
    }

    private void registerStrategy(ContextRefreshedEvent event) {
        ApplicationContext context = event.getApplicationContext();
        Map<String, Object> beansWithAnnotation = context.getBeansWithAnnotation(StrategyMapping.class);
        beansWithAnnotation.forEach((key, value) -> {
            String type = AnnotationUtils.findAnnotation(value.getClass(), StrategyMapping.class).type();
            if (value instanceof Strategy) {
                StrategyMapping.INSTANCE.register(type, (Strategy) value);
            }
        });
    }
}

(4) 註冊中心我們就可以不存class了,存實際的bean

public enum StrategyMapping {
    INSTANCE;

    private final Map<String, Strategy> map = new ConcurrentHashMap<>();

    public void register(String type, Strategy bean) {
        map.put(type, bean);
    }

    public Strategy getStrategy(String type) {
        Strategy bean = map.get(type);
        if (bean == null) {
            throw new UnregisteredException();
        }
        return bean;
    }
}

(5) 使用方式與之前一致

Strategy strategy = StrategyMapping.INSTANCE.getStrategy(petType);
stratefy.invoke();

3.2列舉

採用多型會額外產生很多策略類,如果我們已經預先將petType定義成了列舉,就會發現可以把Strategy中的invoke()方法放到列舉中,從而完成了一種對映關係。

public enum PetType {
    DOG {
        @Override
        public void invoke() {
            // 處理dog
        }
    },
    CAT {
        @Override
        public void invoke() {
            // 處理cat
        }
    },
    PIG {
        @Override
        public void invoke() {
            // 處理pig
        }
    },
    RABBIT {
        @Override
        public void invoke() {
            // 處理rabbit
        }
    };

    public abstract void invoke();
}

這樣在呼叫時的程式碼就類似如下:

PetType petType = PetType.valueOf(type.toUpperCase(Locale.ROOT));
petType.invoke();

3.3函式式簡化策略

同樣面對多型會額外產生很多策略類的問題,除了列舉我們還可以使用函式式的方式來改寫,這裡有個前提最好是策略的內容不會過於複雜,不然在程式碼的可讀性上會比較差

同樣我們會有一個map靜態表,不過map裡面存放的是lambda

Map<String, Runnable> map = ImmutableMap.<String, Runnable>builder()
    .put("dog", () -> {
        // 處理dog
    })
    .put("cat", () -> {
        // 處理cat
    })
    .put("pig", () -> {
        // 處理pig
    })
    .put("rabbit", () -> {
        // 處理rabbit
    })
    .build();

使用方式則變成了

Runnable task = map.get(petType);
task.run();

4.責任鏈模式

與策略模式類似,假設我們已經將不同的業務邏輯都抽離成了單獨的Strategy類,即我們有了DogStrategyCatStrategyPigStrategyRabbitStrategy,在3.策略模式中,我們主要是使用了表驅動的方式,得到了petType和Strategy的對映關係,在執行之前獲取了既定的Strategy。

換一種思路,如果我們不從外部去維護這種對映關係,而是讓各個Strategy自己去判斷是否執行,這樣只需維護一個Strategy組成的List,然後逐個請求、嘗試執行即可。

不過這種方式,在Strategy較多的情況下,遍歷List的效能要比表驅動差。

這種類似責任鏈的方式,也有兩種體現方式,主要差異在於外部迭代還是內部迭代。

4.1 外部迭代

3.1多型類似,我們定義一套策略,不過介面上有些差異

public interface Strategy {
    boolean isSupport(String petType); // 是否由當前策略處理

    void invoke(); // 處理各個邏輯
}
public class DogStrategy implements Strategy {
    @Override
    public boolean isSupport(String petType) {
        return "dog".equals(petType);
    }

    @Override
    public void invoke() {
        // 處理dog
    }
}
public class CatStrategy implements Strategy {
    @Override
    public boolean isSupport(String petType) {
        return "cat".equals(petType);
    }

    @Override
    public void invoke() {
        // 處理cat
    }
}
public class PigStrategy implements Strategy {
    @Override
    public boolean isSupport(String petType) {
        return "pig".equals(petType);
    }

    @Override
    public void invoke() {
        // 處理pig
    }
}
public class RabbitStrategy implements Strategy {
    @Override
    public boolean isSupport(String petType) {
        return "rabbit".equals(petType);
    }

    @Override
    public void invoke() {
        // 處理rabbit
    }
}

然後我們維護一個由Strategy組成的List,這裡同樣可以採用靜態註冊動態註冊的方式,這裡以靜態註冊為例:

List<Strategy> list = ImmutableList.<Strategy>builder()
    .put(new DogStrategy())
    .put(new CatStrategy())
    .put(new PigStrategy())
    .put(new RabbitStrategy())
    .build();

呼叫的方式則變成了

for (Strategy strategy : list) {
    if (strategy.isSupport(petType)) {
        strategy.invoke();
    }
}