【十六】设计模式~~~行为型模式~~~策略模式(Java)

news2024/11/23 23:14:34

5.1. 模式动机

  • 完成一项任务,往往可以有多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务。
  • 在软件开发中也常常遇到类似的情况,实现某一个功能有多个途径,此时可以使用一种设计模式来使得系统可以灵活地选择解决途径,也能够方便地增加新的解决途径。
  • 在软件系统中,有许多算法可以实现某一功能,如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。
  • 除了提供专门的查找算法类之外,还可以在客户端程序中直接包含算法代码,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大量可供选择的算法时问题将变得更加严重。
  • 为了解决这些问题,可以定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法,在这里,每一个封装算法的类我们都可以称之为策略(Strategy),为了保证这些策略的一致性,一般会用一个抽象的策略类来做算法的定义,而具体每种算法则对应于一个具体策略类。

       俗话说:条条大路通罗马。在很多情况下,实现某个目标的途径不止一条,例如我们在外出旅游时可以选择多种不同的出行方式,如骑自行车、坐汽车、坐火车或者坐飞机,可根据实际情况(目的地、旅游预算、旅游时间等)来选择一种最适合的出行方式。在制订旅行计划时,如果目的地较远、时间不多,但不差钱,可以选择坐飞机去旅游;如果目的地虽远、但假期长、且需控制旅游成本时可以选择坐火车或汽车;如果从健康和环保的角度考虑,而且有足够的毅力,自行车游或者徒步旅游也是个不错的选择,。
       在软件开发中,我们也常常会遇到类似的情况,实现某一个功能有多条途径,每一条途径对应一种算法,此时我们可以使用一种设计模式来实现灵活地选择解决途径,也能够方便地增加新的解决途径。本章我们将介绍一种为了适应算法灵活性而产生的设计模式——策略模式。

案例(电影票打折方案)

       Sunny软件公司为某电影院开发了一套影院售票系统,在该系统中需要为不同类型的用户提供不同的电影票打折方式,具体打折方案如下:

  • (1) 学生凭学生证可享受票价8折优惠;
  • (2) 年龄在10周岁及以下的儿童可享受每张票减免10元的优惠(原始票价需大于等于20元);
  • (3) 影院VIP用户除享受票价半价优惠外还可进行积分,积分累计到一定额度可换取电影院赠送的奖品。
    该系统在将来可能还要根据需要引入新的打折方式。

为了实现上述电影票打折功能,Sunny软件公司开发人员设计了一个电影票类MovieTicket,其核心代码片段如下所示:

//电影票类
class MovieTicket {
	private double price; //电影票价格
	private String type; //电影票类型
	
	public void setPrice(double price) {
		this.price = price;
	}
	
	public void setType(String type) {
		this.type = type;
	}
	
	public double getPrice() {
		return this.calculate();
	}
	
         //计算打折之后的票价
	public double calculate() {
                  //学生票折后票价计算
		if(this.type.equalsIgnoreCase("student")) {
			System.out.println("学生票:");
		    return this.price * 0.8;
		}
                  //儿童票折后票价计算
		else if(this.type.equalsIgnoreCase("children") && this.price >= 20 ) {
			System.out.println("儿童票:");
		    return this.price - 10;
		}
                  //VIP票折后票价计算
		else if(this.type.equalsIgnoreCase("vip")) {
			System.out.println("VIP票:");
		    System.out.println("增加积分!");
			return this.price * 0.5;
		}
		else {
			return this.price; //如果不满足任何打折要求,则返回原始票价
		}
	}
}

编写如下客户端测试代码:

class Client {
	public static void main(String args[]) {
		MovieTicket mt = new MovieTicket();
		double originalPrice = 60.0; //原始票价
		double currentPrice; //折后价
		
		mt.setPrice(originalPrice);
		System.out.println("原始价为:" + originalPrice);
		System.out.println("---------------------------------");
			
		mt.setType("student"); //学生票
		currentPrice = mt.getPrice();
		System.out.println("折后价为:" + currentPrice);
		System.out.println("---------------------------------");
		
		mt.setType("children"); //儿童票
		currentPrice = mt.getPrice();
		System.out.println("折后价为:" + currentPrice);
	}
}

编译并运行程序,输出结果如下所示:
在这里插入图片描述

       通过MovieTicket类实现了电影票的折后价计算,该方案解决了电影票打折问题,每一种打折方式都可以称为一种打折算法,更换打折方式只需修改客户端代码中的参数,无须修改已有源代码,但该方案并不是一个完美的解决方案,它至少存在如下三个问题:

  • (1) MovieTicket类的calculate()方法非常庞大,它包含各种打折算法的实现代码,在代码中出现了较长的if…else…语句,不利于测试和维护。
  • (2) 增加新的打折算法或者对原有打折算法进行修改时必须修改MovieTicket类的源代码,违反了“开闭原则”,系统的灵活性和可扩展性较差。
  • (3) 算法的复用性差,如果在另一个系统(如商场销售管理系统)中需要重用某些打折算法,只能通过对源代码进行复制粘贴来重用,无法单独重用其中的某个或某些算法(重用较为麻烦)。

       如何解决这三个问题?导致产生这些问题的主要原因在于MovieTicket类职责过重,它将各种打折算法都定义在一个类中,这既不便于算法的重用,也不便于算法的扩展。因此我们需要对MovieTicket类进行重构,将原本庞大的MovieTicket类的职责进行分解,将算法的定义和使用分离,这就是策略模式所要解决的问题,

5.2. 模式定义

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

5.3. 模式结构

策略模式包含如下角色:

  • Context: 环境类,环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。
  • Strategy: 抽象策略类,它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
  • ConcreteStrategy: 具体策略类,它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。

在这里插入图片描述

       策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列具体策略类里面,作为抽象策略类的子类。在策略模式中,对环境类和抽象策略类的理解非常重要,环境类是需要使用算法的类。在一个系统中可以存在多个环境类,它们可能需要重用一些相同的算法。

       在使用策略模式时,我们需要将算法从Context类中提取出来,首先应该创建一个抽象策略类,其典型代码如下所示:

abstract class AbstractStrategy {
    public abstract void algorithm(); //声明抽象算法
}

然后再将封装每一种具体算法的类作为该抽象策略类的子类,如下代码所示:

class ConcreteStrategyA extends AbstractStrategy {
    //算法的具体实现
    public void algorithm() {
       //算法A
    }
}

       其他具体策略类与之类似,对于Context类而言,在它与抽象策略类之间建立一个关联关系,其典型代码如下所示:

class Context {
private AbstractStrategy strategy; //维持一个对抽象策略类的引用
 
    public void setStrategy(AbstractStrategy strategy) {
        this.strategy= strategy;
    }
 
    //调用策略类中的算法
    public void algorithm() {
        strategy.algorithm();
    }
}

       在Context类中定义一个AbstractStrategy类型的对象strategy,通过注入的方式在客户端传入一个具体策略对象,客户端代码片段如下所示:

……
Context context = new Context();
AbstractStrategy strategy;
strategy = new ConcreteStrategyA(); //可在运行时指定类型
context.setStrategy(strategy);
context.algorithm();
……

       在客户端代码中只需注入一个具体策略对象,可以将具体策略类类名存储在配置文件中,通过反射来动态创建具体策略对象,从而使得用户可以灵活地更换具体策略类,增加新的具体策略类也很方便。策略模式提供了一种可插入式(Pluggable)算法的实现方案。

5.4. 时序图

在这里插入图片描述

5.5. 代码分析

       为了实现打折算法的复用,并能够灵活地向系统中增加新的打折方式,Sunny软件公司开发人员使用策略模式对电影院打折方案进行重构,重构后基本结构如图24-2所示:
在这里插入图片描述

5.5.1 生产

       在图24-2中,MovieTicket充当环境类角色,Discount充当抽象策略角色,StudentDiscount、 ChildrenDiscount 和VIPDiscount充当具体策略角色。完整代码如下所示:

package com.zyz.demo;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/5/28 22:39
 * @Description:
 */

//电影票类:环境类

class MovieTicket {
    private double price;
    private Discount discount; //维持一个对抽象折扣类的引用

    public void setPrice(double price) {
        this.price = price;
    }

    /**
     * 注入一个折扣类对象
     * @param discount
     */
    public void setDiscount(Discount discount) {
        this.discount = discount;
    }

    public double getPrice() {
        //调用折扣类的折扣价计算方法
        return discount.calculate(this.price);
    }
}


//折扣类:抽象策略类

interface Discount {
    public double calculate(double price);
}


//学生票折扣类:具体策略类

class StudentDiscount implements Discount {
    @Override
    public double calculate(double price) {
        System.out.println("学生票:");
        return price * 0.8;
    }
}

//儿童票折扣类:具体策略类

class ChildrenDiscount implements Discount {
    @Override
    public double calculate(double price) {
        System.out.println("儿童票:");
        return price - 10;
    }
}

//VIP会员票折扣类:具体策略类

class VIPDiscount implements Discount {
    @Override
    public double calculate(double price) {
        System.out.println("VIP票:");
        System.out.println("增加积分!");
        return price * 0.5;
    }
}

5.5.2 客户端

package com.zyz.demo;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/5/28 22:43
 * @Description: 客户端
 */
class Client {
    public static void main(String args[]) {
        MovieTicket mt = new MovieTicket();
        double originalPrice = 60.0;
        double currentPrice;

        mt.setPrice(originalPrice);
        System.out.println("原始价为:" + originalPrice);
        System.out.println("---------------------------------");

        Discount discount;
        discount = new StudentDiscount();
        mt.setDiscount(discount); //注入折扣对象

        currentPrice = mt.getPrice();
        System.out.println("折后价为:" + currentPrice);
    }
}

5.5.3 测试结果

在这里插入图片描述

5.6. 模式分析

  • 策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
  • 在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。
  • 策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。

5.7. 实例

5.8. 优点

策略模式的优点

  • 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
  • 策略模式提供了管理相关的算法族的办法。
  • 策略模式提供了可以替换继承关系的办法。
  • 使用策略模式可以避免使用多重条件转移语句。

5.9. 缺点

策略模式的缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

5.10. 适用环境

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

  • 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
  • 一个系统需要动态地在几种算法中选择一种。
  • 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
  • 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。

5.11. 模式应用

5.12. 模式扩展

策略模式与状态模式

  • 可以通过环境类状态的个数来决定是使用策略模式还是状态模式。
  • 策略模式的环境类自己选择一个具体策略类,具体策略类无须关心环境类;而状态模式的环境类由于外在因素需要放进一个具体状态中,以便通过其方法实现状态的切换,因此环境类和状态类之间存在一种双向的关联关系。
  • 使用策略模式时,客户端需要知道所选的具体策略是哪一个,而使用状态模式时,客户端无须关心具体状态,环境类的状态会根据用户的操作自动转换。
  • 如果系统中某个类的对象存在多种状态,不同状态下行为有差异,而且这些状态之间可以发生转换时使用状态模式;如果系统中某个类的某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略模式。

5.13. 总结

  • 在策略模式中定义了一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式。策略模式是一种对象行为型模式。
  • 策略模式包含三个角色:环境类在解决某个问题时可以采用多种策略,在环境类中维护一个对抽象策略类的引用实例;抽象策略类为所支持的算法声明了抽象方法,是所有策略类的父类;具体策略类实现了在抽象策略类中定义的算法。
  • 策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。
    ● 策略模式主要优点在于对“开闭原则”的完美支持,在不修改原有系统的基础上可以更换算法或者增加新的算法,它很好地管理算法族,提高了代码的复用性,是一种替换继承,避免多重条件转移语句的实现方式;其缺点在于客户端必须知道所有的策略类,并理解其区别,同时在一定程度上增加了系统中类的个数,可能会存在很多策略类。
    ● 策略模式适用情况包括:在一个系统里面有许多类,它们之间的区别仅在于它们的行为,使用策略模式可以动态地让一个对象在许多行为中选择一种行为;一个系统需要动态地在几种算法中选择一种;避免使用难以维护的多重条件选择语句;希望在具体策略类中封装算法和与相关的数据结构。

5.14.策略模式的两个典型应用

       策略模式实用性强、扩展性好,在软件开发中得以广泛使用,是使用频率较高的设计模式之一。下面将介绍策略模式的两个典型应用实例,一个来源于Java SE,一个来源于微软公司推出的演示项目PetShop。

       (1) Java SE的容器布局管理就是策略模式的一个经典应用实例,其基本结构示意图如图24-3所示:
在这里插入图片描述

       在Java SE开发中,用户需要对容器对象Container中的成员对象如按钮、文本框等GUI控件进行布局(Layout),在程序运行期间由客户端动态决定一个Container对象如何布局,Java语言在JDK中提供了几种不同的布局方式,封装在不同的类中,如BorderLayout、FlowLayout、GridLayout、GridBagLayout和CardLayout等。在图24-3中,Container类充当环境角色Context,而LayoutManager作为所有布局类的公共父类扮演了抽象策略角色,它给出所有具体布局类所需的接口,而具体策略类是LayoutManager的子类,也就是各种具体的布局类,它们封装了不同的布局方式。
        任何人都可以设计并实现自己的布局类,只需要将自己设计的布局类作为LayoutManager的子类就可以,比如传奇的Borland公司,曾在JBuilder中提供了一种新的布局方式——XYLayout,作为对JDK提供的Layout类的补充。对于客户端而言,只需要使用Container类提供的setLayout()方法就可设置任何具体布局方式,无须关心该布局的具体实现。在JDK中,Container类的代码片段如下:

public class Container extends Component {
    ……
    LayoutManager layoutMgr;
    ……
    public void setLayout(LayoutManager mgr) {
	layoutMgr = mgr;
	……
    }
    ……
}

       从上述代码可以看出,Container作为环境类,针对抽象策略类LayoutManager进行编程,用户在使用时,根据“里氏代换原则”,只需要在setLayout()方法中传入一个具体布局对象即可,无须关心它的具体实现。

       (2) 除了基于Java语言的应用外,在使用其他面向对象技术开发的软件中,策略模式也得到了广泛的应用。
       在微软公司提供的演示项目PetShop 4.0中就使用策略模式来处理同步订单和异步订单的问题。在PetShop 4.0的BLL(Business Logic Layer,业务逻辑层)子项目中有一个OrderAsynchronous类和一个OrderSynchronous类,它们都继承自IOrderStrategy接口,如图24-4所示:
在这里插入图片描述

       在图24-4中,OrderSynchronous以一种同步的方式处理订单,而OrderAsynchronous先将订单存放在一个队列中,然后再对队列里的订单进行处理,以一种异步方式对订单进行处理。BLL的Order类通过反射机制从配置文件中读取策略配置的信息,以决定到底是使用哪种订单处理方式。配置文件web.config中代码片段如下所示:

……
<add key="OrderStrategyClass" value="PetShop.BLL.OrderSynchronous"/>
……

       用户只需要修改配置文件即可更改订单处理方式,提高了系统的灵活性。

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

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

相关文章

学编程能参加哪些比赛?

孩子学习编程可以参加这10个竞赛: 下面将会根据孩子所学的编程语言来列举孩子可以参加的竞赛: 一、Scratch编程可参加的比赛 1、全国中小学生电脑制作大赛 电脑制作大赛不能完全说是编程比赛,更多讲究艺术与科技结合。 评选项目要求学生使用计算机设计电子报、网页设计、F…

计网作业练习一

第一章练习 简答题 1. 简述计算机网络业务的分类及各类业务的特点。 按网络的作用范围划分&#xff1a; &#xff08;1&#xff09;广域网 WAN&#xff0c;作用范围通常为几十到几千公里&#xff0c;也称为远程网&#xff0c;是 Internet 的核心部分。 &#xff08;2&#x…

冒泡排序(超详细图解加代码解析,5分钟看懂)

目录 1.冒泡排序的定义 2.冒泡排序的原理 3.代码及其解析 4.冒泡排序的改进 5.实现冒泡排序函数 生命中永远会有令人懊恼的事&#xff0c;但我知道&#xff0c;我们是为了不留遗憾活着的&#xff0c;对吗&#xff1f; 1.冒泡排序的定义 冒泡排序&#xff08;Bubble Sort&…

C++的一些点

P52 函数-值传递 值传递时&#xff0c;函数的形参发生改变&#xff0c;不会影响实参。 P54 函数-函数声明 先函数声明&#xff0c;就可以把函数定义放在main函数后面 声明可以写多次&#xff0c;但是定义只能写一次。 #include<iostream> using namespace std;int ma…

Tomcat源码:SocketProcessor、ConnectionHandler与Http11Processor

前文&#xff1a; 《Tomcat源码&#xff1a;启动类Bootstrap与Catalina的加载》 《Tomcat源码&#xff1a;容器的生命周期管理与事件监听》 《Tomcat源码&#xff1a;StandardServer与StandardService》 《Tomcat源码&#xff1a;Container接口》 《Tomcat源码&#xff1a…

C++线程 并发编程:std::thread、std::sync与std::packaged_task深度解析

C线程 并发编程&#xff1a;std::thread、std::sync与std::packaged_task深度解析 1. C并发编程概述&#xff08;C Concurrency Overview&#xff09;1.1 并发与并行的区别&#xff08;Difference between Concurrency and Parallelism&#xff09;1.2 C中的线程模型&#xff0…

(二)微服务中间键工作原理——nacos服务端服务注册心跳包(/nacos/v1/ns/instance/beat)源码解读

前言 上节内容我们通过分析nacos客户端源码&#xff0c;了解了nacos客户端是如何向服务端注册服务和发送心跳包的&#xff0c;本节内容话接上一节内容&#xff0c;我们通过分析nacos服务的源码&#xff0c;查看服务端是如何处理客户端注册时候的心跳包的。关于nacos服务端的源…

工程制图篇:公差原则与要求

目录 1、尺寸与几何公差2、公差原则3、公差要求4、关键名词解释5、常用通用量具 1、尺寸与几何公差 1&#xff09;尺寸公差1&#xff1a;上极限尺寸减去下极限尺寸之差&#xff0c;或上极限偏差减去下极限偏差只差。它是允许尺寸的变动量。尺寸公差是一个没有符号的绝对值&…

Cisco.Packet.Tracer思科模拟器练习题

一、网络设备配置 1、将 RA 路由器重命名为 RA&#xff0c;将 RB 路由器重命名为 RB 2、按下方表格中的要求完成设备主机名&#xff08;hostname&#xff09;及接口配置&#xff1b; 3、将 RA 的 DCE 端时间频率设置 64000&#xff0c;并封装 PPP 协议后发送用户名密码 usera…

Spring 源码分析衍生篇十三 :事务扩展机制 TransactionSynchronization

文章目录 一、前言二、TransactionSynchronization 1. TransactionSynchronization 1.1 TransactionSynchronization 的定义1.2 TransactionSynchronizationManager2. TransactionSynchronization 原理简述 2.1 TransactionSynchronization#beforeCommit2.2 TransactionSynchro…

基于状态的系统和有限状态机FSM

基于状态的系统和有限状态机FSM 介绍 基于模型进行测试。模型可能是规格或描述感兴趣的属性。 该模型通常是一个抽象概念&#xff0c;应该相对容易理解。 测试补充了白盒方法。 测试通常是黑盒&#xff1a;不考虑实际系统的结构。 如果模型具有形式语义&#xff0c;则可实现的…

leetcode--二叉搜索树中第K小的元素(java)

二叉搜索树中第K小的元素 leetcode 230 题 二叉搜索树第K 小的元素解题思路代码演示二叉树专题 leetcode 230 题 二叉搜索树第K 小的元素 原题链接&#xff1a; https://leetcode.cn/problems/kth-smallest-element-in-a-bst/ 题目描述 给定一个二叉搜索树的根节点 root &#…

智慧办公室虚拟现实 VR 可视化

“虚拟现实”是来自英文“Virtual Reality”&#xff0c;简称 VR 技术&#xff0c;其是通过利用计算机仿真系统模拟外界环境&#xff0c;主要模拟对象有环境、技能、传感设备和感知等&#xff0c;为用户提供多信息、三维动态、交互式的仿真体验。 图扑软件基于自研可视化引擎 H…

亚马逊云科技位列IDC MarketScape决策支持分析数据平台领导者

随着科学技术的不断发展&#xff0c;人们的生活方式也在不断改变。现在&#xff0c;人们可以通过互联网获得更多的信息&#xff0c;也可以通过智能手机随时随地与他人进行交流。此外&#xff0c;人工智能技术的进步也使得机器能够完成一些复杂的任务&#xff0c;从而提高了人们…

【Azure】微软 Azure 基础解析(六)计算服务中的虚拟机 VM、虚拟机规模集、Azure Functions 与 Azure 容器(ACI)

本系列博文还在更新中&#xff0c;收录在专栏&#xff1a;「Azure探秘&#xff1a;构建云计算世界」 专栏中。 本系列文章列表如下&#xff1a; 【Azure】微软 Azure 基础解析&#xff08;三&#xff09;描述云计算运营中的 CapEx 与 OpEx&#xff0c;如何区分 CapEx 与 OpEx…

写了 7 年代码,第一次见这么狗血的小 Bug!

刚刚修我们鱼聪明 AI 助手平台的一个 Bug&#xff0c;结局很狗血&#xff01;赶紧给大家分享一下&#xff0c;顺便也分享下标准的排查 Bug 思路。 事情是这样的&#xff0c;有小伙伴在鱼聪明平台&#xff08;https://www.yucongming.com&#xff09;创建了一个 AI 助手&#x…

ModStartCMS v6.5.0 菜单多级支持,框架结构优化

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议&#xff0c;免费且不限制商业使用。 功能特性 丰富的模块市…

InnoDB Cluster集群Mysql Router代理层最佳实践

InnoDB Cluster 集群 & Mysql-Router 代理层 前言 Mysql是现今最常用的关系型数据库之一&#xff0c;高可用一直是我们对软件服务的要求。常见的Mysql高可用是主从配置&#xff0c;在主节点挂掉后需要依赖监控脚本进行主从切换将从节点升级&#xff0c;后台服务代码层面也…

正在破坏您的协程(Coroutines)的无声杀手(Silent Killer)

正在破坏您的协程的无声杀手 处理 Kotlin 中的取消异常的唯一安全方法是不重新抛出它们。 今天生产服务器再次停止响应流量。 上个星期&#xff0c;你刚重新启动它们并将其视为故障。但是你总觉得有些奇怪&#xff0c;因为日志中没有任何错误的痕迹&#xff0c;甚至没有警告。…

vue cli配置代理解决跨域问题

跨域问题 是由于违背了同源策略&#xff0c;同源策略规定了协议名、主机名、端口号必须一致 我们目前所处的位置是http localhost 8080&#xff0c;我们想向一台服务器发送请求&#xff0c;它的位置是http localhost 5000&#xff0c;我们的ajax请求从浏览器发送到服务器&#…