定义

对于具有意义的业务概念如钱、坐标、范围等,不愿意进行建模,而是使用基本数据类型进行表示

影响

  • 暴露较多细节
  • 代码内聚性差
  • 可读性差

改进目标

  • 消除基本类型
  • 提升代码可修改性、内聚性、可读性

方法

  • 对象取代基本类型
  • 子类取代类型
  • 多态取代条件表达式
  • 提炼类
  • 引入参数对象

案例:基本类型偏执对可维护性、可读性的影响

代码背景

  • 描述了飞机按照航线类型飞行的业务;
  • 航线包含坐标集合、类型;
  • 航线坐标包含2个int数值;
  • 飞行前需要校验航线,不同航线类型校验内容不同

症状/问题

  • 内聚性、可读性差,对外暴露了内部细节,如坐标的数据结构、航线校验逻辑,代码层次不清晰
  • 通过航线类型字符串区分不同航班执行不同校验逻辑, 后续增加航线类型时需要修改fly方法,方法会越来越复杂,可维护性差

重构目标

  • 提炼坐标、航线对象代替基本类型,并封装相关逻辑
  • 消除航线类型的字符串条件表达式
/**
 * 飞机航线
 */
public class Plane {
    private final Logger logger;

    public Plane(Logger logger) {
        this.logger = logger;
    }

    public void fly(List<int[]> airLine, String airLineType) {
        for (int[] coordinate : airLine) {
            if (coordinate.length != 2) {
                throw new IllegalArgumentException("Air line is invalid.");
            }
            if ("domestic".equals(airLineType)) { // 国内航班
                if (coordinate[0] < 0 || coordinate[0] >= 100) {
                    throw new IllegalArgumentException("Air line is invalid.");
                }
                if (coordinate[1] < 0 || coordinate[1] >= 100) {
                    throw new IllegalArgumentException("Air line is invalid.");
                }
            }
            if ("international".equals(airLineType)) { // 国际航班
                if (coordinate[0] < 100) {
                    throw new IllegalArgumentException("Air line is invalid.");
                }
                if (coordinate[1] < 100) {
                    throw new IllegalArgumentException("Air line is invalid.");
                }
            }
        }
        airLine.forEach(this::fly);
    }

    private void fly(int[] coordinate) {
        logger.info(MessageFormat.format("Fly to ({0},{1})", coordinate[0], coordinate[1]));
    }
}

改进手法:提炼航线对象、提炼航线接口并用多态取代表达式、提炼坐标对象并封装入参细节

/**
 * 飞机航线
 */
public class Plane {
    private final Logger logger;

    public Plane(Logger logger) {
        this.logger = logger;
    }

    public void fly(AirLine airLine) {
        airLine.validate();
        airLine.getCoordinates().forEach(this::fly);
    }

    private void fly(Coordinate coordinate) {
        logger.info(MessageFormat.format("Fly to ({0},{1})", coordinate.getX(), coordinate.getY()));
    }
}
public interface AirLine {
    String DOMESTIC = "domestic";
    String INTERNATIONAL = "international";

    String getAirLineType();

    List<Coordinate> getCoordinates();

    void validate();
}
public abstract class AbstractAirLine implements AirLine {

    private final String airLineType;

    private final List<Coordinate> coordinates;

    AbstractAirLine(String airLineType, List<Coordinate> coordinates) {
        this.airLineType = airLineType;
        this.coordinates = coordinates;
    }

    @Override
    public String getAirLineType() {
        return airLineType;
    }

    @Override
    public List<Coordinate> getCoordinates() {
        return coordinates;
    }

    @Override
    public void validate() {
        for (Coordinate coordinate : getCoordinates()) {
            coordinate.validateLength();
            validateCoordinate(coordinate);
        }
    }

    protected abstract void validateCoordinate(Coordinate coordinate);
}
public class DomesticAirLine extends AbstractAirLine {
    public DomesticAirLine(List<Coordinate> coordinates) {
        super(DOMESTIC, coordinates);
    }

    @Override
    protected void validateCoordinate(Coordinate coordinate) {
        if (coordinate.getX() < 0 || coordinate.getX() >= 100) {
            throw new IllegalArgumentException("Air line is invalid.");
        }
        if (coordinate.getY() < 0 || coordinate.getY() >= 100) {
            throw new IllegalArgumentException("Air line is invalid.");
        }
    }
}
public class InternationalAirLine extends AbstractAirLine {
    public InternationalAirLine(List<Coordinate> coordinates) {
        super(INTERNATIONAL, coordinates);
    }

    @Override
    protected void validateCoordinate(Coordinate coordinate) {
        if (coordinate.getX() < 100) {
            throw new IllegalArgumentException("Air line is invalid.");
        }
        if (coordinate.getY() < 100) {
            throw new IllegalArgumentException("Air line is invalid.");
        }
    }
}
public class AirLineFactory {
    public static AbstractAirLine createAbstractAirLine(String airLineType, List<Coordinate> coordinates) {
        if (AirLine.DOMESTIC.equals(airLineType)) {
            return new DomesticAirLine(coordinates);
        }
        if (AirLine.INTERNATIONAL.equals(airLineType)) {
            return new InternationalAirLine(coordinates);
        }
        return null;
    }
}
public class Coordinate {
    private final int[] coordinate;

    public Coordinate(int[] coordinate) {
        this.coordinate = coordinate;
    }

    public int[] getCoordinate() {
        return coordinate;
    }

    void validateLength() {
        if (getCoordinate().length != 2) {
            throw new IllegalArgumentException("Air line is invalid.");
        }
    }

    int getX() {
        return getCoordinate()[0];
    }

    int getY() {
        return getCoordinate()[1];
    }
}

操作手法

操作快捷键(推荐)Ctrl+Alt+Shift+T(或:鼠标右键“Refactor”)
引入参数对象Introduce Parameter Object
子类取代类型码(创建子类)Alt+Enter -> Create subclassMove Static Members
子类取代类型码(工厂方法代替构造方法)Replace Constructor With Factory Method
多态取代表达式(提取接口)Extract Interface
多态取代表达式(上推成员方法、变量)Pull Member Up
多态取代表达式(下推成员方法、变量)Push Member Down