【Java】代码结构设计思考

背景

这篇博文是博主在做数据图形统计相关接口工作过程中对代码结构设计的一些思考总结,仅代表个人观点。

1.需求简述

提供资金关系数据图形统计,根据不同菜单地址跳转至对应图形页面显示相关业务统计数据。

2.开发设计过程

2.1 在开发的初期,考虑到各个数据图形统计具体实现细节的不同,以及代码的可扩展性(非全面),博主是这样设计的(忽略网络请求的复杂细节以及框架细节)。


【Java】代码结构设计思考_第1张图片
图1 初期结构设计图

服务端提供不同的请求地址给客户端调用,如图中的 /a, /b, /c, 每种请求地址对应一种数据图形的统计。后续各自的service实现各自图形统计的具体逻辑,互不干扰。当我们新增一种图形统计的类型时,只要对应的增加请求地址/d图形D Controller图形D Service,不会影响其他图形数据的统计。后续的增加以此类推。

2.2 初期开发完成后,博主发现虽然各图形service的具体实现逻辑不同,但是对于提供给对应controllerapi都是一样的,而且只需对外提供一个请求链接并用特定字段标记就可以区分,于是对于图1中的结构进行修改。


【Java】代码结构设计思考_第2张图片
图2 改进结构设计图

服务端仅提供一个请求入口地址/statistics,同时用type字段来进行区分当前我们所需要处理的是哪种图形数据的统计,并调用对应的service服务进行统计处理。

这种设计与图1的设计相比而言有以下好处:

  1. 减少了不同种类的图形Controller,减少了请求入口地址
  2. 图形Controller与各个图形Service之间新增了一层图形抽象Service(可以是抽象类、接口),统一了对外提供访问的api

到这一步,其实就有点我们设计模式当中策略模式的意思了。图2中的图形抽象Service可以理解为策略模式当中的抽象策略类,而我们具体的图形A Service图形B Service图形C Service也可以与策略模式中的具体策略类等同。从而使得图形统计的逻辑可以独立于调用方变化。

接下来在看看上层图形Controller的方法该如何写。我们需要根据type值的不同来调用我们的对应的图形Service的方法,这又不得不使用我们的工厂模式,因为我们先要根据type值来获取对应的图形Service的对象。大致的伪代码如下(这里忽略Controller方面的细节):

	.....
	图形抽象Service;
	switch (type) {
		case "a":
			图形抽象Service = 图形AService对象;
			break;
		case "b":
			图形抽象Service = 图形BService对象;
			break;
		case "c":
			图形抽象Service = 图形CService对象;
			break;
		default:
			// 异常
			break;
	}
	图形抽象Service.公共api;
	.....

设计改进

当然, 2.2 设计中创建对象的操作我们完全可以交由spring去执行(通过xml或者注解的方式注入),也就是让spring来管理bean,但我们仍无法摆脱使用switch或者if else来区分类型。新增或者减少类型,我们需要改动原有方法:增加或者减少对应switch或者if else分支。

举个例子,针对 2.2 中的代码,假如我们要新增一类图形D的数据统计,那怎么办?
我们当然会这样做:在实现图形D Service数据统计逻辑的同时,再在上述代码中新增case "d"的分支。

新增图形D的数据统计类是没什么问题的,但是对于getResult这个方法来说就有点违背设计模式六大原则中的开闭原则,因为其不仅对扩展开放了,而且对修改也开放了。

所以博主针对这块,再做一小改进。

回到前面spring管理bean上,看过spring源码的同学应该会更加清楚:在我们的spring容器当中,bean是存储在我们的map当中的,并以idkey值,bean实例value值。那也就是说,假如我们拥有这样一个map:以标记字段值为key值,bean实例value值,那我们就可以通过传入的标记字段值(2.2中的type)直接获取实例bean并调用对应的公共方法。

代码实现

由于上述例子涉及相关业务,相对繁琐,为简单起见,博主以计算几种几何图形为周长面积为例,用squaretrianglecircular作为标记字段type的值,分别对应正方形、全等三角形、圆。

以下列出实现的主要代码。

1.几何图形Service服务管理类

主要用来自己管理bean,指定规则key的值。

/**
 * 几何图形Service服务管理类
 * Created by Hilox on 2018/12/11 0011.
 */
@Service
public class GeometryServiceManager {

    /**
     * 管理实现类
     */
    private static Map<String, Class<?>> initialCategories = new ConcurrentHashMap<>();

    private static Map<String, GeometryAbstractService> categoryMap = new ConcurrentHashMap<>();

    /**
     * 默认构造方法
     */
    public GeometryServiceManager() {}

    /**
     * 子类自注册方法
     */
    public GeometryServiceManager(String name, Class<?> serviceName) {
        initialCategories.put(name, serviceName);
    }

    /**
     * 根据传入标记字段值获取对应的几何图形服务
     * @param name
     * @return
     */
    public GeometryAbstractService getServiceByName(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Type不能为空!");
        }

        // 扫描预置的实现类
        scanForService();

        if (categoryMap.containsKey(name)) {
            return categoryMap.get(name);
        } else {
            throw new IllegalArgumentException("对应几何图形服务未注册!");
        }
    }

    /**
     * 扫描几何图形服务注册实现类
     * 为确保获取正确服务, 需要调用scanForService方可正确注册
     */
    private void scanForService() {
        initialCategories.forEach((key, value) -> registryService(key, (GeometryAbstractService) ApplicationContextHelper.getApplicationContext().getBean(value), true));
    }

    /**
     * 几何图形服务注册方法
     * 新增几何图形服务方法, 则将其注入到集合中
     * @param name
     * @param geometryAbstractService
     * @param replace 同名是否替换现有的服务, 默认为false:不替换
     */
    private void registryService(String name, GeometryAbstractService geometryAbstractService, boolean replace) {
        if (!categoryMap.containsKey(name) || replace) {
            categoryMap.put(name, geometryAbstractService);
        }
    }

    /**
     * 根据名称卸载几何图形服务方法
     * @param name
     */
    private void unRegistryService(String name) {
        if (categoryMap.containsKey(name)) {
            categoryMap.remove(name);
        }
    }

    /**
     * 根据方法卸载几何图形服务方法
     */
    private void unRegistryService(GeometryAbstractService geometryAbstractService) {
        if (categoryMap.containsValue(geometryAbstractService)) {
            categoryMap.remove(geometryAbstractService);
        }
    }
}

2.几何图形Service抽象父类

作为所有几何图形Service的抽象父类,继承了我们自己定义的服务管理类。在这个抽象类中,主要用来写一些对外提供的api,以及所有子类涉及到的公共方法。

/**
 * 所有几何图形Service抽象父类
 * Created by Hilox on 2018/12/11 0011.
 */
public abstract class GeometryAbstractService extends GeometryServiceManager {

    /**
     * 默认构造方法
     */
    public GeometryAbstractService() {}

    /**
     * 子类自注册方法
     */
    public GeometryAbstractService(String name, Class<?> serviceName) {
        super(name, serviceName);
    }

    /**
     * 获取几何图形周长
     * @param condition
     * @return
     */
    public abstract Object getPerimeter(GeometryCondition condition);

    /**
     * 获取几何图形面积
     * @param condition
     * @return
     */
    public abstract Object getArea(GeometryCondition condition);
}

3.具体图形的Service实现

具体图形Service继承了几何图形Service抽象父类,实现了其中的抽象方法的具体内容(当前也可以覆盖父类方法达到目的)。这里最主要的一点就是自注册,通过构造方法调用父类的构造方法,在通过父类构造方法的处理注册到我们的服务管理器类的静态容器当中,以便后续通过标记字段值提取指定服务。

3.1正方形

/**
 * 正方形Service
 * Created by Hilox on 2018/12/11 0011.
 */
@Service
public class SquareService extends GeometryAbstractService {

    /**
     * 自注册
     */
    public SquareService() {
        super(GeometryTypeConstant.SQUARE, SquareService.class);
    }

    @Override
    public BigDecimal getPerimeter(GeometryCondition condition) {
        BigDecimal side = condition.getSide();
        if (side == null) {
            throw new IllegalArgumentException("正方形边长不能为空!");
        }
        // 4 * side
        BigDecimal result = side.multiply(new BigDecimal(4));
        return result;
    }

    @Override
    public BigDecimal getArea(GeometryCondition condition) {
        BigDecimal side = condition.getSide();
        if (side == null) {
            throw new IllegalArgumentException("正方形边长不能为空!");
        }
        // side * side
        BigDecimal result = side.pow(2);
        return result;
    }

}

3.2等边三角形

/**
 * 全等三角形Service
 * Created by Hilox on 2018/12/11 0011.
 */
@Service
public class TriangleService extends GeometryAbstractService {

    /**
     * 自注册
     */
    public TriangleService() {
        super(GeometryTypeConstant.TRIANGLE, TriangleService.class);
    }

    @Override
    public BigDecimal getPerimeter(GeometryCondition condition) {
        BigDecimal side = condition.getSide();
        if (side == null) {
            throw new IllegalArgumentException("全等三角形边长不能为空!");
        }
        // 3 * side
        BigDecimal result = side.multiply(new BigDecimal(3));
        return result;
    }

    @Override
    public Double getArea(GeometryCondition condition) {
        BigDecimal side = condition.getSide();
        if (side == null) {
            throw new IllegalArgumentException("全等三角形边长不能为空!");
        }

        // 30°
        double radians = Math.toRadians(30);
        // 计算cos30°
        double cos = Math.cos(radians);
        double sideDouble = side.doubleValue();
        // 高
        double high = sideDouble * cos;
        return high * sideDouble / 2;
    }
}

3.3圆形

/**
 * 圆形Service
 * Created by Hilox on 2018/12/11 0011.
 */
@Service
public class CircularService extends GeometryAbstractService {

    /**
     * 自注册
     */
    public CircularService() {
        super(GeometryTypeConstant.CIRCULAR, CircularService.class);
    }

    @Override
    public Double getPerimeter(GeometryCondition condition) {
        BigDecimal radius = condition.getRadius();
        if (radius == null) {
            throw new IllegalArgumentException("圆形半径不能为空!");
        }
        // 2 * π * r
        double radiusDouble = radius.doubleValue();
        return 2 * Math.PI * radiusDouble;
    }

    @Override
    public Double getArea(GeometryCondition condition) {
        BigDecimal radius = condition.getRadius();
        if (radius == null) {
            throw new IllegalArgumentException("圆形半径不能为空!");
        }
        // π * r * r
        double radiusDouble = radius.doubleValue();
        return Math.PI * radiusDouble * radiusDouble;
    }
}

4.请求地址入口Controller

通过以下代码可以看到,我们只需要通过标记字段type值来获取对应的服务进行后续的处理,摆脱了使用switch或者if else来区分类型。

/**
 * 几何图形Controller
 * Created by Hilox on 2018/12/11 0011.
 */
@Controller
public class GeometryController {

    @Autowired
    private GeometryServiceManager geometryServiceManager;

    /**
     * 获取几何图形周长
     * @param condition
     * @return
     */
    @RequestMapping("/getPerimeter")
    @ResponseBody
    public Object getPerimeter(@RequestBody GeometryCondition condition) {
        GeometryAbstractService service = geometryServiceManager.getServiceByName(condition.getType());
        return service.getPerimeter(condition);
    }

    /**
     * 获取几何图形面积
     * @param condition
     * @return
     */
    @RequestMapping("/getArea")
    @ResponseBody
    public Object getArea(@RequestBody GeometryCondition condition) {
        GeometryAbstractService service = geometryServiceManager.getServiceByName(condition.getType());
        return service.getArea(condition);
    }
}

源码传送门

【源码地址】:struct-design-one

效果展示

以计算图形周长为例:


【Java】代码结构设计思考_第3张图片
图3 正方形计算结果图


【Java】代码结构设计思考_第4张图片
图4 全等三角形计算结果图


【Java】代码结构设计思考_第5张图片
图5 圆形计算结果图

你可能感兴趣的:(java,代码结构设计,Java)