消除 if-else 和 switch 多分支语句的最佳方案

文章目录

  • 前言
  • 什么情况下考虑消除 if-else 和 switch 多个分支?
  • 一、枚举方案
  • 二、Map + 函数式接口
  • 三、策略模式
  • 总结


前言

注重代码规范的程序中,不允许出现 if-else 和 switch 分支较多的分支语句,否则产生大量的冗余代码、严重影响代码可读性。


什么情况下考虑消除 if-else 和 switch 多个分支?

  1. 分支较多。超过三个分支以上。
  2. 分支具有可扩展性。本身可扩展性高,或在项目的后续模块集成或后期版本升级等情况下,会扩展该分支或者扩展可能性较大。

一、枚举方案

针对不同分支获取不同的值。

例如,我们在对接其他平台时,a和b两个平台之间的某个对应字典的字典值定义不一致,需要进行转换:

/**
 * a平台文件类型
 */
enum AFileType {
	DOC("doc", "文档类型"),
	PDF("pdf", "pdf类型"),
	MP3("mp3", "mp3类型"),
	MP4("mp4", "mp4类型");
	
	private final String value;
	private final String label;

	PhysicalStatus(String value, String label) {
		this.value = value;
		this.label = label;
	}

	public String getValue() {
		return this.value;
	}

	public String getLabel() {
		return this.label;
	}
}
/**
 * b平台文件类型
 */
enum BFileType {
	DOC("1", "文档类型"),
	PDF("2", "pdf类型"),
	MP3("3", "mp3类型"),
	MP4("4", "mp4类型");
	
	private final String value;
	private final String label;

	PhysicalStatus(String value, String label) {
		this.value = value;
		this.label = label;
	}

	public String getValue() {
		return this.value;
	}

	public String getLabel() {
		return this.label;
	}
}

原分支语句:

/**
 * b平台文件类型转换
 * 
 * @param aFileType a平台文件类型
 * @return b平台文件类型
 */
public String convertToBFileType(String aFileType){
	if (StrUtil.equals(aFileType, AFileType.DOC.getValue())) {
		return BFileType.DOC.getValue();
	} else if (StrUtil.equals(aFileType, AFileType.PDF.getValue())) {
		return BFileType.PDF.getValue();
	} else if (StrUtil.equals(aFileType, AFileType.MP3.getValue())) {
		return BFileType.MP3.getValue();
	} else if (StrUtil.equals(aFileType, AFileType.MP4.getValue())) {
		return BFileType.MP4.getValue();
	} else {
		return null;
	}
}

这种为获取不同值的分支语句,一般采用枚举方案。

枚举改造如下:

/**
 * A平台文件类型
 */
enum AFileType {
	DOC("doc", "1", "文档类型"),
	PDF("pdf", "2", "pdf类型"),
	MP3("mp3", "3", "mp3类型"),
	MP4("mp4", "4", "mp4类型");
	
	private final String value;
	private final String bValue;
	private final String label;

	PhysicalStatus(String value, String bValue, String label) {
		this.value = value;
		this.bValue = bValue;
		this.label = label;
	}

	public String getValue() {
		return this.value;
	}
	
	public String getBValue() {
		return this.bValue;
	}

	public String getLabel() {
		return this.label;
	}
	
	/**
	 * 转换为b平台文件类型
	 */
	public static String convertToBFileType(String value) {
		for (BFileType bFileType : BFileType.values()) {
			if (StrUtil.equals(bFileType.getAValue(), value)) {
				return bFileType.getValue();
			}
		}
		return null;
	}
	
}
/**
 * b平台文件类型
 */
enum BFileType {
	DOC("1", "doc", "文档类型"),
	PDF("2", "pdf", "pdf类型"),
	MP3("3", "mp3", "mp3类型"),
	MP4("4", "mp4", "mp4类型");
	
	private final String value;
	private final String aValue;
	private final String label;

	PhysicalStatus(String value, String aValue, String label) {
		this.value = value;
		this.aValue = aValue;
		this.label = label;
	}

	public String getValue() {
		return this.value;
	}
	
	public String getAValue() {
		return this.aValue;
	}

	public String getLabel() {
		return this.label;
	}
	
	/**
	 * 转换为a平台文件类型
	 */
	public static String convertToAFileType(String value) {
		for (AFileType aFileType : AFileType.values()) {
			if (StrUtil.equals(aFileType.getBValue(), value)) {
				return aFileType.getvalue();
			}
		}
		return null;
	}
	
}

改造后的分支语句:

/**
 * b平台文件类型转换
 * 
 * @param aFileType a平台文件类型
 * @return b平台文件类型
 */
public String convertToBFileType(String aFileType){
	return AFileType.convertToBFileType(aFileType);
}

创建了类型的对应关系枚举,采用枚举 for 循环获取对应的枚举值。

二、Map + 函数式接口

针对不同分支执行不同的处理代码。不适于对象封装。

  • 轻量级。

例如,根据不用的优惠券,采用不用的发放方式(轻量型,不适于对象封装):

/**
 * 业务类
 */
@Service
@RequiredArgsConstructor
public class QueryGrantTypeService {
 
    private final GrantTypeSerive grantTypeSerive;
    
    private static Map<String, Function<String, String>> grantTypeMap = new HashMap<>();

    /**
     *  初始化业务分派逻辑
     *  key: 优惠券类型
     *  value: lambda表达式,最终会获得该优惠券的发放方式
     */
    @PostConstruct
    public void dispatcherInit(){
        grantTypeMap.put("红包", resourceId -> grantTypeSerive.redPaper(resourceId));
        grantTypeMap.put("购物券", resourceId -> grantTypeSerive.shopping(resourceId));
        grantTypeMap.put("qq会员", resourceId -> grantTypeSerive.QQVip(resourceId));
    }
 
    public String getResult(String resourceType){
        Function<String, String> function = getGrantTypeMap.get(resourceType); // 根据类型获取lambda表达式
        if(result != null){
            return function.apply(resourceId); // 向lambda表达式传入值并执行
        }
        return null;
    }
}

三、策略模式

针对不同分支执行不同的处理代码。适用于对象封装。

  • 分支有较多的业务代码。
  • 分支对应不同的对象,适合用对象划分,或需要封装不同的属性和方法,或需要封装多个方法。
  • 重量级,每个分支对应创建一个策略类,尽量不要升级为该模式。

例如,根据不同的设备,选择对应的连接方法以及断开方法(设备适用于对象封装,且需要封装两个方法):

  1. 创建统一的处理器接口,将多个分支的业务代码进行抽象封装。
/**
 * 统一分支业务处理接口
 */
public interface IDeviceHandler {

	/**
	 * 连接设备
	 */
	void deviceConnect(Device device);

	/**
	 * 断开设备
	 */
	void deviceDisConnect(Device device);
}
  1. 创建统一的处理器抽象类。
/**
 * 统一分支业务处理器抽象类
 */
public abstract class AbstractDeviceHandler implements IDeviceHandler {

}
  1. 创建分支处理器类。
/**
 * 处理器类001
 */
@Component("MC_001")
public class DeviceMc001Handler extends AbstractDeviceHandler {

	/**
	 * 连接设备
	 */
	@Override
	public void deviceConnect(Device device) {
		// TODO 执行分支业务
	}

	/**
	 * 断开设备
	 */
	void deviceDisConnect(Device device) {
		// TODO 执行分支业务
	}
}
/**
 * 处理器类002
 */
@Component("MC_002")
public class DeviceMc002Handler extends AbstractDeviceHandler {

	/**
	 * 连接设备
	 */
	@Override
	public void deviceConnect(Device device) {
		// TODO 执行分支业务
	}

	/**
	 * 断开设备
	 */
	void deviceDisConnect(Device device) {
		// TODO 执行分支业务
	}
}
  1. 根据 @Component 注解,自动注入业务处理器 map,根据 Bean 的名称获取 map 中的处理器对象,调用对应的处理方法。
/**
 * 业务类
 */
@Service
@RequiredArgsConstructor
public class DeviceService {

	private final Map<String, IDeviceHandler> deviceHandlerMap; // 自动注入业务处理器map

	/**
	 * 连接设备
	 */
	public void connect(Device device){
        IDeviceHandler deviceHandler = deviceHandlerMap.get(device.getType()); // 获取对应的处理器
        if (deviceHandler != null) {
        	deviceHandler.deviceConnect(device); // 调用对应处理器的方法
        }
	}

	/**
	 * 断开设备
	 */
	public void disConnect(Device device){
        IDeviceHandler deviceHandler = deviceHandlerMap.get(device.getType()); // 获取对应的处理器
        if (deviceHandler != null) {
        	deviceHandler.deviceDisConnect(device); // 调用对应处理器的方法
        }
	}
}

总结

分支语句的分支较多,或分支具有可扩展性,则建议使用方案来消除分支语句,避免代码臃肿、可读性低。

根据不同的场景,选择适用的方案:

  • 枚举方案:不同分支仅返回不同的值。
  • Map + 函数式接口:不同分支执行不同的处理代码,不适于对象封装。轻量级。
  • 策略模式:不同分支执行不同的处理代码,适用于对象封装(适合用对象划分,或需要封装不同的属性和方法,或需要封装多个方法)。重量级,每个分支对应创建一个策略类,尽量不要升级为该模式。

你可能感兴趣的:(java,策略模式)