欢迎咱们关注公众号「JAVA前线」查看更多精彩共享文章,首要包含源码剖析、实践使用、架构思想、职场共享、产品考虑等等,一起欢迎咱们加我个人微信「java_front」一同交流学习

1 杂乱、冗杂、杂乱

在开发工作中咱们常常会听到:这个事务很杂乱,这个体系很杂乱,这个逻辑很杂乱,只要是处理遇到困难的场景,好像都能够运用杂乱这个词进行描绘。

可是我以为困难之所以困难,原因仍是有所不同的,不能用杂乱这个词笼而统之,有加以区分的必要。大体上我以为能够分为杂乱、冗杂、杂乱三个类型。

杂乱和冗杂二者均包含分支多和逻辑多的含义,可是不同之处在于,杂乱场景是能够理出条理的,假如规划妥当,是能够规划出很高雅的体系的。可是冗杂场景是难以理出条理的,为了兼容只能打各种补丁,最终根深蒂固只能体系重构。

还有一种类型能够称之为杂乱,当数量达到一定规划时,杂乱和冗杂都能够演化为杂乱。尽管同样是杂乱,可是也有杂乱杂乱和冗杂杂乱的差异。本文只要评论清楚杂乱和杂乱,只要加上数量维度便是杂乱。

咱们在开发中能够写杂乱的代码,要尽量防止冗杂的代码,其间代码耦合便是一种典型的冗杂场景,模块间高度耦合的代码导致最终根本无法维护,本文咱们评论七种代码耦合类型。

2 代码耦合类型

七种代码耦合类型依据耦合程度由高到低排序分别是:内容耦合、公共耦合、外部耦合、操控耦合、符号耦合、数据耦合和非直接耦合。

00 七种代码耦合类型.jpg

2.1 内容耦合

一个模块能够直接拜访另一个模块的内部数据被称为内容耦合,这是耦合性最强的类型,这也是咱们需求尽量防止的。

01 内容耦合.jpg

假设模块A是订单模块,模块B是付出模块,假如付出模块能够直接拜访订单数据表,那么至少会带来以下问题。

榜首个问题是存在重复的数据拜访层代码,付出和订单模块都要写订单数据拜访代码。
第二个问题是假如订单事务变化,需求改动订单数据字段,假如付出模块没有跟着及时
改动,那么可能会形成事务过错。

第三个问题是假如订单事务变化,需求分库分表拆分数据,假如付出模块没有跟着及时改动,例如没有运用shardingKey进行查询或许旧库表停写,那么可能会形成付出模块严重过错。

第四个问题是事务进口没有收敛,拜访进口处处散落,假如想要事务改动则需求多处修正,十分不利于维护。

2.2 公共耦合

多个模块都拜访同一个公共数据环境被称为公共耦合,公共数据环境例如大局数据结构、共享通信区和内存公共掩盖区。

02 公共耦合.jpg

例如在项目中运用Apollo动态装备,装备项A内容是一段JSON,订单模块和付出模块均读取并解析这段数据结构进行事务处理。

public class ApolloConfig {
    @Value("${apollo.json.config}")
    private String jsonConfig;
}
public class JsonConfig {
    public int type;
    public boolean switchOpen;
}
public class OrderServiceImpl {
    public void createOrder() {
        String jsonConfig = apolloConfig.getJsonConfig();
        JsonConfig config = JSONUtils.toBean(jsonConfig, JsonConfig.class);
        if(config.getType() == TypeEnum.ORDER.getCode() && config.isSwitchOpen()) {
            createBizOrder();
        }
    }
}
public class PayServiceImpl {
    public void createPayOrder() {
        String jsonConfig = apolloConfig.getJsonConfig();
        JsonConfig config = JSONUtils.toBean(jsonConfig, JsonConfig.class);
        if(config.getType() == TypeEnum.PAY.getCode() && config.isSwitchOpen()) {
            createBizPayOrder();
        }
    }
}

2.3 外部耦合

多个模块拜访同一个大局简略变量(非大局数据结构)并且不是经过参数表传递此大局变量信息被称为外部耦合。

03 外部耦合.jpg

例如在项目中运用Apollo动态装备,装备项A内容是一个简略变量,订单模块和付出模块均读取这个简略变量进行事务处理。

public class ApolloConfig {
    @Value("${apollo.type.config}")
    private int typeConfig;
}
public class OrderServiceImpl {
    public void createOrder() {
        if(apolloConfig.getTypeConfig() == TypeEnum.ORDER.getCode()) {
            createBizOrder();
        }
    }
}
public class PayServiceImpl {
    public void createPayOrder() {
        if(apolloConfig.getTypeConfig() == TypeEnum.PAY.getCode()) {
            createBizPayOrder();
        }
    }
}

2.4 操控耦合

模块之间传递信息中包含用于操控模块内部的信息被称为操控耦合。操控耦合可能会导致模块之间操控逻辑彼此交织,逻辑之间彼此影响,十分不利于代码维护。

04 操控耦合.jpg

操控耦合代码实例如下,咱们能够看到模块B代码逻辑重度依靠模块A类型,假设A类型发生了变化很可能就会影响B逻辑:

public class ModuleA {
    private int type;
}
public class A {
    private B b = new B();
    public void methondA(int type) {
        ModuleA moduleA = new ModuleA(type);
        b.methondB(moduleA);
    }
}
public class B {
    public void methondB(ModuleA moduleA) {
        if(moduleA.getType() == 1) {
            action1();
        } else if(moduleA.getType() == 2) {
            action2();
        }
    }
}

2.5 符号耦合

多个模块经过参数表传递数据结构信息被称为符号耦合,能够类比JAVA语言引证传递。

05 符号耦合.jpg

2.6 数据耦合

多个模块经过参数表传递简略数据信息被称为符号耦合,能够类比JAVA语言值传递。

06 数据耦合.jpg

2.7 非直接耦合

多个模块之间没有直接联络,经过主模块的操控和调用完成联络被称为非直接耦合,这也是一种理想的耦合办法。

07 非直接耦合.jpg

咱们重点谈一谈非直接耦合。杂乱事务之所以杂乱,一个重要原因是涉及人物或许类型较多,很难平淡无奇地进行规划。假如非要进行平铺规划,必然会出现很多if else代码块。

咱们首先剖析一个下单场景。当时有ABC三种订单类型:A订单价格9折,物流最大分量不能超越9公斤,不支撑退款。B订单价格8折,物流最大分量不能超越8公斤,支撑退款。C订单价格7折,物流最大分量不能超越7公斤,支撑退款。依照需求字面含义平淡无奇地写代码也并不难:

public class OrderServiceImpl implements OrderService {
    @Resource
    private OrderMapper orderMapper;
    @Override
    public void createOrder(OrderBO orderBO) {
        if (null == orderBO) {
            throw new RuntimeException("参数反常");
        }
        if (OrderTypeEnum.isNotValid(orderBO.getType())) {
            throw new RuntimeException("参数反常");
        }
        // A类型订单
        if (OrderTypeEnum.A_TYPE.getCode().equals(orderBO.getType())) {
            orderBO.setPrice(orderBO.getPrice() * 0.9);
            if (orderBO.getWeight() > 9) {
                throw new RuntimeException("超越物流最大分量");
            }
            orderBO.setRefundSupport(Boolean.FALSE);
        }
        // B类型订单
        else if (OrderTypeEnum.B_TYPE.getCode().equals(orderBO.getType())) {
            orderBO.setPrice(orderBO.getPrice() * 0.8);
            if (orderBO.getWeight() > 8) {
                throw new RuntimeException("超越物流最大分量");
            }
            orderBO.setRefundSupport(Boolean.TRUE);
        }
        // C类型订单
        else if (OrderTypeEnum.C_TYPE.getCode().equals(orderBO.getType())) {
            orderBO.setPrice(orderBO.getPrice() * 0.7);
            if (orderBO.getWeight() > 7) {
                throw new RuntimeException("超越物流最大分量");
            }
            orderBO.setRefundSupport(Boolean.TRUE);
        }
        // 保存数据
        OrderDO orderDO = new OrderDO();
        BeanUtils.copyProperties(orderBO, orderDO);
        orderMapper.insert(orderDO);
    }
}

上述代码从功用上完全能够完成事务需求,可是程序员不只要满意功用,还需求考虑代码的可维护性。假如新增一种订单类型,或许新增一个订单属性处理逻辑,那么咱们就要在上述逻辑中新增代码,假如处理不慎就会影响原有逻辑。

为了防止牵一发而动全身这种情况,规划形式中的开闭准则要求咱们面向新增敞开,面向修正封闭,我以为这是规划形式中最重要的一条准则。

需求变化经过扩展,而不是经过修正已有代码完成,这样就保证代码稳定性。扩展也不是随意扩展,由于事前界说了算法,扩展也是依据算法扩展,用笼统构建结构,用完成扩展细节。标准含义的二十三种规划形式说到底最终都是在遵循开闭准则。

如何改动平淡无奇的考虑办法?咱们需求添加剖析维度。其间最常见的是添加横向和纵向两个维度,整体而言横向扩展的是考虑广度,纵向扩展的是考虑深度,对应到体系规划而言能够总结为:纵向做阻隔,横向做编列。

这时咱们能够为问题剖析加上纵向和横向两个维度,挑选运用剖析矩阵办法,其间纵向表明战略,横向表明场景:

08 订单_剖析矩阵.jpg

2.7.1 纵向做阻隔

纵向维度表明战略,不同战略在逻辑上和事务上应该是阻隔的,本实例包含优惠战略、物流战略和退款战略,战略作为笼统,不同订单类型去扩展这个笼统,战略形式十分合适这种场景。本文详细剖析优惠战略,物流战略和退款战略同理。

// 优惠战略
public interface DiscountStrategy {
    public void discount(OrderBO orderBO);
}
// A类型优惠战略
@Component
public class TypeADiscountStrategy implements DiscountStrategy {
    @Override
    public void discount(OrderBO orderBO) {
        orderBO.setPrice(orderBO.getPrice() * 0.9);
    }
}
// B类型优惠战略
@Component
public class TypeBDiscountStrategy implements DiscountStrategy {
    @Override
    public void discount(OrderBO orderBO) {
        orderBO.setPrice(orderBO.getPrice() * 0.8);
    }
}
// C类型优惠战略
@Component
public class TypeCDiscountStrategy implements DiscountStrategy {
    @Override
    public void discount(OrderBO orderBO) {
        orderBO.setPrice(orderBO.getPrice() * 0.7);
    }
}
// 优惠战略工厂
@Component
public class DiscountStrategyFactory implements InitializingBean {
    private Map<String, DiscountStrategy> strategyMap = new HashMap<>();
    @Resource
    private TypeADiscountStrategy typeADiscountStrategy;
    @Resource
    private TypeBDiscountStrategy typeBDiscountStrategy;
    @Resource
    private TypeCDiscountStrategy typeCDiscountStrategy;
    public DiscountStrategy getStrategy(String type) {
        return strategyMap.get(type);
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        strategyMap.put(OrderTypeEnum.A_TYPE.getCode(), typeADiscountStrategy);
        strategyMap.put(OrderTypeEnum.B_TYPE.getCode(), typeBDiscountStrategy);
        strategyMap.put(OrderTypeEnum.C_TYPE.getCode(), typeCDiscountStrategy);
    }
}
// 优惠战略履行
@Component
public class DiscountStrategyExecutor {
    private DiscountStrategyFactory discountStrategyFactory;
    public void discount(OrderBO orderBO) {
        DiscountStrategy discountStrategy = discountStrategyFactory.getStrategy(orderBO.getType());
        if (null == discountStrategy) {
            throw new RuntimeException("无优惠战略");
        }
        discountStrategy.discount(orderBO);
    }
}

2.7.2 横向做编列

横向维度表明场景,一种订单类型在广义上能够以为是一种事务场景,在场景中将独立的战略进行串联,模板办法规划形式适用于这种场景。

模板办法形式一般运用笼统类界说算法骨架,一起界说一些笼统办法,这些笼统办法延迟到子类完成,这样子类不只遵守了算法骨架约定,也完成了自己的算法。既保证了规约也统筹灵活性,这便是用笼统构建结构,用完成扩展细节。

// 创立订单服务
public interface CreateOrderService {
    public void createOrder(OrderBO orderBO);
}
// 笼统创立订单流程
public abstract class AbstractCreateOrderFlow {
    @Resource
    private OrderMapper orderMapper;
    public void createOrder(OrderBO orderBO) {
        // 参数校验
        if (null == orderBO) {
            throw new RuntimeException("参数反常");
        }
        if (OrderTypeEnum.isNotValid(orderBO.getType())) {
            throw new RuntimeException("参数反常");
        }
        // 核算优惠
        discount(orderBO);
        // 核算分量
        weighing(orderBO);
        // 退款支撑
        supportRefund(orderBO);
        // 保存数据
        OrderDO orderDO = new OrderDO();
        BeanUtils.copyProperties(orderBO, orderDO);
        orderMapper.insert(orderDO);
    }
    public abstract void discount(OrderBO orderBO);
    public abstract void weighing(OrderBO orderBO);
    public abstract void supportRefund(OrderBO orderBO);
}
// 完成创立订单流程
@Service
public class CreateOrderFlow extends AbstractCreateOrderFlow {
    @Resource
    private DiscountStrategyExecutor discountStrategyExecutor;
    @Resource
    private ExpressStrategyExecutor expressStrategyExecutor;
    @Resource
    private RefundStrategyExecutor refundStrategyExecutor;
    @Override
    public void discount(OrderBO orderBO) {
        discountStrategyExecutor.discount(orderBO);
    }
    @Override
    public void weighing(OrderBO orderBO) {
        expressStrategyExecutor.weighing(orderBO);
    }
    @Override
    public void supportRefund(OrderBO orderBO) {
        refundStrategyExecutor.supportRefund(orderBO);
    }
}

2.7.3 纵横思想

上述实例事务和代码并不杂乱,其实杂乱事务场景也不过是简略场景的叠加、组合和交织,无外乎也是经过纵向做阻隔、横向做编列寻求答案。

09 订单_纵向阻隔横向编列.jpg

纵向维度笼统出才能池这个概念,才能池中包含许多才能,不同的才能依照不同事务维度聚合,例如优惠才能池,物流才能池,退款才能池。咱们能够看到两种程度的阻隔性,才能池之间彼此阻隔,才能之间也彼此阻隔。

横向维度将才能从才能池选出来,依照事务需求串联在一同,形成不同事务流程。由于才能能够任意组合,所以体现了很强的灵活性。除此之外,不同才能既能够串行履行,假如不同才能之间没有依靠关系,也能够如同流程Y一样并行履行,提升履行效率。

3 文章总结

榜首本文区分了杂乱、冗杂、杂乱这一组概念,杂乱和冗杂尽管都比较难处理,可是杂乱是能够理出条理的,而冗杂最终会根深蒂固。咱们应该尽量防止冗杂的代码。杂乱和冗杂加上数量维度就成为杂乱。

第二本文介绍了七种代码耦合类型,依据耦合程度由高到低排序分别是:内容耦合、公共耦合、外部耦合、操控耦合、符号耦合、数据耦合和非直接耦合。咱们应该尽量写耦合度低的代码。

三本文由一个杂乱订单场景实例动身,重点介绍了非直接耦合类型,能够看到即使是杂乱场景,经过合理的规划也能够高雅完成,期望本文对咱们有所帮助。

欢迎咱们关注公众号「JAVA前线」查看更多精彩共享文章,首要包含源码剖析、实践使用、架构思想、职场共享、产品考虑等等,一起欢迎咱们加我个人微信「java_front」一同交流学习