【设计模式深度剖析】【2】【结构型】【装饰器模式】| 以去咖啡馆买咖啡为例 | 以穿衣服出门类比

👈️上一篇:代理模式

目 录

  • 装饰器模式
  • 定义
    • 英文原话
    • 直译
    • 如何理解呢?
    • 4个角色
    • 类图
      • 1. 抽象构件(Component)角色
      • 2. 具体构件(Concrete Component)角色
      • 3. 装饰(Decorator)角色
      • 4. 具体装饰(Concrete Decorator)角色
      • 说明:
    • 代码示例
  • 典型场景
  • 示例解析:以去咖啡馆买咖啡为例
    • 类图
    • 抽象构件
    • 具体构件
    • 装饰器类
    • 测试类:去咖啡馆买咖啡
    • 注意
  • Java I/O类库借鉴了装饰器模式
    • UML类图
    • 准备待读取的文件
    • 代码示例1:读取文件,并打印输出
      • notes1:
      • notes2:
    • 代码示例2:解决乱码问题
      • notes:
  • 适配器模式与装饰器模式的区别
  • 搞懂了?那我们玩点带有迷惑型的东西
    • 为什么说Java io类库借鉴了装饰模式,而不是等同于装饰模式
    • 类比
  • 拓展内容
    • 抽象类相关基础

装饰器模式

装饰器(Decorator)模式:

就是,是不一样的烟火,对进行任何的装饰之后依旧是那个,归来依旧是那个少年!

装饰模式是继承关系的一个替代方案。装饰类Decorator,不管装饰多少层,返回的对象还是原类型(记住加粗的这句话,默念三遍,很重要)。

不管装饰多少次,类型不要发生变化才是装饰器模式。

装饰者的出现也再一次的证明了面向对象的设计原则:多用组合,少用继承!对扩展开放,对修改关闭!

==> 本文示例源码 <==

定义

英文原话

Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.

直译

动态地向对象附加额外的职责,同时保持相同的接口。装饰器为扩展功能提供了一种比子类化更灵活的替代方案。

如何理解呢?

装饰器模式是继承关系的替代:

装饰模式可以替代继承,解决类膨胀的问题。

下面会进行详细的阐述。

4个角色

类图

在这里插入图片描述

1. 抽象构件(Component)角色

该角色用于规范需要装饰的对象(原始对象)。

2. 具体构件(Concrete Component)角色

该角色实现抽象构件接口,定义一个需要装饰的原始类。

3. 装饰(Decorator)角色

该角色持有一个构件对象的实例,并定义一个与抽象构件接口一致的接口。

4. 具体装饰(Concrete Decorator)角色

该角色负责对构件对象进行装饰。

说明:

装饰类和被装饰类可以独立发展,而不会相互耦合。即Component 类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构件。

装饰模式是继承关系的一个替代方案。装饰类Decorator,不管装饰多少层,返回的对象还是Component(记住加粗的这句话,默念三遍,很重要)。

装饰模式可以动态地扩展一个实现类的功能

装饰模式的缺点:多层的装饰是比较复杂的。

代码示例

package com.polaris.designpattern.list2.structural.pattern2.decorator.proto;

// 抽象组件
interface Component {  
    void operate();  
}  
  
// 具体组件  
class ConcreteComponent implements Component {  
    @Override  
    public void operate() {  
        System.out.println("执行具体组件的操作");  
    }  
}  
  
// 抽象装饰器  
class Decorator implements Component {  
    protected Component component;  
  
    public Decorator(Component component) {  
        this.component = component;  
    }  
  
    @Override  
    public void operate() {  
        if (component != null) {  
            component.operate();  
        }  
    }  
}  
  
// 具体装饰器  
class ConcreteDecorator extends Decorator {  
    public ConcreteDecorator(Component component) {  
        super(component);  
    }  
  
    @Override  
    public void operate() {  
        super.operate(); // 调用抽象装饰器的operation方法  
        // 添加额外的功能或行为  
        addedBehavior();  
    }  
  
    public void addedBehavior() {  
        System.out.println("执行具体装饰器的额外操作");  
    }  
}  
  
// 客户端代码  
public class ClassicalDecoratorDemo {
    public static void main(String[] args) {  
        Component component = new ConcreteComponent();  
  
        // 递归地组合装饰器对象  
        Component decoratedComponent = new ConcreteDecorator(new ConcreteDecorator(component));  
  
        // 执行操作  
        decoratedComponent.operate();  
    }  
}
/* Output:
执行具体组件的操作
执行具体装饰器的额外操作
执行具体装饰器的额外操作
*///~

典型场景

  1. ■ 需要扩展一个类的功能,或给一个类增加附加功能。
  2. ■ 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
  3. ■ 需要为一批类进行改装或加装功能。

NOTES:

  1. 装饰模式是对继承的有力补充。
  2. 单纯使用继承时,在一些情况下就会增加很多子类,而且灵活性差,维护也不容易。
  3. 装饰模式可以替代继承,解决类膨胀的问题,如Java基础类库中的io类库(输入输出流相关的类)大量借鉴了装饰模式(参考进一步分析)。

示例解析:以去咖啡馆买咖啡为例

类图

在这里插入图片描述

抽象构件

对咖啡馆的咖啡进行了抽象

package com.polaris.designpattern.list2.structural.pattern2.decorator;

// 咖啡接口
interface Coffee {
    String getDescription();
    double getCost();
}

具体构件

咖啡馆的最普通的咖啡,对抽象构件的一种实现。

package com.polaris.designpattern.list2.structural.pattern2.decorator;


// 基础咖啡实现
class SimpleCoffee implements Coffee {
    @Override  
    public String getDescription() {  
        return "美式咖啡";  
    }  
  
    @Override  
    public double getCost() {  
        return 2.5;  
    }  
}

装饰器类

抽象出装饰器角色,一个抽象类,实现了抽象构件Coffee接口,

它的实现在这里有两个,一个是加糖装饰器 SugarDecorator,加奶装饰器MilkDecorator ,还可以根据需要继续扩展…

package com.polaris.designpattern.list2.structural.pattern2.decorator;


// 咖啡装饰器接口
abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;  
  
    public CoffeeDecorator(Coffee coffee) {  
        this.coffee = coffee;  
    }  
  
    @Override  
    public String getDescription() {  
        return coffee.getDescription();  
    }  
  
    @Override  
    public double getCost() {  
        return coffee.getCost();  
    }  
}  
  
// 加糖装饰器  
class SugarDecorator extends CoffeeDecorator {  
    public SugarDecorator(Coffee coffee) {  
        super(coffee);  
    }  
  
    @Override  
    public String getDescription() {  
        return coffee.getDescription() + ", 加糖";  
    }  
  
    @Override  
    public double getCost() {  
        return coffee.getCost() + 0.5;  
    }  
}  
  
// 加奶装饰器  
class MilkDecorator extends CoffeeDecorator {  
    public MilkDecorator(Coffee coffee) {  
        super(coffee);  
    }  
  
    @Override  
    public String getDescription() {  
        return coffee.getDescription() + ", 加奶";  
    }  
  
    @Override  
    public double getCost() {  
        return coffee.getCost() + 1.0;  
    }  
}  
  
// ... 可以添加更多装饰器,如肉桂粉装饰器等

测试类:去咖啡馆买咖啡

package com.polaris.designpattern.list2.structural.pattern2.decorator;

public class CoffeeShop {
    public static void main(String[] args) {  
        // 创建基础咖啡  
        Coffee coffee = new SimpleCoffee();
        System.out.println("你点了:" + coffee.getDescription() + ",价格是:" + coffee.getCost());  
  
        // 添加加糖装饰器  
        Coffee sweetCoffee = new SugarDecorator(coffee);
        System.out.println("你加了糖,现在是:" + sweetCoffee.getDescription() + ",价格是:" + sweetCoffee.getCost());  
  
        // 递归添加加奶装饰器(加在已经加糖的咖啡上)  
        Coffee latte = new MilkDecorator(sweetCoffee);
        System.out.println("你又加了奶,现在是:" + latte.getDescription() + ",价格是:" + latte.getCost());  
  
        // 你可以继续递归添加更多装饰器...  
    }  
}

/* Output:
你点了:美式咖啡,价格是:2.5
你加了糖,现在是:美式咖啡, 加糖,价格是:3.0
你又加了奶,现在是:美式咖啡, 加糖, 加奶,价格是:4.0
*///~

注意

从上面示例你也会发现,无论装饰几层,类型不变,都是Coffee类型

Coffee coffee = new SimpleCoffee();

Coffee sweetCoffee = new SugarDecorator(coffee);

Coffee latte = new MilkDecorator(sweetCoffee);

Java I/O类库借鉴了装饰器模式

UML类图

在这里插入图片描述

准备待读取的文件

ExampleFileForRead.txt

相对于当前工程的目录路径是:

src/main/java/com/polaris/designpattern/list2/structural/pattern2/decorator/inputstream/ExampleFileForRead.txt

文件内容如下:

设计模式类型:
1. ■ 创建型(creational)👈️
2. ■ 结构型(structural)👈️
3. ■ 行为型(behavioral)👈️

代码示例1:读取文件,并打印输出

进一步深究

控制台打印输出的中文、特殊字符乱码了

package com.polaris.designpattern.list2.structural.pattern2.decorator.inputstream;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * 存在打印中文、特殊字符乱码问题
 */
public class DecoratorPatternDemo {
  
    public static void main(String[] args) {
        String exampleFile = System.getProperty("user.dir") +
                File.separator +
                "src/main/java/com/polaris/designpattern/list2/structural/pattern2/decorator/inputstreamapi" +
                "/ExampleFileForRead.txt";
        try (
                // 创建一个 FileInputStream 对象,用于从文件中读取数据
                FileInputStream fis = new FileInputStream(exampleFile);
                // 使用 BufferedInputStream 包装 FileInputStream,添加缓冲功能
                BufferedInputStream bis = new BufferedInputStream(fis);
            
                /*行不通:FilterInputStream构造函数受保护
                FileInputStream fis = new FileInputStream(exampleFile);
                FilterInputStream filteris= new FilterInputStream(fis);
                BufferedInputStream bis = new BufferedInputStream(filteris);*/
        ) {  
            // 读取并处理数据,这里只是简单地打印到控制台  
            int data;  
            while ((data = bis.read()) != -1) {  
                System.out.print((char) data);  
            }  
        } catch (IOException e) {
            e.printStackTrace();  
        }  
    }  
}

/* Output:
设计模式类型:
1. ■ 创建型(creational)👈️
2. ■ 结构型(structural)👈️
3. ■ 行为型(behavioral)👈️
*///~

notes1:

上面是一个简单的测试类示例,展示了如何使用 BufferedInputStreamFileInputStream 类似装饰模式的方式。

BufferedInputStreamFilterInputStreamInputStreamFileInputStream 这些类在 Java 的 I/O 框架中与装饰模式(Decorator Pattern)有关,但并不严格地完全遵循装饰模式的经典定义。然而,我们可以说它们的设计受到装饰模式的启发(详细分析见进一步分析),特别是 FilterInputStream 和它的子类 BufferedInputStream 展示了类似装饰模式的行为。

装饰模式允许向一个对象动态地添加职责(即功能),同时保持相同的接口

在 Java I/O 中,InputStream 是所有输入流的基类,定义了一个读取字节的通用接口。

FilterInputStreamInputStream 的一个子类,用于包装另一个 InputStream 并向其添加额外的功能,而不改变其基本的读取接口。

BufferedInputStreamFilterInputStream 的一个子类,它提供了一个带有缓冲区的输入流,用于提高读取性能。它通过包装另一个 InputStream(可能是 FileInputStream 或其他任何 InputStream 的子类)来添加缓冲功能。

notes2:

在这个示例中,FileInputStream 是原始的数据源,而 BufferedInputStream 则作为一个装饰器,为原始数据源添加了缓冲功能。这种关系与装饰模式中的组件(Component)和装饰器(Decorator)之间的关系相似。 FilterInputStream 和它的子类,通过组合(即包装另一个 InputStream 对象)来扩展功能。

可以从源码看出,FilterInputStream 持有一个InputStream的实例,通过构造函数进行的初始化,即FilterInputStream 和子类组合了一个InputStream 对象,

public class FilterInputStream extends InputStream {
/**
     * The input stream to be filtered.
     */
    protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
    
    //... 省略其余代码 ....
}

总的来说,虽然 BufferedInputStreamFilterInputStreamInputStreamFileInputStream 的设计受到装饰模式的启发,但它们并不完全遵循装饰模式的经典定义。不过,这种设计确实展示了如何动态地向对象添加功能而不改变其接口的思想。

代码示例2:解决乱码问题

进一步深究

package com.polaris.designpattern.list2.structural.pattern2.decorator.inputstream;

import java.io.*;
import java.nio.charset.StandardCharsets;



public class DecoratorPatternDemoOk {
  
    public static void main(String[] args) {
        String exampleFile = System.getProperty("user.dir") +
                File.separator +
                "src/main/java/com/polaris/designpattern/list2/structural/pattern2/decorator/inputstream" +
                "/ExampleFileForRead.txt";
  
        try (
                // 创建一个 FileInputStream 对象,用于从文件中读取数据
                FileInputStream fis = new FileInputStream(exampleFile);
                // 使用 InputStreamReader 读取字符,并指定编码为 UTF-8
                Reader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
                // 使用 BufferedReader 包装 InputStreamReader,添加缓冲功能
                BufferedReader br = new BufferedReader(isr);
        ) {
            // 读取并处理数据,这里只是简单地打印到控制台  
            String line;  
            while ((line = br.readLine()) != null) {  
                System.out.println(line);  
            }  
        } catch (IOException e) {
            e.printStackTrace();  
        }  
    }  
}

/* Output:
设计模式类型:
1. ■ 创建型(creational)👈️
2. ■ 结构型(structural)👈️
3. ■ 行为型(behavioral)👈️
*///~

notes:

遇到的乱码问题是因为尝试将文件的字节直接转换为字符((char) data)进行打印,

但是文件的编码(UTF-8)与 Java 默认使用的字符编码(通常是系统默认的,可能不是 UTF-8)不匹配。

在 Java 中,FileInputStream 读取的是原始字节,需要指定正确的字符编码来将这些字节转换为字符串。

为了正确地读取和显示文件内容,应该使用 InputStreamReaderBufferedReader,并指定文件的字符编码,在这个示例,它使用 UTF-8 编码来读取和打印文件内容。

示例中,使用了 InputStreamReader 来读取字符,并指定了编码为 “UTF-8”。

然后,使用BufferedReader包装了 InputStreamReader来提供按行读取的功能

最后,我使用 readLine() 方法读取每一行,并直接打印到控制台。

能够正确地看到文件中的内容,而不会出现乱码。

适配器模式与装饰器模式的区别

装饰器与适配器模式都有一个别名就是包装模式(Wrapper),它们的作用看似都是起到包装一个类或对象的作用,但是使用它们的目的是很不一样的。

  1. 适配器模式的意义是要将一个接口转变成另外一个接口,它的目的是通过改变接口来达到重复使用的目的;
  2. 而装饰器模式不是要改变被装饰对象的接口,而恰恰要保持原有的接口,但是增强原有对象的功能,或者改变原有对象的处理方法而提升性能。所以这两个模式设计的目的是不同的。

搞懂了?那我们玩点带有迷惑型的东西

为什么说Java io类库借鉴了装饰模式,而不是等同于装饰模式

我们之前说Java I/O包下的类库借鉴了装饰器模式

但是它并不完全等同于经典的装饰器模式

装饰器模式通常允许递归地组合装饰器对象,而Java I/O中的FilterInputStream通常只包装一个InputStream对象。

如果我们区分这一差别,他们实际上是不同的;

严格意义上装饰器模式是可以对一个对象进行多次装饰,之后依旧是原类型,即允许递归的组合装饰器对象。

而java io包中类库却不是多次装饰。

以下进一步分析,下图是之前的类图,如果有必要可以回到之前的代码示例

  • 返回代码示例1

  • 返回代码示例2

在这里插入图片描述

那java io类库不是使用的装饰器模式么?大家不都说java io类库使用的是装饰器模式么?

其实准确的说java io类库借鉴了装饰器模式比较合理些

从上面的类图或者回到之前代码示例1,FilterInputStream的构造方法是protected修饰的,我们无法直接通过构造函数构造他的实例,因此直接使用它的子类构造,如这里的BufferedInputStream

通过传入文件路径字符串构造了FileInputStream对象,通过该FileInputStream实例构造了一个BufferedInputStream实例,BufferedInputStream可以看作是对该InputStream装饰了一层,即增加缓冲功能,就完事了,没有后续的装饰了;

而严格意义上的装饰器模式呢,是允许装饰多层的,装饰完毕依旧是原始的类型。

如果我们区分这一差别,即允许递归地组合装饰器对象(装饰完之后还是之前的类型才对,不能改变类型,不是去构造一个新的类型的实例),java io类库就不符合装饰器模式的要求

也就是如果按照严格意义上来说,对InputStream对象装饰多次,最终依旧是InputStream对象,才算是装饰器模式。

显然这里不是,这里经过了String =1=> InputStream(FileInputStream) =2=> InputStream(BufferedInputStream)第1步之后就不再是String类型了。只是这里形式上看上去和递归地组合装饰器对象非常相像,new BufferedInputStream(new FileInputStream("path_of_file"))

因此这里说是借鉴装饰器模式,且借鉴装饰器模式的部分只就是InputStream(FileInputStream) => InputStream(BufferedInputStream)这个过程。

略微延伸一点,我们打个比方,

java io类库由扩展了,增加了一个XxxInputStream类,以实现对InputStream增加Xxx功能,它需要传入InputStream类型实例来构造,那么它可能的调用方式是这样的:

InputStream fis = new FileInputStream("path_of_file");
XxxInputStream xis = new XxxInputStream(new BufferedInputStream(fis));

它允许递归地组合装饰器对象,这样他就是严格的装饰器模式了。

如果你说,上面代码示例2,形式上很像递归的组合装饰器对象呐,

InputStream fis = new FileInputStream(exampleFile);
BufferedReader br = new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8));

但是实际不是。

即使解决乱码问题,但是那是又引入了另外的接口Reader,与InputStream不是一回事,构造InputStreamReader的实例,通过传入FileInputStream实例(也就是一个InputStream实例),就转换成了Reader类型,就不再是InputStream类型,然后,通过传入该InputStreamReader,并指定编码格式UTF-8,构造了一个带有缓冲功能的BufferedReader,这里可以看简单看作是BufferedReaderInputStreamReader装饰了一层,即缓冲功能,就完事了,没有后续的装饰了,

类比

你大概也发现了,通过上面的比较较真的分析之后,我们对装饰器模式有了更深的认识,就是不管装饰多少次,类型不要发生变化才是装饰器模式。

并不是说只要像这样调用new C(new B(new A()))形似递归的组合装饰器,就是装饰器模式,只要其中发生了类型变化就不再是严格意义上的装饰器模式了。

可以用穿衣服出门类比:

当前的java io类库中的设计可以视为借鉴了装饰器模式,可以看作只包装一层的装饰器模式,就好比穿外套就出门了;

而严格的讲,**经典的装饰器模式(允许递归地组合装饰器对象)**是说你穿上内裤穿上袜子穿上衬衫穿上西服打上领带,这每一步操作都是对(一个对象,人类的一个实例,即一个人类对象)的装饰

穿上内裤之后是你,

穿上袜子是你,

穿上衬衫是你,

穿上西服是你,

打上领带是你。

就是,是不一样的烟火,对进行任何的装饰之后依旧是那个,归来依旧是那个少年!

拓展内容

抽象类相关基础

这部分内容属于总结,不需要死记硬背,用的多了回过头来看都是很简单的内容。

  1. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类

    这里提一下:不考虑默认方法(使用default关键字定义)和静态方法(使用static关键字定义)情况下,接口中的方法都是抽象方法。

    在Java 8及之前的版本中,接口中只能包含抽象方法和常量(即静态且final的变量)

    public interface MyInterface {  
        // 抽象方法  
        void method1();  
      
        // Java 8及以后:默认方法  
        default void method2() {  
            System.out.println("Default implementation of method2");  
        }  
      
        // Java 8及以后:静态方法  
        static void method3() {  
            System.out.println("Static method in interface");  
        }  
    }
    
  2. 构造方法,类方法(用static修饰的方法)不能声明为抽象方法

  3. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类

  4. 实例化

    1. 抽象类和接口均不能实例化,但是可以指向(引用)具体实现(非抽象子类)
    2. 抽象类虽然自身不可以实例化,但是其子类覆盖了所有的抽象方法后,是可以实例化的,所以抽象类的构造函数适用于给其子类对象进行初始化
  5. 构造器:构造函数是对象的基本,没有构造函数就没有对象。

    1. 若在抽象类中显式的声明了有参构造器子类继承时就必须写一个构造函数来调用父类的有参构造器

    2. 具体子类继承抽象父类,父类的有参构造器子类必须显示调用,即在子类的构造器中显式地super(param_object)调用

    3. 这是因为
      构造函数用于构造对象,但是抽象类不能被实例化,
      一旦抽象类声明了有参构造器,而此构造器不能直接被调用(除非子类显式调用),就不能被客户端将参数直接通过调用该抽象类的有参构造器传参进去,
      这个参数对象只能通过具体子类在构造对象时传入,即子类构造器中必须要显式地调用父类有参构造器super(param_object)

    4. 构造器参数类型可以放大,这样会更通用些,比如你要传入某个实现类的实例,可以将构造器参数类型设置为接口类型,这是多态的一种表现,你可以传入任何属于该类型或该类型的子类型对象。

    5. 如果父类中有无参构造器,在子类中可以不写构造器或显式的调用父类的构造函器,Java会自动调用父类无参构造器。

      以下代码是对抽象类构造器部分解释的编码

      abstract class Parent{
          public Parent(Param param){
      
          }
          public abstract void m();
      }
      
      class Son extends Parent{
      
          // param type can be larger.
          public Son(Param param) {
              // must invoke manually.
              super(param);
          }
      
          @Override
          public void m() {
      
          }
      }
      
      interface Param{
      
      }
      
      class ParamTypeA implements Param{
      
      }
      
      public class Demo {
          public static void main(String[] args) {
              Param param = new ParamTypeA();
              Son son = new Son(param);
          }
      }
      

👈️上一篇:代理模式

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

5分钟在 VSCode 中使用 PlantUML 绘图

去年&#xff0c;写过一篇在 VSCode 中使用 PlantUML 的博客&#xff0c;那时候我嫌弃本地安装麻烦&#xff0c;所以采用的是在本地运行 docker 容器的方法部署的 PlantUML 服务端。不过&#xff0c;现在来看这样还必须依赖在本地手动启动 docker 容器&#xff08;如果有一个不…

7.类和对象

类和对象 当我们没有去了解过java的知识点中 不免产生一些问题&#xff1a; 什么是类&#xff1f;什么是对象&#xff1f; 记住一句话&#xff1a;在java当中 一切皆对象 类&#xff1a;是用来描述一个对象的 而对象是一个真正存在的实体 在Java这门纯面向对象的语言中 我们…

Nginx企业级负载均衡:技术详解系列(10)—— Nginx核心配置详解(HTTP配置块)

你好&#xff0c;我是赵兴晨&#xff0c;97年文科程序员。 今天咱们聊聊Nginx核心配置中的HTTP配置块&#xff0c;这个配置块在我们的日常使用中极为常见&#xff0c;它的重要性不言而喻。 HTTP配置块在Nginx的配置架构中占据着核心地位&#xff0c;它直接关系到服务器如何处…

panic: concurrent write to websocket connection【golang、websocket】

文章目录 异常信息原由代码错误点 解决办法 异常信息 panic: concurrent write to websocket connection原由 golang 编写 websocket go版本&#xff1a;1.19 使用了第三方框架&#xff1a; https://github.com/gorilla/websocket/tree/main 代码 server.go // Copyright …

蓝桥楼赛第30期-Python-第三天赛题 从参数中提取信息题解

楼赛 第30期 Python 模块大比拼 提取用户输入信息 介绍 正则表达式&#xff08;英文为 Regular Expression&#xff0c;常简写为regex、regexp 或 RE&#xff09;&#xff0c;也叫规则表达式、正规表达式&#xff0c;是计算机科学的一个概念。 所谓“正则”&#xff0c;可以…

nssctf——web

[SWPUCTF 2021 新生赛]gift_F12 1.打开环境后&#xff0c;这里说要900多天会有flag&#xff0c;这是不可能的 2.f12查看源码&#xff0c;然后在html中查找flag &#xff08;在最上方的栏目中&#xff0c;或者按ctrlf&#xff09; [SWPUCTF 2021 新生赛]jicao 1.打开环境是一段…

数据结构(树)

1.树的概念和结构 树&#xff0c;顾名思义&#xff0c;它看起来像一棵树&#xff0c;是由n个结点组成的非线性的数据结构。 下面就是一颗树&#xff1a; 树的一些基本概念&#xff1a; 结点的度&#xff1a;一个结点含有的子树的个数称为该结点的度&#xff1b; 如上图&#…

Qt | QCalendarWidget 类(日历)

01、QCalendarWidget 类 1、QCalendarWidget 类是 QWidget 的直接子类,该类用于日历,见下图 02、QCalendarWidget 属性 ①、dateEditAcceptDelay:int 访问函数:int dateEditAcceptDelay()const; void setDateEditAcceptDelay(int) 获取和设置日期编辑器的延迟时间(以毫秒…

go routing 之 gorilla/mux

1. 背景 继续学习 go 2. 关于 routing 的学习 上一篇 go 用的库是&#xff1a;net/http &#xff0c;这次我们使用官方的库 github.com/gorilla/mux 来实现 routing。 3. demo示例 package mainimport ("fmt""net/http""github.com/gorilla/mux&…

设计模式11——代理模式

写文章的初心主要是用来帮助自己快速的回忆这个模式该怎么用&#xff0c;主要是下面的UML图可以起到大作用&#xff0c;在你学习过一遍以后可能会遗忘&#xff0c;忘记了不要紧&#xff0c;只要看一眼UML图就能想起来了。同时也请大家多多指教。 代理模式&#xff08;Proxy&am…

ATA-7020高压放大器原理介绍

高压放大器是一种电子设备&#xff0c;用于增加输入信号的幅度&#xff0c;使其输出具有更大的电压。它在各种领域中发挥着关键作用&#xff0c;尤其是在需要高电压信号的应用中&#xff0c;如声学、医学成像、科学研究等领域。 高压放大器工作原理介绍&#xff1a; 信号输入&a…

图像上下文学习|多模态基础模型中的多镜头情境学习

【原文】众所周知&#xff0c;大型语言模型在小样本上下文学习&#xff08;ICL&#xff09;方面非常有效。多模态基础模型的最新进展实现了前所未有的长上下文窗口&#xff0c;为探索其执行 ICL 的能力提供了机会&#xff0c;并提供了更多演示示例。在这项工作中&#xff0c;我…

go mod模式下,import gitlab中的项目

背景 为了go项目能够尽可能复用代码&#xff0c;把一些公用的工具类&#xff0c;公用的方法等放到共用包里统一管理。把共用包放到gitlab的私有仓库中。 遇到的问题 通过https方式&#xff0c;执行go get报了错误。 通过ssh方式&#xff0c;执行go get报了错误。 修改配置&am…

Android:使用Kotlin搭建MVC架构模式

一、简介Android MVC架构模式 M 层 model &#xff0c;负责处理数据&#xff0c;例如网络请求、数据变化 V 层 对应的是布局 C 层 Controller&#xff0c; 对应的是Activity&#xff0c;处理业务逻辑&#xff0c;包含V层的事情&#xff0c;还会做其他的事情&#xff0c;导致 ac…

WebRTC-SFU服务器-Janus部署【保姆级部署教程】

一、SFU WebRTC SFU(Selective Forwarding Unit)构架是一种通过服务器来路由和转发WebRTC客户端音视频数据流的方法。这种构架的核心特点是将服务器模拟成一个WebRTC的Peer客户端,从而实现了音视频流的直接转发。 在SFU构架中,服务器作为中心节点,但并不负责音视频流的混…

TG5032CGN TCXO 超高稳定10pin端子型适用于汽车动力转向控制器

TG5032CGN TCXO / VC-TCXO是一款应用广泛的晶振&#xff0c;具有超高稳定性&#xff0c;CMOS输出和使用晶体基振的削波正弦波输出形式。且有低相位噪声优势&#xff0c;是温补晶体振荡器(TCXO)和压控晶体振荡器(VCXO)结合的产物&#xff0c;具有TCXO和VCXO的共同优点&#xff0…

海山数据库(He3DB)代理ProxySQL使用详解:(一)架构说明与安装

一、ProxySQL介绍 1.1 简介 业界比较知名的MySQL代理&#xff0c;由ProxySQL LLC公司开发并提供专业的服务支持&#xff0c;基于GPLv3开源协议进行发布,大部分配置项可动态变更。后端的MySQL实例可根据用途配置到不同的hostgroup中&#xff0c;由ProxySQL基于7层网络协议,将来…

Python 实现Word (DOC或DOCX)与TXT文本格式互转

目录 引言 安装Python库 使用Python将Word转换为TXT文本格式 使用Python将TXT文本格式转换为Word 引言 Word文档和TXT文本文件是日常工作和生活中两种常见的文件格式&#xff0c;各有其特点和优势。Word文档能够保留丰富的格式设置&#xff0c;如字体、段落、表格、图片等…

格雷母线与卸料小车的非接触式定位技术

在现代化的工业生产中&#xff0c;自动化与智能化已成为提高生产效率、降低成本的关键手段。特别是在钢铁、矿山等重工业领域&#xff0c;卸料小车作为物料搬运的重要设备&#xff0c;其定位精度和工作效率直接影响了整个生产线的运行状况。格雷母线高精度位移测量系统的引入&a…

worklist配置调试日志记录

工作记录用,不拘小节&#xff01; 设备请求日志 2024-05-23 09:03:14,503 [WorkListServer: 10.87.232.253 [18]] INFO - LISTMWL Request from [gehc]: (0008,0005) CS [ISO_IR 100] # 10 Specific Character Set 1-N (0008,0020) DA [] …