设计模式学习笔记 - 设计模式与范式 -行为型:8.状态模式:游戏、工作流引擎中常用的状态机是如何实现的?

news2024/11/24 17:46:59

概述

本章学习状态模式。在实际的开发中,状态模式并不是很常用,但是在能够用到的场景里,它可以发挥很大的作用。从这一点上看,它有点像我们之前讲到的组合模式。

状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。不过,状态机的实现方式有很多种,除了状态模式,比较常用的还有分支逻辑法和查表法。本章就详细讲讲这几种实现方式,并且对比一下它们的优劣和应用场景。


什么是有限状态机

有限状态机,英文翻译是 Finite State Machine,缩写为 FSM,简称状态机。状态机有三个组成部分:状态(State)、事件(Event)、动作(Action)。

  • 事件也称为转移条件(Transaction Condition)。
  • 事件触发状态的转移及动作的执行。
  • 不过,动作不是必须得,也可能只转移状态,不执行任何动作。

对于刚刚给出的状态机定义,结合一个具体的例子进行解释。

“超级马里奥” 游戏不知道你玩过没有?在游戏中,马里奥可以变身为多种形态,比如小马里奥(Small Mario)、超级马里奥(Super Mario)、火焰马里奥(Fire Mario)、斗篷马里奥(Cape Mario)等等。在不同的游戏情节下,各个形态会互相转化,并相应地增减积分。比如,初始状态是小马里奥,吃了蘑菇后就变成超级马里奥,并且增加 100 积分。

实际上,马里奥形态的转变就是一个状态机。其中,马里奥的不同形态就是状态机种的 “状态”,游戏情节(比如吃了蘑菇)就是状态机种的 “事件”,加减积分就是状态机种的 “动作”。比如,吃蘑菇这个事件,会触发状态的转移(从小马里奥转移到超级马里奥),以及触发动作的执行(增加 100 积分)。

为方便讲解,我对游戏背景做了简化,只保留了部分状态和事件,简化之后的状态转移如下所示:

在这里插入图片描述
如何编程来实现上面的状态机呢?

我写了个骨架代码,如下所示。其中,obtainMushRoom()obtainCape()obtainFileFlower()meetMonster() 这个几个函数,能够根据当前的状态和事件,更新状态和增减积分。不过,具体的代码实现暂时没给出。你可以先试着自己补全一下。

public enum State {
    SMALL(0),
    SUPER(1),
    FIRE(2),
    CAPE(3),
    ;

    private int value;

    State(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

public class MarioStateMachine {
    private int score;
    private State currentState;

    public MarioStateMachine() {
        this.score = 0;
        this.currentState = State.SMALL;
    }

    public void obtainMushRoom() {
        //TODO
    }

    public void obtainCape() {
        //TODO
    }

    public void obtainFireFlower() {
        //TODO
    }

    public void meetMonster() {
        //TODO
    }

    public int getScore() {
        return score;
    }

    public State getState() {
        return currentState;
    }
}

public class ApplicationDemo {
    public static void main(String[] args) {
        MarioStateMachine mario = new MarioStateMachine();
        mario.obtainMushRoom();
        int score = mario.getScore();
        State state = mario.getState();
        System.out.println("mario score: " + score + "; state: " + state);
    }
}

状态机实现方式一:分支逻辑法

对于如何实现状态机,有三种方式。其中,最简单直接的实现方式是,参照状态转移图,将每一个状态转移,原模原样地直译成代码。这样编写的代码会包含大量的 if-esle 或 switch-else 分支判断逻辑,甚至是嵌套的分支判断逻辑,所以,我把这种方法暂且命名为分支逻辑法

按照这个思路,将上面的骨架代码补全一下。补全之后的代码如下所示:

public class MarioStateMachine {
    private int score;
    private State currentState;

    public MarioStateMachine() {
        this.score = 0;
        this.currentState = State.SMALL;
    }

    public void obtainMushRoom() {
        if (currentState == State.SMALL) {
            this.currentState = State.SUPER;
            this.score += 100;
        }
    }

    public void obtainCape() {
        if (currentState == State.SMALL || currentState == State.SUPER) {
            this.currentState = State.CAPE;
            this.score += 200;
        }
    }

    public void obtainFireFlower() {
        if (currentState == State.SMALL || currentState == State.SUPER) {
            this.currentState = State.FIRE;
            this.score += 300;
        }
    }

    public void meetMonster() {
        if (currentState == State.SUPER) {
            this.currentState = State.SMALL;
            this.score -= 100;
        }
        if (currentState == State.CAPE) {
            this.currentState = State.SMALL;
            this.score -= 200;
        }
        if (currentState == State.FIRE) {
            this.currentState = State.SMALL;
            this.score -= 300;
        }
    }

    public int getScore() {
        return score;
    }

    public State getState() {
        return currentState;
    }
}

对于简单的状态机来说,分支逻辑这种实现方式是可以接收的。但是,对于复杂的状态机来说,这种实现方式及其容易漏写或错写某个状态转移。此外,代码中充斥着大量的 if-else 或者 switch-case 分支判断逻辑,可读性和可维护性都很差。如果哪些修改了状态机种的某个转移,我们要在冗长的分支逻辑中找到对应地代码进行修改,很容易改错,引入 BUG。

状态机实现方式二:查表法

实际上,上面的实现方式有点类似 hard code,对于复杂的状态机来说不适用,而状态机的第二种实现方式查表法,就更加合适了。接下来,看下如何利用查表法来补全骨架代码。

实际上,除了用状态转移图来表示之外,状态机还可以用二维表来表示,如下所示。这个二维表中,第一维表示当前的状态,第二维表示当前状态经过事件之后,转移到的新状态及其执行的动作。

E1(Got MushRoom)E2(Got Cape)E3(Got Fire Flower)E4(Meet Monster)
SmallSuper/+100Cape/+200Fire/+300/
Super/Cape/+200Fire/+300Small/-100
Cape///Small/-200
Fire///Small/-300

注:表中的斜杠表示不存在这种状态转移

相对于分支逻辑的实现方式,查表法的代码实现更加清晰,可读性和可维护性更好。当修改状态机时,我们只需要修改 transactionTableactionTable 两个二维数组即可。实际上,如果我们把二维数组存储在配置文件中,当需要修改状态机时,甚至可以不修改代码,只需要修改配置文件就可以了。具体代码如下所示:

public enum Event {
    GOT_MUSHROOM(0),
    GOT_CAPE(1),
    GOT_FIRE(2),
    MEET_MONSTER(3),
    ;

    private int value;

    Event(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

public class MarioStateMachine {
    private int score;
    private State currentState;

    private static final State[][] transitionTable = {
            {SUPER, CAPE, FIRE, SMALL},
            {SUPER, CAPE, FIRE, SMALL},
            {CAPE, CAPE, CAPE, SMALL},
            {FIRE, FIRE, FIRE, SMALL}
    };

    private static final int[][] actionTable = {
            {100, 200, 300, 0},
            {0, 200, 300, -100},
            {0, 0, 0, -200},
            {0, 0, 0, -300},
    };

    public MarioStateMachine() {
        this.score = 0;
        this.currentState = State.SMALL;
    }

    public void obtainMushRoom() {
        executeEvent(Event.GOT_MUSHROOM);
    }

    public void obtainCape() {
        executeEvent(Event.GOT_CAPE);
    }

    public void obtainFireFlower() {
        executeEvent(Event.GOT_FIRE);
    }

    public void meetMonster() {
        executeEvent(Event.MEET_MONSTER);
    }

    private void executeEvent(Event event) {
        int stateValue = currentState.getValue();
        int eventValue = event.getValue();
        this.currentState = transitionTable[stateValue][eventValue];
        this.score += actionTable[stateValue][eventValue];
    }

    public int getScore() {
        return score;
    }

    public State getState() {
        return currentState;
    }
}

状态机实现方式三:状态模式

在查表法的代码实现中,事件触发的动作只能是简单的积分加减,所以,我们用一个 int 类型的二维数组 actionTable 就能表示,二维数组中的值表示积分的加减值。但是,如果要执行的动作并非这么简单,而是一系列复杂的逻辑操作(比如加减积分、写数据库,还有可能发送消息通知等等),我们就没法用如此简单的二维数组来表示了。也就是说,查表法的实现方式有一定的局限性。

虽然分支逻辑的实现方式不存在这个问题,但它又存在前面讲到的其他问题,比如分支判断逻辑较多,导致代码的可读性和可维护性不好等。实际上,对于分支逻辑法存在的问题,可以使用状态模式来解决。

状态模式通过将事件触发的状态转移和动作执行,拆分到不同的类中,来避免分支判断逻辑。我们还是结合代码来理解这句话。

其中,IMario 是状态的接口,定义了所有事件。SmallMarioSuperMarioCapeMarioFireMarioIMario 接口的实现类,分别对应状态机种的 4 个状态。原来所有的状态转移和动作执行的代码逻辑,都集中在 MarioStateMachine 中,现在,这些代码被拆分到了这 4 个状态类中。

public interface IMario {
    State getName();
    // 以下是定义的事件
    void obtainMushRoom();
    void obtainCape();
    void obtainFireFlower();
    void meetMonster();
}

public class SmallMario implements IMario {
    private MarioStateMachine stateMachine;

    public SmallMario(MarioStateMachine stateMachine) {
        this.stateMachine = stateMachine;
    }

    @Override
    public State getName() {
        return State.SMALL;
    }

    @Override
    public void obtainMushRoom() {
        stateMachine.setCurrentState(new SuperMario(stateMachine));
        stateMachine.setScore(stateMachine.getScore() + 100);
    }

    @Override
    public void obtainCape() {
        stateMachine.setCurrentState(new CapeMario(stateMachine));
        stateMachine.setScore(stateMachine.getScore() + 200);
    }

    @Override
    public void obtainFireFlower() {
        stateMachine.setCurrentState(new FireMario(stateMachine));
        stateMachine.setScore(stateMachine.getScore() + 300);
    }

    @Override
    public void meetMonster() {
        // do nothing...
    }
}

public class SuperMario implements IMario {
    private MarioStateMachine stateMachine;

    public SuperMario(MarioStateMachine stateMachine) {
        this.stateMachine = stateMachine;
    }

    @Override
    public State getName() {
        return State.SUPER;
    }


    @Override
    public void obtainMushRoom() {
        // do nothing...
    }

    @Override
    public void obtainCape() {
        stateMachine.setCurrentState(new CapeMario(stateMachine));
        stateMachine.setScore(stateMachine.getScore() + 200);
    }

    @Override
    public void obtainFireFlower() {
        stateMachine.setCurrentState(new FireMario(stateMachine));
        stateMachine.setScore(stateMachine.getScore() + 300);
    }

    @Override
    public void meetMonster() {
        stateMachine.setCurrentState(new SmallMario(stateMachine));
        stateMachine.setScore(stateMachine.getScore() - 100);
    }
}

public class CapeMario implements IMario {
    private MarioStateMachine stateMachine;

    public CapeMario(MarioStateMachine stateMachine) {
        this.stateMachine = stateMachine;
    }

    @Override
    public State getName() {
        return State.CAPE;
    }

    @Override
    public void obtainMushRoom() {
        // do nothing...
    }

    @Override
    public void obtainCape() {
        // do nothing...
    }

    @Override
    public void obtainFireFlower() {
        stateMachine.setCurrentState(new FireMario(stateMachine));
        stateMachine.setScore(stateMachine.getScore() + 300);
    }

    @Override
    public void meetMonster() {
        stateMachine.setCurrentState(new SmallMario(stateMachine));
        stateMachine.setScore(stateMachine.getScore() - 200);
    }
}

public class FireMario implements IMario {
    private MarioStateMachine stateMachine;

    public FireMario(MarioStateMachine stateMachine) {
        this.stateMachine = stateMachine;
    }

    @Override
    public State getName() {
        return State.FIRE;
    }


    @Override
    public void obtainMushRoom() {
        // do nothing...
    }

    @Override
    public void obtainCape() {
        // do nothing...
    }

    @Override
    public void obtainFireFlower() {
        // do nothing...
    }

    @Override
    public void meetMonster() {
        stateMachine.setCurrentState(new SmallMario(stateMachine));
        stateMachine.setScore(stateMachine.getScore() - 300);
    }
}

public class MarioStateMachine {
    private int score;
    private IMario currentState; // 不在使用枚举表示状态

    public MarioStateMachine() {
        this.score = 0;
        this.currentState = new SmallMario(this);
    }

    public void obtainMushRoom() {
        currentState.obtainMushRoom();
    }

    public void obtainCape() {
        currentState.obtainCape();
    }

    public void obtainFireFlower() {
        currentState.obtainFireFlower();
    }

    public void meetMonster() {
        currentState.meetMonster();
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public void setCurrentState(IMario currentState) {
        this.currentState = currentState;
    }

    public IMario getState() {
        return currentState;
    }
}

上面的代码实现不难看懂,只需要注意一点,即 MarioStateMachine 和各个状态类之间是双向依赖关系。 MarioStateMachine 依赖各个类是理所当然的,但是反过来,各个状态类为什么要依赖 MarioStateMachine 呢? 这是因为,各个状态类需要更新 MarioStateMachine 中的属性, scorecurrentState

实际上,上面的代码还可以继续优化,可以将状态类设置成单例,比较状态类中不包含任何成员变量。但是,当状态类设计成单例之后,就无法通过构造函数来传递 MarioStateMachine 了,而状态类又要依赖 MarioStateMachine ,那该如何解决呢?

实际上,在《创建型:2.单例模式(中):为什么不推荐使用单例模式?又有何替代方案?》中,提到过集中解决方法,你可以回过头去查看下。在这里,可以通过函数参数将 MarioStateMachine 传递进状态类。根据这个设计思路,对上面的代码进行重构。重构之后的代码如下所示:

public interface IMario {
    State getName();
    // 以下是定义的事件
    void obtainMushRoom(MarioStateMachine stateMachine);
    void obtainCape(MarioStateMachine stateMachine);
    void obtainFireFlower(MarioStateMachine stateMachine);
    void meetMonster(MarioStateMachine stateMachine);
}

public class SmallMario implements IMario {
    private static final SmallMario instance = new SmallMario();
    private SmallMario() {
    }
    public static SmallMario getInstance() {
        return instance;
    }

    @Override
    public State getName() {
        return State.SMALL;
    }

    @Override
    public void obtainMushRoom(MarioStateMachine stateMachine) {
        stateMachine.setCurrentState(SuperMario.getInstance());
        stateMachine.setScore(stateMachine.getScore() + 100);
    }

    @Override
    public void obtainCape(MarioStateMachine stateMachine) {
        stateMachine.setCurrentState(CapeMario.getInstance());
        stateMachine.setScore(stateMachine.getScore() + 200);
    }

    @Override
    public void obtainFireFlower(MarioStateMachine stateMachine) {
        stateMachine.setCurrentState(FireMario.getInstance());
        stateMachine.setScore(stateMachine.getScore() + 300);
    }

    @Override
    public void meetMonster(MarioStateMachine stateMachine) {
        // do nothing...
    }
}

public class SuperMario implements IMario {
    private static final SuperMario instance = new SuperMario();
    private SuperMario() {
    }
    public static SuperMario getInstance() {
        return instance;
    }

    @Override
    public State getName() {
        return State.SUPER;
    }


    @Override
    public void obtainMushRoom(MarioStateMachine stateMachine) {
        // do nothing...
    }

    @Override
    public void obtainCape(MarioStateMachine stateMachine) {
        stateMachine.setCurrentState(CapeMario.getInstance());
        stateMachine.setScore(stateMachine.getScore() + 200);
    }

    @Override
    public void obtainFireFlower(MarioStateMachine stateMachine) {
        stateMachine.setCurrentState(FireMario.getInstance());
        stateMachine.setScore(stateMachine.getScore() + 300);
    }

    @Override
    public void meetMonster(MarioStateMachine stateMachine) {
        stateMachine.setCurrentState(SmallMario.getInstance());
        stateMachine.setScore(stateMachine.getScore() - 100);
    }
}

public class CapeMario implements IMario {
    private static final CapeMario instance = new CapeMario();
    private CapeMario() {
    }
    public static CapeMario getInstance() {
        return instance;
    }

    @Override
    public State getName() {
        return State.CAPE;
    }

    @Override
    public void obtainMushRoom(MarioStateMachine stateMachine) {
        // do nothing...
    }

    @Override
    public void obtainCape(MarioStateMachine stateMachine) {
        // do nothing...
    }

    @Override
    public void obtainFireFlower(MarioStateMachine stateMachine) {
        stateMachine.setCurrentState(FireMario.getInstance());
        stateMachine.setScore(stateMachine.getScore() + 300);
    }

    @Override
    public void meetMonster(MarioStateMachine stateMachine) {
        stateMachine.setCurrentState(SmallMario.getInstance());
        stateMachine.setScore(stateMachine.getScore() - 200);
    }
}

public class FireMario implements IMario {
    private static final FireMario instance = new FireMario();
    private FireMario() {
    }
    public static FireMario getInstance() {
        return instance;
    }

    @Override
    public State getName() {
        return State.FIRE;
    }


    @Override
    public void obtainMushRoom(MarioStateMachine stateMachine) {
        // do nothing...
    }

    @Override
    public void obtainCape(MarioStateMachine stateMachine) {
        // do nothing...
    }

    @Override
    public void obtainFireFlower(MarioStateMachine stateMachine) {
        // do nothing...
    }

    @Override
    public void meetMonster(MarioStateMachine stateMachine) {
        stateMachine.setCurrentState(SmallMario.getInstance());
        stateMachine.setScore(stateMachine.getScore() - 300);
    }
}

public class MarioStateMachine {
    private int score;
    private IMario currentState; // 不在使用枚举表示状态

    public MarioStateMachine() {
        this.score = 0;
        this.currentState = SmallMario.getInstance();
    }

    public void obtainMushRoom() {
        currentState.obtainMushRoom(this);
    }

    public void obtainCape() {
        currentState.obtainCape(this);
    }

    public void obtainFireFlower() {
        currentState.obtainFireFlower(this);
    }

    public void meetMonster() {
        currentState.meetMonster(this);
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public void setCurrentState(IMario currentState) {
        this.currentState = currentState;
    }

    public IMario getState() {
        return currentState;
    }
}

实际上,像游戏这种比较复杂的状态机,包含的状态比较多,优先推荐使用查表法,而状态模式会引入非常多的状态类,会导致代码比较难维护。

相反,像电商下单、外卖下单这种类型的状态机,它们的状态并不多,状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能会比较复杂,所以更加推荐使用状态模式来实现。

总结

本章讲解了状态模式。虽然网上有各种各样的状态模式,但是你只要记住状态模式是状态机的一种实现方式即可

状态机又叫有限状态机,它由3部分组成:状态、事件、动作。

  • 其中事件也称为转移条件。
  • 事件触发状态的转移及动作的执行。
  • 不过动作不是必须的,也可能只转移状态,不执行任何动作。

针对状态机,本章总结了三种实现方式。

  • 第一种实现方式叫分支逻辑法。利用 if-else 或 switch-case 分支逻辑,参照状态转移图,将每个状态转移原模原样的直译成代码。对于简单的状态机来说,这种实现方式最简单、最直接,是首选。
  • 第二种实现方式叫查表法。对于状态很多、状态转移比较复杂的状态机来说,查表法比较合适。通过二维素组来表示状态转移图,能极大地提高代码的可读性和可维护性。
  • 第三张实现方式叫状态模式。对于状态不多、状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机来说,首选这种实现方式。

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

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

相关文章

✌2024/4/3—力扣—整数反转

代码实现: int reverse(int x) {long num 0;while (x ! 0) {num num * 10 x % 10;x x / 10;}if ((int)num ! num) {return 0;}return (int)num; }

使用手动连接,将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中,在自定义的槽函数中调用关闭函数

使用手动连接,将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中,在自定义的槽函数中调用关闭函数 将登录按钮使用qt4版本的连接到自定义的槽函数中,在槽函数中判断ui界面上输入的账号是否为"admin",密码是否为…

Houdini笔记操作技巧_集锦

个人记录下,谨防遗忘。同时丰富下Hou的中文搜素环境。 1、自定义启动界面 ① 设置完界面后,保存自定义界面(Save Current Desktop As...) ② Edit-->Preferences-->General UIInterface-->Startup in Desktop&#xff1…

qt打包程序打包之跨平台

本文讲的是linux系统中的程序打包,首先我们创建一个简单的程序,我的程序叫做debtest 然后设置qmake来源,这个直接决定了程序依赖的qt库的位置,如果我们需要指定qt标准库的位置,那么qt环境就需要在那个位置。 修改前&am…

C# 如何修改项目名称

目录 背景具体步骤1、Visual Studio中修改项目名和程序集名称以及命名空间2、修改项目文件夹名3、修改解决方案里项目的路径4、再次打开解决方案,问题解决步骤总结 名词解释解决方案(Solution)项目(Project)程序集&…

【操作系统】CentOS7入门级安装

下载镜像 CentOS镜像下载Download (centos.org) 我们选择第一个 X86_64 CentOS Mirrors List 版本描述X86_X64带64位的32位扩展版(一般安装这个)ARM64 (aarch64)嵌入式。适用于微端(树莓派、机械臂、机械中控)IBM Power (ppc64le)专用于IBM POWER服务器 选择一个合适的链接 …

DSL - Wire 实现-ApiHug101

🤗 ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱,有温度,有质量,有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace docs/ha…

javaScript手写专题——防抖/节流/闭包/Promise/深浅拷贝

目录 目录 一、 防抖/节流/闭包/定时器 编写一个组件,在input中输入文本,在给定的数据中查找相关的项目,并渲染搜索结果列表 1.新增InputSearch.vue组件 key的作用 2.新增 InputView.vue 3.添加路由 4.效果演示 follow up加上防抖怎么处理 1.…

如何从头开始编写LoRA代码,这有一份教程

ChatGPT狂飙160天,世界已经不是之前的样子。 新建了免费的人工智能中文站https://ai.weoknow.com 新建了收费的人工智能中文站https://ai.hzytsoft.cn/ 更多资源欢迎关注 作者表示:在各种有效的 LLM 微调方法中,LoRA 仍然是他的首选。 LoRA&…

机器学习-随机森林算法预测温度

文章目录 算法简介解决问题获取数据集探索性数据分析查看数据集字段信息查看数据集综合统计结果查看特征值随时间变化趋势 数据预处理处理缺失数据字符列编码数据集分割训练集、验证集、测试集数据集分割 构建模型并训练结果分析与评估进一步优化实际使用经验总结 算法简介 随…

基于遗传优化的SVD水印嵌入提取算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于遗传优化的的SVD水印嵌入提取算法。对比遗传优化前后SVD水印提取性能,并分析不同干扰情况下水印提取效果。 2.测试软件版本以及运行结果展示 MA…

深度学习实践(一)基于Transformer英译汉模型

本文目录 前述一、环境依赖二、数据准备1. 数据加载2. 构建单词表程序解析(1)将列表里每个子列表的所有单词合并到一个新列表(没有子列表)中。(2)Counter()-- 统计迭代对象各元素出现…

【Spring AOP】@Aspect结合案例详解(一): @Pointcut使用@annotation + 五种通知Advice注解(已附源码)

文章目录 前言AOP与Spring AOPAspect简单案例快速入门 一、Pointcutannotation 二、五种通知Advice1. Before前置通知2. After后置通知3. AfterRunning返回通知4. AfterThrowing异常通知5. Around环绕通知 总结 前言 在微服务流行的当下,在使用SpringCloud/Springb…

Mogdb双网卡同步最佳实践

大家都知道Oracle数据库无论是单机还是RAC集群在进行生产部署实施时,我们都会对网卡做冗余考虑,比如使用双网卡,比如public、心跳网络。这样的目的主要是为了安全,避免淡点故障。当然也网卡Bond不仅是可以做主备还可以支持负载均衡…

redis分布式锁+redisson框架

目录 🧂1.锁的类型 🌭2.基于redis实现分布式 🥓3. 基于redisson实现分布式锁 1.锁的类型 1.本地锁:synchronize、lock等,锁在当前进程内,集群部署下依旧存在问题2.分布式锁:redis、zookeeper等…

OLAP介绍

OLAP OLAP介绍 Rollup OLAP(在线分析处理)的上下文中,"Rollup"是一个重要的概念,它指的是在多维数据集中自动地聚合数据到更高的层次或维度的过程。这种操作通常用于快速计算和展示汇总数据,以便于用户进…

包和final.Java

1,包 包就是文件夹。用来管理不同功能的Java类,方便后期代码的维护。 (1)包名的规则是什么? 公司域名反写报的作用,需要全部英文小写,见名知意。com.itheima.domain (2&#xff…

15.队列集

1.简介 在使用队列进行任务之间的“沟通交流”时,一个队列只允许任务间传递的消息为同一种数据类型,如果需要在任务间传递不同数据类型的消息时,那么就可以使用队列集。FreeRTOS提供的队列集功能可以对多个队列进行“监听”,只要…

Redis高级-分布式缓存

分布式缓存 – 基于Redis集群解决单机Redis存在的问题 单机的Redis存在四大问题: 0.目标 1.Redis持久化 Redis有两种持久化方案: RDB持久化AOF持久化 1.1.RDB持久化 RDB全称Redis Database Backup file(Redis数据备份文件)…

QT drawPixmap和drawImage处理图片模糊问题

drawPixmap和drawImage显示图片时,如果图片存在缩放时,会出现模糊现象,例如将一个100x100 的图片显示到30x30的区域,这个时候就会出现模糊。如下: 实际图片: 这个问题就是大图显示成小图造成的像素失真。 当…