java八股文面试[设计模式]——行为型模式

news2024/11/17 15:32:29

目录

策略模式

观察者模式

责任链模式

模板方法模式

状态模式


行为型模式关注的是各个类之间的相互作用,将职责划分清楚,使得我们的代码更加地清晰。

策略模式

策略模式太常用了

下面设计的场景是,我们需要画一个图形,可选的策略就是用红色笔来画,还是绿色笔来画,或者蓝色笔来画。

首先,先定义一个策略接口:

public interface Strategy {
   public void draw(int radius, int x, int y);
}

然后我们定义具体的几个策略:

public class RedPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用红色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class GreenPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用绿色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class BluePen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用蓝色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}

使用策略的类:

public class Context {
   private Strategy strategy;

   public Context(Strategy strategy){
      this.strategy = strategy;
   }

   public int executeDraw(int radius, int x, int y){
      return strategy.draw(radius, x, y);
   }
}

客户端演示:

public static void main(String[] args) {
    Context context = new Context(new BluePen()); // 使用绿色笔来画
      context.executeDraw(10, 0, 0);
}

放到一张图上,让大家看得清晰些:

图片

这个时候,大家有没有联想到结构型模式中的桥梁模式,它们其实非常相似,我把桥梁模式的图拿过来大家对比下:

图片

要我说的话,它们非常相似,桥梁模式在左侧加了一层抽象而已。桥梁模式的耦合更低,结构更复杂一些

观察者模式

观察者模式对于我们来说,真是再简单不过了。无外乎两个操作,观察者订阅自己关心的主题和主题有数据变化后通知观察者们。

首先,需要定义主题,每个主题需要持有观察者列表的引用,用于在数据变更的时候通知各个观察者:

public class Subject {
    private List<Observer> observers = new ArrayList<Observer>();
    private int state;
    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;
        // 数据已变更,通知观察者们
        notifyAllObservers();
    }
    // 注册观察者
    public void attach(Observer observer) {
        observers.add(observer);
    }
    // 通知观察者们
    public void notifyAllObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

定义观察者接口:

public abstract class Observer {
    protected Subject subject;
    public abstract void update();
}

其实如果只有一个观察者类的话,接口都不用定义了,不过,通常场景下,既然用到了观察者模式,我们就是希望一个事件出来了,会有多个不同的类需要处理相应的信息。比如,订单修改成功事件,我们希望发短信的类得到通知、发邮件的类得到通知、处理物流信息的类得到通知等。

我们来定义具体的几个观察者类:

public class BinaryObserver extends Observer {
    // 在构造方法中进行订阅主题
    public BinaryObserver(Subject subject) {
        this.subject = subject;
        // 通常在构造方法中将 this 发布出去的操作一定要小心
        this.subject.attach(this);
    }
    // 该方法由主题类在数据变更的时候进行调用
    @Override
    public void update() {
        String result = Integer.toBinaryString(subject.getState());
        System.out.println("订阅的数据发生变化,新的数据处理为二进制值为:" + result);
    }
}

public class HexaObserver extends Observer {
    public HexaObserver(Subject subject) {
        this.subject = subject;
        this.subject.attach(this);
    }
    @Override
    public void update() {
        String result = Integer.toHexString(subject.getState()).toUpperCase();
        System.out.println("订阅的数据发生变化,新的数据处理为十六进制值为:" + result);
    }
}

客户端使用也非常简单:

public static void main(String[] args) {
    // 先定义一个主题
    Subject subject1 = new Subject();
    // 定义观察者
    new BinaryObserver(subject1);
    new HexaObserver(subject1);

    // 模拟数据变更,这个时候,观察者们的 update 方法将会被调用
    subject.setState(11);
}

output:

订阅的数据发生变化,新的数据处理为二进制值为:1011
订阅的数据发生变化,新的数据处理为十六进制值为:B

当然,JDK 也提供了相似的支持,具体的大家可以参考 java.util.Observable 和 java.util.Observer 这两个类。

实际生产过程中,观察者模式往往用消息中间件来实现,如果要实现单机观察者模式,笔者建议读者使用 Guava 中的 EventBus,它有同步实现也有异步实现,本文主要介绍设计模式,就不展开说了。

还有,即使是上面的这个代码,也会有很多变种,大家只要记住核心的部分,那就是一定有一个地方存放了所有的观察者,然后在事件发生的时候,遍历观察者,调用它们的回调函数

责任链模式

责任链通常需要先建立一个单向链表,然后调用方只需要调用头部节点就可以了,后面会自动流转下去。比如流程审批就是一个很好的例子,只要终端用户提交申请,根据申请的内容信息,自动建立一条责任链,然后就可以开始流转了。

有这么一个场景,用户参加一个活动可以领取奖品,但是活动需要进行很多的规则校验然后才能放行,比如首先需要校验用户是否是新用户、今日参与人数是否有限额、全场参与人数是否有限额等等。设定的规则都通过后,才能让用户领走奖品。

如果产品给你这个需求的话,我想大部分人一开始肯定想的就是,用一个 List 来存放所有的规则,然后 foreach 执行一下每个规则就好了。不过,读者也先别急,看看责任链模式和我们说的这个有什么不一样?

首先,我们要定义流程上节点的基类:

public abstract class RuleHandler {
    // 后继节点
    protected RuleHandler successor;

    public abstract void apply(Context context);

    public void setSuccessor(RuleHandler successor) {
        this.successor = successor;
    }

    public RuleHandler getSuccessor() {
        return successor;
    }
}

接下来,我们需要定义具体的每个节点了。

校验用户是否是新用户:

public class NewUserRuleHandler extends RuleHandler {
    public void apply(Context context) {
        if (context.isNewUser()) {
            // 如果有后继节点的话,传递下去
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(context);
            }
        } else {
            throw new RuntimeException("该活动仅限新用户参与");
        }
    }
}

验用户所在地区是否可以参与:

public class LocationRuleHandler extends RuleHandler {
    public void apply(Context context) {
        boolean allowed = activityService.isSupportedLocation(context.getLocation);
        if (allowed) {
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(context);
            }
        } else {
            throw new RuntimeException("非常抱歉,您所在的地区无法参与本次活动");
        }
    }
}

校验奖品是否已领完:

public class LimitRuleHandler extends RuleHandler {
    public void apply(Context context) {
        int remainedTimes = activityService.queryRemainedTimes(context); // 查询剩余奖品
        if (remainedTimes > 0) {
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(userInfo);
            }
        } else {
            throw new RuntimeException("您来得太晚了,奖品被领完了");
        }
    }
}

客户端:

public static void main(String[] args) {
    RuleHandler newUserHandler = new NewUserRuleHandler();
    RuleHandler locationHandler = new LocationRuleHandler();
    RuleHandler limitHandler = new LimitRuleHandler();

    // 假设本次活动仅校验地区和奖品数量,不校验新老用户
    locationHandler.setSuccessor(limitHandler);

    locationHandler.apply(context);
}

代码其实很简单,就是先定义好一个链表,然后在通过任意一节点后,如果此节点有后继节点,那么传递下去。

至于它和我们前面说的用一个 List 存放需要执行的规则的做法有什么异同,留给读者自己琢磨吧。

模板方法模式

在含有继承结构的代码中,模板方法模式是非常常用的。

通常会有一个抽象类:

public abstract class AbstractTemplate {
    // 这就是模板方法
    public void templateMethod() {
        init();
        apply(); // 这个是重点
        end(); // 可以作为钩子方法
    }

    protected void init() {
        System.out.println("init 抽象层已经实现,子类也可以选择覆写");
    }

    // 留给子类实现
    protected abstract void apply();

    protected void end() {
    }
}

模板方法中调用了 3 个方法,其中 apply() 是抽象方法,子类必须实现它,其实模板方法中有几个抽象方法完全是自由的,我们也可以将三个方法都设置为抽象方法,让子类来实现。也就是说,模板方法只负责定义第一步应该要做什么,第二步应该做什么,第三步应该做什么,至于怎么做,由子类来实现

我们写一个实现类:

public class ConcreteTemplate extends AbstractTemplate {
    public void apply() {
        System.out.println("子类实现抽象方法 apply");
    }

    public void end() {
        System.out.println("我们可以把 method3 当做钩子方法来使用,需要的时候覆写就可以了");
    }
}

客户端调用演示:

public static void main(String[] args) {
    AbstractTemplate t = new ConcreteTemplate();
    // 调用模板方法
    t.templateMethod();
}

代码其实很简单,基本上看到就懂了,关键是要学会用到自己的代码中。

状态模式

废话我就不说了,我们说一个简单的例子。商品库存中心有个最基本的需求是减库存和补库存,我们看看怎么用状态模式来写。

核心在于,我们的关注点不再是 Context 是该进行哪种操作,而是关注在这个 Context 会有哪些操作。

定义状态接口:

public interface State {
    public void doAction(Context context);
}

定义减库存的状态:

public class DeductState implements State {

    public void doAction(Context context) {
        System.out.println("商品卖出,准备减库存");
        context.setState(this);

        //... 执行减库存的具体操作
    }

    public String toString() {
        return "Deduct State";
    }
}

定义补库存状态:

public class RevertState implements State {

    public void doAction(Context context) {
        System.out.println("给此商品补库存");
        context.setState(this);

        //... 执行加库存的具体操作
    }

    public String toString() {
        return "Revert State";
    }
}

前面用到了 context.setState(this),我们来看看怎么定义 Context 类:(内部包含状态类State)

public class Context {
    private State state;
      private String name;
      public Context(String name) {
        this.name = name;
    }

      public void setState(State state) {
        this.state = state;
    }
      public void getState() {
        return this.state;
    }
}

我们来看下客户端调用,大家就一清二楚了:

public static void main(String[] args) {
    // 我们需要操作的是 iPhone X
    Context context = new Context("iPhone X");

    // 看看怎么进行补库存操作
      State revertState = new RevertState();
      revertState.doAction(context);

    // 同样的,减库存操作也非常简单
      State deductState = new DeductState();
      deductState.doAction(context);

      // 如果需要我们可以获取当前的状态
    // context.getState().toString();
}

读者可能会发现,在上面这个例子中,如果我们不关心当前 context 处于什么状态,那么 Context 就可以不用维护 state 属性了,那样代码会简单很多。

不过,商品库存这个例子毕竟只是个例,我们还有很多实例是需要知道当前 context 处于什么状态的。

行为型模式总结

行为型模式部分介绍了策略模式、观察者模式、责任链模式、模板方法模式和状态模式,其实,经典的行为型模式还包括备忘录模式命令模式等,但是它们的使用场景比较有限,而且本文篇幅也挺大了,我就不进行介绍了。

知识来源:

图文详解 23 种设计模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/984861.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

leetcode872. 叶子相似的树(java)

叶子相似的树 题目描述递归 题目描述 难度 - 简单 leetcode - 872. 叶子相似的树 请考虑一棵二叉树上所有的叶子&#xff0c;这些叶子的值按从左到右的顺序排列形成一个 叶值序列 。 举个例子&#xff0c;如上图所示&#xff0c;给定一棵叶值序列为 (6, 7, 4, 9, 8) 的树。 如果…

cuda显存访问耗时

背景&#xff1a; 项目中有个数据量大小为5195 * 512 * 128float 1.268G的显存&#xff0c;发现有个函数调用很耗时&#xff0c;函数里面就是对这个显存进行128个元素求和&#xff0c;得到一个5195 * 512的图像 分析 1. 为什么耗时 直观上感觉这个流程应该不怎么耗时才对&a…

TDengine 官网换了新“皮肤”,来看看这个风格是不是你的菜

改版升级&#xff0c;不同以“网”&#xff01;为了更好地服务客户&#xff0c;让大家能够更便捷、清晰地了解我们的产品和功能&#xff0c;我们决定给 TDengine 官网换个新“皮肤”~精心筹备下&#xff0c;新官网终于成功与大家见面啦——https://www.taosdata.com/。TDengine…

《向量数据库指南》——AI原生向量数据库Milvus Cloud 2.3稳定性

在当今的互联网时代,稳定性是所有系统和应用程序的关键要素。无论是大型数据中心还是个人电脑,稳定性都是保证正常运行和用户体验的基础。在这个背景下,我们来谈谈 Milvus,一个开源的向量数据库,它在 2.1.0 版本中引入了内存多副本的概念。 Milvus 是一个开源的向量数据库…

9:00面试,9:08就出来了,问的实在有点变态

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降30%,…

osgEarth::ElevationQuery的setMaxTilesToCache函数的作用

【引子】 osgEarth::ElevationQuery类用于查询高程&#xff0c;自测效率比较低&#xff08;大概每个点需要四五十毫秒&#xff09;&#xff0c;成员函数setMaxTilesToCache(int)&#xff0c;之前看说明仍然不是很确认其具体的意义&#xff0c;想尝试设置是否能加速下。 看之前的…

MATLAB实现TopSis优劣解距离法——分析《世界征服者3》将领排名

问题背景 世界征服者3游戏中有150的将领角色&#xff0c;每个将领都有自己的兵种优势、军阶、技能等不同的属性&#xff0c;如何教务客观、综合全面地选拔出其中排名前50的将领&#xff1f;基于TOPSIS优劣解距离法以及聚类算法&#xff0c;给出大家较为客观的排名。 一.问题描…

使用树莓派搭建个人网站,并发布到外网可访问:实用步骤解析

文章目录 前言使用 Raspberry Pi Imager 安装 Raspberry Pi OS测试 web 站点安装静态样例站点 将web站点发布到公网安装 Cpolarcpolar进行token认证生成cpolar随机域名网址生成cpolar二级子域名将参数保存到cpolar配置文件中测试修改后配置文件配置cpolar服务开机自启动 前言 …

Day_81-87 CNN卷积神经网络

目录 一. CNN卷积神经网络与传统神经网络的不同 1. 模型图 2. 参数分布情况 3. 卷积神经网络和传统神经网络的层次结构 4. 传统神经网络的缺点&#xff1a; 二. CNN的基本操作 1. 卷积 2. 池化 三. CNN实现过程 1. 算法流程图 2. 输入层 3. 卷积层 4. 激活层 5. 池化层 6. 全连…

LINE自动回复:快速回复提升客服效率

2023年&#xff0c;LINE在其4个主要市场&#xff1a;对话、日本、台湾和泰国拥有约1.78亿月活跃用户。 LINE不仅是一个通讯软件&#xff0c;更提供广泛的服务&#xff0c;包括语音和视讯通话、群组、发布社交帖子及商务功能。近年来&#xff0c;越来越多的企业在客户服务中使用…

搭建花店小程序商城的详细步骤

首先&#xff0c;你需要找一个专业成熟的小程序商城制作平台。一个优秀的小程序商城制作平台应该具备丰富的行业模板、简便的设计工具和强大的功能模块。在这里&#xff0c;我们推荐乔拓云平台&#xff0c;其后台管理页面友好&#xff0c;设计搭建页面功能齐全&#xff0c;且针…

画流程图大家都用哪些工具?

流程图工具是一个用于创建图表和图形功能的应用程序。这些流程图工具允许团队成员在绘图软件上合作&#xff0c;制作的流程图可以提供清晰的视觉效果、即时沟通、有效的协调和有效的分析&#xff0c;从而提高设计的工作效率。下面将推荐6个流程图工具&#xff0c;我相信总有一个…

C高级第2天

写一个1.sh脚本&#xff0c;将以下内容放到脚本中&#xff1a; 在家目录下创建目录文件&#xff0c;dir 在dir下创建dir1和dir2 把当前目录下的所有文件拷贝到dir1中&#xff0c; 把当前目录下的所有脚本文件拷贝到dir2中 把dir2打包并压缩为dir2.tar.xz 再把dir2.tar.xz…

Vue的props配置项

简介&#xff1a;Vue中的组件模板是可以复用的&#xff0c;但是模板中的数据是可以改变的。props配置项用于解决组件迁移复用时接受和保存传递给组件的数据的问题。 1.如何给组件传递数据&#xff1f; 答&#xff1a;按照key:value的形式进行传递。 2.如何保存传递给组件的数…

Vulnhub: Masashi: 1靶机

kali&#xff1a;192.168.111.111 靶机&#xff1a;192.168.111.236 信息收集 端口扫描 nmap -A -sC -v -sV -T5 -p- --scripthttp-enum 192.168.111.236查看80端口的robots.txt提示三个文件 snmpwalk.txt内容&#xff0c;tftp服务在1337端口 sshfolder.txt内容&#xff0c…

鞋店小程序商城开发指南

网站制作教程分享&#xff01;新手也能学会的鞋店自建网站教程 在开发鞋店小程序商城时&#xff0c;您可以按照以下步骤进行操作&#xff1a; 1. 浏览器搜索【乔拓云】注册登录账号进入操作后台&#xff0c;找到并点击【商城】中的【去管理】进入商城的后台管理页面。接着&…

ABAP BP维护客户cl_md_bp_maintain=>maintain

ps_head结构如下 下面是封装好的form示例 *&---------------------------------------------------------------------* *& Form frm_modify_customer *&---------------------------------------------------------------------* *& text *&--------…

异步编程 - 07 基于JDK中的Future实现异步编程(下)_当Stream遇见CompletableFuture

文章目录 JDK8 StreamStream遇见CompletableFuture小结 JDK8 Stream JDK8中提供了流式对数据进行处理的功能&#xff0c;它的出现允许我们以声明式方式对数据集合进行处理。所谓声明式是相对于我们平时所用的命令式编程来说的&#xff0c;使用声明式编程会让我们对业务的表达更…

意向客户的信息获取到底是怎样的,快来get一下

客户信息获取技术真的可以为企业提供精准客源吗&#xff1f;这个渠道到底安不安全&#xff0c;技术到底成不成熟&#xff1f;效果到底如何&#xff1f;下面简单的和大家分析一下。 客户信息获取技术是怎样的 手机采集引流方面&#xff0c;上量不精准&#xff0c;精准不上量的说…

《Web安全基础》05. XSS · CSRF · SSRF · RCE

web 1&#xff1a;XSS1.1&#xff1a;简介1.2&#xff1a;防护与绕过1.2.1&#xff1a;HttpOnly1.2.2&#xff1a;WAF 绕过 1.3&#xff1a;相关资源 2&#xff1a;CSRF3&#xff1a;SSRF4&#xff1a;RCE 本系列侧重方法论&#xff0c;各工具只是实现目标的载体。 命令与工具只…