责任链、模板方法、策略、工厂、代理、观察者模式

2024-10-30 09:09

引言

本文以一个实际案例来介绍在解决业务需求的路上,如何通过常用的设计模式来逐级优化我们的代码,以把我们所了解的到设计模式真实的应用于实战。

背景

假定我们现在有一个订单流程管理系统,这个系统对于用户发起的一笔订单,需要你编写代码按照以下环节进行依次处理

图片

注:本文不会对每个环节的实现细节进行描述,读者也不必了解这每个环节的实现,我们只需要关注代码架构设计

第一次迭代

按照背景,我们如果不是打算if-else一撸到底的话,我们最合适使用的设计模式应该是责任链模式,于是我们先打算用责任链模式来做我们的第一次迭代。

图片

先整体看下类图:

图片

我们定义一个抽象类,抽象类中定义了下一个处理器,方便后期我们读取配置直接构建责任链处理顺序:

@Data
public abstract class BizOrderHandler {

    /**
     * 下一个处理器
     */
    private BizOrderHandler nextBizOrderHandler;

    /**
     * 处理器执行方法
     * @param param 责任链参数
     * @return 执行结果
     */
    public abstract Result handle(ProductVO param);


    /**
     * 链路传递
     * @param param
     * @return
     */
    protected Result next(ProductVO param) {
        //下一个链路没有处理器了,直接返回
        if (Objects.isNull(nextBizOrderHandler)) {
            return Result.success();
        }
        //执行下一个处理器
        return nextBizOrderHandler.handle(param);
    }

}

然后我们将需要实现的流程都来实现这个接口 (为了简单只列举一个)

public class StorageCheckBizOrderHandler extends BizOrderHandler {
    @Override
    public Result handle(ProductVO param) {
        // 这里写上仓储管理的业务逻辑
        System.out.println("StorageCheckBizOrderHandler doing business!");
        return super.next(param);
    }
}

通过调用父类的next方法实现了链式传递,接下来我们就可以使用责任链来实现业务了

public class OrderHandleCases {

    static Map<String, BizOrderHandler> handlerMap = new HashMap<>();

    static {
        handlerMap.put("Storage", new StorageCheckBizOrderHandler());
        handlerMap.put("Payment", new PaymentBizOrderHandler());
        handlerMap.put("RightCenter", new RightCenterBizOrderHandler());
        handlerMap.put("PointDeduction", new PointDeductBizOrderHandler());
    }

    public static void main(String[] args) { 
        // 这里可以从nacos配置中心读取责任链配置
        BizOrderHandler handler1 = initHandler(Lists.newArrayList("Storage", "RightCenter", "PointDeduction","Payment"));
        // 虚拟化一个产品订单
        ProductVO productVO = ProductVO.builder().build();
        Result result = handler1.handle(productVO);
        System.out.println("订单处理 成功");
    }


    /**
     * 根据责任链配置构建责任链
     * @param handlerNameChain 责任链执行顺序
     * @return 首个处理器
     */
    private static BizOrderHandler initHandler(List<String> handlerNameChain) {
        List<BizOrderHandler> handlers = new ArrayList<>();
        for (int i = 0; i < handlerNameChain.size(); i++) {
            String cur = handlerNameChain.get(i);
            String next = i + 1 < handlerNameChain.size() ? handlerNameChain.get((i + 1)) : null;
            BizOrderHandler handler = handlerMap.get(cur);
            if (next != null) {
                handler.setNextBizOrderHandler(handlerMap.get(next));
            }
            handlers.add(handler);
        }
        return handlers.get(0);
    }

}

上面的代码中通过initHandler这个方法来组建整个责任链条,还能实现动态配置,比如后期需要撤掉积分模块的商品处理,改个配置就行,感觉责任链完美搞定了这个问题,第一版就这样开心上线。

第二次迭代

产品又来了,提了一个新的需求

产品说,我们需要支持多租户,每种租户的订单流程都是不一样的

租户A:仓储检查->权益扣减->积分扣减->剩余金额支付

租户B:仓储检查->积分扣减->权益扣减

也就是说现在流程变成这样:

图片

来了多租户,这有何难,再加一条责任链配置不就好了,直接写代码如下:

public class OrderHandleCases {

    static Map<String, BizOrderHandler> handlerMap = new HashMap<>();

    static {
        handlerMap.put("Storage", new StorageCheckBizOrderHandler());
        handlerMap.put("Payment", new PaymentBizOrderHandler());
        handlerMap.put("RightCenter", new RightCenterBizOrderHandler());
        handlerMap.put("PointDeduction", new PointDeductBizOrderHandler());
    }

    public static void main(String[] args) {
        // 租户A的责任链
        BizOrderHandler handler1 = initHandler(Lists.newArrayList("Storage", "RightCenter", "PointDeduction", "Payment"));
        ProductVO productVO = ProductVO.builder().build();
        Result result1 = handler1.handle(productVO);
       // 租户B的责任链
        BizOrderHandler handler2 = initHandler(Lists.newArrayList("Storage", "PointDeduction", "RightCenter"));
        Result result2 = handler2.handle(productVO);
        System.out.println("订单处理 成功");

    }

    /**
     * 根据责任链配置构建责任链
     * @param handlerNameChain 责任链执行顺序
     * @return 首个处理器
     */
    private static BizOrderHandler initHandler(List<String> handlerNameChain) {
        List<BizOrderHandler> handlers = new ArrayList<>();
        for (int i = 0; i < handlerNameChain.size(); i++) {
            String cur = handlerNameChain.get(i);
            String next = i + 1 < handlerNameChain.size() ? handlerNameChain.get((i + 1)) : null;
            BizOrderHandler handler = handlerMap.get(cur);
            if (next != null) {
                handler.setNextBizOrderHandler(handlerMap.get(next));
            }
            handlers.add(handler);
        }
        return handlers.get(0);
    }

}

上面的代码相比之前的就是多加了一条新的租户B的责任链配置。感觉这个功能不就实现了嘛 结果一运行:堆栈溢出!

图片

咋回事 怎么堆栈溢出了,咱们仔细看一下 发现咱们的Map里面存放的实例全部是单例,搞出来了环形链表了....

图片

上图中黑色箭头代表第一个租户的流程,绿色箭头代表第二个租户,第二个租户的流程在执行到权益扣减环节,后面由于第一个租户配置的下一个环节是积分扣减,于是在这里形成了环。

看来单例不行,咱们得搞多例 既然需要多次构建对象,于是咱们搬出来下一个设计模式“简单工厂模式”:

public class BizOrderHandlerFactory {

    public static BizOrderHandler buildBizOrderHandler(String bizType) {
        switch (bizType) {
            case "Storage":
                return new StorageCheckBizOrderHandler();
            case "Payment":
                return new PaymentBizOrderHandler();
            case "RightCenter":
                return new RightCenterBizOrderHandler();
            case "PointDeduction":
                return new PointDeductBizOrderHandler();
            default:
                return null;
        }
    }

}

然后我们改写initHandler方法,不再从map中取实例,转为从工厂方法里面获取实例:

private static BizOrderHandler initHandlerPro(List<String> handlerNameChain) {
    List<BizOrderHandler> handlers = new ArrayList<>();
    for (String s : handlerNameChain) {
        BizOrderHandler handler = BizOrderHandlerFactory.buildBizOrderHandler(s);
        handlers.add(handler);
    }
    for (int i = 0; i < handlers.size(); i++) {
        BizOrderHandler handler = handlers.get(i);
        BizOrderHandler nextHandler = i + 1 < handlerNameChain.size() ? handlers.get(i + 1) : null;
        handler.setNextBizOrderHandler(nextHandler);
    }
    return handlers.get(0);
}

public static void main(String[] args) {
    BizOrderHandler handler1 = initHandlerPro(Lists.newArrayList("Storage", "Payment", "RightCenter", "PointDeduction"));
    BizOrderHandler handler2 = initHandlerPro(Lists.newArrayList("Storage", "RightCenter", "PointDeduction"));
    ProductVO productVO = ProductVO.builder().build();
    Result result = handler1.handle(productVO);
    System.out.println("订单处理 成功--租户1");
    result = handler2.handle(productVO);
    System.out.println("订单处理 成功--租户2");
}

执行代码:

图片

好了问题完美解决,现在多租户也支持了。上线搞定这次需求

第三次迭代

产品又又来了,提了一个新的需求

产品说,我们需要支持条件判断,租户A要求,权益扣减和积分扣减必须全部成功完成一个就可以进入支付环节,不必都要把权益扣减和积分扣减流程走一遍

分析一下这个需求权益扣减和积分扣减都要完成才可以进入支付环节 当然最简单的改法是在权益和积分环节做个判断,要是失败了就跳出责任链,但是假如产品经理下次又说权益扣减和积分扣减完成一个就能进入支付,我们还得修改这个权益和积分实现里面的判断,频繁修改实现可并不是好事。那咱们可以考虑代理模式,熟悉网关的都知道网关其实就是一个大代理,咱们按照这种思想可以搞一个网关代理权益扣减和积分扣减环节。于是咱们搞出来一个“网关”组件

@Data
public class BizOrderHandlerUnionGateway extends BizOrderHandler {
    List<BizOrderHandler> proxyHandlers;

    @Override
    public Result handle(ProductVO param) {
        boolean isAllSuccess = true;
        if (proxyHandlers != null) {
            for (BizOrderHandler handler : proxyHandlers) {
                Result result = handler.handle(param);
                if (result.isSuccess()) {
                    // 一个代理执行器 执行成功 则继续执行
                    continue;
                } else {
                    isAllSuccess = false;
                    break;
                }
            }
        }
        if (isAllSuccess) {
            return super.next(param);
        }else{
            throw new RuntimeException("execute Failed");
        }
    }
}

上面的网关叫做union网关也就是并集网关,也就是说代理的处理器全部都执行成功才继续传递责任链,需要注意的是这个类也是 BizOrderHandler的一个实现,只不过它的内部没有逻辑,只是对proxyHandlers中的组件进行代理。

然后简单修改下工厂 加一个分支:

public static BizOrderHandler buildBizOrderHandler(String bizType) {
    switch (bizType) {
        case "Storage":
            return new StorageCheckBizOrderHandler();
        case "Payment":
            return new PaymentBizOrderHandler();
        case "RightCenter":
            return new RightCenterBizOrderHandler();
        case "PointDeduction":
            return new PointDeductBizOrderHandler();
        case "UnionGateway":
            return new BizOrderHandlerUnionGateway();
        default:
            return null;
    }
}

然后我们用下面的方法获取首个执行节点,就可以执行整个责任链了:

private static BizOrderHandler initHandlerWithGateway() {
    BizOrderHandler storage = BizOrderHandlerFactory.buildBizOrderHandler("Storage");
    BizOrderHandler payment = BizOrderHandlerFactory.buildBizOrderHandler("Payment");
    BizOrderHandler rightCenter = BizOrderHandlerFactory.buildBizOrderHandler("RightCenter");
    BizOrderHandler pointDeduction = BizOrderHandlerFactory.buildBizOrderHandler("PointDeduction");
    BizOrderHandlerUnionGateway unionGateway = (BizOrderHandlerUnionGateway) BizOrderHandlerFactory.buildBizOrderHandler("UnionGateway");
    storage.setNextBizOrderHandler(unionGateway);
    unionGateway.setNextBizOrderHandler(payment);
    // unionGateway 加入责任链,权益和积分交给这个uniongateway进行代理控制
    unionGateway.setProxyHandlers(Lists.newArrayList(rightCenter, pointDeduction));
    return storage;
}

第四次迭代

产品又又又来了,这次提了一个技术需求

用户反馈生产订单流接口响应过慢,页面卡顿,观察接口发现目前的订单流程需要走的链路比较冗长,虽然用了责任链模式但本质上代码执行仍然是同步的,导致一个订单流完成耗费的时间过长,现在希望订单流接口异步化,然后需要发挥分布式部署的优势,每一个环节可以单独分散到每个单个部署节点上执行。

这次我们发现问题需要异步化还要分布式,这怎么办,显然简单的内存责任链不行了,咱们得上升到分布式责任链模式的方式,那怎么实现分布式责任链呢,咱们可以借助MQ来实现消息触发,于是观察者模式上线,这次咱们借助观察者模式的思想彻底完成分布式重构。

ps:果然需求演进的最后就是重构,不重构没有KPI。

咱们首先定义一个事件,这个就是订单流事件:

@Data
public class OrderFlowEvent implements Serializable {

    private String orderNo;

    private String currentFlow;

    private String nextFlow;

}

这个事件可以在订单流发起的时候丢到消息队列里面,然后就可以进行订单流的流转了,下面我们来看消息处理逻辑,咱们使用模板方法再次进行一次代码优化,这里还是一个抽象类,然后我们的,支付、权益、积分只需要实现这个抽象类实现handleEvent逻辑就可以了,此外我们只用一个Topic,当前环节处理完成之后如果还有后续流程则再次发送消息到消息队列,进行下一步处理,此外handlerMap 代表责任链名称和责任链处理器的对应关系,handlerChain则是责任链的环节配置。

@Data
public abstract class BizHandler {

    String topicName = "biz_handle_topic";

    Map<String, BizHandler> handlerMap = new HashMap<>();

    Map<String, String> handlerChain = new LinkedHashMap<>();

    /**
     * 模板方法:在收到订单流的消息之后将进到这里进行业务逻辑处理
     *
     * @param msg 订单流消息
     */
    public void handle(String msg) {
        if (CollectionUtils.isEmpty(handlerMap) || CollectionUtils.isEmpty(handlerChain)) {
            //log.warn("handlerMap or handlerChain is empty");
            return;
        }
        OrderFlowEvent orderFlowEvent = JSON.parseObject(msg, OrderFlowEvent.class);
        String currentFlow = orderFlowEvent.getCurrentFlow();
        String nextFlow = handlerChain.get(currentFlow);
        // 当前环节的处理器进行业务处理
        Result result = handlerMap.get(currentFlow).handleEvent(orderFlowEvent);
        if (!result.isSuccess()) {
            throw new RuntimeException("handleException");
        }
        if (nextFlow == null) {
            return;
        }
        if (result.isSuccess()) {
            // 处理成功并且还有后续流程则再次向订单流Topic中发送消息
            sendFlowMsg(result.getData(), nextFlow, handlerChain.get(nextFlow));
        }
    }

    public abstract Result handleEvent(OrderFlowEvent orderFlowEvent);

    public void sendFlowMsg(Object data, String currentFlow, String nextFlow) {
        OrderFlowEvent orderFlowEvent = new OrderFlowEvent();
        orderFlowEvent.setCurrentFlow(currentFlow);
        orderFlowEvent.setNextFlow(nextFlow);
        MqUtils.sendMsg(topicName, JSON.toJSONString(orderFlowEvent));
    }


}

例如仓储环节可以这样实现:

public class StorageBizHandler extends BizHandler {

    @Override
    public Result handleEvent(OrderFlowEvent orderFlowEvent) {
        System.out.println("StorageBizHandler handle orderFlowEvent=" + JSON.toJSONString(orderFlowEvent));
        return Result.success();
    }
}

使用的时候则可以这样:

public class OrderFlowClient {

    void handleOrder() {
        // 定义一下流程名称和流程实例的对应关系
        Map<String, BizHandler> handlerMap = new HashMap<>();
        handlerMap.put("Storage", new StorageBizHandler());
        handlerMap.put("PointDeduction", new PointDeductionBizHandler());
        handlerMap.put("Payment", new PaymentBizHandler());

        //注意这里用LinkedHashMap 保证顺序 key标识当前流程 value标识下一个流程
        Map<String, String> handlerChain = new LinkedHashMap<>();
        handlerChain.put("Storage", "PointDeduction");
        handlerChain.put("PointDeduction", "Payment");
        handlerChain.put("Payment", null);

        // 开启分布式订单流转
        Map.Entry<String, String> first = handlerChain.entrySet().iterator().next();
        String key = first.getKey();
        OrderFlowEvent orderFlowEvent = new OrderFlowEvent();
        orderFlowEvent.setCurrentFlow("Storage");
        orderFlowEvent.setOrderNo("order001123124123");
        handlerMap.get(key).handle(JSON.toJSONString(orderFlowEvent));
    }

}

最后咱们完成了这次大的技术演进需求,但是就到此结束了吗?按照这种设计思路改动之后你发现分布式环境下各种并发问题又出现了,于是你还需要分布式锁来控制,有了分布式锁你发现环节失败了还得引入重试逻辑,重试应该怎么设计,所以发现到了分布式系统下问题变得复杂了,还得继续想办法一个个攻克。

总结

本文通过一次简单的需求演进分别讲述了责任链、模板方法、策略模式、工厂模式、代理模式、观察者模式的使用,通过实际场景介绍下不同需求下如何通过适合的设计模式来解决问题。

在 Java 开发领域,我们经常遇到工厂、单例、观察者、MVC 等术语。理解这些概念的本质至关重要,因为它直接影响我们代码的质量。在本文中,我们将深入研究 Java 生态系统中常用的各种设计模式,阐明它们的重要性和实际应用。

策略、委派、代理、桥接

策略模式

策略模式简介

策略模式:策略模式是一种行为型模式,它将对象和行为分开,将行为定义为 一个行为接口 和 具体行为的实现。策略模式最大的特点是行为的变化,行为之间可以相互替换。每个if判断都可以理解为就是一个策略。本模式使得算法可独立于使用它的用户而变化

模式结构

策略模式包含如下角色

  • Strategy: 抽象策略类:策略是一个接口,该接口定义若干个算法标识,即定义了若干个抽象方法(如下图的algorithm())

  • Context: 环境类 /上下文类:

    • 上下文是依赖于接口的类(是面向策略设计的类,如下图Context类),即上下文包含用策略(接口)声明的变量(如下图的strategy成员变量)。

    • 上下文提供一个方法(如下图Context类中的的lookAlgorithm()方法),持有一个策略类的引用,最终给客户端调用。该方法委托策略变量调用具体策略所实现的策略接口中的方法(实现接口的类重写策略(接口)中的方法,来完成具体功能)

  • ConcreteStrategy: 具体策略类:具体策略是实现策略接口的类(如下图的ConcreteStrategyA类和ConcreteStrategyB类)。具体策略实现策略接口所定义的抽象方法,即给出算法标识的具体方法。(说白了就是重写策略类的方法!)

    在这里插入图片描述

案例

image-20241029225548921

传统实现方式

代码

public Double calculationPrice(String type, Double originalPrice, int n) {

    //中级会员计费
    if (type.equals("intermediateMember")) {
        return originalPrice * n - originalPrice * 0.1;
    }
    //高级会员计费
    if (type.equals("advancePrimaryMember")) {
        return originalPrice * n - originalPrice * 0.2;
    }
    //普通会员计费
    return originalPrice;
}

传统的实现方式,通过传统if代码判断。这样就会导致后期的维护性非常差。当后期需要新增计费方式,还需要在这里再加上if(),也不符合设计模式的开闭原则。

策略模式实现

抽象类策略

package StrategyExercise;

public interface MemberStrategy {
    // 一个计算价格的抽象方法
    //price商品的价格 n商品的个数
    public double calcPrice(double price, int n);
}

具体实现类

// 普通会员——不打折
public class PrimaryMemberStrategy implements MemberStrategy { // 实现策略
    //重写策略方法具体实现功能
    @Override
    public double calcPrice(double price, int n) {
        return price * n;
    }
}
package StrategyExercise;

// 中级会员 打百分之10的折扣
public class IntermediateMemberStrategy implements MemberStrategy{
    @Override
    public double calcPrice(double price, int n) {
        double money = (price * n) - price * n * 0.1;
        return money;
    }
}
package StrategyExercise;

// 高级会员类 20%折扣
public class AdvanceMemberStrategy implements MemberStrategy{
    @Override
    public double calcPrice(double price, int n) {
        double money = price * n - price * n * 0.2;
        return money;
    }
}

上下文类

  • 也叫做上下文类或环境类,起承上启下封装作用。

package StrategyExercise;

/**

 * 负责和具体的策略类交互
 * 这样的话,具体的算法和直接的客户端调用分离了,使得算法可以独立于客户端独立的变化。
   */

// 上下文类/环境类
public class MemberContext {
    // 用户折扣策略接口
    private MemberStrategy memberStrategy;
    // 注入构造方法
    public MemberContext(MemberStrategy memberStrategy) {
        this.memberStrategy = memberStrategy;
    }

    // 计算价格
    public double qoutePrice(double goodsPrice, int n){
        // 通过接口变量调用对应的具体策略
        return memberStrategy.calcPrice(goodsPrice, n);
    }
}

测试类

package StrategyExercise;

// 测试类
public class Application {
    public static void main(String[] args) {   
	// 具体行为策略
    // 接口回调(向上转型)
    MemberStrategy primaryMemberStrategy = new PrimaryMemberStrategy();
    MemberStrategy intermediateMemberStrategy = new IntermediateMemberStrategy();
    MemberStrategy advanceMemberStrategy = new AdvanceMemberStrategy();

    // 用户选择不同策略
    MemberContext primaryContext = new MemberContext(primaryMemberStrategy);
    MemberContext intermediateContext = new MemberContext(intermediateMemberStrategy);
    MemberContext advanceContext = new MemberContext(advanceMemberStrategy);

    //计算一本300块钱的书
    System.out.println("普通会员的价格:"+ primaryContext.qoutePrice(300,1));// 普通会员:300
    System.out.println("中级会员的价格:"+ intermediateContext.qoutePrice(300,1));// 中级会员 270
    System.out.println("高级会员的价格:"+ advanceContext.qoutePrice(300,1));// 高级会员240
}
}

运行结果

普通会员的价格:300.0 中级会员的价格:270.0 高级会员的价格:240.0

上述案例UML类图

在这里插入图片描述

策略模式优缺点

优点
  • 策略模式提供了对“开闭原则”的完美支持,用户可以在不 修改原有系统的基础上选择算法或行为,也可以灵活地增加 新的算法或行为。

  • 策略模式提供了管理相关的算法族的办法。

  • 策略模式提供了可以替换继承关系的办法。

  • 使用策略模式可以避免使用多重条件转移语句。

缺点
  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。

  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一 定程度上减少对象的数量。

策略模式适用场景

在以下情况下可以使用策略模式:

  • 如果在一个系统里面有许多类,它们之间的区别仅在于它们 的行为,那么使用策略模式可以动态地让一个对象在许多行 为中选择一种行为。

  • 一个系统需要动态地在几种算法中选择一种。

  • 如果一个对象有很多的行为,如果不用恰当的模式,这些行 为就只好使用多重的条件选择语句来实现。

  • 不希望客户端知道复杂的、与算法相关的数据结构,在具体 策略类中封装算法和相关的数据结构,提高算法的保密性与 安全性。

    在我们生活中比较常见的应用模式有:

1、电商网站支付方式,一般分为银联、微信、支付宝,可以采用策略模式 2、电商网站活动方式,一般分为满减送、限时折扣、包邮活动,拼团等可以采用策略模式

总结

• 在策略模式中定义了一系列算法,将每一个算法封装起来,并让它们 可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为 政策模式。策略模式是一种对象行为型模式。

• 策略模式包含三个角色:环境类在解决某个问题时可以采用多种策略, 在环境类中维护一个对抽象策略类的引用实例;抽象策略类为所支持 的算法声明了抽象方法,是所有策略类的父类;具体策略类实现了在 抽象策略类中定义的算法。

• 策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派 给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的 策略类里面,作为一个抽象策略类的子类。

• 策略模式主要优点在于对“开闭原则”的完美支持,在不修改原有系 统的基础上可以更换算法或者增加新的算法,它很好地管理算法族, 提高了代码的复用性,是一种替换继承,避免多重条件转移语句的 实现方式;其缺点在于客户端必须知道所有的策略类,并理解其区 别,同时在一定程度上增加了系统中类的个数,可能会存在很多策 略类。

• 策略模式适用情况包括:在一个系统里面有许多类,它们之间的区 别仅在于它们的行为,使用策略模式可以动态地让一个对象在许多 行为中选择一种行为;一个系统需要动态地在几种算法中选择一种; 避免使用难以维护的多重条件选择语句;希望在具体策略类中封装 算法和与相关的数据结构。

注:如果文章有任何错误或不足,请各位大佬尽情指出,评论留言留下您宝贵的建议!如果这篇文章对你有些许帮助,希望可爱亲切的您点个赞推荐一手,非常感谢啦

委派模式

什么是委派模式

现实生活中也常有委派的场景发生,例如:老板(Boss)给项目经理(Leader)下达任务,项目经理会根据实际情况给每个员工派发工作任务,待员工把工作任务完成之后,再由项目经理汇报工作进度和结果给老板。

委派模式定义

委派模式(DelegatePattern)又叫委托模式,是一种面向对象的设计模式,允许对象组合实现与继承相同的代码重用。他的基本作用就是负责任务的调用和分配任务,是一种特殊的静态代理,可以理解为全权代理,但是代理模式注重过程,而委派模式注重结果。委派模式属于行为型模式,不属于GOF23种设计模式中。

源码实现

img

创建IEmployee接口:

package delegate;
public interface IEmployee {
   void doing(String task);
}

创建员工EmployeeA类:

package com;
public class EmployeeA implements IEmployee {
    protected String name;

    public EmployeeA(String name){
        this.name = name;
    }

    @Override
    public void doing(String task) {
        System.out.println("我是员工A,我擅长"+name+",现在开始做"+task+"工作");
    }
}

创建员工EmployeeB类:

package com;

public class EmployeeB implements IEmployee {
    protected String name;

    public EmployeeB(String name){
        this.name = name;
    }
    @Override
    public void doing(String task) {
        System.out.println("我是员工B,我擅长"+name+",现在开始做"+task+"工作");
    }
}

创建领导Leader类:

委派者角色

package com;
import java.util.HashMap;
public class Leader implements IEmployee {
    HashMap<String,IEmployee> map = new HashMap<String,IEmployee>();

    public Leader(){
        map.put("登录",new EmployeeA("开发"));
        map.put("登录页面",new EmployeeB("UI"));
    }

    @Override
    public void doing(String task) {
       map.get(task).doing(task);
    }
}

创建老板Boss类:

package com;
/**

 * 委派模式(Delegate Pattern)又叫委托模式,是一种面向对象的设计模式,允许对象组合实现与继承相同的代码重用。

 * 他的基本作用就是负责任务的调用和分配任务,是一种特殊的静态代理,可以理解为全权代理,但是代理模式注重过程,而委派模式注重结果。

 * 委派模式属于行为型模式,不属于GOF23种设计模式中。
   */
   public class Boss {

   public static void main(String[] args) {
       new Leader().doing("登录");
       new Leader().doing("登录页面");
   }

}

测试结果img

代理模式

简介

1.1 代理是一个中间者的角色 它屏蔽了访问方和委托方之间的直接接触。也就是说访问方不能直接调用委托方的这个对象,而是必须实例化一个跟委托方有同样接口的代理方,通过这个代理方来完成对委托方的调用。访问方只和代理方打交道,这个代理方有点像掮客的角色,现实生活中的代理类似于中介机构。

1.2 什么时候需要用到代理模式呢?

  • 访问方不想和委托方有直接接触,或者直接接触有困难。

  • 访问方对委托方的访问需要增加额外的处理,例如访问前和访问后都做一些处理。这种情况下我们不能直接对委托方的方法进行修改,因为这样会违反“开闭原则”。

1.3 代理模式有两类:静态代理和动态代理,下面我们通过代码分别详细说明。

静态代理

用一个类,把另一个类的功能封装了一下,原来的功能是不改变的。

静态代理的代理类每次都需要手动创建。

public interface Icar {
    void move();
}
public class Benz implements Icar{
    @Override
    public void move() {
        System.out.println("Benz move ...");
    }
}
/**
 * 静态代理
 */
public class BenzProxy implements Icar{
    private Benz benz;

    public BenzProxy() {
        benz = new Benz();
    }

    @Override
    public void move() {
        //做一些前置工作 例如:检查车子的状况
        System.out.println("检查车子的状况。。。");
        benz.move();
        //做一些后置工作 例如检查结果
        System.out.println("车子2020-10-21号出厂,空调坏了。");

    }
}

最后调用

public class ProxyMain {
    public static void main(String[] args) {
        BenzProxy benzProxy = new BenzProxy();
        benzProxy.move();
    }
}

动态代理

动态代理的代理类可以根据委托类自动生成,而不需要像静态代理那样通过手动创建。动态代理类的代码不是在Java代码中定义的,而是在运行的时候动态生成的。这里主要用到InvocationHandler接口。

//动态代理类
public class CarHandler implements InvocationHandler {
    //目标类的引用
    private Object target;

    public CarHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object object, Method method, Object[] args) throws Throwable {
        //做一些前置工作 例如:检查车子的状况
        before();
        Object result = method.invoke(target, args);
        //做一些后置工作 例如检查结果
        after();
        return result;
    }
    private void before() {
        System.out.println("检查车子的状况。。。");
    }
    private void after() {
        System.out.println("车子2019-10-1号出厂,空调坏了。");
    }
}

调用动态代理:

public static void main(String[] args) {
    ICar iCar = new Benz();
    InvocationHandler handler = new CarHandler(iCar);
    ICar proxy = (ICar) Proxy.newProxyInstance(ICar.class.getClassLoader(), new Class[]{ICar.class}, handler);
    proxy.move();
}

动态代理应用:简单工厂

接着上面动态代理调用方的使用方式,通过工厂模式加上泛型的方式,优化一下动态代理的生成和调用。

public class ProxyFactory <T>{
    private T client;//目标对象
    private IBefore before;
    private IAfter after;

    public <T> T createProxy(){
        ClassLoader loader = client.getClass().getClassLoader();
        Class<?>[] interfaces = client.getClass().getInterfaces();
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                if ("getName".equals(method.getName())){
                    //可根据name值过滤方法
                }
                //前置
                if (before != null){
                    before.doBefore();
                }
                //执行目标对象的方法
                Object result = method.invoke(client, objects);
                //后置
                if (after != null){
                    after.doAfter();
                }
                return result;
            }
        };
        return (T) Proxy.newProxyInstance(loader,interfaces,handler);
    }

    public void setClient(T client) {
        this.client = client;
    }

    public void setBefore(IBefore before) {
        this.before = before;
    }

    public void setAfter(IAfter after) {
        this.after = after;
    }
}

创建前后置接口

public interface IAfter {
    void doAfter();
}
public interface IBefore {
    void doBefore();
}

使用和具体的产品业务分离出来了,方便维护和拓展:

public static void main(String[] args) {
    //创建工厂
    ProxyFactory factory = new ProxyFactory();

    factory.setBefore(new IBefore() {
        @Override
        public void doBefore() {
            System.out.println(" doBefore...");
        }
    });
    factory.setClient(new Benz());

    factory.setAfter(new IAfter() {
        @Override
        public void doAfter() {
            System.out.println(" doAfter...");
        }
    });
    //创建代理
    ICar iCar = (ICar) factory.createProxy();
    iCar.move();
}

动态代理应用:AOP

AOP的英文全称是Aspect-Oriented Programming,即面向切面编程。AOP的实现方式之一是动态代理。 简单来说,AOP能够动态地将代码切入指定的位置,在指定位置上实现编程,从而达到动态改变原有代码的目的。 上面的IBefore和IAfter接口实际上就是简单地实现了AOP,例如在invoke具体方法之前和之后插入一些操作。 另外Proxy.newProxyInstance这个方法:

  public static Object newProxyInstance(ClassLoader 
    loader,Class<?>[] interfaces,InvocationHandler h)
  • loader:委托类的classLoader。

  • interfaces:代理类需要实现的接口,这个接口同委托类的接口。

  • h:调用处理器,只有一个invoke方法,调用委托类的任何方法都是通过它的invoke方法来完成的。

桥接模式

Java数据库连接JDBC

概述

现在有一个需求,需要创建不同的图形,并且每个图形都有可能会有不同的颜色。我们可以利用继承的方式来设计类的关系:

img

我们可以发现有很多的类,假如我们再增加一个形状或再增加一种颜色,就需要创建更多的类。

试想,在一个有多种可能会变化的维度的系统中,用继承方式会造成类爆炸,扩展起来不灵活。每次在一个维度上新增一个具体实现都要增加多个子类。为了更加灵活的设计系统,我们此时可以考虑使用桥接模式。

定义:

  • 将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

结构

桥接(Bridge)模式包含以下主要角色:

  • 抽象化(Abstraction)角色 :定义抽象类,并包含一个对实现化对象的引用。

  • 扩展抽象化(Refined Abstraction)角色 :是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。

  • 实现化(Implementor)角色 :定义实现化角色的接口,供扩展抽象化角色调用。

  • 具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现。

案例

【例】视频播放器

需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含了两个维度,适合使用桥接模式。

类图如下:

img

代码如下:

//视频文件
public interface VideoFile {
    void decode(String fileName);
}

//avi文件
public class AVIFile implements VideoFile {
    public void decode(String fileName) {
        System.out.println("avi视频文件:"+ fileName);
    }
}

//rmvb文件
public class REVBBFile implements VideoFile {

    public void decode(String fileName) {
        System.out.println("rmvb文件:" + fileName);
    }

}

//操作系统版本
public abstract class OperatingSystemVersion {

    protected VideoFile videoFile;
     
    public OperatingSystemVersion(VideoFile videoFile) {
        this.videoFile = videoFile;
    }
     
    public abstract void play(String fileName);

}

//Windows版本
public class Windows extends OperatingSystemVersion {

    public Windows(VideoFile videoFile) {
        super(videoFile);
    }
     
    public void play(String fileName) {
        videoFile.decode(fileName);
    }

}

//mac版本
public class Mac extends OperatingSystemVersion {

    public Mac(VideoFile videoFile) {
        super(videoFile);
    }
     
    public void play(String fileName) {
    videoFile.decode(fileName);
    }

}

//测试类
public class Client {
    public static void main(String[] args) {
        OperatingSystem os = new Windows(new AVIFile());
        os.play("战狼3");
    }
}

好处:

  • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。 如:如果现在还有一种视频文件类型wmv,我们只需要再定义一个类实现VideoFile接口即可,其他类不需要发生变化。

  • 实现细节对客户透明

相关文章
热点文章
精彩视频
Tags

站点地图 在线访客: 今日访问量: 昨日访问量: 总访问量: