如何少写if-else

Posted 菜鸟修仙传

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何少写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();
    }
}

以上是关于如何少写if-else的主要内容,如果未能解决你的问题,请参考以下文章

20个简洁的 JS 代码片段

20个简洁的 JS 代码片段

[原创]java WEB学习笔记61:Struts2学习之路--通用标签 property,uri,param,set,push,if-else,itertor,sort,date,a标签等(代码片段

JSTL的if-else表式

如何替换多个 if-else 语句来优化代码?

我的简单 if-else 语句无法访问代码如何?