【七】设计模式~~~结构型模式~~~桥接模式(Java)

news2024/11/28 12:21:07

【学习难度:★★★☆☆,使用频率:★★★☆☆】

2.1. 模式动机

       在正式介绍桥接模式之前,我先跟大家谈谈两种常见文具的区别,它们是毛笔和蜡笔。假如我们需要大中小3种型号的画笔,能够绘制12种不同的颜色,如果使用蜡笔,需要准备3×12 = 36支,但如果使用毛笔的话,只需要提供3种型号的毛笔,外加12个颜料盒即可,涉及到的对象个数仅为 3 + 12 = 15,远小于36,却能实现与36支蜡笔同样的功能。如果增加一种新型号的画笔,并且也需要具有12种颜色,对应的蜡笔需增加12支,而毛笔只需增加一支。为什么会这样呢?通过分析我们可以得知:在蜡笔中,颜色和型号两个不同的变化维度(即两个不同的变化原因)融合在一起,无论是对颜色进行扩展还是对型号进行扩展都势必会影响另一个维度;但在毛笔中,颜色和型号实现了分离,增加新的颜色或者型号对另一方都没有任何影响。如果使用软件工程中的术语,我们可以认为在蜡笔中颜色和型号之间存在较强的耦合性,而毛笔很好地将二者解耦,使用起来非常灵活,扩展也更为方便。在软件开发中,我们也提供了一种设计模式来处理与画笔类似的具有多变化维度的情况,即本章将要介绍的桥接模式。

       设想如果要绘制矩形、圆形、椭圆、正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等,此时至少有如下两种设计方案:

  • 第一种设计方案是为每一种形状都提供一套各种颜色的版本。
  • 第二种设计方案是根据实际需要对形状和颜色进行组合
    对于有两个变化维度(即两个变化的原因)的系统,采用方案二来进行设计系统中类的个数更少,且系统扩展更为方便。设计方案二即是桥接模式的应用。桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。

2.2. 模式定义

       桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

2.3. 模式结构

       桥接模式包含如下角色:

  • Abstraction:抽象类,用于定义抽象类的接口,它一般是抽象类而不是接口,其中定义了一个Implementor(实现类接口)类型的对象并可以维护该对象,它与Implementor之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法。
  • RefinedAbstraction:扩充抽象类,扩充由Abstraction定义的接口,通常情况下它不再是抽象类而是具体类,它实现了在Abstraction中声明的抽象业务方法,在RefinedAbstraction中可以调用在Implementor中定义的业务方法。
  • Implementor:实现类接口,定义实现类的接口,这个接口不一定要与Abstraction的接口完全一致,事实上这两个接口可以完全不同,一般而言,Implementor接口仅提供基本操作,而Abstraction定义的接口可能会做更多更复杂的操作。Implementor接口对这些基本操作进行了声明,而具体实现交给其子类。通过关联关系,在Abstraction中不仅拥有自己的方法,还可以调用到Implementor中定义的方法,使用关联关系来替代继承关系。
  • ConcreteImplementor:具体实现类,具体实现Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同实现,在程序运行时,ConcreteImplementor对象将替换其父类对象,提供给抽象类具体的业务操作方法。
    在这里插入图片描述

2.4. 时序图

在这里插入图片描述

2.5. 代码分析

       在使用桥接模式时,我们首先应该识别出一个类所具有的两个独立变化的维度,将它们设计为两个独立的继承等级结构,为两个维度都提供抽象层,并建立抽象耦合。通常情况下,我们将具有两个独立变化维度的类的一些普通业务方法和与之关系最密切的维度设计为“抽象类”层次结构(抽象部分),而将另一个维度设计为“实现类”层次结构(实现部分)。例如:对于毛笔而言,由于型号是其固有的维度,因此可以设计一个抽象的毛笔类,在该类中声明并部分实现毛笔的业务方法,而将各种型号的毛笔作为其子类;颜色是毛笔的另一个维度,由于它与毛笔之间存在一种“设置”的关系,因此我们可以提供一个抽象的颜色接口,而将具体的颜色作为实现该接口的子类。在此,型号可认为是毛笔的抽象部分,而颜色是毛笔的实现部分,结构示意图如图10-4所示:
在这里插入图片描述

       在图10-4中,如果需要增加一种新型号的毛笔,只需扩展左侧的“抽象部分”,增加一个新的扩充抽象类;如果需要增加一种新的颜色,只需扩展右侧的“实现部分”,增加一个新的具体实现类。扩展非常方便,无须修改已有代码,且不会导致类的数目增长过快。

       在具体编码实现时,由于在桥接模式中存在两个独立变化的维度,为了使两者之间耦合度降低,首先需要针对两个不同的维度提取抽象类和实现类接口,并建立一个抽象关联关系。对于“实现部分”维度,典型的实现类接口代码如下所示:

interface Implementor {
	public void operationImpl();
}

       在实现Implementor接口的子类中实现了在该接口中声明的方法,用于定义与该维度相对应的一些具体方法。
       对于另一“抽象部分”维度而言,其典型的抽象类代码如下所示:

abstract class Abstraction {
	protected Implementor impl; //定义实现类接口对象
	
	public void setImpl(Implementor impl) {
		this.impl=impl;
	}
	public abstract void operation();  //声明抽象业务方法
}

       在抽象类Abstraction中定义了一个实现类接口类型的成员对象impl,再通过注入的方式给该对象赋值,一般将该对象的可见性定义为protected,以便在其子类中访问Implementor的方法,其子类一般称为扩充抽象类或细化抽象类(RefinedAbstraction),典型的RefinedAbstraction类代码如下所示:

class RefinedAbstraction extends Abstraction {
	public void operation() {
		//业务代码
		impl.operationImpl();  //调用实现类的方法
		//业务代码
	}
}

       对于客户端而言,可以针对两个维度的抽象层编程,在程序运行时再动态确定两个维度的子类,动态组合对象,将两个独立变化的维度完全解耦,以便能够灵活地扩充任一维度而对另一维度不造成任何影响。

2.6. 模式分析

       理解桥接模式,重点需要理解如何将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化。

  • 抽象化:抽象化就是忽略一些信息,把不同的实体当作同样的实体对待。在面向对象中,将对象的共同性质抽取出来形成类的过程即为抽象化的过程。
  • 实现化:针对抽象化给出的具体实现,就是实现化,抽象化与实现化是一对互逆的概念,实现化产生的对象比抽象化更具体,是对抽象化事物的进一步具体化的产物。
  • 脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。

2.7. 实例

       如果需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Linux、Unix等)上播放多种格式的视频文件,常见的视频格式包括MPEG、RMVB、AVI、WMV等。现使用桥接模式设计该播放器。

2.7.1 老方法(类图)

        Sunny软件公司欲开发一个跨平台图像浏览系统,要求该系统能够显示BMP、JPG、GIF、PNG等多种格式的文件,并且能够在Windows、Linux、Unix等多个操作系统上运行。系统首先将各种格式的文件解析为像素矩阵(Matrix),然后将像素矩阵显示在屏幕上,在不同的操作系统中可以调用不同的绘制函数来绘制像素矩阵。系统需具有较好的扩展性以支持新的文件格式和操作系统。

        Sunny软件公司的开发人员针对上述要求,提出了一个初始设计方案,其基本结构如图10-1所示:
在这里插入图片描述

        在图10-1的初始设计方案中,使用了一种多层继承结构,Image是抽象父类,而每一种类型的图像类,如BMPImage、JPGImage等作为其直接子类,不同的图像文件格式具有不同的解析方法,可以得到不同的像素矩阵;由于每一种图像又需要在不同的操作系统中显示,不同的操作系统在屏幕上显示像素矩阵有所差异,因此需要为不同的图像类再提供一组在不同操作系统显示的子类,如为BMPImage提供三个子类BMPWindowsImp、BMPLinuxImp和BMPUnixImp,分别用于在Windows、Linux和Unix三个不同的操作系统下显示图像。

        我们现在对该设计方案进行分析,发现存在如下两个主要问题:

  • (1)、由于采用了多层继承结构,导致系统中类的个数急剧增加,图10-1中,在各种图像的操作系统实现层提供了12个具体类,加上各级抽象层的类,系统中类的总个数达到了17个,在该设计方案中,具体层的类的个数 = 所支持的图像文件格式数×所支持的操作系统数。
  • (2)、系统扩展麻烦,由于每一个具体类既包含图像文件格式信息,又包含操作系统信息,因此无论是增加新的图像文件格式还是增加新的操作系统,都需要增加大量的具体类,例如在图10-1中增加一种新的图像文件格式TIF,则需要增加3个具体类来实现该格式图像在3种不同操作系统的显示;如果增加一个新的操作系统Mac OS,为了在该操作系统下能够显示各种类型的图像,需要增加4个具体类。这将导致系统变得非常庞大,增加运行和维护开销。
    如何解决这两个问题?我们通过分析可得知,该系统存在两个独立变化的维度:图像文件格式和操作系统,如图10-2所示:
    在这里插入图片描述

        在图10-2中,如何将各种不同类型的图像文件解析为像素矩阵与图像文件格式本身相关,而如何在屏幕上显示像素矩阵则仅与操作系统相关。正因为图10-1所示结构将这两种职责集中在一个类中,导致系统扩展麻烦,从类的设计角度分析,具体类BMPWindowsImp、BMPLinuxImp和BMPUnixImp等违反了“单一职责原则”,因为不止一个引起它们变化的原因,它们将图像文件解析和像素矩阵显示这两种完全不同的职责融合在一起,任意一个职责发生改变都需要修改它们,系统扩展困难。

        如何改进?我们的方案是将图像文件格式(对应图像格式的解析)与操作系统(对应像素矩阵的显示)两个维度分离,使得它们可以独立变化,增加新的图像文件格式或者操作系统时都对另一个维度不造成任何影响。

2.7.2 新方法(完整代码)

        为了减少所需生成的子类数目,实现将操作系统和图像文件格式两个维度分离,使它们可以独立改变,Sunny公司开发人员使用桥接模式来重构跨平台图像浏览系统的设计,其基本结构如图10-5所示:
在这里插入图片描述

       在图10-5中,Image充当抽象类,其子类JPGImage、PNGImage、BMPImage和GIFImage充当扩充抽象类;ImageImp充当实现类接口,其子类WindowsImp、LinuxImp和UnixImp充当具体实现类。完整代码如下所示:

package com.zyz.demo;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/5/13 20:41
 * @Description:
 */

/**
 * 像素矩阵类:辅助类,各种格式的文件最终都被转化为像素矩阵,不同的操作系统提供不同的方式显示像素矩阵
 */
class Matrix {
    //此处代码省略
}

/**
 * 抽象图像类:抽象类
 */
abstract class Image {
    protected ImageImp imp;

    public void setImageImp(ImageImp imp) {
        this.imp = imp;
    }

    public abstract void parseFile(String fileName);

}


/**
 * 抽象操作系统实现类:实现类接口
 */
interface ImageImp {
    public void doPaint(Matrix m);  //显示像素矩阵m
}

/**
 * Windows操作系统实现类:具体实现类
 */
class WindowsImp implements ImageImp {
    @Override
    public void doPaint(Matrix m) {
        //调用Windows系统的绘制函数绘制像素矩阵
        System.out.print("在Windows操作系统中显示图像:");
    }
}

/**
 * Linux操作系统实现类:具体实现类
 */
class LinuxImp implements ImageImp {
    @Override
    public void doPaint(Matrix m) {
        //调用Linux系统的绘制函数绘制像素矩阵
        System.out.print("在Linux操作系统中显示图像:");
    }
}

/**
 * Unix操作系统实现类:具体实现类
 */
class UnixImp implements ImageImp {
    @Override
    public void doPaint(Matrix m) {
        //调用Unix系统的绘制函数绘制像素矩阵
        System.out.print("在Unix操作系统中显示图像:");
    }
}

/**
 * JPG格式图像:扩充抽象类
 */
class JPGImage extends Image {
    @Override
    public void parseFile(String fileName) {
        //模拟解析JPG文件并获得一个像素矩阵对象m;
        Matrix m = new Matrix();
        imp.doPaint(m);
        System.out.println(fileName + ",格式为JPG。");

    }
}


/**
 * PNG格式图像:扩充抽象类
 */
class PNGImage extends Image {
    @Override
    public void parseFile(String fileName) {
        //模拟解析PNG文件并获得一个像素矩阵对象m;
        Matrix m = new Matrix();
        imp.doPaint(m);
        System.out.println(fileName + ",格式为PNG。");
    }
}

/**
 * BMP格式图像:扩充抽象类
 */
class BMPImage extends Image {
    @Override
    public void parseFile(String fileName) {
        //模拟解析BMP文件并获得一个像素矩阵对象m;
        Matrix m = new Matrix();
        imp.doPaint(m);
        System.out.println(fileName + ",格式为BMP。");
    }
}

/**
 * GIF格式图像:扩充抽象类
 */
class GIFImage extends Image {
    @Override
    public void parseFile(String fileName) {
        //模拟解析GIF文件并获得一个像素矩阵对象m;
        Matrix m = new Matrix();
        imp.doPaint(m);
        System.out.println(fileName + ",格式为GIF。");
    }
}

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

package com.zyz.demo;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/5/13 20:56
 * @Description: 客户端
 */
public class Client {
    public static void main(String[] args) {
        Image image;
        ImageImp imp;
        image = new JPGImage();
        imp = new WindowsImp();
        image.setImageImp(imp);
        image.parseFile("小龙女");
    }
}

测试结果
在这里插入图片描述

       如果需要更换图像文件格式或者更换操作系统,只需修改配置文件即可,在实际使用时,可以通过分析图像文件格式后缀名来确定具体的文件格式,在程序运行时获取操作系统信息来确定操作系统类型,无须使用配置文件。当增加新的图像文件格式或者操作系统时,原有系统无须做任何修改,只需增加一个对应的扩充抽象类或具体实现类即可,系统具有较好的可扩展性,完全符合“开闭原则”。

2.8. 优点

桥接模式的优点:

  • 分离抽象接口及其实现部分。
  • 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。
  • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
  • 实现细节对客户透明,可以对用户隐藏实现细节。

2.9. 缺点

桥接模式的缺点:

  • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
  • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

2.10. 适用环境

在以下情况下可以使用桥接模式:

  • 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
  • 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
  • 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
  • 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
  • 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

2.11. 模式应用

       一个Java桌面软件总是带有所在操作系统的视感(LookAndFeel),如果一个Java软件是在Unix系统上开发的,那么开发人员看到的是Motif用户界面的视感;在Windows上面使用这个系统的用户看到的是Windows用户界面的视感;而一个在Macintosh上面使用的用户看到的则是Macintosh用户界面的视感,Java语言是通过所谓的Peer架构做到这一点的。Java为AWT中的每一个GUI构件都提供了一个Peer构件,在AWT中的Peer架构就使用了桥接模式。

2.12. 模式扩展

适配器模式与桥接模式的联用:

  • 桥接模式和适配器模式用于设计的不同阶段,桥接模式用于系统的初步设计,对于存在两个独立变化维度的类可以将其分为抽象化和实现化两个角色,使它们可以分别进行变化;而在初步设计完成之后,当发现系统与已有类无法协同工作时,可以采用适配器模式。但有时候在设计初期也需要考虑适配器模式,特别是那些涉及到大量第三方应用接口的情况。

2.13. 总结

  • 桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
  • 桥接模式包含如下四个角色:抽象类中定义了一个实现类接口类型的对象并可以维护该对象;扩充抽象类扩充由抽象类定义的接口,它实现了在抽象类中定义的抽象业务方法,在扩充抽象类中可以调用在实现类接口中定义的业务方法;实现类接口定义了实现类的接口,实现类接口仅提供基本操作,而抽象类定义的接口可能会做更多更复杂的操作;具体实现类实现了实现类接口并且具体实现它,在不同的具体实现类中提供基本操作的不同实现,在程序运行时,具体实现类对象将替换其父类对象,提供给客户端具体的业务操作方法。
  • 在桥接模式中,抽象化(Abstraction)与实现化(Implementation)脱耦,它们可以沿着各自的维度独立变化。
  • 桥接模式的主要优点是分离抽象接口及其实现部分,是比多继承方案更好的解决方法,桥接模式还提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,实现细节对客户透明,可以对用户隐藏实现细节;其主要缺点是增加系统的理解与设计难度,且识别出系统中两个独立变化的维度并不是一件容易的事情。
  • 桥接模式适用情况包括:需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系;抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响;一个类存在两个独立变化的维度,且这两个维度都需要进行扩展;设计要求需要独立管理抽象化角色和具体化角色;不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统。

2.14 扩展(读取xml文件)

为了让系统具有更好的灵活性和可扩展性,我们引入了配置文件,将具体扩充抽象类和具体实现类类名都存储在配置文件中,再通过反射生成对象,将生成的具体实现类对象注入到扩充抽象类对象中,其中,配置文件config.xml的代码如下所示:

<?xml version="1.0"?>
<config>
    <!--RefinedAbstraction-->
    <className>MyPNGImage</className>
    <!--ConcreteImplementor-->
    <className>MyUnixImp</className>
</config>

用于读取配置文件config.xml并反射生成对象的XMLUtil类的代码如下所示:

package com.zyz.demo.config;

import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
/**
 * @author zyz
 * @version 1.0
 * @data 2023/5/13 21:43
 * @Description:
 */
public class XMLUtil {
    /**
     * 该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
     * @param args
     * @return
     */
    public static Object getBean(String args) {
        try {
            String path = "F:\\java学习资料(后端)\\github管理后端学习资料\\后端学习\\设计模式\\代码\\DesignPatterns-Java-Examples\\7. 桥接模式\\src\\main\\resources\\config.xml";
            //创建文档对象
            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = dFactory.newDocumentBuilder();
            Document doc;
            doc = builder.parse(new File(path));
            NodeList nl=null;
            Node classNode=null;
            String cName=null;
            nl = doc.getElementsByTagName("className");

            if(args.equals("image")) {
                //获取第一个包含类名的节点,即扩充抽象类
                classNode=nl.item(0).getFirstChild();

            }
            else if(args.equals("os")) {
                //获取第二个包含类名的节点,即具体实现类
                classNode=nl.item(1).getFirstChild();
            }

            cName=classNode.getNodeValue();
            //通过类名生成实例对象并将其返回
            Class c=Class.forName("com.zyz.demo." + cName);
            Object obj=c.newInstance();
            return obj;
        }
        catch(Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

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

package com.zyz.demo;

import com.zyz.demo.config.XMLUtil;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/5/13 21:47
 * @Description: 客户端
 */
public class Client1 {
    public static void main(String[] args) {
        Image image;
        ImageImp imp;
        image = (Image)XMLUtil.getBean("image");
        imp = (ImageImp) XMLUtil.getBean("os");
        image.setImageImp(imp);
        image.parseFile("杨过");
    }
}

测试结果

在这里插入图片描述

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

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

相关文章

Android 应用快捷(shortcut)功能的详解(一)

一、介绍 在现如今的应用中&#xff0c;一些开发者为了把核心的业务尽快的方便让使用者触摸到&#xff0c;想尽各种办法。比如前期的桌面插件&#xff0c;点击直接拉起核心业务。现在我们在主流的APP中&#xff0c;只要你长按桌面快捷键&#xff0c;发现弹出来的是不是单一的卸…

MapReduce实战案例(2)

案例二: MR实战之数据分类输出(自定义outputFormat) 2.1 项目准备 需求 现有一些原始日志需要做增强解析处理&#xff0c;流程&#xff1a; a) 从原始日志文件中读取数据b) 根据日志中的一个URL字段到外部知识库中获取信息增强到原始日志c) 如果成功增强&#xff0c;则输出…

提升日期处理效率:day.js 实战经验分享

theme: smartblue 本文简介 点赞 关注 收藏 学会了 本文主要介绍我在工作中使用 day.js 较多的方法。本文并不能代替 day.js 官方文档&#xff0c;日常工作中该查文档的还是要查文档。本文是写给刚接触 day.js 的工友&#xff0c;让这部分工友能更顺利上手 day.js。本文不涉…

SMESwin Unet:融合CNN和Transformer进行医学图像分割

文章目录 SMESwin Unet: Merging CNN and Transformer for Medical Image Segmentation摘要本文方法SuperpixelMCCT SMESwin Unet: Merging CNN and Transformer for Medical Image Segmentation 摘要 视觉Transformer 是自去年以来医学图像分割领域最受欢迎的新范式&#xf…

ts报错“this“ 隐式具有类型 “any“,因为它没有类型注释。解决方案

序 1、参考博文》①严格模式 - 知乎&#xff0c;②ts的tsconfig.son中文说明③TypeScript Number | 菜鸟教程 2、解决&#xff08;ts报错“this“ 隐式具有类型 “any“&#xff0c;因为它没有类型注释。&#xff09; 3、解决&#xff08;函数内this是undefined 的问题&#xf…

汽车EDI:如何与Stellantis建立EDI连接?

Stellantis 是一家实力雄厚的汽车制造公司&#xff0c;由法国标致雪铁龙集团&#xff08;PSA集团&#xff09;和意大利菲亚特克莱斯勒汽车集团&#xff08;FCA集团&#xff09;合并而成&#xff0c;是世界上第四大汽车制造商&#xff0c;拥有包括标致、雪铁龙、菲亚特、克莱斯勒…

Hive学习---1、Hive入门、Hive 安装

1、Hive入门 1.1 什么是Hive 1、Hive简介 Hive是由Facebook开源&#xff0c;基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张表&#xff0c;并提供类SQL查询功能。 2、Hive本质 Hive是一个Hadoop客户端&#xff0c;用于将HQL&#xff08;Hive SQL…

【六一为孩子建模吧】沐风老师3DMAX建模雕刻插件SculptTool使用教程

3DMAX建模雕刻插件&#xff0c;该工具旨在使对角色和地形等有机模型进行小型编辑成为可能&#xff0c;而无需离开3dMax并启动如ZBrush等专用雕刻应用程序&#xff0c;就可以在3DMAX中直接对小型模型进行简单的雕刻建模处理&#xff0c;这样会方便很多。 【适用版本】 3dMax202…

【C++初阶】:string类

string 一string的基本用法二.迭代器1.基本使用2.语法糖3.反向迭代器4.const迭代器 三.容量四.插入和删除五.一个例题&#xff1a;解析协议六.读取空格七.一些其他函数 一string的基本用法 文档里 一般使用 二.迭代器 1.基本使用 string里重载了一种非常厉害的运算符[ ] 这个运…

【web安全】文件包含漏洞

目录 1.什么是文件包含漏洞 2.产生原因 3.文件包含的类型 3.1本地文件包含 3.2远程文件包含 4.攻击利用手法 4.1 file:协议 4.2 php://协议 ​4.3 zip://,bzip2://,zlib://协议 4.4 data://协议 4.5 PHP伪协议总结 5.如何防御&#xff1f; 6.常见系统的默认路径…

Tcl-11. 列表操作

Tcl 中的列表操作&#xff1a; 列表则是具有特殊解释的字符串。Tcl 中的列表操作和其它 Tcl 命令 一样具有相同的结构。 列表可应1用在诸如 foreach 这样的以列表为变元的循环命令中&#xff0c;也应于构建 eval 命令的延迟命令字符串。 一、list 命令 list 命令用来创建列表…

城市内涝的解决措施,城市内涝积水监测预警系统解决方案

随着城市化进程的加快&#xff0c;城市土地面积不断扩大&#xff0c;原本吸收雨水的土地被水泥、沥青等硬质材料所取代&#xff0c;导致雨水无法迅速渗透和排泄&#xff0c;增加了城市内涝的风险。同时&#xff0c;气候变化带来的极端降雨事件频率增加&#xff0c;更加加剧了内…

mysql触发器监听数据投递中间件

目前市面上有许多的 CDC&#xff08;Change Data Capture&#xff09; 框架用于监听数据库的数据变动&#xff0c;例如&#xff1a;canal、Debezium、Maxwell等都是用来解析 binlog 日志实现事件的监听。但是有一个情况就是如果公司对 binlog 日志文件的权限管控的很严格&#…

传统ERP和SaaS ERP区别在哪里?

一、ERP和SaaS ERP概念 企业资源计划 (ERP) 系统是许多企业的主干&#xff0c;助力管理整个企业内的会计、采购流程、项目等。对于许多 IT 部门而言&#xff0c;ERP 系统通常意味着大型、昂贵且耗时的部署&#xff0c;并可能需要进行大量硬件或基础设施投资。然而&#xff0c;…

基于标准库函数的STM32的freertos的移植(三)——MDK工程搭建、配置与修改

1.打开MDK5软件&#xff0c;新建MDK工程&#xff0c;将新建工程文件保存在Project_Stm32f407/mdk文件夹下&#xff0c;并将工程命名为freertos_M4&#xff0c;选择MCU型号为STM32F407ZG&#xff0c;新建工程文件的步骤如下图所示&#xff1a; 图1 新建工程 图2 保存工程路径和工…

ChatGPT-AI地图

ChatGPT-AI地图 1、AI-对话 应用名称应用地址ChatGPThttps://chat.openai.com/NotionAINotion AIA.I. Data Sidekick&#xff1a;AI工具编写 SQL、文档等的速度提高10倍[AirOpsWritesonic&#xff1a;人工智能写作辅助工具Writesonic - Best AI Writer, Copywriting & Par…

《深入理解计算机系统(CSAPP)》第3章 程序的机器级表示 - 学习笔记

写在前面的话&#xff1a;此系列文章为笔者学习CSAPP时的个人笔记&#xff0c;分享出来与大家学习交流&#xff0c;目录大体与《深入理解计算机系统》书本一致。因是初次预习时写的笔记&#xff0c;在复习回看时发现部分内容存在一些小问题&#xff0c;因时间紧张来不及再次整理…

MySQL 数值函数

文章目录 数值函数1. abs(num)2. ceil(num)3. floor(num)4. mod(num1,num2)5. rand()6. round(num,n)7. truncate(num,n)8. sqrt(num) 数值函数 数值函数用来处理数值方面的运算&#xff0c;能够提高用户的工作效率。常用的数值函数如下表所示&#xff0c;函数括号内为输入的参…

牛客网基础语法11~20题

前言&#xff1a;今天是咱们第二期刷牛客网上的题目。 目标&#xff1a;对输入输出的格式&#xff0c;方法&#xff0c;类型掌握熟练&#xff0c;对double&#xff0c;float理解深入&#xff0c;编程思想更进一步。 鸡汤&#xff1a;人活着&#xff0c;再苦再累&#xff0c;都别…

16-Vue技术栈之常用的 Composition API

目录 1、什么是组合式 API&#xff1f;2、拉开序幕的setup3、ref函数4、reactive函数5、Vue3.0中的响应式原理5.1 vue2.x的响应式5.2 Vue3.0的响应式 6、reactive对比ref7、setup的两个注意点8、计算属性与监视8.1 computed函数8.2 watch函数8.3 watchEffect函数 9、 生命周期1…