Design Pattern - 结构型

结构性模式用于解决将对象和类 组装成较大的结构 ,并同时保持结构的灵活和高效。 适配器模式 理论 适配器模式的主要作用是把原本不兼容的接口,通过适配修改做到统一。 对业务代码做不同接口的兼容,比如中台服务,中台需要把各个业务线的各种类型服务做统一保证,再对外提供接口进行使用。 可以用于对于业务的适配,适配器不只是可以适配接口,还可以适配一些属性信息。 案例 - 模拟了了三个不不同类型的MQ消息,⽽而在消息体中都有⼀一…

结构性模式用于解决将对象和类组装成较大的结构,并同时保持结构的灵活和高效。

适配器模式

理论

适配器模式的主要作用是把原本不兼容的接口,通过适配修改做到统一。

对业务代码做不同接口的兼容,比如中台服务,中台需要把各个业务线的各种类型服务做统一保证,再对外提供接口进行使用。

可以用于对于业务的适配,适配器不只是可以适配接口,还可以适配一些属性信息。

案例

  • 模拟了了三个不不同类型的MQ消息,⽽而在消息体中都有⼀一些必要的字段,⽐比如;⽤用户ID、时间、业务ID,但是每个MQ的字段属性并不不⼀一样。就像⽤用户ID在不不同的MQ⾥里里也有不不同的字段:uId、userId等。
  • 同时还提供了了两个不不同类型的接⼝口,⼀一个⽤用于查询内部订单订单下单数量量,⼀一个⽤用于查询第三⽅方是否⾸首单。
public class RebateInfo {

    private String userId;  // 用户ID
    private String bizId;   // 业务ID
    private Date bizTime;   // 业务时间
    private String desc;    // 业务描述

}

public interface OrderAdapterService {

    boolean isFirst(String uId);

}

public class MQAdapter {

    public static RebateInfo filter(String strJson, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        return filter(JSON.parseObject(strJson, Map.class), link);
    }

    public static RebateInfo filter(Map obj, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        RebateInfo rebateInfo = new RebateInfo();
        for (String key : link.keySet()) {
            Object val = obj.get(link.get(key));
            RebateInfo.class.getMethod("set" + key.substring(0, 1).toUpperCase() + key.substring(1), String.class).invoke(rebateInfo, val.toString());
        }
        return rebateInfo;
    }

}

public class InsideOrderService implements OrderAdapterService {

    private OrderService orderService = new OrderService();

    public boolean isFirst(String uId) {
        return orderService.queryUserOrderCount(uId) <= 1;
    }

}

public class POPOrderAdapterServiceImpl implements OrderAdapterService {

    private POPOrderService popOrderService = new POPOrderService();

    public boolean isFirst(String uId) {
        return popOrderService.isFirstOrder(uId);
    }

}

public class ApiTest {

    @Test
    public void test_MQAdapter() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ParseException {

        SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date parse = s.parse("2020-06-01 23:20:16");


        create_account create_account = new create_account();
        create_account.setNumber("100001");
        create_account.setAddress("河北省.廊坊市.广阳区.大学里职业技术学院");
        create_account.setAccountDate(parse);
        create_account.setDesc("在校开户");

        HashMap<String, String> link01 = new HashMap<String, String>();
        link01.put("userId", "number");
        link01.put("bizId", "number");
        link01.put("bizTime", "accountDate");
        link01.put("desc", "desc");
        RebateInfo rebateInfo01 = MQAdapter.filter(create_account.toString(), link01);
        System.out.println("mq.create_account(适配前)" + create_account.toString());
        System.out.println("mq.create_account(适配后)" + JSON.toJSONString(rebateInfo01));

        System.out.println("");

        OrderMq orderMq = new OrderMq();
        orderMq.setUid("100001");
        orderMq.setSku("10928092093111123");
        orderMq.setOrderId("100000890193847111");
        orderMq.setCreateOrderTime(parse);

        HashMap<String, String> link02 = new HashMap<String, String>();
        link02.put("userId", "uid");
        link02.put("bizId", "orderId");
        link02.put("bizTime", "createOrderTime");
        RebateInfo rebateInfo02 = MQAdapter.filter(orderMq.toString(), link02);
        System.out.println("mq.orderMq(适配前)" + orderMq.toString());
        System.out.println("mq.orderMq(适配后)" + JSON.toJSONString(rebateInfo02));
    }

    @Test
    public void test_itfAdapter() {
        OrderAdapterService popOrderAdapterService = new POPOrderAdapterServiceImpl();
        System.out.println("判断首单,接口适配(POP):" + popOrderAdapterService.isFirst("100001"));

        OrderAdapterService insideOrderService = new InsideOrderService();
        System.out.println("判断首单,接口适配(自营):" + insideOrderService.isFirst("100001"));
    }

}

对不同的mq消息进行了统一的参数映射。

在实际业务开发中,除了反射的使用歪,还可以加入代理类把映射的配置交给它。这样就可以不需要每一个mq都手动创建类了。

适配器模式用来保证最终的接口是统一包装,外部使用不需要关心内部的具体逻辑。并且在调用的时候只需要传入统一的参数

总结

  • 对于MQ这种消息体中不同属性同类的值,可以适配加上代理类,就可以通过简单的配置方式对接对方提供的MQ消息,而不需要大量重复的开发,非常利于拓展。

桥接模式

理论

桥接模式的主要作用是通过抽象部分与实现部分分离,把多种可匹配的使用进行组合,核心实现就是在A类中含有B类接口,通过构造函数传递B类的实现,这个B类就是设计的桥。

案例

JDBC多种驱动的实现、同品牌类型的台式机和笔记本平板、业务实现中的多类接口同组过滤服务等。可以使用桥接模式进行实现,因为在一些组合中如果每一个类都实现不同服务,可能会出现笛卡尔积。

模拟一个第三方支付平台,可以使用人脸让用户支付更加容易。这里就出现了多支付与多模式的融合使用,如果给每一个支付都实现一次不同的模式,就需要开发很多类,并且如果后续继续接入更多的支付服务或者支付方式,可能性会越变越多。

/**
 * 桥接接口
 */
public interface IPayMode {

    boolean security(String uId);

}

public class PayCypher implements IPayMode{

    protected Logger logger = LoggerFactory.getLogger(PayCypher.class);

    public boolean security(String uId) {
        logger.info("密码支付,风控校验环境安全");
        return true;
    }

}

public class PayFaceMode implements IPayMode{

    protected Logger logger = LoggerFactory.getLogger(PayCypher.class);

    public boolean security(String uId) {
        logger.info("人脸支付,风控校验脸部识别");
        return true;
    }

}

public class PayFingerprintMode implements IPayMode{

    protected Logger logger = LoggerFactory.getLogger(PayCypher.class);

    public boolean security(String uId) {
        logger.info("指纹支付,风控校验指纹信息");
        return true;
    }

}

public abstract class Pay {

    protected Logger logger = LoggerFactory.getLogger(Pay.class);

    protected IPayMode payMode;

    public Pay(IPayMode payMode) {
        this.payMode = payMode;
    }

    public abstract String transfer(String uId, String tradeId, BigDecimal amount);

}

public class WxPay extends Pay {

    public WxPay(IPayMode payMode) {
        super(payMode);
    }

    public String transfer(String uId, String tradeId, BigDecimal amount) {
        logger.info("模拟微信渠道支付划账开始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        boolean security = payMode.security(uId);
        logger.info("模拟微信渠道支付风控校验。uId:{} tradeId:{} security:{}", uId, tradeId, security);
        if (!security) {
            logger.info("模拟微信渠道支付划账拦截。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
            return "0001";
        }
        logger.info("模拟微信渠道支付划账成功。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        return "0000";
    }

}

public class ZfbPay extends Pay {

    public ZfbPay(IPayMode payMode) {
        super(payMode);
    }

    public String transfer(String uId, String tradeId, BigDecimal amount) {
        logger.info("模拟支付宝渠道支付划账开始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        boolean security = payMode.security(uId);
        logger.info("模拟支付宝渠道支付风控校验。uId:{} tradeId:{} security:{}", uId, tradeId, security);
        if (!security) {
            logger.info("模拟支付宝渠道支付划账拦截。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
            return "0001";
        }
        logger.info("模拟支付宝渠道支付划账成功。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        return "0000";
    }

}

public class ApiTest {

    @Test
    public void test_pay() {

        System.out.println("\r\n模拟测试场景;微信支付、人脸方式。");
        Pay wxPay = new WxPay(new PayFaceMode());
        wxPay.transfer("weixin_1092033111", "100000109893", new BigDecimal(100));

        System.out.println("\r\n模拟测试场景;支付宝支付、指纹方式。");
        Pay zfbPay = new ZfbPay(new PayFingerprintMode());
        zfbPay.transfer("jlu19dlxo111", "100000109894", new BigDecimal(100));

    }

}

案例使用IPayMode接口桥接了不同的验证模式。

Pay类定义了桥接接口IPayMode,可以选择使用某个验证模式来进行支付。

以上优化主要针对桥接模式的使用进行重构 if 逻辑部分,关于调用部分可以使用 抽象工厂 或 策略模式 配合map结构,将服务配置化。

总结

从桥接模式的实现形式来看满足了了单一职责和开闭原则,让每一部分内容都很清晰易易于维护和拓拓展,但如果我们是实现的高内聚的代码,那么就会很复杂。所以在选择重构代码的时候,需要考虑好整体的设计,否则选不到合理的设计模式,将会让代码变得难以开发

组合模式

理论

把相似对象(也可以称为方法)组合成一组可被调用的结构树对象的设计思路叫组合模式。

这种设计方式可以让服务组节点进行自由组合对外提供服务,比如有三个原子检验功能(A:身份证 B:银行卡 C:手机号)服务并对外提供调用使用。有些调用需要使用AB组合,有些调用需要使用CBA组合,还有一些可能只使用三者中的一个。这个时候就可以使用组合模式进行构建服务,对于不同类型的调用方配置不同的组织关系树。树结构可以存储到数据库,也可以通过图形结构来控制。

案例

image-20250323202546870

以上是一个非常简化版的营销规则 决策树 ,根据 性别 、 年龄 来发放不同类型的优惠券,来刺激消费起到精准用户促活的目的。

image-20250323202703879

/**
 * 规则树聚合
 */
public class TreeRich {

    private TreeRoot treeRoot;                          //树根信息
    private Map<Long, TreeNode> treeNodeMap;        //树节点ID -> 子节点

    public TreeRich(TreeRoot treeRoot, Map<Long, TreeNode> treeNodeMap) {
        this.treeRoot = treeRoot;
        this.treeNodeMap = treeNodeMap;
    }
}

/**
 * 决策结果
 */
public class EngineResult {

    private boolean isSuccess; //执行结果
    private String userId;   //用户ID
    private Long treeId;     //规则树ID
    private Long nodeId;   //果实节点ID
    private String nodeValue;//果实节点值

    public EngineResult() {
    }

    public EngineResult(boolean isSuccess) {
        this.isSuccess = isSuccess;
    }

    public EngineResult(String userId, Long treeId, Long nodeId, String nodeValue) {
        this.isSuccess = true;
        this.userId = userId;
        this.treeId = treeId;
        this.nodeId = nodeId;
        this.nodeValue = nodeValue;
    }

    public boolean isSuccess() {
        return isSuccess;
    }
}

/**
 * 规则树节点信息
 */
public class TreeNode {

    private Long treeId;            //规则树ID
    private Long treeNodeId;        //规则树节点ID
    private Integer nodeType;       //节点类型;1子叶、2果实
    private String nodeValue;       //节点值[nodeType=2];果实值
    private String ruleKey;         //规则Key
    private String ruleDesc;        //规则描述
    private List<TreeNodeLink> treeNodeLinkList; //节点链路
}

/**
 * 规则树线信息
 */
public class TreeNodeLink {

    private Long nodeIdFrom;        //节点From
    private Long nodeIdTo;          //节点To
    private Integer ruleLimitType;  //限定类型;1:=;2:>;3:<;4:>=;5<=;6:enum[枚举范围]
    private String ruleLimitValue;  //限定值
}

/**
 * 树根信息
 */
public class TreeRoot {

    private Long treeId;         //规则树ID
    private Long treeRootNodeId; //规则树根ID
    private String treeName;     //规则树名称

}


public interface IEngine {

    EngineResult process(final Long treeId, final String userId, TreeRich treeRich, final Map<String, String> decisionMatter);

}

public class EngineConfig {

    static Map<String, LogicFilter> logicFilterMap;

    static {
        logicFilterMap = new ConcurrentHashMap<>();
        logicFilterMap.put("userAge", new UserAgeFilter());
        logicFilterMap.put("userGender", new UserGenderFilter());
    }

    public Map<String, LogicFilter> getLogicFilterMap() {
        return logicFilterMap;
    }

    public void setLogicFilterMap(Map<String, LogicFilter> logicFilterMap) {
        this.logicFilterMap = logicFilterMap;
    }

}

public abstract class EngineBase extends EngineConfig implements IEngine {

    private Logger logger = LoggerFactory.getLogger(EngineBase.class);

    @Override
    public abstract EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter);

    protected TreeNode engineDecisionMaker(TreeRich treeRich, Long treeId, String userId, Map<String, String> decisionMatter) {
        TreeRoot treeRoot = treeRich.getTreeRoot();
        Map<Long, TreeNode> treeNodeMap = treeRich.getTreeNodeMap();
        // 规则树根ID
        Long rootNodeId = treeRoot.getTreeRootNodeId();
        TreeNode treeNodeInfo = treeNodeMap.get(rootNodeId);
        //节点类型[NodeType];1子叶、2果实
        while (treeNodeInfo.getNodeType().equals(1)) {
            String ruleKey = treeNodeInfo.getRuleKey();
            LogicFilter logicFilter = logicFilterMap.get(ruleKey);
            String matterValue = logicFilter.matterValue(treeId, userId, decisionMatter);
            Long nextNode = logicFilter.filter(matterValue, treeNodeInfo.getTreeNodeLinkList());
            treeNodeInfo = treeNodeMap.get(nextNode);
            logger.info("决策树引擎=>{} userId:{} treeId:{} treeNode:{} ruleKey:{} matterValue:{}", treeRoot.getTreeName(), userId, treeId, treeNodeInfo.getTreeNodeId(), ruleKey, matterValue);
        }
        return treeNodeInfo;
    }

}

public class TreeEngineHandle extends EngineBase {

    @Override
    public EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter) {
        // 决策流程
        TreeNode treeNode = engineDecisionMaker(treeRich, treeId, userId, decisionMatter);
        // 决策结果
        return new EngineResult(userId, treeId, treeNode.getTreeNodeId(), treeNode.getNodeValue());
    }

}

public interface LogicFilter {

    /**
     * 逻辑决策器
     *
     * @param matterValue          决策值
     * @param treeNodeLineInfoList 决策节点
     * @return 下一个节点Id
     */
    Long filter(String matterValue, List<TreeNodeLink> treeNodeLineInfoList);

    /**
     * 获取决策值
     *
     * @param decisionMatter 决策物料
     * @return 决策值
     */
    String matterValue(Long treeId, String userId, Map<String, String> decisionMatter);

}

public abstract class BaseLogic implements LogicFilter {

    @Override
    public Long filter(String matterValue, List<TreeNodeLink> treeNodeLinkList) {
        for (TreeNodeLink nodeLine : treeNodeLinkList) {
            if (decisionLogic(matterValue, nodeLine)) return nodeLine.getNodeIdTo();
        }
        return 0L;
    }

    @Override
    public abstract String matterValue(Long treeId, String userId, Map<String, String> decisionMatter);

    private boolean decisionLogic(String matterValue, TreeNodeLink nodeLink) {
        switch (nodeLink.getRuleLimitType()) {
            case 1:
                return matterValue.equals(nodeLink.getRuleLimitValue());
            case 2:
                return Double.parseDouble(matterValue) > Double.parseDouble(nodeLink.getRuleLimitValue());
            case 3:
                return Double.parseDouble(matterValue) < Double.parseDouble(nodeLink.getRuleLimitValue());
            case 4:
                return Double.parseDouble(matterValue) >= Double.parseDouble(nodeLink.getRuleLimitValue());
            case 5:
                return Double.parseDouble(matterValue) <= Double.parseDouble(nodeLink.getRuleLimitValue());
            default:
                return false;
        }
    }

}

public class UserGenderFilter extends BaseLogic {

    @Override
    public String matterValue(Long treeId, String userId, Map<String, String> decisionMatter) {
        return decisionMatter.get("gender");
    }

}

public class UserAgeFilter extends BaseLogic {

    @Override
    public String matterValue(Long treeId, String userId, Map<String, String> decisionMatter) {
        return decisionMatter.get("age");
    }

}
  • 这一部分是组合模式非常重要的使用,在已经建造好的决策树关系下,可以创建出树的各个节点,以及对节点间使用链路进行串联。
  • 后续任何业务的拓展都可以在里面添加相应的节点,并做动态化配置。
  • 这部分手动组合的方式可以提取到数据库中,或者是拓展到图形界面进行配置操作。

总结

  • 组合模式主要解决的是一系列简单逻辑节点或者拓展的复杂逻辑节点在不同结构的组织下,保证对于外部的调用保持简单

装饰器模式

理论

装饰器模式的核心是再不改原有类的基础上给类新增功能。

类似的思路,可以是继承、AOP切片等。但装饰器模式是另外一种思路更为灵活,可以避免继承导致的子类过多,也可以避免AOP带来的复杂性。

常见的装饰器模式的应用如下:

new BufferedReader(new FileReader(""));

案例

模拟一个单点登录功能,项目初期,内部的ERP使用只需要判断账户验证即可,验证通过后即可访问ERP的所有资源。但随着业务的不断发展,不同人员对于ERP的使用需求开始出现明显的差异化,同时为了保证数据的安全性,不能让每个用户拥有最高的权限。

public class SsoInterceptor implements HandlerInterceptor{

    public boolean preHandle(String request, String response, Object handler) {
        // 模拟获取cookie
        String ticket = request.substring(1, 8);
        // 模拟校验
        return ticket.equals("success");
    }

}

public interface HandlerInterceptor {

    boolean preHandle(String request, String response, Object handler);

}


public abstract class SsoDecorator implements HandlerInterceptor {

    private HandlerInterceptor handlerInterceptor;

    private SsoDecorator(){}

    public SsoDecorator(HandlerInterceptor handlerInterceptor) {
        this.handlerInterceptor = handlerInterceptor;
    }

    public boolean preHandle(String request, String response, Object handler) {
        return handlerInterceptor.preHandle(request, response, handler);
    }

}



public class LoginSsoDecorator extends SsoDecorator {

    private Logger logger = LoggerFactory.getLogger(LoginSsoDecorator.class);

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

    static {
        authMap.put("huahua", "queryUserInfo");
        authMap.put("doudou", "queryUserInfo");
    }

    public LoginSsoDecorator(HandlerInterceptor handlerInterceptor) {
        super(handlerInterceptor);
    }

    @Override
    public boolean preHandle(String request, String response, Object handler) {
        boolean success = super.preHandle(request, response, handler);
        if (!success) return false;
        String userId = request.substring(8);
        String method = authMap.get(userId);
        logger.info("模拟单点登录方法访问拦截校验:{} {}", userId, method);
        // 模拟方法校验
        return "queryUserInfo".equals(method);
    }
}

在装饰器模式中有比较重要的几个需要抽象出来的点。

  1. 抽象构件角色(Component) - 定义抽象接口 (HandlerInterceptor)
  2. 具体构件角色 (ConcreteComponent)- 实现抽象接口(SsoInterceptor)
  3. 装饰角色(Decorator)- 定义抽象类并继承接口中的方法,保证一致性(SsoDecorator)
  4. 具体装饰角色(ConcreteDecoratot)- 扩展装饰具体的实现逻辑(LoginSsoDecorator)

装饰类的核心写法有三点:

  1. 继承了处理接口
  2. 提供了构造函数
  3. 覆盖了处理方法

总结

  • 使用装饰器模式满足单一职责原则,你可以在自己的装饰类中完成功能逻辑的扩展,而不影响主类,同时可以按需在运行时添加和删除这部分逻辑。另外装饰器模式与继承父类重写方法,在某些时候需要按需选择,并不一定某一个就是最好。
  • 装饰器实现的重点是对抽象类继承接口方式的使用,同时设定被继承的接口可以通过构造函数传递其实现类,由此增加扩展性并重写方法里可以实现此部分父类实现的功能。
  • 就像夏天热你穿短裤,冬天冷你穿棉裤,雨天挨浇你穿雨衣一样,你的根本本身没有被改变,而你的需求却被不同的装饰而实现。生活中往往比比皆是设计,当你可以融合这部分活灵活现的例子到代码实现中,往往会创造出更加优雅的实现方式。